SpriteChecker.hh 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. #ifndef SPRITECHECKER_HH
  2. #define SPRITECHECKER_HH
  3. #include "VDP.hh"
  4. #include "VDPVRAM.hh"
  5. #include "VRAMObserver.hh"
  6. #include "DisplayMode.hh"
  7. #include "serialize_meta.hh"
  8. #include "ranges.hh"
  9. #include "unreachable.hh"
  10. #include <cstdint>
  11. namespace openmsx {
  12. class RenderSettings;
  13. class BooleanSetting;
  14. class SpriteChecker final : public VRAMObserver
  15. {
  16. public:
  17. /** Bitmap of length 32 describing a sprite pattern.
  18. * Visible pixels are 1, transparent pixels are 0.
  19. * If the sprite is less than 32 pixels wide,
  20. * the lower bits are unused.
  21. */
  22. using SpritePattern = uint32_t;
  23. /** Contains all the information to draw a line of a sprite.
  24. */
  25. struct SpriteInfo {
  26. /** Pattern of this sprite line, corrected for magnification.
  27. */
  28. SpritePattern pattern;
  29. /** X-coordinate of sprite, corrected for early clock.
  30. */
  31. int16_t x;
  32. /** Bit 3..0 are index in palette.
  33. * Bit 6 is 0 for sprite mode 1 like behaviour,
  34. * or 1 for OR-ing of sprite colors.
  35. * Other bits are undefined.
  36. */
  37. byte colorAttrib;
  38. };
  39. /** Create a sprite checker.
  40. * @param vdp The VDP this sprite checker is part of.
  41. * @param renderSettings TODO
  42. * @param time TODO
  43. */
  44. SpriteChecker(VDP& vdp, RenderSettings& renderSettings,
  45. EmuTime::param time);
  46. /** Puts the sprite checker in its initial state.
  47. * @param time The moment in time this reset occurs.
  48. */
  49. void reset(EmuTime::param time);
  50. /** Update sprite checking to specified time.
  51. * This includes a VRAM sync.
  52. * @param time The moment in emulated time to update to.
  53. */
  54. inline void sync(EmuTime::param time) {
  55. if (!updateSpritesMethod) {
  56. // Optimization: skip vram sync and sprite checks
  57. // in sprite mode 0.
  58. return;
  59. }
  60. // Debug:
  61. // This method is not re-entrant, so check explicitly that it is not
  62. // re-entered. This can disappear once the VDP-internal scheduling
  63. // has become stable.
  64. #ifdef DEBUG
  65. static bool syncInProgress = false;
  66. assert(!syncInProgress);
  67. syncInProgress = true;
  68. #endif
  69. vram.sync(time);
  70. checkUntil(time);
  71. #ifdef DEBUG
  72. syncInProgress = false;
  73. #endif
  74. }
  75. /** Clear status bits triggered by reading of S#0.
  76. */
  77. inline void resetStatus() {
  78. // TODO: Used to be 0x5F, but that is contradicted by
  79. // TMS9918.pdf. Check on real MSX.
  80. vdp.setSpriteStatus(vdp.getStatusReg0() & 0x1F);
  81. }
  82. /** Informs the sprite checker of a VDP display mode change.
  83. * @param mode The new display mode.
  84. * @param time The moment in emulated time this change occurs.
  85. */
  86. inline void updateDisplayMode(DisplayMode mode, EmuTime::param time) {
  87. sync(time);
  88. setDisplayMode(mode);
  89. // The following is only required when switching from sprite
  90. // mode0 to some other mode (in other case it has no effect).
  91. // Because in mode 0, currentLine is not updated.
  92. currentLine = frameStartTime.getTicksTill_fast(time)
  93. / VDP::TICKS_PER_LINE;
  94. // Every line in mode0 has 0 sprites, but none of the lines
  95. // are ever requested by the renderer, except for the last
  96. // line, because sprites are checked one line before they
  97. // are displayed. Though frameStart() already makes sure
  98. // spriteCount contains zero for all lines.
  99. // spriteCount[currentLine - 1] = 0;
  100. }
  101. /** Informs the sprite checker of a VDP display enabled change.
  102. * @param enabled The new display enabled state.
  103. * @param time The moment in emulated time this change occurs.
  104. */
  105. inline void updateDisplayEnabled(bool enabled, EmuTime::param time) {
  106. (void)enabled;
  107. sync(time);
  108. // TODO: Speed up sprite checking in display disabled case.
  109. }
  110. /** Informs the sprite checker of sprite enable changes.
  111. * @param enabled The new sprite enabled state.
  112. * @param time The moment in emulated time this change occurs.
  113. */
  114. inline void updateSpritesEnabled(bool enabled, EmuTime::param time) {
  115. (void)enabled;
  116. sync(time);
  117. // TODO: Speed up sprite checking in display disabled case.
  118. }
  119. /** Informs the sprite checker of sprite size or magnification changes.
  120. * @param sizeMag The new size and magnification state.
  121. * Bit 0 is magnification: 0 = normal, 1 = doubled.
  122. * Bit 1 is size: 0 = 8x8, 1 = 16x16.
  123. * @param time The moment in emulated time this change occurs.
  124. */
  125. inline void updateSpriteSizeMag(byte sizeMag, EmuTime::param time) {
  126. (void)sizeMag;
  127. sync(time);
  128. // TODO: Precalc something?
  129. }
  130. /** Informs the sprite checker of a change in the TP bit (R#8 bit 5)
  131. * @param tp The new transparency value.
  132. * @param time The moment in emulated time this change occurs.
  133. */
  134. inline void updateTransparency(bool tp, EmuTime::param time) {
  135. (void)tp;
  136. sync(time);
  137. }
  138. /** Informs the sprite checker of a vertical scroll change.
  139. * @param scroll The new scroll value.
  140. * @param time The moment in emulated time this change occurs.
  141. */
  142. inline void updateVerticalScroll(int scroll, EmuTime::param time) {
  143. (void)scroll;
  144. sync(time);
  145. // TODO: Precalc something?
  146. }
  147. /** Update sprite checking until specified line.
  148. * VRAM must be up-to-date before this method is called.
  149. * It is not allowed to call this method in a spriteless display mode.
  150. * @param time The moment in emulated time to update to.
  151. */
  152. inline void checkUntil(EmuTime::param time) {
  153. // TODO:
  154. // Currently the sprite checking is done atomically at the end of
  155. // the display line. In reality, sprite checking is probably done
  156. // during most of the line. Run tests on real MSX to make a more
  157. // accurate model of sprite checking.
  158. int limit = frameStartTime.getTicksTill_fast(time)
  159. / VDP::TICKS_PER_LINE;
  160. if (currentLine < limit) {
  161. // Call the right update method for the current display mode.
  162. (this->*updateSpritesMethod)(limit);
  163. }
  164. }
  165. /** Get X coordinate of sprite collision.
  166. */
  167. inline int getCollisionX(EmuTime::param time) {
  168. sync(time);
  169. return collisionX;
  170. }
  171. /** Get Y coordinate of sprite collision.
  172. */
  173. inline int getCollisionY(EmuTime::param time) {
  174. sync(time);
  175. return collisionY;
  176. }
  177. /** Reset sprite collision coordinates.
  178. * This happens directly after a read, so a timestamp for syncing is
  179. * not necessary.
  180. */
  181. inline void resetCollision() {
  182. collisionX = collisionY = 0;
  183. }
  184. /** Signals the start of a new frame.
  185. * @param time Moment in emulated time the new frame starts.
  186. */
  187. inline void frameStart(EmuTime::param time) {
  188. frameStartTime.reset(time);
  189. currentLine = 0;
  190. ranges::fill(spriteCount, 0);
  191. // TODO: Reset anything else? Does the real VDP?
  192. }
  193. /** Signals the end of the current frame.
  194. * @param time Moment in emulated time the current frame ends.
  195. */
  196. inline void frameEnd(EmuTime::param time) {
  197. sync(time);
  198. }
  199. /** Get sprites for a display line.
  200. * Returns the contents of the line the last time it was sprite checked;
  201. * before getting the sprites, you should sync to a moment in time
  202. * after the sprites are checked, or you'll get last frame's sprites.
  203. * @param line The absolute line number for which sprites should
  204. * be returned. Range is [0..313) for PAL and [0..262) for NTSC.
  205. * @param visibleSprites Output parameter in which the pointer to
  206. * a SpriteInfo array containing the sprites to be displayed is
  207. * returned.
  208. * The array's contents are valid until the next time the VDP
  209. * is scheduled.
  210. * @return The number of sprites stored in the visibleSprites array.
  211. */
  212. inline int getSprites(int line, const SpriteInfo*& visibleSprites) const {
  213. // Compensate for the fact sprites are checked one line earlier
  214. // than they are displayed.
  215. line--;
  216. // TODO: Is there ever a sprite on absolute line 0?
  217. // Maybe there is, but it is never displayed.
  218. if (line < 0) return 0;
  219. visibleSprites = spriteBuffer[line];
  220. return spriteCount[line];
  221. }
  222. // VRAMObserver implementation:
  223. void updateVRAM(unsigned /*offset*/, EmuTime::param time) override {
  224. checkUntil(time);
  225. }
  226. void updateWindow(bool /*enabled*/, EmuTime::param time) override {
  227. sync(time);
  228. }
  229. template<typename Archive>
  230. void serialize(Archive& ar, unsigned version);
  231. private:
  232. /** Calculate 'updateSpritesMethod' and 'planar'.
  233. */
  234. inline void setDisplayMode(DisplayMode mode) {
  235. switch (mode.getSpriteMode(vdp.isMSX1VDP())) {
  236. case 0:
  237. updateSpritesMethod = nullptr;
  238. break;
  239. case 1:
  240. updateSpritesMethod = &SpriteChecker::updateSprites1;
  241. break;
  242. case 2:
  243. updateSpritesMethod = &SpriteChecker::updateSprites2;
  244. planar = mode.isPlanar();
  245. // An alternative is to have a planar and non-planar
  246. // updateSprites2 method.
  247. break;
  248. default:
  249. UNREACHABLE;
  250. }
  251. }
  252. /** Calculate sprite patterns for sprite mode 1.
  253. */
  254. void updateSprites1(int limit);
  255. /** Calculate sprite patterns for sprite mode 2.
  256. */
  257. void updateSprites2(int limit);
  258. /** Calculates a sprite pattern.
  259. * @param patternNr Number of the sprite pattern [0..255].
  260. * For 16x16 sprites, patternNr should be a multiple of 4.
  261. * @param y The line number within the sprite: 0 <= y < size.
  262. * @return A bit field of the sprite pattern.
  263. * Bit 31 is the leftmost bit of the sprite.
  264. * Unused bits are zero.
  265. */
  266. inline SpritePattern calculatePatternNP(unsigned patternNr, unsigned y);
  267. inline SpritePattern calculatePatternPlanar(unsigned patternNr, unsigned y);
  268. /** Check sprite collision and number of sprites per line.
  269. * This routine implements sprite mode 1 (MSX1).
  270. * Separated from display code to make MSX behaviour consistent
  271. * no matter how displaying is handled.
  272. * @param minLine The first line number (inclusive) for which sprites
  273. * should be checked.
  274. * @param maxLine The last line number (exclusive) for which sprites
  275. * should be checked.
  276. * @effect Fills in the spriteBuffer and spriteCount arrays.
  277. */
  278. inline void checkSprites1(int minLine, int maxLine);
  279. /** Check sprite collision and number of sprites per line.
  280. * This routine implements sprite mode 2 (MSX2).
  281. * Separated from display code to make MSX behaviour consistent
  282. * no matter how displaying is handled.
  283. * @param minLine The first line number (inclusive) for which sprites
  284. * should be checked.
  285. * @param maxLine The last line number (exclusive) for which sprites
  286. * should be checked.
  287. * @effect Fills in the spriteBuffer and spriteCount arrays.
  288. */
  289. inline void checkSprites2(int minLine, int maxLine);
  290. using UpdateSpritesMethod = void (SpriteChecker::*)(int limit);
  291. UpdateSpritesMethod updateSpritesMethod;
  292. /** The VDP this sprite checker is part of.
  293. */
  294. VDP& vdp;
  295. /** The VRAM to get sprites data from.
  296. */
  297. VDPVRAM& vram;
  298. /** Limit number of sprites per display line?
  299. * Option only affects display, not MSX state.
  300. * In other words: when false there is no limit to the number of
  301. * sprites drawn, but the status register acts like the usual limit
  302. * is still effective.
  303. */
  304. BooleanSetting& limitSpritesSetting;
  305. /** The emulation time when this frame was started (vsync).
  306. */
  307. Clock<VDP::TICKS_PER_SECOND> frameStartTime;
  308. /** Sprites are checked up to and excluding this display line.
  309. */
  310. int currentLine;
  311. /** X coordinate of sprite collision.
  312. * 9 bits long -> [0..511]?
  313. */
  314. int collisionX;
  315. /** Y coordinate of sprite collision.
  316. * 9 bits long -> [0..511]?
  317. * Bit 9 contains EO, I guess that's a copy of the even/odd flag
  318. * of the frame on which the collision occurred.
  319. */
  320. int collisionY;
  321. /** Buffer containing the sprites that are visible on each
  322. * display line.
  323. */
  324. SpriteInfo spriteBuffer[313][32 + 1]; // +1 for sentinel
  325. /** Buffer containing the number of sprites that are visible
  326. * on each display line.
  327. * In other words, spriteCount[i] is the number of sprites
  328. * in spriteBuffer[i].
  329. */
  330. uint8_t spriteCount[313];
  331. /** Is current display mode planar or not?
  332. * TODO: Introduce separate update methods for planar/nonplanar modes.
  333. */
  334. bool planar;
  335. };
  336. SERIALIZE_CLASS_VERSION(SpriteChecker, 2);
  337. } // namespace openmsx
  338. #endif