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 "narrow.hh"
9#include "ranges.hh"
10#include "serialize_meta.hh"
11#include "unreachable.hh"
12#include <array>
13#include <cstdint>
14#include <span>
15
16namespace openmsx {
17
18class RenderSettings;
19class BooleanSetting;
20
21class SpriteChecker final : public VRAMObserver
22{
23public:
29 using SpritePattern = uint32_t;
30
33 struct SpriteInfo {
39 int16_t x;
46 };
47
53 SpriteChecker(VDP& vdp, RenderSettings& renderSettings,
54 EmuTime::param time);
55
59 void reset(EmuTime::param time);
60
65 inline void sync(EmuTime::param time) {
66 if (!updateSpritesMethod) {
67 // Optimization: skip vram sync and sprite checks
68 // in sprite mode 0.
69 return;
70 }
71 // Debug:
72 // This method is not re-entrant, so check explicitly that it is not
73 // re-entered. This can disappear once the VDP-internal scheduling
74 // has become stable.
75 #ifdef DEBUG
76 static bool syncInProgress = false;
77 assert(!syncInProgress);
78 syncInProgress = true;
79 #endif
80 vram.sync(time);
81 checkUntil(time);
82 #ifdef DEBUG
83 syncInProgress = false;
84 #endif
85 }
86
89 inline void resetStatus() {
90 // TODO: Used to be 0x5F, but that is contradicted by
91 // TMS9918.pdf. Check on real MSX.
92 vdp.setSpriteStatus(vdp.getStatusReg0() & 0x1F);
93 }
94
99 inline void updateDisplayMode(DisplayMode mode, EmuTime::param time) {
100 sync(time);
101 setDisplayMode(mode);
102
103 // The following is only required when switching from sprite
104 // mode0 to some other mode (in other case it has no effect).
105 // Because in mode 0, currentLine is not updated.
106 currentLine = narrow<int>(frameStartTime.getTicksTill_fast(time)
108 // Every line in mode0 has 0 sprites, but none of the lines
109 // are ever requested by the renderer, except for the last
110 // line, because sprites are checked one line before they
111 // are displayed. Though frameStart() already makes sure
112 // spriteCount contains zero for all lines.
113 // spriteCount[currentLine - 1] = 0;
114 }
115
120 inline void updateDisplayEnabled(bool enabled, EmuTime::param time) {
121 (void)enabled;
122 sync(time);
123 // TODO: Speed up sprite checking in display disabled case.
124 }
125
130 inline void updateSpritesEnabled(bool enabled, EmuTime::param time) {
131 (void)enabled;
132 sync(time);
133 // TODO: Speed up sprite checking in display disabled case.
134 }
135
142 inline void updateSpriteSizeMag(byte sizeMag, EmuTime::param time) {
143 (void)sizeMag;
144 sync(time);
145 // TODO: Precalc something?
146 }
147
152 inline void updateTransparency(bool tp, EmuTime::param time) {
153 (void)tp;
154 sync(time);
155 }
156
161 inline void updateVerticalScroll(int scroll, EmuTime::param time) {
162 (void)scroll;
163 sync(time);
164 // TODO: Precalc something?
165 }
166
172 inline void checkUntil(EmuTime::param time) {
173 // TODO:
174 // Currently the sprite checking is done atomically at the end of
175 // the display line. In reality, sprite checking is probably done
176 // during most of the line. Run tests on real MSX to make a more
177 // accurate model of sprite checking.
178 int limit = narrow<int>(frameStartTime.getTicksTill_fast(time)
180 if (currentLine < limit) {
181 // Call the right update method for the current display mode.
182 (this->*updateSpritesMethod)(limit);
183 }
184 }
185
188 [[nodiscard]] inline int getCollisionX(EmuTime::param time) {
189 sync(time);
190 return collisionX;
191 }
192
195 [[nodiscard]] inline int getCollisionY(EmuTime::param time) {
196 sync(time);
197 return collisionY;
198 }
199
204 inline void resetCollision() {
205 collisionX = collisionY = 0;
206 }
207
211 inline void frameStart(EmuTime::param time) {
212 frameStartTime.reset(time);
213 currentLine = 0;
214 ranges::fill(spriteCount, 0);
215 // TODO: Reset anything else? Does the real VDP?
216 }
217
221 inline void frameEnd(EmuTime::param time) {
222 sync(time);
223 }
224
238 [[nodiscard]] inline std::span<const SpriteInfo> getSprites(int line) const {
239 // Compensate for the fact sprites are checked one line earlier
240 // than they are displayed.
241 line--;
242
243 // TODO: Is there ever a sprite on absolute line 0?
244 // Maybe there is, but it is never displayed.
245 if (line < 0) return {};
246
247 return subspan(spriteBuffer[line], 0, spriteCount[line]);
248 }
249
250 // VRAMObserver implementation:
251
252 void updateVRAM(unsigned /*offset*/, EmuTime::param time) override {
253 checkUntil(time);
254 }
255
256 void updateWindow(bool /*enabled*/, EmuTime::param time) override {
257 sync(time);
258 }
259
260 template<typename Archive>
261 void serialize(Archive& ar, unsigned version);
262
263private:
266 inline void setDisplayMode(DisplayMode mode) {
267 switch (mode.getSpriteMode(vdp.isMSX1VDP())) {
268 case 0:
269 updateSpritesMethod = nullptr;
270 break;
271 case 1:
272 updateSpritesMethod = &SpriteChecker::updateSprites1;
273 break;
274 case 2:
275 updateSpritesMethod = &SpriteChecker::updateSprites2;
276 planar = mode.isPlanar();
277 // An alternative is to have a planar and non-planar
278 // updateSprites2 method.
279 break;
280 default:
282 }
283 }
284
287 void updateSprites1(int limit);
288
291 void updateSprites2(int limit);
292
301 [[nodiscard]] inline SpritePattern calculatePatternNP(unsigned patternNr, unsigned y);
302 [[nodiscard]] inline SpritePattern calculatePatternPlanar(unsigned patternNr, unsigned y);
303
314 inline void checkSprites1(int minLine, int maxLine);
315
326 inline void checkSprites2(int minLine, int maxLine);
327
328private:
329 using UpdateSpritesMethod = void (SpriteChecker::*)(int limit);
330 UpdateSpritesMethod updateSpritesMethod;
331
334 VDP& vdp;
335
338 VDPVRAM& vram;
339
346 BooleanSetting& limitSpritesSetting;
347
350 Clock<VDP::TICKS_PER_SECOND> frameStartTime;
351
354 int currentLine;
355
359 int collisionX;
360
366 int collisionY;
367
371 std::array<std::array<SpriteInfo, 32 + 1>, 313> spriteBuffer; // +1 for sentinel
372
378 std::array<uint8_t, 313> spriteCount;
379
383 bool planar;
384};
386
387} // namespace openmsx
388
389#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:140
constexpr int getSpriteMode(bool isMSX1) const
Get the sprite mode of this display mode.
Definition: DisplayMode.hh:158
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.
std::span< const SpriteInfo > getSprites(int line) const
Get sprites for a display line.
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.
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:64
byte getStatusReg0() const
Should only be used by SpriteChecker.
Definition: VDP.hh:571
static constexpr int TICKS_PER_LINE
Number of VDP clock ticks per line.
Definition: VDP.hh:73
void setSpriteStatus(byte value)
Should only be used by SpriteChecker.
Definition: VDP.hh:581
bool isMSX1VDP() const
Is this an MSX1 VDP?
Definition: VDP.hh:96
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)
constexpr void fill(ForwardRange &&range, const T &value)
Definition: ranges.hh:287
constexpr auto subspan(Range &&range, size_t offset, size_t count=std::dynamic_extent)
Definition: ranges.hh:446
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