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 "ranges.hh"
10 #include "unreachable.hh"
11 #include <cstdint>
12 
13 namespace openmsx {
14 
15 class RenderSettings;
16 class BooleanSetting;
17 
18 class SpriteChecker final : public VRAMObserver
19 {
20 public:
26  using SpritePattern = uint32_t;
27 
30  struct SpriteInfo {
36  int16_t x;
43  };
44 
50  SpriteChecker(VDP& vdp, RenderSettings& renderSettings,
51  EmuTime::param time);
52 
56  void reset(EmuTime::param time);
57 
62  inline void sync(EmuTime::param time) {
63  if (!updateSpritesMethod) {
64  // Optimization: skip vram sync and sprite checks
65  // in sprite mode 0.
66  return;
67  }
68  // Debug:
69  // This method is not re-entrant, so check explicitly that it is not
70  // re-entered. This can disappear once the VDP-internal scheduling
71  // has become stable.
72  #ifdef DEBUG
73  static bool syncInProgress = false;
74  assert(!syncInProgress);
75  syncInProgress = true;
76  #endif
77  vram.sync(time);
78  checkUntil(time);
79  #ifdef DEBUG
80  syncInProgress = false;
81  #endif
82  }
83 
86  inline void resetStatus() {
87  // TODO: Used to be 0x5F, but that is contradicted by
88  // TMS9918.pdf. Check on real MSX.
89  vdp.setSpriteStatus(vdp.getStatusReg0() & 0x1F);
90  }
91 
96  inline void updateDisplayMode(DisplayMode mode, EmuTime::param time) {
97  sync(time);
98  setDisplayMode(mode);
99 
100  // The following is only required when switching from sprite
101  // mode0 to some other mode (in other case it has no effect).
102  // Because in mode 0, currentLine is not updated.
103  currentLine = frameStartTime.getTicksTill_fast(time)
105  // Every line in mode0 has 0 sprites, but none of the lines
106  // are ever requested by the renderer, except for the last
107  // line, because sprites are checked one line before they
108  // are displayed. Though frameStart() already makes sure
109  // spriteCount contains zero for all lines.
110  // spriteCount[currentLine - 1] = 0;
111  }
112 
117  inline void updateDisplayEnabled(bool enabled, EmuTime::param time) {
118  (void)enabled;
119  sync(time);
120  // TODO: Speed up sprite checking in display disabled case.
121  }
122 
127  inline void updateSpritesEnabled(bool enabled, EmuTime::param time) {
128  (void)enabled;
129  sync(time);
130  // TODO: Speed up sprite checking in display disabled case.
131  }
132 
139  inline void updateSpriteSizeMag(byte sizeMag, EmuTime::param time) {
140  (void)sizeMag;
141  sync(time);
142  // TODO: Precalc something?
143  }
144 
149  inline void updateVerticalScroll(int scroll, EmuTime::param time) {
150  (void)scroll;
151  sync(time);
152  // TODO: Precalc something?
153  }
154 
160  inline void checkUntil(EmuTime::param time) {
161  // TODO:
162  // Currently the sprite checking is done atomically at the end of
163  // the display line. In reality, sprite checking is probably done
164  // during most of the line. Run tests on real MSX to make a more
165  // accurate model of sprite checking.
166  int limit = frameStartTime.getTicksTill_fast(time)
168  if (currentLine < limit) {
169  // Call the right update method for the current display mode.
170  (this->*updateSpritesMethod)(limit);
171  }
172  }
173 
176  inline int getCollisionX(EmuTime::param time) {
177  sync(time);
178  return collisionX;
179  }
180 
183  inline int getCollisionY(EmuTime::param time) {
184  sync(time);
185  return collisionY;
186  }
187 
192  inline void resetCollision() {
193  collisionX = collisionY = 0;
194  }
195 
199  inline void frameStart(EmuTime::param time) {
200  frameStartTime.reset(time);
201  currentLine = 0;
202  ranges::fill(spriteCount, 0);
203  // TODO: Reset anything else? Does the real VDP?
204  }
205 
209  inline void frameEnd(EmuTime::param time) {
210  sync(time);
211  }
212 
226  inline int getSprites(int line, const SpriteInfo*& visibleSprites) const {
227  // Compensate for the fact sprites are checked one line earlier
228  // than they are displayed.
229  line--;
230 
231  // TODO: Is there ever a sprite on absolute line 0?
232  // Maybe there is, but it is never displayed.
233  if (line < 0) return 0;
234 
235  visibleSprites = spriteBuffer[line];
236  return spriteCount[line];
237  }
238 
239  // VRAMObserver implementation:
240 
241  void updateVRAM(unsigned /*offset*/, EmuTime::param time) override {
242  checkUntil(time);
243  }
244 
245  void updateWindow(bool /*enabled*/, EmuTime::param time) override {
246  sync(time);
247  }
248 
249  template<typename Archive>
250  void serialize(Archive& ar, unsigned version);
251 
252 private:
255  inline void setDisplayMode(DisplayMode mode) {
256  switch (mode.getSpriteMode(vdp.isMSX1VDP())) {
257  case 0:
258  updateSpritesMethod = nullptr;
259  break;
260  case 1:
261  updateSpritesMethod = &SpriteChecker::updateSprites1;
262  break;
263  case 2:
264  updateSpritesMethod = &SpriteChecker::updateSprites2;
265  planar = mode.isPlanar();
266  // An alternative is to have a planar and non-planar
267  // updateSprites2 method.
268  break;
269  default:
270  UNREACHABLE;
271  }
272  }
273 
276  void updateSprites1(int limit);
277 
280  void updateSprites2(int limit);
281 
290  inline SpritePattern calculatePatternNP(unsigned patternNr, unsigned y);
291  inline SpritePattern calculatePatternPlanar(unsigned patternNr, unsigned y);
292 
303  inline void checkSprites1(int minLine, int maxLine);
304 
315  inline void checkSprites2(int minLine, int maxLine);
316 
317  using UpdateSpritesMethod = void (SpriteChecker::*)(int limit);
318  UpdateSpritesMethod updateSpritesMethod;
319 
322  VDP& vdp;
323 
326  VDPVRAM& vram;
327 
334  BooleanSetting& limitSpritesSetting;
335 
338  Clock<VDP::TICKS_PER_SECOND> frameStartTime;
339 
342  int currentLine;
343 
347  int collisionX;
348 
354  int collisionY;
355 
359  SpriteInfo spriteBuffer[313][32 + 1]; // +1 for sentinel
360 
366  uint8_t spriteCount[313];
367 
371  bool planar;
372 };
374 
375 } // namespace openmsx
376 
377 #endif
void resetStatus()
Clear status bits triggered by reading of S#0.
void updateVRAM(unsigned, EmuTime::param time) override
Informs the observer of a change in VRAM contents.
void reset(EmuTime::param time)
Puts the sprite checker in its initial state.
void updateSpritesEnabled(bool enabled, EmuTime::param time)
Informs the sprite checker of sprite enable changes.
static const int TICKS_PER_LINE
Number of VDP clock ticks per line.
Definition: VDP.hh:72
byte colorAttrib
Bit 3..0 are index in palette.
void updateDisplayMode(DisplayMode mode, EmuTime::param time)
Informs the sprite checker of a VDP display mode change.
uint8_t byte
8 bit unsigned integer
Definition: openmsx.hh:26
void updateDisplayEnabled(bool enabled, EmuTime::param time)
Informs the sprite checker of a VDP display enabled change.
Represents a VDP display mode.
Definition: DisplayMode.hh:14
int getCollisionY(EmuTime::param time)
Get Y coordinate of sprite collision.
int getCollisionX(EmuTime::param time)
Get X coordinate of sprite collision.
void fill(ForwardRange &&range, const T &value)
Definition: ranges.hh:179
void frameEnd(EmuTime::param time)
Signals the end of the current frame.
int getSpriteMode(bool isMSX1) const
Get the sprite mode of this display mode.
Definition: DisplayMode.hh:170
void serialize(Archive &ar, unsigned version)
void updateVerticalScroll(int scroll, EmuTime::param time)
Informs the sprite checker of a vertical scroll change.
SERIALIZE_CLASS_VERSION(CassettePlayer, 2)
int getSprites(int line, const SpriteInfo *&visibleSprites) const
Get sprites for a display line.
SpritePattern pattern
Pattern of this sprite line, corrected for magnification.
bool isPlanar() const
Is VRAM "planar" in the current display mode? Graphic 6 and 7 spread their bytes over two VRAM ICs...
Definition: DisplayMode.hh:152
void frameStart(EmuTime::param time)
Signals the start of a new frame.
Interface that can be registered at VRAMWindow, to be called when the contents of the VRAM inside tha...
Definition: VRAMObserver.hh:11
uint32_t SpritePattern
Bitmap of length 32 describing a sprite pattern.
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
Contains all the information to draw a line of a sprite.
void updateWindow(bool, EmuTime::param time) override
Informs the observer that the entire VRAM window will change.
void sync(EmuTime::param time)
Update sprite checking to specified time.
Unified implementation of MSX Video Display Processors (VDPs).
Definition: VDP.hh:61
void resetCollision()
Reset sprite collision coordinates.
Class containing all settings for renderers.
Manages VRAM contents and synchronises the various users of the VRAM.
Definition: VDPVRAM.hh:384
int16_t x
X-coordinate of sprite, corrected for early clock.
SpriteChecker(VDP &vdp, RenderSettings &renderSettings, EmuTime::param time)
Create a sprite checker.
void updateSpriteSizeMag(byte sizeMag, EmuTime::param time)
Informs the sprite checker of sprite size or magnification changes.
#define UNREACHABLE
Definition: unreachable.hh:38
void checkUntil(EmuTime::param time)
Update sprite checking until specified line.