OXYGENHUB
◆ main openmsx / src / video / v9990 / V9990PxConverter.cc
12.6 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
e8555c89 Andy McCall 1
#include "V9990PxConverter.hh"
2
3
#include "V9990.hh"
4
#include "V9990VRAM.hh"
5
6
#include "ScopedAssign.hh"
7
#include "narrow.hh"
8
#include "ranges.hh"
9
10
#include <algorithm>
11
#include <array>
12
#include <cassert>
13
#include <cstdint>
14
#include <optional>
15
16
namespace openmsx {
17
18
using Pixel = V9990P1Converter::Pixel;
19
20
V9990P1Converter::V9990P1Converter(V9990& vdp_, std::span</*const*/ Pixel, 64> palette64_)
21
	: vdp(vdp_), vram(vdp.getVRAM())
22
	, palette64(palette64_)
23
{
24
}
25
26
V9990P2Converter::V9990P2Converter(V9990& vdp_, std::span</*const*/ Pixel, 64> palette64_)
27
	: vdp(vdp_), vram(vdp.getVRAM()), palette64(palette64_)
28
{
29
}
30
31
struct P1Policy {
32
	static uint8_t readNameTable(const V9990VRAM& vram, unsigned addr) {
33
		return vram.readVRAMP1(addr);
34
	}
35
	static uint8_t readPatternTable(const V9990VRAM& vram, unsigned addr) {
36
		return vram.readVRAMP1(addr);
37
	}
38
	static uint8_t readSpriteAttr(const V9990VRAM& vram, unsigned addr) {
39
		return vram.readVRAMP1(addr);
40
	}
41
	static unsigned spritePatOfst(uint8_t spriteNo, uint8_t spriteY) {
42
		return (128 * ((spriteNo & 0xF0) + spriteY))
43
		     + (  8 *  (spriteNo & 0x0F));
44
	}
45
	static constexpr unsigned SCREEN_WIDTH = 256;
46
	static constexpr unsigned IMAGE_WIDTH = 2 * SCREEN_WIDTH;
47
	static constexpr unsigned NAME_CHARS = IMAGE_WIDTH / 8;
48
	static constexpr unsigned PATTERN_CHARS = SCREEN_WIDTH / 8;
49
};
50
struct P1BackgroundPolicy : P1Policy {
51
	static void draw1(
52
		std::span<const Pixel, 16> palette, Pixel* __restrict buffer,
53
		uint8_t* __restrict /*info*/, size_t p)
54
	{
55
		*buffer = palette[p];
56
	}
57
	static constexpr bool DRAW_BACKDROP = true;
58
};
59
struct P1ForegroundPolicy : P1Policy {
60
	static void draw1(
61
		std::span<const Pixel, 16> palette, Pixel* __restrict buffer,
62
		uint8_t* __restrict info, size_t p)
63
	{
64
		*info = bool(p);
65
		if (p) *buffer = palette[p];
66
	}
67
	static constexpr bool DRAW_BACKDROP = false;
68
};
69
struct P2Policy {
70
	static uint8_t readNameTable(const V9990VRAM& vram, unsigned addr) {
71
		return vram.readVRAMDirect(addr);
72
	}
73
	static uint8_t readPatternTable(const V9990VRAM& vram, unsigned addr) {
74
		return vram.readVRAMBx(addr);
75
	}
76
	static uint8_t readSpriteAttr(const V9990VRAM& vram, unsigned addr) {
77
		return vram.readVRAMDirect(addr);
78
	}
79
	static unsigned spritePatOfst(uint8_t spriteNo, uint8_t spriteY) {
80
		return (256 * (((spriteNo & 0xE0) >> 1) + spriteY))
81
		     + (  8 *  (spriteNo & 0x1F));
82
	}
83
	static void draw1(
84
		std::span<const Pixel, 16> palette, Pixel* __restrict buffer,
85
		uint8_t* __restrict info, size_t p)
86
	{
87
		*info = bool(p);
88
		*buffer = palette[p];
89
	}
90
	static constexpr bool DRAW_BACKDROP = true;
91
	static constexpr unsigned SCREEN_WIDTH = 512;
92
	static constexpr unsigned IMAGE_WIDTH = 2 * SCREEN_WIDTH;
93
	static constexpr unsigned NAME_CHARS = IMAGE_WIDTH / 8;
94
	static constexpr unsigned PATTERN_CHARS = SCREEN_WIDTH / 8;
95
};
96
97
template<typename Policy, bool ALIGNED>
98
static unsigned getPatternAddress(
99
	V9990VRAM& vram, unsigned nameAddr, unsigned patternBase, unsigned x, unsigned y)
100
{
101
	assert(!ALIGNED || ((x & 7) == 0));
102
	unsigned patternNum = (Policy::readNameTable(vram, nameAddr + 0) +
103
	                       Policy::readNameTable(vram, nameAddr + 1) * 256) & 0x1FFF;
104
	constexpr auto PATTERN_PITCH = Policy::PATTERN_CHARS * 8 * (8 / 2);
105
	unsigned x2 = (patternNum % Policy::PATTERN_CHARS) * 4 + (ALIGNED ? 0 : ((x & 7) / 2));
106
	unsigned y2 = (patternNum / Policy::PATTERN_CHARS) * PATTERN_PITCH + y;
107
	return patternBase + y2 + x2;
108
}
109
110
template<typename Policy>
111
static constexpr unsigned nextNameAddr(unsigned addr)
112
{
113
	constexpr auto MASK = (2 * Policy::NAME_CHARS) - 1;
114
	return (addr & ~MASK) | ((addr + 2) & MASK);
115
}
116
117
template<typename Policy, bool CHECK_WIDTH>
118
static void draw2(
119
	V9990VRAM& vram, std::span<const Pixel, 16> palette, Pixel* __restrict& buffer, uint8_t* __restrict& info,
120
	unsigned& address, int& width)
121
{
122
	uint8_t data = Policy::readPatternTable(vram, address++);
123
	Policy::draw1(palette, buffer + 0, info + 0, data >> 4);
124
	if (!CHECK_WIDTH || (width != 1)) {
125
		Policy::draw1(palette, buffer + 1, info + 1, data & 0x0F);
126
	}
127
	width  -= 2;
128
	buffer += 2;
129
	info   += 2;
130
}
131
132
template<typename Policy>
133
static void renderPattern(
134
	V9990VRAM& vram, Pixel* __restrict buffer, std::span<uint8_t> info_,
135
	Pixel bgCol, unsigned x, unsigned y,
136
	unsigned nameTable, unsigned patternBase,
137
	std::span</*const*/ Pixel, 16> palette0, std::span</*const*/ Pixel, 16> palette1)
138
{
139
	assert(x < Policy::IMAGE_WIDTH);
140
	auto width = narrow<int>(info_.size());
141
	if (width == 0) return;
142
	uint8_t* info = info_.data();
143
144
	std::optional<ScopedAssign<Pixel>> col0, col1; // optimized away when not used
145
	if constexpr (Policy::DRAW_BACKDROP) {
146
		// Speedup drawing by temporarily replacing palette index 0.
147
		// OK because palette0 and palette1 never partially overlap, IOW either:
148
		// - palette0 == palette1           (fully overlap)
149
		// - abs(palette0 - palette1) >= 16 (no overlap at all)
150
		col0.emplace(palette0[0], bgCol);
151
		col1.emplace(palette1[0], bgCol);
152
	}
153
154
	unsigned nameAddr = nameTable + (((y / 8) * Policy::NAME_CHARS + (x / 8)) * 2);
155
	y = (y & 7) * Policy::NAME_CHARS * 2;
156
157
	if (x & 7) {
158
		unsigned address = getPatternAddress<Policy, false>(vram, nameAddr, patternBase, x, y);
159
		if (x & 1) {
160
			uint8_t data = Policy::readPatternTable(vram, address);
161
			Policy::draw1((address & 1) ? palette1 : palette0, buffer, info, data & 0x0F);
162
			++address;
163
			++x;
164
			++buffer;
165
			++info;
166
			--width;
167
		}
168
		while ((x & 7) && (width > 0)) {
169
			draw2<Policy, true>(vram, (address & 1) ? palette1 : palette0, buffer, info, address, width);
170
			x += 2;
171
		}
172
		nameAddr = nextNameAddr<Policy>(nameAddr);
173
	}
174
	assert((x & 7) == 0 || (width <= 0));
175
	while ((width & ~7) > 0) {
176
		unsigned address = getPatternAddress<Policy, true>(vram, nameAddr, patternBase, x, y);
177
		draw2<Policy, false>(vram, palette0, buffer, info, address, width);
178
		draw2<Policy, false>(vram, palette1, buffer, info, address, width);
179
		draw2<Policy, false>(vram, palette0, buffer, info, address, width);
180
		draw2<Policy, false>(vram, palette1, buffer, info, address, width);
181
		nameAddr = nextNameAddr<Policy>(nameAddr);
182
	}
183
	assert(width < 8);
184
	if (width > 0) {
185
		unsigned address = getPatternAddress<Policy, true>(vram, nameAddr, patternBase, x, y);
186
		do {
187
			draw2<Policy, true>(vram, (address & 1) ? palette1 : palette0, buffer, info, address, width);
188
		} while (width > 0);
189
	}
190
}
191
192
template<typename Policy> // only used for P1
193
static void renderPattern2(
194
	V9990VRAM& vram, Pixel* buffer, std::span<uint8_t, 256> info, Pixel bgCol, unsigned width1, unsigned width2,
195
	unsigned displayAX, unsigned displayAY, unsigned nameA, unsigned patternA, std::span</*const*/ Pixel, 16> palA,
196
	unsigned displayBX, unsigned displayBY, unsigned nameB, unsigned patternB, std::span</*const*/ Pixel, 16> palB)
197
{
198
	renderPattern<Policy>(
199
		vram, buffer, subspan(info, 0, width1), bgCol,
200
		displayAX, displayAY, nameA, patternA, palA, palA);
201
202
	buffer += width1;
203
	width2 -= width1;
204
	displayBX = (displayBX + width1) & 511;
205
206
	renderPattern<Policy>(
207
		vram, buffer, subspan(info, width1, width2), bgCol,
208
		displayBX, displayBY, nameB, patternB, palB, palB);
209
}
210
211
template<typename Policy>
212
static void renderSprites(
213
	V9990VRAM& vram, unsigned spritePatternTable, std::span<const Pixel, 64> palette64,
214
	Pixel* __restrict buffer, std::span<uint8_t> info,
215
	int displayX, int displayEnd, unsigned displayY)
216
{
217
	constexpr unsigned spriteTable = 0x3FE00;
218
219
	// determine visible sprites
220
	std::array<int, 16 + 1> visibleSprites;
221
	int index = 0;
222
	int index_max = 16;
223
	for (auto sprite : xrange(125)) {
224
		unsigned spriteInfo = spriteTable + 4 * sprite;
225
		uint8_t spriteY = Policy::readSpriteAttr(vram, spriteInfo) + 1;
226
		auto posY = narrow_cast<uint8_t>(displayY - spriteY);
227
		if (posY < 16) {
228
			if (uint8_t attr = Policy::readSpriteAttr(vram, spriteInfo + 3);
229
			    attr & 0x10) {
230
				// Invisible sprites do contribute towards the
231
				// 16-sprites-per-line limit.
232
				index_max--;
233
			} else {
234
				visibleSprites[index++] = sprite;
235
			}
236
			if (index == index_max) break;
237
		}
238
	}
239
	visibleSprites[index] = -1;
240
241
	// actually draw sprites
242
	for (unsigned sprite = 0; visibleSprites[sprite] != -1; ++sprite) {
243
		unsigned addr = spriteTable + 4 * visibleSprites[sprite];
244
		uint8_t spriteAttr = Policy::readSpriteAttr(vram, addr + 3);
245
		bool front = (spriteAttr & 0x20) == 0;
246
		uint8_t level = front ? 2 : 1;
247
		int spriteX = Policy::readSpriteAttr(vram, addr + 2);
248
		spriteX += 256 * (spriteAttr & 0x03);
249
		if (spriteX > 1008) spriteX -= 1024; // hack X coord into -16..1008
250
		uint8_t spriteY  = Policy::readSpriteAttr(vram, addr + 0);
251
		uint8_t spriteNo = Policy::readSpriteAttr(vram, addr + 1);
252
		spriteY = narrow_cast<uint8_t>(displayY - (spriteY + 1));
253
		unsigned patAddr = spritePatternTable + Policy::spritePatOfst(spriteNo, spriteY);
254
		auto palette16 = subspan<16>(palette64, (spriteAttr >> 2) & 0x30);
255
		for (int x = 0; x < 16; x +=2) {
256
			auto draw = [&](int xPos, size_t p) {
257
				if ((displayX <= xPos) && (xPos < displayEnd)) {
258
					size_t xx = xPos - displayX;
259
					if (p) {
260
						if (info[xx] < level) {
261
							buffer[xx] = palette16[p];
262
						}
263
						info[xx] = 2; // also if back-sprite is behind foreground
264
					}
265
				}
266
			};
267
			uint8_t data = Policy::readPatternTable(vram, patAddr++);
268
			draw(spriteX + x + 0, data >> 4);
269
			draw(spriteX + x + 1, data & 0x0F);
270
		}
271
	}
272
}
273
274
void V9990P1Converter::convertLine(
275
	std::span<Pixel> buf, unsigned displayX, unsigned displayY,
276
	unsigned displayYA, unsigned displayYB, bool drawSprites)
277
{
278
	Pixel* __restrict linePtr = buf.data();
279
	auto displayWidth = narrow<unsigned>(buf.size());
280
281
	unsigned prioX = vdp.getPriorityControlX();
282
	unsigned prioY = vdp.getPriorityControlY();
283
	if (displayY >= prioY) prioX = 0;
284
285
	unsigned displayAX = (displayX + vdp.getScrollAX()) & 511;
286
	unsigned displayBX = (displayX + vdp.getScrollBX()) & 511;
287
288
	// Configurable 'roll' only applies to layer A.
289
	// Layer B always rolls at 512 lines.
290
	unsigned rollMask = vdp.getRollMask(0x1FF);
291
	unsigned scrollAY = vdp.getScrollAY();
292
	unsigned scrollBY = vdp.getScrollBY();
293
	unsigned scrollAYBase = scrollAY & ~rollMask & 0x1FF;
294
	unsigned displayAY = scrollAYBase + ((displayYA + scrollAY) & rollMask);
295
	unsigned displayBY =                 (displayYB + scrollBY) & 0x1FF;
296
297
	unsigned displayEnd = displayX + displayWidth;
298
	unsigned end1 = std::max(0, narrow<int>(std::min(prioX, displayEnd)) - narrow<int>(displayX));
299
300
	std::array<uint8_t, 256> info; // filled in later: 0->background, 1->foreground, 2->sprite (front or back)
301
302
	// background + backdrop color
303
	Pixel bgCol = palette64[vdp.getBackDropColor()];
304
	uint8_t offset = vdp.getPaletteOffset();
305
	auto palA = subspan<16>(palette64, (offset & 0x03) << 4);
306
	auto palB = subspan<16>(palette64, (offset & 0x0C) << 2);
307
	renderPattern2<P1BackgroundPolicy>( // does not yet fill-in 'info'
308
		vram, linePtr, /*dummy*/info, bgCol, end1, displayWidth,
309
		displayBX, displayBY, 0x7E000, 0x40000, palB,
310
		displayAX, displayAY, 0x7C000, 0x00000, palA);
311
312
	// foreground + fill-in 'info'
313
	assert(displayWidth <= 256);
314
	renderPattern2<P1ForegroundPolicy>( // fills 'info' with '0' or '1'
315
		vram, linePtr, info, bgCol, end1, displayWidth,
316
		displayAX, displayAY, 0x7C000, 0x00000, palA,
317
		displayBX, displayBY, 0x7E000, 0x40000, palB);
318
319
	// combined back+front sprite plane
320
	if (drawSprites) {
321
		unsigned spritePatternTable = vdp.getSpritePatternAddress(V9990DisplayMode::P1);
322
		renderSprites<P1Policy>( // uses and updates 'info'
323
			vram, spritePatternTable, palette64,
324
			linePtr, info, displayX, displayEnd, displayY);
325
	}
326
}
327
328
void V9990P2Converter::convertLine(
329
	std::span<Pixel> buf, unsigned displayX, unsigned displayY,
330
	unsigned displayYA, bool drawSprites)
331
{
332
	Pixel* __restrict linePtr = buf.data();
333
	auto displayWidth = narrow<unsigned>(buf.size());
334
335
	unsigned displayAX = (displayX + vdp.getScrollAX()) & 1023;
336
337
	unsigned scrollY = vdp.getScrollAY();
338
	unsigned rollMask = vdp.getRollMask(0x1FF);
339
	unsigned scrollYBase = scrollY & ~rollMask & 0x1FF;
340
	unsigned displayAY = scrollYBase + ((displayYA + scrollY) & rollMask);
341
342
	unsigned displayEnd = displayX + displayWidth;
343
344
	// image plane + backdrop color + fill-in 'info'
345
	assert(displayWidth <= 512);
346
	std::array<uint8_t, 512> info; // filled in later: 0->background, 1->foreground, 2->sprite (front or back)
347
	Pixel bgCol = palette64[vdp.getBackDropColor()];
348
	uint8_t offset = vdp.getPaletteOffset();
349
	auto palette0 = subspan<16>(palette64, (offset & 0x03) << 4);
350
	auto palette1 = subspan<16>(palette64, (offset & 0x0C) << 2);
351
	renderPattern<P2Policy>(
352
		vram, linePtr, subspan(info, 0, displayWidth), bgCol,
353
		displayAX, displayAY, 0x7C000, 0x00000, palette0, palette1);
354
355
	// combined back+front sprite plane
356
	if (drawSprites) {
357
		unsigned spritePatternTable = vdp.getSpritePatternAddress(V9990DisplayMode::P2);
358
		renderSprites<P2Policy>(
359
			vram, spritePatternTable, palette64,
360
			linePtr, info, displayX, displayEnd, displayY);
361
	}
362
}
363
364
} // namespace openmsx