OXYGENHUB
◆ main openmsx / src / video / SpriteChecker.hh
12.4 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#ifndef SPRITECHECKER_HH
2
#define SPRITECHECKER_HH
3
4
#include "DisplayMode.hh"
5
#include "VDP.hh"
6
#include "VDPVRAM.hh"
7
#include "VRAMObserver.hh"
8
9
#include "narrow.hh"
10
#include "ranges.hh"
11
#include "serialize_meta.hh"
12
#include "unreachable.hh"
13
14
#include <algorithm>
15
#include <array>
16
#include <cstdint>
17
#include <span>
18
19
namespace openmsx {
20
21
class RenderSettings;
22
class BooleanSetting;
23
24
class SpriteChecker final : public VRAMObserver
25
{
26
public:
27
	/** Bitmap of length 32 describing a sprite pattern.
28
	  * Visible pixels are 1, transparent pixels are 0.
29
	  * If the sprite is less than 32 pixels wide,
30
	  * the lower bits are unused.
31
	  */
32
	using SpritePattern = uint32_t;
33
34
	/** Contains all the information to draw a line of a sprite.
35
	  */
36
	struct SpriteInfo {
37
		/** Pattern of this sprite line, corrected for magnification.
38
		  */
39
		SpritePattern pattern;
40
		/** X-coordinate of sprite, corrected for early clock.
41
		  */
42
		int16_t x;
43
		/** Bit 3..0 are index in palette.
44
		  * Bit 6 is 0 for sprite mode 1 like behaviour,
45
		  * or 1 for OR-ing of sprite colors.
46
		  * Other bits are undefined.
47
		  */
48
		uint8_t colorAttrib;
49
	};
50
51
	static constexpr SpritePattern doublePattern(SpritePattern a)
52
	{
53
		// bit-pattern "abcd...." gets expanded to "aabbccdd"
54
		// upper 16 bits (of a 32 bit number) contain the pattern
55
		// lower 16 bits must be zero
56
		//                               // abcdefghijklmnop0000000000000000
57
		a = (a | (a >> 8)) & 0xFF00FF00; // abcdefgh00000000ijklmnop00000000
58
		a = (a | (a >> 4)) & 0xF0F0F0F0; // abcd0000efgh0000ijkl0000mnop0000
59
		a = (a | (a >> 2)) & 0xCCCCCCCC; // ab00cd00ef00gh00ij00kl00mn00op00
60
		a = (a | (a >> 1)) & 0xAAAAAAAA; // a0b0c0d0e0f0g0h0i0j0k0l0m0n0o0p0
61
		return a | (a >> 1);             // aabbccddeeffgghhiijjkkllmmnnoopp
62
	}
63
64
	/** Create a sprite checker.
65
	  * @param vdp The VDP this sprite checker is part of.
66
	  * @param renderSettings TODO
67
	  * @param time TODO
68
	  */
69
	SpriteChecker(VDP& vdp, RenderSettings& renderSettings,
70
	              EmuTime time);
71
72
	/** Puts the sprite checker in its initial state.
73
	  * @param time The moment in time this reset occurs.
74
	  */
75
	void reset(EmuTime time);
76
77
	/** Update sprite checking to specified time.
78
	  * This includes a VRAM sync.
79
	  * @param time The moment in emulated time to update to.
80
	  */
81
	void sync(EmuTime time) {
82
		if (!updateSpritesMethod) {
83
			// Optimization: skip vram sync and sprite checks
84
			// in sprite mode 0.
85
			return;
86
		}
87
		// Debug:
88
		// This method is not re-entrant, so check explicitly that it is not
89
		// re-entered. This can disappear once the VDP-internal scheduling
90
		// has become stable.
91
		#ifdef DEBUG
92
		static bool syncInProgress = false;
93
		assert(!syncInProgress);
94
		syncInProgress = true;
95
		#endif
96
		vram.sync(time);
97
		checkUntil(time);
98
		#ifdef DEBUG
99
		syncInProgress = false;
100
		#endif
101
	}
102
103
	/** Clear status bits triggered by reading of S#0.
104
	  */
105
	void resetStatus() {
106
		// TODO: Used to be 0x5F, but that is contradicted by
107
		//       TMS9918.pdf. Check on real MSX.
108
		vdp.setSpriteStatus(vdp.getStatusReg0() & 0x1F);
109
	}
110
111
	/** Informs the sprite checker of a VDP display mode change.
112
	  * @param mode The new display mode.
113
	  * @param time The moment in emulated time this change occurs.
114
	  */
115
	void updateDisplayMode(DisplayMode mode, EmuTime time) {
116
		sync(time);
117
		setDisplayMode(mode);
118
119
		// The following is only required when switching from sprite
120
		// mode0 to some other mode (in other case it has no effect).
121
		// Because in mode 0, currentLine is not updated.
122
		currentLine = narrow<int>(frameStartTime.getTicksTill(time)
123
		                 / VDP::TICKS_PER_LINE);
124
		// Every line in mode0 has 0 sprites, but none of the lines
125
		// are ever requested by the renderer, except for the last
126
		// line, because sprites are checked one line before they
127
		// are displayed. Though frameStart() already makes sure
128
		// spriteCount contains zero for all lines.
129
		//   spriteCount[currentLine - 1] = 0;
130
	}
131
132
	/** Informs the sprite checker of a VDP display enabled change.
133
	  * @param enabled The new display enabled state.
134
	  * @param time The moment in emulated time this change occurs.
135
	  */
136
	void updateDisplayEnabled(bool enabled, EmuTime time) {
137
		(void)enabled;
138
		sync(time);
139
		// TODO: Speed up sprite checking in display disabled case.
140
	}
141
142
	/** Informs the sprite checker of sprite enable changes.
143
	  * @param enabled The new sprite enabled state.
144
	  * @param time The moment in emulated time this change occurs.
145
	  */
146
	void updateSpritesEnabled(bool enabled, EmuTime time) {
147
		(void)enabled;
148
		sync(time);
149
		// TODO: Speed up sprite checking in display disabled case.
150
	}
151
152
	/** Informs the sprite checker of sprite size or magnification changes.
153
	  * @param sizeMag The new size and magnification state.
154
	  *   Bit 0 is magnification: 0 = normal, 1 = doubled.
155
	  *   Bit 1 is size: 0 = 8x8, 1 = 16x16.
156
	  * @param time The moment in emulated time this change occurs.
157
	  */
158
	void updateSpriteSizeMag(uint8_t sizeMag, EmuTime time) {
159
		(void)sizeMag;
160
		sync(time);
161
		// TODO: Precalc something?
162
	}
163
164
	/** Informs the sprite checker of a change in the TP bit (R#8 bit 5)
165
	  * @param tp The new transparency value.
166
	  * @param time The moment in emulated time this change occurs.
167
	  */
168
	void updateTransparency(bool tp, EmuTime time) {
169
		(void)tp;
170
		sync(time);
171
	}
172
173
	/** Informs the sprite checker of a vertical scroll change.
174
	  * @param scroll The new scroll value.
175
	  * @param time The moment in emulated time this change occurs.
176
	  */
177
	void updateVerticalScroll(int scroll, EmuTime time) {
178
		(void)scroll;
179
		sync(time);
180
		// TODO: Precalc something?
181
	}
182
183
	/** Update sprite checking until specified line.
184
	  * VRAM must be up-to-date before this method is called.
185
	  * It is not allowed to call this method in a spriteless display mode.
186
	  * @param time The moment in emulated time to update to.
187
	  */
188
	void checkUntil(EmuTime time) {
189
		// TODO:
190
		// Currently the sprite checking is done atomically at the end of
191
		// the display line. In reality, sprite checking is probably done
192
		// during most of the line. Run tests on real MSX to make a more
193
		// accurate model of sprite checking.
194
		int limit = narrow<int>(frameStartTime.getTicksTill(time)
195
		               / VDP::TICKS_PER_LINE);
196
		if (currentLine < limit) {
197
			// Call the right update method for the current display mode.
198
			(this->*updateSpritesMethod)(limit);
199
		}
200
	}
201
202
	/** Get X coordinate of sprite collision.
203
	  */
204
	[[nodiscard]] int getCollisionX(EmuTime time) {
205
		sync(time);
206
		return collisionX;
207
	}
208
209
	/** Get Y coordinate of sprite collision.
210
	  */
211
	[[nodiscard]] int getCollisionY(EmuTime time) {
212
		sync(time);
213
		return collisionY;
214
	}
215
216
	/** Reset sprite collision coordinates.
217
	  * This happens directly after a read, so a timestamp for syncing is
218
	  * not necessary.
219
	  */
220
	void resetCollision() {
221
		collisionX = collisionY = 0;
222
	}
223
224
	/** Signals the start of a new frame.
225
	  * @param time Moment in emulated time the new frame starts.
226
	  */
227
	void frameStart(EmuTime time) {
228
		frameStartTime.reset(time);
229
		currentLine = 0;
230
		std::ranges::fill(spriteCount, 0);
231
		// TODO: Reset anything else? Does the real VDP?
232
	}
233
234
	/** Signals the end of the current frame.
235
	  * @param time Moment in emulated time the current frame ends.
236
	  */
237
	void frameEnd(EmuTime time) {
238
		sync(time);
239
	}
240
241
	/** Get sprites for a display line.
242
	  * Returns the contents of the line the last time it was sprite checked;
243
	  * before getting the sprites, you should sync to a moment in time
244
	  * after the sprites are checked, or you'll get last frame's sprites.
245
	  * @param line The absolute line number for which sprites should
246
	  *   be returned. Range is [0..313) for PAL and [0..262) for NTSC.
247
	  * @return The to-be-displayed sprites.
248
	  *   The buffers content remains valid until the next time the VDP
249
	  *   is scheduled.
250
	  *   This buffer is followed by a sentinel, this sentinel is NOT
251
	  *   included in the returned span (IOW the span can be extended with
252
	  *   one extra element which is the sentinel).
253
	  */
254
	[[nodiscard]] std::span<const SpriteInfo> getSprites(int line) const {
255
		// Compensate for the fact sprites are checked one line earlier
256
		// than they are displayed.
257
		line--;
258
259
		// TODO: Is there ever a sprite on absolute line 0?
260
		//       Maybe there is, but it is never displayed.
261
		if (line < 0) return {};
262
263
		return subspan(spriteBuffer[line], 0, spriteCount[line]);
264
	}
265
266
	// VRAMObserver implementation:
267
268
	void updateVRAM(unsigned /*offset*/, EmuTime time) override {
269
		checkUntil(time);
270
	}
271
272
	void updateWindow(bool /*enabled*/, EmuTime time) override {
273
		sync(time);
274
	}
275
276
	template<typename Archive>
277
	void serialize(Archive& ar, unsigned version);
278
279
private:
280
	/** Calculate 'updateSpritesMethod' and 'planar'.
281
	  */
282
	void setDisplayMode(DisplayMode mode) {
283
		switch (mode.getSpriteMode(vdp.isMSX1VDP())) {
284
		case 0:
285
			updateSpritesMethod = nullptr;
286
			break;
287
		case 1:
288
			updateSpritesMethod = &SpriteChecker::updateSprites1;
289
			break;
290
		case 2:
291
			updateSpritesMethod = &SpriteChecker::updateSprites2;
292
			planar = mode.isPlanar();
293
			// An alternative is to have a planar and non-planar
294
			// updateSprites2 method.
295
			break;
296
		default:
297
			UNREACHABLE;
298
		}
299
	}
300
301
	/** Calculate sprite patterns for sprite mode 1.
302
	  */
303
	void updateSprites1(int limit);
304
305
	/** Calculate sprite patterns for sprite mode 2.
306
	  */
307
	void updateSprites2(int limit);
308
309
	/** Calculates a sprite pattern.
310
	  * @param patternNr Number of the sprite pattern [0..255].
311
	  *   For 16x16 sprites, patternNr should be a multiple of 4.
312
	  * @param y The line number within the sprite: 0 <= y < size.
313
	  * @return A bit field of the sprite pattern.
314
	  *   Bit 31 is the leftmost bit of the sprite.
315
	  *   Unused bits are zero.
316
	  */
317
	[[nodiscard]] SpritePattern calculatePatternNP(unsigned patternNr, unsigned y) const;
318
	[[nodiscard]] SpritePattern calculatePatternPlanar(unsigned patternNr, unsigned y) const;
319
320
	/** Check sprite collision and number of sprites per line.
321
	  * This routine implements sprite mode 1 (MSX1).
322
	  * Separated from display code to make MSX behaviour consistent
323
	  * no matter how displaying is handled.
324
	  * @param minLine The first line number (inclusive) for which sprites
325
	  *                should be checked.
326
	  * @param maxLine The last line number (exclusive) for which sprites
327
	  *                should be checked.
328
	  * @effect Fills in the spriteBuffer and spriteCount arrays.
329
	  */
330
	void checkSprites1(int minLine, int maxLine);
331
332
	/** Check sprite collision and number of sprites per line.
333
	  * This routine implements sprite mode 2 (MSX2).
334
	  * Separated from display code to make MSX behaviour consistent
335
	  * no matter how displaying is handled.
336
	  * @param minLine The first line number (inclusive) for which sprites
337
	  *                should be checked.
338
	  * @param maxLine The last line number (exclusive) for which sprites
339
	  *                should be checked.
340
	  * @effect Fills in the spriteBuffer and spriteCount arrays.
341
	  */
342
	void checkSprites2(int minLine, int maxLine);
343
344
private:
345
	using UpdateSpritesMethod = void (SpriteChecker::*)(int limit);
346
	UpdateSpritesMethod updateSpritesMethod;
347
348
	/** The VDP this sprite checker is part of.
349
	  */
350
	VDP& vdp;
351
352
	/** The VRAM to get sprites data from.
353
	  */
354
	VDPVRAM& vram;
355
356
	/** Limit number of sprites per display line?
357
	  * Option only affects display, not MSX state.
358
	  * In other words: when false there is no limit to the number of
359
	  * sprites drawn, but the status register acts like the usual limit
360
	  * is still effective.
361
	  */
362
	BooleanSetting& limitSpritesSetting;
363
364
	/** The emulation time when this frame was started (vsync).
365
	  */
366
	Clock<VDP::TICKS_PER_SECOND> frameStartTime;
367
368
	/** Sprites are checked up to and excluding this display line.
369
	  */
370
	int currentLine;
371
372
	/** X coordinate of sprite collision.
373
	  * 9 bits long -> [0..511]?
374
	  */
375
	int collisionX;
376
377
	/** Y coordinate of sprite collision.
378
	  * 9 bits long -> [0..511]?
379
	  * Bit 9 contains EO, I guess that's a copy of the even/odd flag
380
	  * of the frame on which the collision occurred.
381
	  */
382
	int collisionY;
383
384
	/** Buffer containing the sprites that are visible on each
385
	  * display line.
386
	  */
387
	std::array<std::array<SpriteInfo, 32 + 1>, VDP::NUM_LINES_MAX> spriteBuffer; // +1 for sentinel
388
389
	/** Buffer containing the number of sprites that are visible
390
	  * on each display line.
391
	  * In other words, spriteCount[i] is the number of sprites
392
	  * in spriteBuffer[i].
393
	  */
394
	std::array<uint8_t, VDP::NUM_LINES_MAX> spriteCount;
395
396
	/** Is current display mode planar or not?
397
	  * TODO: Introduce separate update methods for planar/non-planar modes.
398
	  */
399
	bool planar;
400
};
401
SERIALIZE_CLASS_VERSION(SpriteChecker, 2);
402
403
} // namespace openmsx
404
405
#endif