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