openMSX
SpriteChecker.hh
Go to the documentation of this file.
1 #ifndef SPRITECHECKER_HH
2 #define SPRITECHECKER_HH
3 
4 #include "VDP.hh"
5 #include "VDPVRAM.hh"
6 #include "VRAMObserver.hh"
7 #include "DisplayMode.hh"
8 #include "serialize_meta.hh"
9 #include "unreachable.hh"
10 
11 namespace openmsx {
12 
13 class RenderSettings;
14 class BooleanSetting;
15 
17 {
18 public:
24  typedef unsigned SpritePattern;
25 
28  struct SpriteInfo {
34  short int x;
41  };
42 
48  SpriteChecker(VDP& vdp, RenderSettings& renderSettings,
49  EmuTime::param time);
50 
54  void reset(EmuTime::param time);
55 
60  inline void sync(EmuTime::param time) {
61  if (!updateSpritesMethod) {
62  // Optimization: skip vram sync and sprite checks
63  // in sprite mode 0.
64  return;
65  }
66  // Debug:
67  // This method is not re-entrant, so check explicitly that it is not
68  // re-entered. This can disappear once the VDP-internal scheduling
69  // has become stable.
70  #ifdef DEBUG
71  static bool syncInProgress = false;
72  assert(!syncInProgress);
73  syncInProgress = true;
74  #endif
75  vram.sync(time);
76  checkUntil(time);
77  #ifdef DEBUG
78  syncInProgress = false;
79  #endif
80  }
81 
84  inline void resetStatus() {
85  // TODO: Used to be 0x5F, but that is contradicted by
86  // TMS9918.pdf. Check on real MSX.
87  vdp.setSpriteStatus(vdp.getStatusReg0() & 0x1F);
88  }
89 
94  inline void updateDisplayMode(DisplayMode mode, EmuTime::param time) {
95  sync(time);
96  setDisplayMode(mode);
97 
98  // The following is only required when switching from sprite
99  // mode0 to some other mode (in other case it has no effect).
100  // Because in mode 0, currentLine is not updated.
101  currentLine = frameStartTime.getTicksTill_fast(time)
103  // Every line in mode0 has 0 sprites, but none of the lines
104  // are ever requested by the renderer, except for the last
105  // line, because sprites are checked one line before they
106  // are displayed. Though frameStart() already makes sure
107  // spriteCount contains zero for all lines.
108  // spriteCount[currentLine - 1] = 0;
109  }
110 
115  inline void updateDisplayEnabled(bool enabled, EmuTime::param time) {
116  (void)enabled;
117  sync(time);
118  // TODO: Speed up sprite checking in display disabled case.
119  }
120 
125  inline void updateSpritesEnabled(bool enabled, EmuTime::param time) {
126  (void)enabled;
127  sync(time);
128  // TODO: Speed up sprite checking in display disabled case.
129  }
130 
137  inline void updateSpriteSizeMag(byte sizeMag, EmuTime::param time) {
138  (void)sizeMag;
139  sync(time);
140  // TODO: Precalc something?
141  }
142 
147  inline void updateVerticalScroll(int scroll, EmuTime::param time) {
148  (void)scroll;
149  sync(time);
150  // TODO: Precalc something?
151  }
152 
158  inline void checkUntil(EmuTime::param time) {
159  // TODO:
160  // Currently the sprite checking is done atomically at the end of
161  // the display line. In reality, sprite checking is probably done
162  // during most of the line. Run tests on real MSX to make a more
163  // accurate model of sprite checking.
164  int limit = frameStartTime.getTicksTill_fast(time)
166  if (currentLine < limit) {
167  // Call the right update method for the current display mode.
168  (this->*updateSpritesMethod)(limit);
169  }
170  }
171 
174  inline int getCollisionX(EmuTime::param time) {
175  sync(time);
176  return collisionX;
177  }
178 
181  inline int getCollisionY(EmuTime::param time) {
182  sync(time);
183  return collisionY;
184  }
185 
190  inline void resetCollision() {
191  collisionX = collisionY = 0;
192  }
193 
197  inline void frameStart(EmuTime::param time) {
198  frameStartTime.reset(time);
199  currentLine = 0;
200  for (int i = 0; i < 313; i++) spriteCount[i] = 0;
201  // TODO: Reset anything else? Does the real VDP?
202  }
203 
207  inline void frameEnd(EmuTime::param time) {
208  sync(time);
209  }
210 
224  inline int getSprites(int line, const SpriteInfo*& visibleSprites) const {
225  // Compensate for the fact sprites are checked one line earlier
226  // than they are displayed.
227  line--;
228 
229  // TODO: Is there ever a sprite on absolute line 0?
230  // Maybe there is, but it is never displayed.
231  if (line < 0) return 0;
232 
233  visibleSprites = spriteBuffer[line];
234  return spriteCount[line];
235  }
236 
237  // VRAMObserver implementation:
238 
239  void updateVRAM(unsigned /*offset*/, EmuTime::param time) {
240  checkUntil(time);
241  }
242 
243  void updateWindow(bool /*enabled*/, EmuTime::param time) {
244  sync(time);
245  }
246 
247  template<typename Archive>
248  void serialize(Archive& ar, unsigned version);
249 
250 private:
253  inline void setDisplayMode(DisplayMode mode) {
254  switch (mode.getSpriteMode()) {
255  case 0:
256  updateSpritesMethod = nullptr;
257  break;
258  case 1:
259  updateSpritesMethod = &SpriteChecker::updateSprites1;
260  break;
261  case 2:
262  updateSpritesMethod = &SpriteChecker::updateSprites2;
263  planar = mode.isPlanar();
264  // An alternative is to have a planar and non-planar
265  // updateSprites2 method.
266  break;
267  default:
268  UNREACHABLE;
269  }
270  }
271 
274  void updateSprites1(int limit);
275 
278  void updateSprites2(int limit);
279 
288  inline SpritePattern calculatePatternNP(unsigned patternNr, unsigned y);
289  inline SpritePattern calculatePatternPlanar(unsigned patternNr, unsigned y);
290 
301  inline void checkSprites1(int minLine, int maxLine);
302 
313  inline void checkSprites2(int minLine, int maxLine);
314 
315  typedef void (SpriteChecker::*UpdateSpritesMethod)(int limit);
316  UpdateSpritesMethod updateSpritesMethod;
317 
320  VDP& vdp;
321 
324  VDPVRAM& vram;
325 
332  BooleanSetting& limitSpritesSetting;
333 
336  Clock<VDP::TICKS_PER_SECOND> frameStartTime;
337 
340  int currentLine;
341 
345  int collisionX;
346 
352  int collisionY;
353 
357  SpriteInfo spriteBuffer[313][32 + 1]; // +1 for sentinel
358 
364  int spriteCount[313];
365 
369  bool planar;
370 };
372 
373 } // namespace openmsx
374 
375 #endif