SpriteChecker.cc 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  1. /*
  2. TODO:
  3. - Verify model for 5th sprite number calculation.
  4. For example, does it have the right value in text mode?
  5. - Further investigate sprite collision registers:
  6. - If there is NO collision, the value of these registers constantly changes.
  7. Could this be some kind of indication for the scanline XY coords???
  8. - Bit 9 of the Y coord (odd/even page??) is not yet implemented.
  9. */
  10. #include "SpriteChecker.hh"
  11. #include "RenderSettings.hh"
  12. #include "BooleanSetting.hh"
  13. #include "serialize.hh"
  14. #include <algorithm>
  15. #include <cassert>
  16. namespace openmsx {
  17. SpriteChecker::SpriteChecker(VDP& vdp_, RenderSettings& renderSettings,
  18. EmuTime::param time)
  19. : vdp(vdp_), vram(vdp.getVRAM())
  20. , limitSpritesSetting(renderSettings.getLimitSpritesSetting())
  21. , frameStartTime(time)
  22. {
  23. vram.spriteAttribTable.setObserver(this);
  24. vram.spritePatternTable.setObserver(this);
  25. }
  26. void SpriteChecker::reset(EmuTime::param time)
  27. {
  28. vdp.setSpriteStatus(0); // TODO 0x00 or 0x1F (blueMSX has 0x1F)
  29. collisionX = 0;
  30. collisionY = 0;
  31. frameStart(time);
  32. updateSpritesMethod = &SpriteChecker::updateSprites1;
  33. }
  34. static inline SpriteChecker::SpritePattern doublePattern(SpriteChecker::SpritePattern a)
  35. {
  36. // bit-pattern "abcd...." gets expanded to "aabbccdd"
  37. // upper 16 bits (of a 32 bit number) contain the pattern
  38. // lower 16 bits must be zero
  39. // // abcdefghijklmnop0000000000000000
  40. a = (a | (a >> 8)) & 0xFF00FF00; // abcdefgh00000000ijklmnop00000000
  41. a = (a | (a >> 4)) & 0xF0F0F0F0; // abcd0000efgh0000ijkl0000mnop0000
  42. a = (a | (a >> 2)) & 0xCCCCCCCC; // ab00cd00ef00gh00ij00kl00mn00op00
  43. a = (a | (a >> 1)) & 0xAAAAAAAA; // a0b0c0d0e0f0g0h0i0j0k0l0m0n0o0p0
  44. return a | (a >> 1); // aabbccddeeffgghhiijjkkllmmnnoopp
  45. }
  46. inline SpriteChecker::SpritePattern SpriteChecker::calculatePatternNP(
  47. unsigned patternNr, unsigned y)
  48. {
  49. const byte* patternPtr = vram.spritePatternTable.getReadArea(0, 256 * 8);
  50. unsigned index = patternNr * 8 + y;
  51. SpritePattern pattern = patternPtr[index] << 24;
  52. if (vdp.getSpriteSize() == 16) {
  53. pattern |= patternPtr[index + 16] << 16;
  54. }
  55. return !vdp.isSpriteMag() ? pattern : doublePattern(pattern);
  56. }
  57. inline SpriteChecker::SpritePattern SpriteChecker::calculatePatternPlanar(
  58. unsigned patternNr, unsigned y)
  59. {
  60. const byte* ptr0;
  61. const byte* ptr1;
  62. vram.spritePatternTable.getReadAreaPlanar(0, 256 * 8, ptr0, ptr1);
  63. unsigned index = patternNr * 8 + y;
  64. const byte* patternPtr = (index & 1) ? ptr1 : ptr0;
  65. index /= 2;
  66. SpritePattern pattern = patternPtr[index] << 24;
  67. if (vdp.getSpriteSize() == 16) {
  68. pattern |= patternPtr[index + (16 / 2)] << 16;
  69. }
  70. return !vdp.isSpriteMag() ? pattern : doublePattern(pattern);
  71. }
  72. void SpriteChecker::updateSprites1(int limit)
  73. {
  74. if (vdp.spritesEnabledFast()) {
  75. if (vdp.isDisplayEnabled()) {
  76. // in display area
  77. checkSprites1(currentLine, limit);
  78. } else {
  79. // in border, only check last line of top border
  80. int l0 = vdp.getLineZero() - 1;
  81. if ((currentLine <= l0) && (l0 < limit)) {
  82. checkSprites1(l0, l0 + 1);
  83. }
  84. }
  85. }
  86. currentLine = limit;
  87. }
  88. inline void SpriteChecker::checkSprites1(int minLine, int maxLine)
  89. {
  90. // This implementation contains a double for-loop. The outer loop goes
  91. // over the sprites, the inner loop over the to-be-checked lines. This
  92. // is not the order in which the real VDP performs this operation: the
  93. // real VDP renders line-per-line and for each line checks all 32
  94. // sprites.
  95. //
  96. // Though this 'reverse' order allows to skip over very large regions
  97. // of the inner loop: we only have to process the lines were a
  98. // particular sprite is actually visible. I measured this makes this
  99. // routine 4x-5x faster!
  100. //
  101. // This routine also needs to detect the sprite number of the 'first'
  102. // 5th-sprite-condition. With 'first' meaning the first line where this
  103. // condition occurs. Because our loops are swapped compared to the real
  104. // VDP, we need some extra fixup logic to correctly detect this.
  105. // Calculate display line.
  106. // This is the line sprites are checked at; the line they are displayed
  107. // at is one lower.
  108. int displayDelta = vdp.getVerticalScroll() - vdp.getLineZero();
  109. // Get sprites for this line and detect 5th sprite if any.
  110. bool limitSprites = limitSpritesSetting.getBoolean();
  111. int size = vdp.getSpriteSize();
  112. bool mag = vdp.isSpriteMag();
  113. int magSize = (mag + 1) * size;
  114. const byte* attributePtr = vram.spriteAttribTable.getReadArea(0, 32 * 4);
  115. byte patternIndexMask = size == 16 ? 0xFC : 0xFF;
  116. int fifthSpriteNum = -1; // no 5th sprite detected yet
  117. int fifthSpriteLine = 999; // larger than any possible valid line
  118. int sprite = 0;
  119. for (/**/; sprite < 32; ++sprite) {
  120. int y = attributePtr[4 * sprite + 0];
  121. if (y == 208) break;
  122. for (int line = minLine; line < maxLine; ++line) {
  123. // Calculate line number within the sprite.
  124. int displayLine = line + displayDelta;
  125. int spriteLine = (displayLine - y) & 0xFF;
  126. if (spriteLine >= magSize) {
  127. // Skip ahead till sprite becomes visible.
  128. line += 256 - spriteLine - 1; // -1 because of for-loop
  129. continue;
  130. }
  131. int visibleIndex = spriteCount[line];
  132. if (visibleIndex == 4) {
  133. // Find earliest line where this condition occurs.
  134. if (line < fifthSpriteLine) {
  135. fifthSpriteLine = line;
  136. fifthSpriteNum = sprite;
  137. }
  138. if (limitSprites) continue;
  139. }
  140. SpriteInfo& sip = spriteBuffer[line][visibleIndex];
  141. int patternIndex = attributePtr[4 * sprite + 2] & patternIndexMask;
  142. if (mag) spriteLine /= 2;
  143. sip.pattern = calculatePatternNP(patternIndex, spriteLine);
  144. sip.x = attributePtr[4 * sprite + 1];
  145. byte colorAttrib = attributePtr[4 * sprite + 3];
  146. if (colorAttrib & 0x80) sip.x -= 32;
  147. sip.colorAttrib = colorAttrib;
  148. spriteCount[line] = visibleIndex + 1;
  149. }
  150. }
  151. // Update status register.
  152. byte status = vdp.getStatusReg0();
  153. if (fifthSpriteNum != -1) {
  154. // Five sprites on a line.
  155. // According to TMS9918.pdf 5th sprite detection is only
  156. // active when F flag is zero.
  157. if ((status & 0xC0) == 0) {
  158. status = 0x40 | (status & 0x20) | fifthSpriteNum;
  159. }
  160. }
  161. if (~status & 0x40) {
  162. // No 5th sprite detected, store number of latest sprite processed.
  163. status = (status & 0x20) | std::min(sprite, 31);
  164. }
  165. vdp.setSpriteStatus(status);
  166. // Optimisation:
  167. // If collision already occurred,
  168. // that state is stable until it is reset by a status reg read,
  169. // so no need to execute the checks.
  170. // The spriteBuffer array is filled now, so we can bail out.
  171. if (vdp.getStatusReg0() & 0x20) return;
  172. /*
  173. Model for sprite collision: (or "coincidence" in TMS9918 data sheet)
  174. - Reset when status reg is read.
  175. - Set when sprite patterns overlap.
  176. - ??? Color doesn't matter: sprites of color 0 can collide.
  177. ??? This conflicts with: https://github.com/openMSX/openMSX/issues/1198
  178. - Sprites that are partially off-screen position can collide, but only
  179. on the in-screen pixels. In other words: sprites cannot collide in
  180. the left or right border, only in the visible screen area. Though
  181. they can collide in the V9958 extra border mask. This behaviour is
  182. the same in sprite mode 1 and 2.
  183. Implemented by checking every pair for collisions.
  184. For large numbers of sprites that would be slow,
  185. but there are max 4 sprites and therefore max 6 pairs.
  186. If any collision is found, method returns at once.
  187. */
  188. bool can0collide = vdp.canSpriteColor0Collide();
  189. for (int line = minLine; line < maxLine; ++line) {
  190. int minXCollision = 999;
  191. for (int i = std::min<int>(4, spriteCount[line]); --i >= 1; /**/) {
  192. auto color1 = spriteBuffer[line][i].colorAttrib & 0xf;
  193. if (!can0collide && (color1 == 0)) continue;
  194. int x_i = spriteBuffer[line][i].x;
  195. SpritePattern pattern_i = spriteBuffer[line][i].pattern;
  196. for (int j = i; --j >= 0; ) {
  197. auto color2 = spriteBuffer[line][j].colorAttrib & 0xf;
  198. if (!can0collide && (color2 == 0)) continue;
  199. // Do sprite i and sprite j collide?
  200. int x_j = spriteBuffer[line][j].x;
  201. int dist = x_j - x_i;
  202. if ((-magSize < dist) && (dist < magSize)) {
  203. SpritePattern pattern_j = spriteBuffer[line][j].pattern;
  204. if (dist < 0) {
  205. pattern_j <<= -dist;
  206. } else {
  207. pattern_j >>= dist;
  208. }
  209. SpritePattern colPat = pattern_i & pattern_j;
  210. if (x_i < 0) {
  211. assert(x_i >= -32);
  212. colPat &= (1 << (32 + x_i)) - 1;
  213. }
  214. if (colPat) {
  215. int xCollision = x_i + Math::countLeadingZeros(colPat);
  216. assert(xCollision >= 0);
  217. minXCollision = std::min(minXCollision, xCollision);
  218. }
  219. }
  220. }
  221. }
  222. if (minXCollision < 256) {
  223. vdp.setSpriteStatus(vdp.getStatusReg0() | 0x20);
  224. // verified: collision coords are also filled
  225. // in for sprite mode 1
  226. // x-coord should be increased by 12
  227. // y-coord 8
  228. collisionX = minXCollision + 12;
  229. collisionY = line - vdp.getLineZero() + 8;
  230. return; // don't check lines with higher Y-coord
  231. }
  232. }
  233. }
  234. void SpriteChecker::updateSprites2(int limit)
  235. {
  236. // TODO merge this with updateSprites1()?
  237. if (vdp.spritesEnabledFast()) {
  238. if (vdp.isDisplayEnabled()) {
  239. // in display area
  240. checkSprites2(currentLine, limit);
  241. } else {
  242. // in border, only check last line of top border
  243. int l0 = vdp.getLineZero() - 1;
  244. if ((currentLine <= l0) && (l0 < limit)) {
  245. checkSprites2(l0, l0 + 1);
  246. }
  247. }
  248. }
  249. currentLine = limit;
  250. }
  251. inline void SpriteChecker::checkSprites2(int minLine, int maxLine)
  252. {
  253. // See comment in checkSprites1() about order of inner and outer loops.
  254. // Calculate display line.
  255. // This is the line sprites are checked at; the line they are displayed
  256. // at is one lower.
  257. int displayDelta = vdp.getVerticalScroll() - vdp.getLineZero();
  258. // Get sprites for this line and detect 5th sprite if any.
  259. bool limitSprites = limitSpritesSetting.getBoolean();
  260. int size = vdp.getSpriteSize();
  261. bool mag = vdp.isSpriteMag();
  262. int magSize = (mag + 1) * size;
  263. int patternIndexMask = (size == 16) ? 0xFC : 0xFF;
  264. int ninthSpriteNum = -1; // no 9th sprite detected yet
  265. int ninthSpriteLine = 999; // larger than any possible valid line
  266. // Because it gave a measurable performance boost, we duplicated the
  267. // code for planar and non-planar modes.
  268. int sprite = 0;
  269. if (planar) {
  270. const byte* attributePtr0;
  271. const byte* attributePtr1;
  272. vram.spriteAttribTable.getReadAreaPlanar(
  273. 512, 32 * 4, attributePtr0, attributePtr1);
  274. // TODO: Verify CC implementation.
  275. for (/**/; sprite < 32; ++sprite) {
  276. int y = attributePtr0[2 * sprite + 0];
  277. if (y == 216) break;
  278. for (int line = minLine; line < maxLine; ++line) {
  279. // Calculate line number within the sprite.
  280. int displayLine = line + displayDelta;
  281. int spriteLine = (displayLine - y) & 0xFF;
  282. if (spriteLine >= magSize) {
  283. // Skip ahead till sprite is visible.
  284. line += 256 - spriteLine - 1;
  285. continue;
  286. }
  287. int visibleIndex = spriteCount[line];
  288. if (visibleIndex == 8) {
  289. // Find earliest line where this condition occurs.
  290. if (line < ninthSpriteLine) {
  291. ninthSpriteLine = line;
  292. ninthSpriteNum = sprite;
  293. }
  294. if (limitSprites) continue;
  295. }
  296. if (mag) spriteLine /= 2;
  297. int colorIndex = (~0u << 10) | (sprite * 16 + spriteLine);
  298. byte colorAttrib =
  299. vram.spriteAttribTable.readPlanar(colorIndex);
  300. SpriteInfo& sip = spriteBuffer[line][visibleIndex];
  301. int patternIndex = attributePtr0[2 * sprite + 1] & patternIndexMask;
  302. sip.pattern = calculatePatternPlanar(patternIndex, spriteLine);
  303. sip.x = attributePtr1[2 * sprite + 0];
  304. if (colorAttrib & 0x80) sip.x -= 32;
  305. sip.colorAttrib = colorAttrib;
  306. // set sentinel (see below)
  307. spriteBuffer[line][visibleIndex + 1].colorAttrib = 0;
  308. spriteCount[line] = visibleIndex + 1;
  309. }
  310. }
  311. } else {
  312. const byte* attributePtr0 =
  313. vram.spriteAttribTable.getReadArea(512, 32 * 4);
  314. // TODO: Verify CC implementation.
  315. for (/**/; sprite < 32; ++sprite) {
  316. int y = attributePtr0[4 * sprite + 0];
  317. if (y == 216) break;
  318. for (int line = minLine; line < maxLine; ++line) {
  319. // Calculate line number within the sprite.
  320. int displayLine = line + displayDelta;
  321. int spriteLine = (displayLine - y) & 0xFF;
  322. if (spriteLine >= magSize) {
  323. // Skip ahead till sprite is visible.
  324. line += 256 - spriteLine - 1;
  325. continue;
  326. }
  327. int visibleIndex = spriteCount[line];
  328. if (visibleIndex == 8) {
  329. // Find earliest line where this condition occurs.
  330. if (line < ninthSpriteLine) {
  331. ninthSpriteLine = line;
  332. ninthSpriteNum = sprite;
  333. }
  334. if (limitSprites) continue;
  335. }
  336. if (mag) spriteLine /= 2;
  337. int colorIndex = (~0u << 10) | (sprite * 16 + spriteLine);
  338. byte colorAttrib =
  339. vram.spriteAttribTable.readNP(colorIndex);
  340. // Sprites with CC=1 are only visible if preceded by
  341. // a sprite with CC=0. However they DO contribute towards
  342. // the max-8-sprites-per-line limit, so we can't easily
  343. // filter them here. See also
  344. // https://github.com/openMSX/openMSX/issues/497
  345. SpriteInfo& sip = spriteBuffer[line][visibleIndex];
  346. int patternIndex = attributePtr0[4 * sprite + 2] & patternIndexMask;
  347. sip.pattern = calculatePatternNP(patternIndex, spriteLine);
  348. sip.x = attributePtr0[4 * sprite + 1];
  349. if (colorAttrib & 0x80) sip.x -= 32;
  350. sip.colorAttrib = colorAttrib;
  351. // Set sentinel. Sentinel is actually only
  352. // needed for sprites with CC=1.
  353. // In the past we set the sentinel (for all
  354. // lines) at the end. But it's slightly faster
  355. // to do it only for lines that actually
  356. // contain sprites (even if sentinel gets
  357. // overwritten a couple of times for lines with
  358. // many sprites).
  359. spriteBuffer[line][visibleIndex + 1].colorAttrib = 0;
  360. spriteCount[line] = visibleIndex + 1;
  361. }
  362. }
  363. }
  364. // Update status register.
  365. byte status = vdp.getStatusReg0();
  366. if (ninthSpriteNum != -1) {
  367. // Nine sprites on a line.
  368. // According to TMS9918.pdf 5th sprite detection is only
  369. // active when F flag is zero. Stuck to this for V9938.
  370. // Dragon Quest 2 needs this.
  371. if ((status & 0xC0) == 0) {
  372. status = 0x40 | (status & 0x20) | ninthSpriteNum;
  373. }
  374. }
  375. if (~status & 0x40) {
  376. // No 9th sprite detected, store number of latest sprite processed.
  377. status = (status & 0x20) | std::min(sprite, 31);
  378. }
  379. vdp.setSpriteStatus(status);
  380. // Optimisation:
  381. // If collision already occurred,
  382. // that state is stable until it is reset by a status reg read,
  383. // so no need to execute the checks.
  384. // The visibleSprites array is filled now, so we can bail out.
  385. if (vdp.getStatusReg0() & 0x20) return;
  386. /*
  387. Model for sprite collision: (or "coincidence" in TMS9918 data sheet)
  388. - Reset when status reg is read.
  389. - Set when sprite patterns overlap.
  390. - ??? Color doesn't matter: sprites of color 0 can collide.
  391. ??? TODO: V9938 data book denies this (page 98).
  392. ??? This conflicts with: https://github.com/openMSX/openMSX/issues/1198
  393. - Sprites that are partially off-screen position can collide, but only
  394. on the in-screen pixels. In other words: sprites cannot collide in
  395. the left or right border, only in the visible screen area. Though
  396. they can collide in the V9958 extra border mask. This behaviour is
  397. the same in sprite mode 1 and 2.
  398. Implemented by checking every pair for collisions.
  399. For large numbers of sprites that would be slow.
  400. There are max 8 sprites and therefore max 42 pairs.
  401. TODO: Maybe this is slow... Think of something faster.
  402. Probably new approach is needed anyway for OR-ing.
  403. */
  404. bool can0collide = vdp.canSpriteColor0Collide();
  405. for (int line = minLine; line < maxLine; ++line) {
  406. int minXCollision = 999; // no collision
  407. SpriteInfo* visibleSprites = spriteBuffer[line];
  408. for (int i = std::min<int>(8, spriteCount[line]); --i >= 1; /**/) {
  409. auto colorAttrib1 = visibleSprites[i].colorAttrib;
  410. if (!can0collide && ((colorAttrib1 & 0xf) == 0)) continue;
  411. // If CC or IC is set, this sprite cannot collide.
  412. if (colorAttrib1 & 0x60) continue;
  413. int x_i = visibleSprites[i].x;
  414. SpritePattern pattern_i = visibleSprites[i].pattern;
  415. for (int j = i; --j >= 0; ) {
  416. auto colorAttrib2 = visibleSprites[j].colorAttrib;
  417. if (!can0collide && ((colorAttrib2 & 0xf) == 0)) continue;
  418. // If CC or IC is set, this sprite cannot collide.
  419. if (colorAttrib2 & 0x60) continue;
  420. // Do sprite i and sprite j collide?
  421. int x_j = visibleSprites[j].x;
  422. int dist = x_j - x_i;
  423. if ((-magSize < dist) && (dist < magSize)) {
  424. SpritePattern pattern_j = visibleSprites[j].pattern;
  425. if (dist < 0) {
  426. pattern_j <<= -dist;
  427. } else {
  428. pattern_j >>= dist;
  429. }
  430. SpritePattern colPat = pattern_i & pattern_j;
  431. if (x_i < 0) {
  432. assert(x_i >= -32);
  433. colPat &= (1 << (32 + x_i)) - 1;
  434. }
  435. if (colPat) {
  436. int xCollision = x_i + Math::countLeadingZeros(colPat);
  437. assert(xCollision >= 0);
  438. minXCollision = std::min(minXCollision, xCollision);
  439. }
  440. }
  441. }
  442. }
  443. if (minXCollision < 256) {
  444. vdp.setSpriteStatus(vdp.getStatusReg0() | 0x20);
  445. // x-coord should be increased by 12
  446. // y-coord 8
  447. collisionX = minXCollision + 12;
  448. collisionY = line - vdp.getLineZero() + 8;
  449. return; // don't check lines with higher Y-coord
  450. }
  451. }
  452. }
  453. // version 1: initial version
  454. // version 2: bug fix: also serialize 'currentLine'
  455. template<typename Archive>
  456. void SpriteChecker::serialize(Archive& ar, unsigned version)
  457. {
  458. if (ar.isLoader()) {
  459. // Recalculate from VDP state:
  460. // - frameStartTime
  461. frameStartTime.reset(vdp.getFrameStartTime());
  462. // - updateSpritesMethod, planar
  463. setDisplayMode(vdp.getDisplayMode());
  464. // We don't serialize spriteCount[] and spriteBuffer[].
  465. // These are only used to draw the MSX screen, they don't have
  466. // any influence on the MSX state. So the effect of not
  467. // serializing these two is that no sprites will be shown in the
  468. // first (partial) frame after loadstate.
  469. ranges::fill(spriteCount, 0);
  470. // content of spriteBuffer[] doesn't matter if spriteCount[] is 0
  471. }
  472. ar.serialize("collisionX", collisionX,
  473. "collisionY", collisionY);
  474. if (ar.versionAtLeast(version, 2)) {
  475. ar.serialize("currentLine", currentLine);
  476. } else {
  477. currentLine = 0;
  478. }
  479. }
  480. INSTANTIATE_SERIALIZE_METHODS(SpriteChecker);
  481. } // namespace openmsx