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.getLimitSpritesSetting())
24  , frameStartTime(time)
25 {
26  vram.spriteAttribTable.setObserver(this);
28 }
29 
30 void SpriteChecker::reset(EmuTime::param time)
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  // This implementation contains a double for-loop. The outer loop goes
101  // over the sprites, the inner loop over the to-be-checked lines. This
102  // is not the order in which the real VDP performs this operation: the
103  // real VDP renders line-per-line and for each line checks all 32
104  // sprites.
105  //
106  // Though this 'reverse' order allows to skip over very large regions
107  // of the inner loop: we only have to process the lines were a
108  // particular sprite is actually visible. I measured this makes this
109  // routine 4x-5x faster!
110  //
111  // This routine also needs to detect the sprite number of the 'first'
112  // 5th-sprite-condition. With 'first' meaning the first line where this
113  // condition occurs. Because our loops are swapped compared to the real
114  // VDP, we need some extra fixup logic to correctly detect this.
115 
116  // Calculate display line.
117  // This is the line sprites are checked at; the line they are displayed
118  // at is one lower.
119  int displayDelta = vdp.getVerticalScroll() - vdp.getLineZero();
120 
121  // Get sprites for this line and detect 5th sprite if any.
122  bool limitSprites = limitSpritesSetting.getBoolean();
123  int size = vdp.getSpriteSize();
124  bool mag = vdp.isSpriteMag();
125  int magSize = (mag + 1) * size;
126  const byte* attributePtr = vram.spriteAttribTable.getReadArea(0, 32 * 4);
127  byte patternIndexMask = size == 16 ? 0xFC : 0xFF;
128  int fifthSpriteNum = -1; // no 5th sprite detected yet
129  int fifthSpriteLine = 999; // larger than any possible valid line
130 
131  int sprite = 0;
132  for (; sprite < 32; ++sprite) {
133  int y = attributePtr[4 * sprite + 0];
134  if (y == 208) break;
135 
136  for (int line = minLine; line < maxLine; ++line) {
137  // Calculate line number within the sprite.
138  int displayLine = line + displayDelta;
139  int spriteLine = (displayLine - y) & 0xFF;
140  if (spriteLine >= magSize) {
141  // Skip ahead till sprite becomes visible.
142  line += 256 - spriteLine - 1; // -1 because of for-loop
143  continue;
144  }
145 
146  int visibleIndex = spriteCount[line];
147  if (visibleIndex == 4) {
148  // Find earliest line where this condition occurs.
149  if (line < fifthSpriteLine) {
150  fifthSpriteLine = line;
151  fifthSpriteNum = sprite;
152  }
153  if (limitSprites) continue;
154  }
155 
156  SpriteInfo& sip = spriteBuffer[line][visibleIndex];
157  int patternIndex = attributePtr[4 * sprite + 2] & patternIndexMask;
158  if (mag) spriteLine /= 2;
159  sip.pattern = calculatePatternNP(patternIndex, spriteLine);
160  sip.x = attributePtr[4 * sprite + 1];
161  byte colorAttrib = attributePtr[4 * sprite + 3];
162  if (colorAttrib & 0x80) sip.x -= 32;
163  sip.colorAttrib = colorAttrib;
164 
165  spriteCount[line] = visibleIndex + 1;
166  }
167  }
168 
169  // Update status register.
170  byte status = vdp.getStatusReg0();
171  if (fifthSpriteNum != -1) {
172  // Five sprites on a line.
173  // According to TMS9918.pdf 5th sprite detection is only
174  // active when F flag is zero.
175  if ((status & 0xC0) == 0) {
176  status = 0x40 | (status & 0x20) | fifthSpriteNum;
177  }
178  }
179  if (~status & 0x40) {
180  // No 5th sprite detected, store number of latest sprite processed.
181  status = (status & 0x20) | std::min(sprite, 31);
182  }
183  vdp.setSpriteStatus(status);
184 
185  // Optimisation:
186  // If collision already occurred,
187  // that state is stable until it is reset by a status reg read,
188  // so no need to execute the checks.
189  // The spriteBuffer array is filled now, so we can bail out.
190  if (vdp.getStatusReg0() & 0x20) return;
191 
192  /*
193  Model for sprite collision: (or "coincidence" in TMS9918 data sheet)
194  - Reset when status reg is read.
195  - Set when sprite patterns overlap.
196  - ??? Color doesn't matter: sprites of color 0 can collide.
197  ??? This conflicts with: https://github.com/openMSX/openMSX/issues/1198
198  - Sprites that are partially off-screen position can collide, but only
199  on the in-screen pixels. In other words: sprites cannot collide in
200  the left or right border, only in the visible screen area. Though
201  they can collide in the V9958 extra border mask. This behaviour is
202  the same in sprite mode 1 and 2.
203 
204  Implemented by checking every pair for collisions.
205  For large numbers of sprites that would be slow,
206  but there are max 4 sprites and therefore max 6 pairs.
207  If any collision is found, method returns at once.
208  */
209  bool tp = vdp.getTransparency(); // verified: TP also has effect in sprite mode 1
210  for (int line = minLine; line < maxLine; ++line) {
211  int minXCollision = 999;
212  for (int i = std::min<int>(4, spriteCount[line]); --i >= 1; ) {
213  auto color1 = spriteBuffer[line][i].colorAttrib & 0xf;
214  if (tp && (color1 == 0)) continue;
215  int x_i = spriteBuffer[line][i].x;
216  SpritePattern pattern_i = spriteBuffer[line][i].pattern;
217  for (int j = i; --j >= 0; ) {
218  auto color2 = spriteBuffer[line][j].colorAttrib & 0xf;
219  if (tp && (color2 == 0)) continue;
220  // Do sprite i and sprite j collide?
221  int x_j = spriteBuffer[line][j].x;
222  int dist = x_j - x_i;
223  if ((-magSize < dist) && (dist < magSize)) {
224  SpritePattern pattern_j = spriteBuffer[line][j].pattern;
225  if (dist < 0) {
226  pattern_j <<= -dist;
227  } else {
228  pattern_j >>= dist;
229  }
230  SpritePattern colPat = pattern_i & pattern_j;
231  if (x_i < 0) {
232  assert(x_i >= -32);
233  colPat &= (1 << (32 + x_i)) - 1;
234  }
235  if (colPat) {
236  int xCollision = x_i + Math::countLeadingZeros(colPat);
237  assert(xCollision >= 0);
238  minXCollision = std::min(minXCollision, xCollision);
239  }
240  }
241  }
242  }
243  if (minXCollision < 256) {
244  vdp.setSpriteStatus(vdp.getStatusReg0() | 0x20);
245  // verified: collision coords are also filled
246  // in for sprite mode 1
247  // x-coord should be increased by 12
248  // y-coord 8
249  collisionX = minXCollision + 12;
250  collisionY = line - vdp.getLineZero() + 8;
251  return; // don't check lines with higher Y-coord
252  }
253  }
254 }
255 
256 void SpriteChecker::updateSprites2(int limit)
257 {
258  // TODO merge this with updateSprites1()?
259  if (vdp.spritesEnabledFast()) {
260  if (vdp.isDisplayEnabled()) {
261  // in display area
262  checkSprites2(currentLine, limit);
263  } else {
264  // in border, only check last line of top border
265  int l0 = vdp.getLineZero() - 1;
266  if ((currentLine <= l0) && (l0 < limit)) {
267  checkSprites2(l0, l0 + 1);
268  }
269  }
270  }
271  currentLine = limit;
272 }
273 
274 inline void SpriteChecker::checkSprites2(int minLine, int maxLine)
275 {
276  // See comment in checkSprites1() about order of inner and outer loops.
277 
278  // Calculate display line.
279  // This is the line sprites are checked at; the line they are displayed
280  // at is one lower.
281  int displayDelta = vdp.getVerticalScroll() - vdp.getLineZero();
282 
283  // Get sprites for this line and detect 5th sprite if any.
284  bool limitSprites = limitSpritesSetting.getBoolean();
285  int size = vdp.getSpriteSize();
286  bool mag = vdp.isSpriteMag();
287  int magSize = (mag + 1) * size;
288  int patternIndexMask = (size == 16) ? 0xFC : 0xFF;
289  int ninthSpriteNum = -1; // no 9th sprite detected yet
290  int ninthSpriteLine = 999; // larger than any possible valid line
291 
292  // Because it gave a measurable performance boost, we duplicated the
293  // code for planar and non-planar modes.
294  int sprite = 0;
295  if (planar) {
296  const byte* attributePtr0;
297  const byte* attributePtr1;
299  512, 32 * 4, attributePtr0, attributePtr1);
300  // TODO: Verify CC implementation.
301  for (; sprite < 32; ++sprite) {
302  int y = attributePtr0[2 * sprite + 0];
303  if (y == 216) break;
304 
305  for (int line = minLine; line < maxLine; ++line) {
306  // Calculate line number within the sprite.
307  int displayLine = line + displayDelta;
308  int spriteLine = (displayLine - y) & 0xFF;
309  if (spriteLine >= magSize) {
310  // Skip ahead till sprite is visible.
311  line += 256 - spriteLine - 1;
312  continue;
313  }
314 
315  int visibleIndex = spriteCount[line];
316  if (visibleIndex == 8) {
317  // Find earliest line where this condition occurs.
318  if (line < ninthSpriteLine) {
319  ninthSpriteLine = line;
320  ninthSpriteNum = sprite;
321  }
322  if (limitSprites) continue;
323  }
324 
325  if (mag) spriteLine /= 2;
326  int colorIndex = (~0u << 10) | (sprite * 16 + spriteLine);
327  byte colorAttrib =
328  vram.spriteAttribTable.readPlanar(colorIndex);
329 
330  SpriteInfo& sip = spriteBuffer[line][visibleIndex];
331  int patternIndex = attributePtr0[2 * sprite + 1] & patternIndexMask;
332  sip.pattern = calculatePatternPlanar(patternIndex, spriteLine);
333  sip.x = attributePtr1[2 * sprite + 0];
334  if (colorAttrib & 0x80) sip.x -= 32;
335  sip.colorAttrib = colorAttrib;
336 
337  // set sentinel (see below)
338  spriteBuffer[line][visibleIndex + 1].colorAttrib = 0;
339  spriteCount[line] = visibleIndex + 1;
340  }
341  }
342  } else {
343  const byte* attributePtr0 =
344  vram.spriteAttribTable.getReadArea(512, 32 * 4);
345  // TODO: Verify CC implementation.
346  for (; sprite < 32; ++sprite) {
347  int y = attributePtr0[4 * sprite + 0];
348  if (y == 216) break;
349 
350  for (int line = minLine; line < maxLine; ++line) {
351  // Calculate line number within the sprite.
352  int displayLine = line + displayDelta;
353  int spriteLine = (displayLine - y) & 0xFF;
354  if (spriteLine >= magSize) {
355  // Skip ahead till sprite is visible.
356  line += 256 - spriteLine - 1;
357  continue;
358  }
359 
360  int visibleIndex = spriteCount[line];
361  if (visibleIndex == 8) {
362  // Find earliest line where this condition occurs.
363  if (line < ninthSpriteLine) {
364  ninthSpriteLine = line;
365  ninthSpriteNum = sprite;
366  }
367  if (limitSprites) continue;
368  }
369 
370  if (mag) spriteLine /= 2;
371  int colorIndex = (~0u << 10) | (sprite * 16 + spriteLine);
372  byte colorAttrib =
373  vram.spriteAttribTable.readNP(colorIndex);
374  // Sprites with CC=1 are only visible if preceded by
375  // a sprite with CC=0. However they DO contribute towards
376  // the max-8-sprites-per-line limit, so we can't easily
377  // filter them here. See also
378  // https://github.com/openMSX/openMSX/issues/497
379 
380  SpriteInfo& sip = spriteBuffer[line][visibleIndex];
381  int patternIndex = attributePtr0[4 * sprite + 2] & patternIndexMask;
382  sip.pattern = calculatePatternNP(patternIndex, spriteLine);
383  sip.x = attributePtr0[4 * sprite + 1];
384  if (colorAttrib & 0x80) sip.x -= 32;
385  sip.colorAttrib = colorAttrib;
386 
387  // Set sentinel. Sentinel is actually only
388  // needed for sprites with CC=1.
389  // In the past we set the sentinel (for all
390  // lines) at the end. But it's slightly faster
391  // to do it only for lines that actually
392  // contain sprites (even if sentinel gets
393  // overwritten a couple of times for lines with
394  // many sprites).
395  spriteBuffer[line][visibleIndex + 1].colorAttrib = 0;
396  spriteCount[line] = visibleIndex + 1;
397  }
398  }
399  }
400 
401  // Update status register.
402  byte status = vdp.getStatusReg0();
403  if (ninthSpriteNum != -1) {
404  // Nine sprites on a line.
405  // According to TMS9918.pdf 5th sprite detection is only
406  // active when F flag is zero. Stuck to this for V9938.
407  // Dragon Quest 2 needs this.
408  if ((status & 0xC0) == 0) {
409  status = 0x40 | (status & 0x20) | ninthSpriteNum;
410  }
411  }
412  if (~status & 0x40) {
413  // No 9th sprite detected, store number of latest sprite processed.
414  status = (status & 0x20) | std::min(sprite, 31);
415  }
416  vdp.setSpriteStatus(status);
417 
418  // Optimisation:
419  // If collision already occurred,
420  // that state is stable until it is reset by a status reg read,
421  // so no need to execute the checks.
422  // The visibleSprites array is filled now, so we can bail out.
423  if (vdp.getStatusReg0() & 0x20) return;
424 
425  /*
426  Model for sprite collision: (or "coincidence" in TMS9918 data sheet)
427  - Reset when status reg is read.
428  - Set when sprite patterns overlap.
429  - ??? Color doesn't matter: sprites of color 0 can collide.
430  ??? TODO: V9938 data book denies this (page 98).
431  ??? This conflicts with: https://github.com/openMSX/openMSX/issues/1198
432  - Sprites that are partially off-screen position can collide, but only
433  on the in-screen pixels. In other words: sprites cannot collide in
434  the left or right border, only in the visible screen area. Though
435  they can collide in the V9958 extra border mask. This behaviour is
436  the same in sprite mode 1 and 2.
437 
438  Implemented by checking every pair for collisions.
439  For large numbers of sprites that would be slow.
440  There are max 8 sprites and therefore max 42 pairs.
441  TODO: Maybe this is slow... Think of something faster.
442  Probably new approach is needed anyway for OR-ing.
443  */
444  bool tp = vdp.getTransparency();
445  for (int line = minLine; line < maxLine; ++line) {
446  int minXCollision = 999; // no collision
447  SpriteInfo* visibleSprites = spriteBuffer[line];
448  for (int i = std::min<int>(8, spriteCount[line]); --i >= 1; ) {
449  auto colorAttrib1 = visibleSprites[i].colorAttrib;
450  if (tp && ((colorAttrib1 & 0xf) == 0)) continue; // no collision with color=0
451  // If CC or IC is set, this sprite cannot collide.
452  if (colorAttrib1 & 0x60) continue;
453 
454  int x_i = visibleSprites[i].x;
455  SpritePattern pattern_i = visibleSprites[i].pattern;
456  for (int j = i; --j >= 0; ) {
457  auto colorAttrib2 = visibleSprites[j].colorAttrib;
458  if (tp && ((colorAttrib2 & 0xf) == 0)) continue; // no collision with color=0
459  // If CC or IC is set, this sprite cannot collide.
460  if (colorAttrib2 & 0x60) continue;
461 
462  // Do sprite i and sprite j collide?
463  int x_j = visibleSprites[j].x;
464  int dist = x_j - x_i;
465  if ((-magSize < dist) && (dist < magSize)) {
466  SpritePattern pattern_j = visibleSprites[j].pattern;
467  if (dist < 0) {
468  pattern_j <<= -dist;
469  } else {
470  pattern_j >>= dist;
471  }
472  SpritePattern colPat = pattern_i & pattern_j;
473  if (x_i < 0) {
474  assert(x_i >= -32);
475  colPat &= (1 << (32 + x_i)) - 1;
476  }
477  if (colPat) {
478  int xCollision = x_i + Math::countLeadingZeros(colPat);
479  assert(xCollision >= 0);
480  minXCollision = std::min(minXCollision, xCollision);
481  }
482  }
483  }
484  }
485  if (minXCollision < 256) {
486  vdp.setSpriteStatus(vdp.getStatusReg0() | 0x20);
487  // x-coord should be increased by 12
488  // y-coord 8
489  collisionX = minXCollision + 12;
490  collisionY = line - vdp.getLineZero() + 8;
491  return; // don't check lines with higher Y-coord
492  }
493  }
494 }
495 
496 // version 1: initial version
497 // version 2: bug fix: also serialize 'currentLine'
498 template<typename Archive>
499 void SpriteChecker::serialize(Archive& ar, unsigned version)
500 {
501  if (ar.isLoader()) {
502  // Recalculate from VDP state:
503  // - frameStartTime
504  frameStartTime.reset(vdp.getFrameStartTime());
505  // - updateSpritesMethod, planar
506  setDisplayMode(vdp.getDisplayMode());
507 
508  // We don't serialize spriteCount[] and spriteBuffer[].
509  // These are only used to draw the MSX screen, they don't have
510  // any influence on the MSX state. So the effect of not
511  // serializing these two is that no sprites will be shown in the
512  // first (partial) frame after loadstate.
513  ranges::fill(spriteCount, 0);
514  // content of spriteBuffer[] doesn't matter if spriteCount[] is 0
515  }
516  ar.serialize("collisionX", collisionX,
517  "collisionY", collisionY);
518  if (ar.versionAtLeast(version, 2)) {
519  ar.serialize("currentLine", currentLine);
520  } else {
521  currentLine = 0;
522  }
523 }
525 
526 } // namespace openmsx
void reset(EmuTime::param time)
Puts the sprite checker in its initial state.
void setSpriteStatus(byte value)
Should only be used by SpriteChecker.
Definition: VDP.hh:563
constexpr void reset(EmuTime::param e)
Reset the clock to start ticking at the given time.
Definition: Clock.hh:102
EmuTime::param getFrameStartTime() const
Definition: VDP.hh:455
byte colorAttrib
Bit 3..0 are index in palette.
vecN< N, T > min(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:274
byte readNP(unsigned index) const
Reads a byte from VRAM in its current state.
Definition: VDPVRAM.hh:253
uint8_t byte
8 bit unsigned integer
Definition: openmsx.hh:26
DisplayMode getDisplayMode() const
Get the display mode the VDP is in.
Definition: VDP.hh:141
int getLineZero() const
Get the absolute line number of display line zero.
Definition: VDP.hh:314
byte getVerticalScroll() const
Gets the current vertical scroll (line displayed at Y=0).
Definition: VDP.hh:269
VRAMWindow spriteAttribTable
Definition: VDPVRAM.hh:671
void setObserver(VRAMObserver *newObserver)
Register an observer on this VRAM window.
Definition: VDPVRAM.hh:279
size_t size(std::string_view utf8)
void fill(ForwardRange &&range, const T &value)
Definition: ranges.hh:191
void serialize(Archive &ar, unsigned version)
bool getBoolean() const noexcept
SpritePattern pattern
Pattern of this sprite line, corrected for magnification.
void frameStart(EmuTime::param time)
Signals the start of a new frame.
bool getTransparency() const
Gets the current transparency setting.
Definition: VDP.hh:174
uint32_t SpritePattern
Bitmap of length 32 describing a sprite pattern.
bool isSpriteMag() const
Are sprites magnified?
Definition: VDP.hh:467
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
VRAMWindow spritePatternTable
Definition: VDPVRAM.hh:672
int getSpriteSize() const
Gets the sprite size in pixels (8/16).
Definition: VDP.hh:461
Contains all the information to draw a line of a sprite.
byte getStatusReg0() const
Should only be used by SpriteChecker.
Definition: VDP.hh:553
Unified implementation of MSX Video Display Processors (VDPs).
Definition: VDP.hh:61
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:981
bool spritesEnabledFast() const
Same as spritesEnabled(), but may only be called in sprite mode 1 or 2.
Definition: VDP.hh:254
Class containing all settings for renderers.
const byte * getReadArea(unsigned index, unsigned size) const
Gets a pointer to a contiguous part of the VRAM.
Definition: VDPVRAM.hh:219
byte readPlanar(unsigned index) const
Similar to readNP, but now with planar addressing.
Definition: VDPVRAM.hh:261
void getReadAreaPlanar(unsigned index, unsigned size, const byte *&ptr0, const byte *&ptr1) const
Similar to getReadArea(), but now with planar addressing mode.
Definition: VDPVRAM.hh:233
bool isDisplayEnabled() const
Is the display enabled? Both the regular border and forced blanking by clearing the display enable bi...
Definition: VDP.hh:237
int16_t x
X-coordinate of sprite, corrected for early clock.
SpriteChecker(VDP &vdp, RenderSettings &renderSettings, EmuTime::param time)
Create a sprite checker.
unsigned countLeadingZeros(unsigned x)
Count the number of leading zero-bits in the given word.
Definition: Math.hh:235