OXYGENHUB
◆ main openmsx / src / video / SpriteConverter.hh
6.3 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
e8555c89 Andy McCall 1
/*
2
TODO:
3
- Implement sprite pixels in Graphic 5.
4
*/
5
6
#ifndef SPRITECONVERTER_HH
7
#define SPRITECONVERTER_HH
8
9
#include "SpriteChecker.hh"
10
11
#include "DisplayMode.hh"
12
13
#include "narrow.hh"
14
15
#include <cstdint>
16
#include <ranges>
17
#include <span>
18
19
namespace openmsx {
20
21
/** Utility class for converting VRAM contents to host pixels.
22
  */
23
class SpriteConverter
24
{
25
public:
26
	using Pixel = uint32_t;
27
28
	// TODO: Move some methods to .cc?
29
30
	/** Constructor.
31
	  * After construction, also call the various set methods to complete
32
	  * initialisation.
33
	  * @param spriteChecker_ Delivers the sprite data to be rendered.
34
	  * @param pal The initial palette. Can later be changed via setPallette().
35
	  */
36
	explicit SpriteConverter(SpriteChecker& spriteChecker_,
37
	                         std::span<const Pixel, 16> pal)
38
		: spriteChecker(spriteChecker_)
39
		, palette(pal)
40
	{
41
	}
42
43
	/** Update the transparency setting.
44
	  * @param enabled The new value.
45
	  */
46
	void setTransparency(bool enabled)
47
	{
48
		transparency = enabled;
49
	}
50
51
	/** Notify SpriteConverter of a display mode change.
52
	  * @param newMode The new display mode.
53
	  */
54
	void setDisplayMode(DisplayMode newMode)
55
	{
56
		mode = newMode;
57
	}
58
59
	/** Set palette to use for converting sprites.
60
	  * This palette is stored by reference, so any modifications to it
61
	  * will be used while drawing.
62
	  * @param newPalette 16-entry array containing the sprite palette.
63
	  */
64
	void setPalette(std::span<const Pixel, 16> newPalette)
65
	{
66
		palette = newPalette;
67
	}
68
69
	static bool clipPattern(int& x, SpriteChecker::SpritePattern& pattern,
70
	                        int minX, int maxX)
71
	{
72
		if (int before = minX - x; before > 0) {
73
			if (before >= 32) {
74
				// 32 pixels before minX -> not visible
75
				return false;
76
			}
77
			pattern <<= before;
78
			x = minX;
79
		}
80
		if (int after = maxX - x; after < 32) {
81
			// close to maxX (or past)
82
			if (after <= 0) {
83
				// past maxX -> not visible
84
				return false;
85
			}
86
			auto mask = narrow_cast<int>(0x8000'0000);
87
			pattern &= (mask >> (after - 1));
88
		}
89
		return true; // visible
90
	}
91
92
	/** Draw sprites in sprite mode 1.
93
	  * @param absLine Absolute line number.
94
	  *     Range is [0..262) for NTSC and [0..313) for PAL.
95
	  * @param minX Minimum X coordinate to draw (inclusive).
96
	  * @param maxX Maximum X coordinate to draw (exclusive).
97
	  * @param pixelPtr Pointer to memory to draw to.
98
	  */
99
	void drawMode1(int absLine, int minX, int maxX, std::span<Pixel> pixelPtr) const
100
	{
101
		// Determine sprites visible on this line.
102
		auto visibleSprites = spriteChecker.getSprites(absLine);
103
		// Optimisation: return at once if no sprites on this line.
104
		// Lines without any sprites are very common in most programs.
105
		if (visibleSprites.empty()) return;
106
107
		// Render using overdraw.
108
		for (const auto& si : std::views::reverse(visibleSprites)) {
109
			// Get sprite info.
110
			Pixel colIndex = si.colorAttrib & 0x0F;
111
			// Don't draw transparent sprites in sprite mode 1.
112
			// Verified on real V9958: TP bit also has effect in
113
			// sprite mode 1.
114
			if (colIndex == 0 && transparency) continue;
115
			Pixel color = palette[colIndex];
116
			SpriteChecker::SpritePattern pattern = si.pattern;
117
			int x = si.x;
118
			// Clip sprite pattern to render range.
119
			if (!clipPattern(x, pattern, minX, maxX)) continue;
120
			// Convert pattern to pixels.
121
			Pixel* p = &pixelPtr[x];
122
			while (pattern) {
123
				// Draw pixel if sprite has a dot.
124
				if (pattern & 0x8000'0000) {
125
					*p = color;
126
				}
127
				// Advancing behaviour.
128
				pattern <<= 1;
129
				p++;
130
			}
131
		}
132
	}
133
134
	/** Draw sprites in sprite mode 2.
135
	  * Make sure the pixel pointers point to a large enough memory area:
136
	  * 256 pixels for ZOOM_256 and ZOOM_REAL in 256-pixel wide modes;
137
	  * 512 pixels for ZOOM_REAL in 512-pixel wide modes.
138
	  * @param absLine Absolute line number.
139
	  *     Range is [0..262) for NTSC and [0..313) for PAL.
140
	  * @param minX Minimum X coordinate to draw (inclusive).
141
	  * @param maxX Maximum X coordinate to draw (exclusive).
142
	  * @param pixelPtr Pointer to memory to draw to.
143
	  */
144
	template<unsigned MODE>
145
	void drawMode2(int absLine, int minX, int maxX, std::span<Pixel> pixelPtr) const
146
	{
147
		// Determine sprites visible on this line.
148
		auto visibleSprites = spriteChecker.getSprites(absLine);
149
		// Optimisation: return at once if no sprites on this line.
150
		// Lines without any sprites are very common in most programs.
151
		if (visibleSprites.empty()) return;
152
		std::span visibleSpritesWithSentinel{visibleSprites.data(),
153
		                                     visibleSprites.size() +1};
154
155
		// Sprites with CC=1 are only visible if preceded by a sprite
156
		// with CC=0. Therefor search for first sprite with CC=0.
157
		int first = 0;
158
		do {
159
			if ((visibleSprites[first].colorAttrib & 0x40) == 0) [[likely]] {
160
				break;
161
			}
162
			++first;
163
		} while (first < int(visibleSprites.size()));
164
		for (int i = narrow<int>(visibleSprites.size() - 1); i >= first; --i) {
165
			const SpriteChecker::SpriteInfo& info = visibleSprites[i];
166
			int x = info.x;
167
			SpriteChecker::SpritePattern pattern = info.pattern;
168
			// Clip sprite pattern to render range.
169
			if (!clipPattern(x, pattern, minX, maxX)) continue;
170
			uint8_t c = info.colorAttrib & 0x0F;
171
			if (c == 0 && transparency) continue;
172
			while (pattern) {
173
				if (pattern & 0x8000'0000) {
174
					uint8_t color = c;
175
					// Merge in any following CC=1 sprites.
176
					for (int j = i + 1; /*sentinel*/; ++j) {
177
						const SpriteChecker::SpriteInfo& info2 =
178
							visibleSpritesWithSentinel[j];
179
						if (!(info2.colorAttrib & 0x40)) break;
180
						unsigned shift2 = x - info2.x;
181
						if ((shift2 < 32) &&
182
						   ((info2.pattern << shift2) & 0x8000'0000)) {
183
							color |= info2.colorAttrib & 0x0F;
184
						}
185
					}
186
					if constexpr (MODE == DisplayMode::GRAPHIC5) {
187
						Pixel pixL = palette[color >> 2];
188
						Pixel pixR = palette[color & 3];
189
						pixelPtr[x * 2 + 0] = pixL;
190
						pixelPtr[x * 2 + 1] = pixR;
191
					} else {
192
						Pixel pix = palette[color];
193
						if constexpr (MODE == DisplayMode::GRAPHIC6) {
194
							pixelPtr[x * 2 + 0] = pix;
195
							pixelPtr[x * 2 + 1] = pix;
196
						} else {
197
							pixelPtr[x] = pix;
198
						}
199
					}
200
				}
201
				++x;
202
				pattern <<= 1;
203
			}
204
		}
205
	}
206
207
private:
208
	SpriteChecker& spriteChecker;
209
210
	/** The current sprite palette.
211
	  */
212
	std::span<const Pixel, 16> palette;
213
214
	/** VDP transparency setting (R#8, bit5).
215
	  */
216
	bool transparency;
217
218
	/** The current display mode.
219
	  */
220
	DisplayMode mode;
221
};
222
223
} // namespace openmsx
224
225
#endif