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 updateTransparency(bool tp, EmuTime::param time) {
150  (void)tp;
151  sync(time);
152  }
153 
158  inline void updateVerticalScroll(int scroll, EmuTime::param time) {
159  (void)scroll;
160  sync(time);
161  // TODO: Precalc something?
162  }
163 
169  inline void checkUntil(EmuTime::param time) {
170  // TODO:
171  // Currently the sprite checking is done atomically at the end of
172  // the display line. In reality, sprite checking is probably done
173  // during most of the line. Run tests on real MSX to make a more
174  // accurate model of sprite checking.
175  int limit = frameStartTime.getTicksTill_fast(time)
177  if (currentLine < limit) {
178  // Call the right update method for the current display mode.
179  (this->*updateSpritesMethod)(limit);
180  }
181  }
182 
185  [[nodiscard]] inline int getCollisionX(EmuTime::param time) {
186  sync(time);
187  return collisionX;
188  }
189 
192  [[nodiscard]] inline int getCollisionY(EmuTime::param time) {
193  sync(time);
194  return collisionY;
195  }
196 
201  inline void resetCollision() {
202  collisionX = collisionY = 0;
203  }
204 
208  inline void frameStart(EmuTime::param time) {
209  frameStartTime.reset(time);
210  currentLine = 0;
211  ranges::fill(spriteCount, 0);
212  // TODO: Reset anything else? Does the real VDP?
213  }
214 
218  inline void frameEnd(EmuTime::param time) {
219  sync(time);
220  }
221 
235  [[nodiscard]] inline int getSprites(int line, const SpriteInfo*& visibleSprites) const {
236  // Compensate for the fact sprites are checked one line earlier
237  // than they are displayed.
238  line--;
239 
240  // TODO: Is there ever a sprite on absolute line 0?
241  // Maybe there is, but it is never displayed.
242  if (line < 0) return 0;
243 
244  visibleSprites = spriteBuffer[line];
245  return spriteCount[line];
246  }
247 
248  // VRAMObserver implementation:
249 
250  void updateVRAM(unsigned /*offset*/, EmuTime::param time) override {
251  checkUntil(time);
252  }
253 
254  void updateWindow(bool /*enabled*/, EmuTime::param time) override {
255  sync(time);
256  }
257 
258  template<typename Archive>
259  void serialize(Archive& ar, unsigned version);
260 
261 private:
264  inline void setDisplayMode(DisplayMode mode) {
265  switch (mode.getSpriteMode(vdp.isMSX1VDP())) {
266  case 0:
267  updateSpritesMethod = nullptr;
268  break;
269  case 1:
270  updateSpritesMethod = &SpriteChecker::updateSprites1;
271  break;
272  case 2:
273  updateSpritesMethod = &SpriteChecker::updateSprites2;
274  planar = mode.isPlanar();
275  // An alternative is to have a planar and non-planar
276  // updateSprites2 method.
277  break;
278  default:
279  UNREACHABLE;
280  }
281  }
282 
285  void updateSprites1(int limit);
286 
289  void updateSprites2(int limit);
290 
299  [[nodiscard]] inline SpritePattern calculatePatternNP(unsigned patternNr, unsigned y);
300  [[nodiscard]] inline SpritePattern calculatePatternPlanar(unsigned patternNr, unsigned y);
301 
312  inline void checkSprites1(int minLine, int maxLine);
313 
324  inline void checkSprites2(int minLine, int maxLine);
325 
326 private:
327  using UpdateSpritesMethod = void (SpriteChecker::*)(int limit);
328  UpdateSpritesMethod updateSpritesMethod;
329 
332  VDP& vdp;
333 
336  VDPVRAM& vram;
337 
344  BooleanSetting& limitSpritesSetting;
345 
348  Clock<VDP::TICKS_PER_SECOND> frameStartTime;
349 
352  int currentLine;
353 
357  int collisionX;
358 
364  int collisionY;
365 
369  SpriteInfo spriteBuffer[313][32 + 1]; // +1 for sentinel
370 
376  uint8_t spriteCount[313];
377 
381  bool planar;
382 };
384 
385 } // namespace openmsx
386 
387 #endif
constexpr void reset(EmuTime::param e)
Reset the clock to start ticking at the given time.
Definition: Clock.hh:102
constexpr unsigned getTicksTill_fast(EmuTime::param e) const
Same as above, only faster, Though the time interval may not be too large.
Definition: Clock.hh:70
Represents a VDP display mode.
Definition: DisplayMode.hh:16
constexpr 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:148
constexpr int getSpriteMode(bool isMSX1) const
Get the sprite mode of this display mode.
Definition: DisplayMode.hh:166
Class containing all settings for renderers.
void frameStart(EmuTime::param time)
Signals the start of a new frame.
void sync(EmuTime::param time)
Update sprite checking to specified time.
void reset(EmuTime::param time)
Puts the sprite checker in its initial state.
void serialize(Archive &ar, unsigned version)
void frameEnd(EmuTime::param time)
Signals the end of the current frame.
uint32_t SpritePattern
Bitmap of length 32 describing a sprite pattern.
void updateTransparency(bool tp, EmuTime::param time)
Informs the sprite checker of a change in the TP bit (R#8 bit 5)
int getCollisionX(EmuTime::param time)
Get X coordinate of sprite collision.
void updateVerticalScroll(int scroll, EmuTime::param time)
Informs the sprite checker of a vertical scroll change.
void updateWindow(bool, EmuTime::param time) override
Informs the observer that the entire VRAM window will change.
void updateDisplayMode(DisplayMode mode, EmuTime::param time)
Informs the sprite checker of a VDP display mode change.
void resetStatus()
Clear status bits triggered by reading of S#0.
void updateDisplayEnabled(bool enabled, EmuTime::param time)
Informs the sprite checker of a VDP display enabled change.
void checkUntil(EmuTime::param time)
Update sprite checking until specified line.
void updateSpriteSizeMag(byte sizeMag, EmuTime::param time)
Informs the sprite checker of sprite size or magnification changes.
int getCollisionY(EmuTime::param time)
Get Y coordinate of sprite collision.
void resetCollision()
Reset sprite collision coordinates.
void updateVRAM(unsigned, EmuTime::param time) override
Informs the observer of a change in VRAM contents.
SpriteChecker(VDP &vdp, RenderSettings &renderSettings, EmuTime::param time)
Create a sprite checker.
void updateSpritesEnabled(bool enabled, EmuTime::param time)
Informs the sprite checker of sprite enable changes.
int getSprites(int line, const SpriteInfo *&visibleSprites) const
Get sprites for a display line.
void sync(EmuTime::param time)
Update VRAM state to specified moment in time.
Definition: VDPVRAM.hh:402
Unified implementation of MSX Video Display Processors (VDPs).
Definition: VDP.hh:63
byte getStatusReg0() const
Should only be used by SpriteChecker.
Definition: VDP.hh:568
static constexpr int TICKS_PER_LINE
Number of VDP clock ticks per line.
Definition: VDP.hh:72
void setSpriteStatus(byte value)
Should only be used by SpriteChecker.
Definition: VDP.hh:578
bool isMSX1VDP() const
Is this an MSX1 VDP?
Definition: VDP.hh:93
Interface that can be registered at VRAMWindow, to be called when the contents of the VRAM inside tha...
Definition: VRAMObserver.hh:11
This file implemented 3 utility functions:
Definition: Autofire.cc:9
SERIALIZE_CLASS_VERSION(CassettePlayer, 2)
void fill(ForwardRange &&range, const T &value)
Definition: ranges.hh:226
Contains all the information to draw a line of a sprite.
SpritePattern pattern
Pattern of this sprite line, corrected for magnification.
byte colorAttrib
Bit 3..0 are index in palette.
int16_t x
X-coordinate of sprite, corrected for early clock.
#define UNREACHABLE
Definition: unreachable.hh:38