OXYGENHUB
◆ main openmsx / src / video / v9990 / V9990BitmapConverter.cc
17.8 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#include "V9990BitmapConverter.hh"
2
3
#include "V9990.hh"
4
#include "V9990VRAM.hh"
5
6
#include "narrow.hh"
7
#include "unreachable.hh"
8
9
#include <array>
10
#include <cassert>
11
#include <concepts>
12
#include <cstdint>
13
14
namespace openmsx {
15
16
V9990BitmapConverter::V9990BitmapConverter(
17
		V9990& vdp_,
18
		std::span<const Pixel,    64> palette64_,  std::span<const int16_t,  64> palette64_32768_,
19
		std::span<const Pixel,   256> palette256_, std::span<const int16_t, 256> palette256_32768_,
20
		std::span<const Pixel, 32768> palette32768_)
21
	: vdp(vdp_), vram(vdp.getVRAM())
22
	, palette64 (palette64_ ), palette64_32768 (palette64_32768_ )
23
	, palette256(palette256_), palette256_32768(palette256_32768_)
24
	, palette32768(palette32768_)
25
{
26
	setColorMode(V9990ColorMode::PP, V9990DisplayMode::B0); // initialize with dummy values
27
}
28
29
template<bool YJK, bool PAL, bool SKIP, std::unsigned_integral Pixel, typename ColorLookup>
30
static inline void draw_YJK_YUV_PAL(
31
	ColorLookup color, const V9990VRAM& vram,
32
	Pixel* __restrict& out, unsigned& address, int firstX = 0)
33
{
34
	std::array<uint8_t, 4> data;
35
	for (auto& d : data) {
36
		d = vram.readVRAMBx(address++);
37
	}
38
39
	int u = (data[2] & 7) + ((data[3] & 3) << 3) - ((data[3] & 4) << 3);
40
	int v = (data[0] & 7) + ((data[1] & 3) << 3) - ((data[1] & 4) << 3);
41
42
	for (auto i : xrange(SKIP ? firstX : 0, 4)) {
43
		if (PAL && (data[i] & 0x08)) {
44
			*out++ = color.lookup64(data[i] >> 4);
45
		} else {
46
			int y = (data[i] & 0xF8) >> 3;
47
			int r = std::clamp(y + u,                   0, 31);
48
			int g = std::clamp((5 * y - 2 * u - v) / 4, 0, 31);
49
			int b = std::clamp(y + v,                   0, 31);
50
			// The only difference between YUV and YJK is that
51
			// green and blue are swapped.
52
			if constexpr (YJK) std::swap(g, b);
53
			*out++ = color.lookup32768((g << 10) + (r << 5) + b);
54
		}
55
	}
56
}
57
58
template<std::unsigned_integral Pixel, typename ColorLookup>
59
static void rasterBYUV(
60
	ColorLookup color, const V9990& vdp, const V9990VRAM& vram,
61
	std::span<Pixel> buf, unsigned x, unsigned y)
62
{
63
	Pixel* __restrict out = buf.data();
64
	int nrPixels = narrow<int>(buf.size());
65
	unsigned address = (x & ~3) + y * vdp.getImageWidth();
66
	if (x & 3) {
67
		draw_YJK_YUV_PAL<false, false, true>(
68
			color, vram, out, address, x & 3);
69
		nrPixels -= narrow<int>(4 - (x & 3));
70
	}
71
	for (/**/; nrPixels > 0; nrPixels -= 4) {
72
		draw_YJK_YUV_PAL<false, false, false>(
73
			color, vram, out, address);
74
	}
75
	// Note: this can draw up to 3 pixels too many, but that's ok.
76
}
77
78
template<std::unsigned_integral Pixel, typename ColorLookup>
79
static void rasterBYUVP(
80
	ColorLookup color, const V9990& vdp, const V9990VRAM& vram,
81
	std::span<Pixel> buf, unsigned x, unsigned y)
82
{
83
	// TODO this mode cannot be shown in B4 and higher resolution modes
84
	//      (So the dual palette for B4 modes is not an issue here.)
85
	Pixel* __restrict out = buf.data();
86
	int nrPixels = narrow<int>(buf.size());
87
	unsigned address = (x & ~3) + y * vdp.getImageWidth();
88
	if (x & 3) {
89
		draw_YJK_YUV_PAL<false, true, true>(
90
			color, vram, out, address, x & 3);
91
		nrPixels -= narrow<int>(4 - (x & 3));
92
	}
93
	for (/**/; nrPixels > 0; nrPixels -= 4) {
94
		draw_YJK_YUV_PAL<false, true, false>(
95
			color, vram, out, address);
96
	}
97
	// Note: this can draw up to 3 pixels too many, but that's ok.
98
}
99
100
template<std::unsigned_integral Pixel, typename ColorLookup>
101
static void rasterBYJK(
102
	ColorLookup color, const V9990& vdp, const V9990VRAM& vram,
103
	std::span<Pixel> buf, unsigned x, unsigned y)
104
{
105
	Pixel* __restrict out = buf.data();
106
	int nrPixels = narrow<int>(buf.size());
107
	unsigned address = (x & ~3) + y * vdp.getImageWidth();
108
	if (x & 3) {
109
		draw_YJK_YUV_PAL<true, false, true>(
110
			color, vram, out, address, x & 3);
111
		nrPixels -= narrow<int>(4 - (x & 3));
112
	}
113
	for (/**/; nrPixels > 0; nrPixels -= 4) {
114
		draw_YJK_YUV_PAL<true, false, false>(
115
			color, vram, out, address);
116
	}
117
	// Note: this can draw up to 3 pixels too many, but that's ok.
118
}
119
120
template<std::unsigned_integral Pixel, typename ColorLookup>
121
static void rasterBYJKP(
122
	ColorLookup color, const V9990& vdp, const V9990VRAM& vram,
123
	std::span<Pixel> buf, unsigned x, unsigned y)
124
{
125
	// TODO this mode cannot be shown in B4 and higher resolution modes
126
	//      (So the dual palette for B4 modes is not an issue here.)
127
	Pixel* __restrict out = buf.data();
128
	int nrPixels = narrow<int>(buf.size());
129
	unsigned address = (x & ~3) + y * vdp.getImageWidth();
130
	if (x & 3) {
131
		draw_YJK_YUV_PAL<true, true, true>(
132
			color, vram, out, address, x & 3);
133
		nrPixels -= narrow<int>(4 - (x & 3));
134
	}
135
	for (/**/; nrPixels > 0; nrPixels -= 4) {
136
		draw_YJK_YUV_PAL<true, true, false>(
137
			color, vram, out, address);
138
	}
139
	// Note: this can draw up to 3 pixels too many, but that's ok.
140
}
141
142
template<std::unsigned_integral Pixel, typename ColorLookup>
143
static void rasterBD16(
144
	ColorLookup color, const V9990& vdp, const V9990VRAM& vram,
145
	std::span<Pixel> buf, unsigned x, unsigned y)
146
{
147
	Pixel* __restrict out = buf.data();
148
	int nrPixels = narrow<int>(buf.size());
149
	unsigned address = 2 * (x + y * vdp.getImageWidth());
150
	if (vdp.isSuperimposing()) {
151
		auto transparent = color.lookup256(0);
152
		for (/**/; nrPixels > 0; --nrPixels) {
153
			uint8_t high = vram.readVRAMBx(address + 1);
154
			if (high & 0x80) {
155
				*out = transparent;
156
			} else {
157
				uint8_t low  = vram.readVRAMBx(address + 0);
158
				*out = color.lookup32768(low + 256 * high);
159
			}
160
			address += 2;
161
			out += 1;
162
		}
163
	} else {
164
		for (/**/; nrPixels > 0; --nrPixels) {
165
			uint8_t low  = vram.readVRAMBx(address++);
166
			uint8_t high = vram.readVRAMBx(address++);
167
			*out++ = color.lookup32768((low + 256 * high) & 0x7FFF);
168
		}
169
	}
170
}
171
172
template<std::unsigned_integral Pixel, typename ColorLookup>
173
static void rasterBD8(
174
	ColorLookup color, const V9990& vdp, const V9990VRAM& vram,
175
	std::span<Pixel> buf, unsigned x, unsigned y)
176
{
177
	Pixel* __restrict out = buf.data();
178
	int nrPixels = narrow<int>(buf.size());
179
	unsigned address = x + y * vdp.getImageWidth();
180
	for (/**/; nrPixels > 0; --nrPixels) {
181
		*out++ = color.lookup256(vram.readVRAMBx(address++));
182
	}
183
}
184
185
template<std::unsigned_integral Pixel, typename ColorLookup>
186
static void rasterBP6(
187
	ColorLookup color, const V9990& vdp, const V9990VRAM& vram,
188
	std::span<Pixel> buf, unsigned x, unsigned y)
189
{
190
	Pixel* __restrict out = buf.data();
191
	int nrPixels = narrow<int>(buf.size());
192
	unsigned address = x + y * vdp.getImageWidth();
193
	for (/**/; nrPixels > 0; --nrPixels) {
194
		*out++ = color.lookup64(vram.readVRAMBx(address++) & 0x3F);
195
	}
196
}
197
198
template<std::unsigned_integral Pixel, typename ColorLookup>
199
static void rasterBP4(
200
	ColorLookup color, const V9990& vdp, const V9990VRAM& vram,
201
	std::span<Pixel> buf, unsigned x, unsigned y)
202
{
203
	Pixel* __restrict out = buf.data();
204
	int nrPixels = narrow<int>(buf.size());
205
	assert(nrPixels > 0);
206
	unsigned address = (x + y * vdp.getImageWidth()) / 2;
207
	color.set64Offset((vdp.getPaletteOffset() & 0xC) << 2);
208
	if (x & 1) {
209
		uint8_t data = vram.readVRAMBx(address++);
210
		*out++ = color.lookup64(data & 0x0F);
211
		--nrPixels;
212
	}
213
	for (/**/; nrPixels > 0; nrPixels -= 2) {
214
		uint8_t data = vram.readVRAMBx(address++);
215
		*out++ = color.lookup64(data >> 4);
216
		*out++ = color.lookup64(data & 0x0F);
217
	}
218
	// Note: this possibly draws 1 pixel too many, but that's ok.
219
}
220
template<std::unsigned_integral Pixel, typename ColorLookup>
221
static void rasterBP4HiRes(
222
	ColorLookup color, const V9990& vdp, const V9990VRAM& vram,
223
	std::span<Pixel> buf, unsigned x, unsigned y)
224
{
225
	// Verified on real HW:
226
	//   Bit PLT05 in palette offset is ignored, instead for even pixels
227
	//   bit 'PLT05' is '0', for odd pixels it's '1'.
228
	Pixel* __restrict out = buf.data();
229
	int nrPixels = narrow<int>(buf.size());
230
	unsigned address = (x + y * vdp.getImageWidth()) / 2;
231
	color.set64Offset((vdp.getPaletteOffset() & 0x4) << 2);
232
	if (x & 1) {
233
		uint8_t data = vram.readVRAMBx(address++);
234
		*out++ = color.lookup64(32 | (data & 0x0F));
235
		--nrPixels;
236
	}
237
	for (/**/; nrPixels > 0; nrPixels -= 2) {
238
		uint8_t data = vram.readVRAMBx(address++);
239
		*out++ = color.lookup64( 0 | (data >> 4  ));
240
		*out++ = color.lookup64(32 | (data & 0x0F));
241
	}
242
	// Note: this possibly draws 1 pixel too many, but that's ok.
243
}
244
245
template<std::unsigned_integral Pixel, typename ColorLookup>
246
static void rasterBP2(
247
	ColorLookup color, const V9990& vdp, const V9990VRAM& vram,
248
	std::span<Pixel> buf, unsigned x, unsigned y)
249
{
250
	Pixel* __restrict out = buf.data();
251
	int nrPixels = narrow<int>(buf.size());
252
	assert(nrPixels > 0);
253
	unsigned address = (x + y * vdp.getImageWidth()) / 4;
254
	color.set64Offset(vdp.getPaletteOffset() << 2);
255
	if (x & 3) {
256
		uint8_t data = vram.readVRAMBx(address++);
257
		if ((x & 3) <= 1) *out++ = color.lookup64((data & 0x30) >> 4);
258
		if ((x & 3) <= 2) *out++ = color.lookup64((data & 0x0C) >> 2);
259
		if (true)         *out++ = color.lookup64((data & 0x03) >> 0);
260
		nrPixels -= narrow<int>(4 - (x & 3));
261
	}
262
	for (/**/; nrPixels > 0; nrPixels -= 4) {
263
		uint8_t data = vram.readVRAMBx(address++);
264
		*out++ = color.lookup64((data & 0xC0) >> 6);
265
		*out++ = color.lookup64((data & 0x30) >> 4);
266
		*out++ = color.lookup64((data & 0x0C) >> 2);
267
		*out++ = color.lookup64((data & 0x03) >> 0);
268
	}
269
	// Note: this can draw up to 3 pixels too many, but that's ok.
270
}
271
template<std::unsigned_integral Pixel, typename ColorLookup>
272
static void rasterBP2HiRes(
273
	ColorLookup color, const V9990& vdp, const V9990VRAM& vram,
274
	std::span<Pixel> buf, unsigned x, unsigned y)
275
{
276
	// Verified on real HW:
277
	//   Bit PLT05 in palette offset is ignored, instead for even pixels
278
	//   bit 'PLT05' is '0', for odd pixels it's '1'.
279
	Pixel* __restrict out = buf.data();
280
	int nrPixels = narrow<int>(buf.size());
281
	assert(nrPixels > 0);
282
	unsigned address = (x + y * vdp.getImageWidth()) / 4;
283
	color.set64Offset((vdp.getPaletteOffset() & 0x7) << 2);
284
	if (x & 3) {
285
		uint8_t data = vram.readVRAMBx(address++);
286
		if ((x & 3) <= 1) *out++ = color.lookup64(32 | ((data & 0x30) >> 4));
287
		if ((x & 3) <= 2) *out++ = color.lookup64( 0 | ((data & 0x0C) >> 2));
288
		if (true)         *out++ = color.lookup64(32 | ((data & 0x03) >> 0));
289
		nrPixels -= narrow<int>(4 - (x & 3));
290
	}
291
	for (/**/; nrPixels > 0; nrPixels -= 4) {
292
		uint8_t data = vram.readVRAMBx(address++);
293
		*out++ = color.lookup64( 0 | ((data & 0xC0) >> 6));
294
		*out++ = color.lookup64(32 | ((data & 0x30) >> 4));
295
		*out++ = color.lookup64( 0 | ((data & 0x0C) >> 2));
296
		*out++ = color.lookup64(32 | ((data & 0x03) >> 0));
297
	}
298
	// Note: this can draw up to 3 pixels too many, but that's ok.
299
}
300
301
// Helper class to translate V9990 palette indices into host Pixel values.
302
template<std::unsigned_integral Pixel>
303
class PaletteLookup
304
{
305
public:
306
	PaletteLookup(std::span<const Pixel,    64> palette64_,
307
	              std::span<const Pixel,   256> palette256_,
308
	              std::span<const Pixel, 32768> palette32768_)
309
	        : palette64Base(palette64_)
310
	        , palette64(palette64_)
311
	        , palette256(palette256_)
312
	        , palette32768(palette32768_)
313
	{
314
	}
315
316
	void set64Offset(size_t offset) { palette64 = palette64Base.subspan(offset); }
317
	[[nodiscard]] Pixel lookup64   (size_t idx) const { return palette64   [idx]; }
318
	[[nodiscard]] Pixel lookup256  (size_t idx) const { return palette256  [idx]; }
319
	[[nodiscard]] Pixel lookup32768(size_t idx) const { return palette32768[idx]; }
320
321
private:
322
	std::span<const Pixel,    64> palette64Base;
323
	std::span<const Pixel>        palette64;
324
	std::span<const Pixel,   256> palette256;
325
	std::span<const Pixel, 32768> palette32768;
326
};
327
328
// Helper class to translate V9990 palette indices (64-entry, 256-entry and
329
// 32768-entry palettes) into V9990 32768-entry palette indices.
330
class IndexLookup
331
{
332
public:
333
	IndexLookup(std::span<const int16_t, 64> palette64_, std::span<const int16_t, 256> palette256_)
334
	        : palette64_32768Base(palette64_)
335
	        , palette64_32768(palette64_)
336
	        , palette256_32768(palette256_)
337
	{
338
	}
339
340
	void set64Offset(size_t offset) { palette64_32768 = palette64_32768Base.subspan(offset); }
341
	[[nodiscard]] int16_t lookup64   (size_t idx) const { return palette64_32768 [idx]; }
342
	[[nodiscard]] int16_t lookup256  (size_t idx) const { return palette256_32768[idx]; }
343
	[[nodiscard]] int16_t lookup32768(size_t idx) const { return int16_t(idx); }
344
345
private:
346
	std::span<const int16_t,  64> palette64_32768Base;
347
	std::span<const int16_t>      palette64_32768;
348
	std::span<const int16_t, 256> palette256_32768;
349
};
350
351
template<std::unsigned_integral Pixel, typename ColorLookup>
352
static void raster(V9990ColorMode colorMode, bool highRes,
353
                   ColorLookup color, const V9990& vdp, const V9990VRAM& vram,
354
                   std::span<Pixel> out, unsigned x, unsigned y)
355
{
356
	switch (colorMode) {
357
	using enum V9990ColorMode;
358
	case BYUV:  return rasterBYUV <Pixel>(color, vdp, vram, out, x, y);
359
	case BYUVP: return rasterBYUVP<Pixel>(color, vdp, vram, out, x, y);
360
	case BYJK:  return rasterBYJK <Pixel>(color, vdp, vram, out, x, y);
361
	case BYJKP: return rasterBYJKP<Pixel>(color, vdp, vram, out, x, y);
362
	case BD16:  return rasterBD16 <Pixel>(color, vdp, vram, out, x, y);
363
	case BD8:   return rasterBD8  <Pixel>(color, vdp, vram, out, x, y);
364
	case BP6:   return rasterBP6  <Pixel>(color, vdp, vram, out, x, y);
365
	case BP4:   return highRes ? rasterBP4HiRes<Pixel>(color, vdp, vram, out, x, y)
366
	                           : rasterBP4     <Pixel>(color, vdp, vram, out, x, y);
367
	case BP2:   return highRes ? rasterBP2HiRes<Pixel>(color, vdp, vram, out, x, y)
368
	                           : rasterBP2     <Pixel>(color, vdp, vram, out, x, y);
369
	default:    UNREACHABLE;
370
	}
371
}
372
373
// Missing details in the V9990 application manual (reverse engineered from
374
// tests on a real gfx9000):
375
//  * Cursor 0 is drawn on top of cursor 1  (IOW cursor 0 has higher priority).
376
//    This remains the case when both cursors use the 'EOR' feature (it's not
377
//    so that EOR is applied twice).
378
//  * The CC1,CC0,EOR bits in the cursor attribute table work like this:
379
//     (CC1,CC0):
380
//       when (0,0): pick the resulting color from bitmap rendering (this is
381
//                   a 15 bit RGB value)
382
//       when (x,y): pick the color from palette with index R#28:x:y (this is
383
//                   also a 15 bit RGB color)
384
//     (EOR):
385
//       when 0: use the above color unchanged
386
//       when 1: flip all the bits in the above color (IOW XOR with 0x7fff)
387
//    From this follows:
388
//      (CC1,CC0,EOR)==(0,0,0):
389
//        Results in an invisible cursor: each pixel is colored the same as the
390
//        corresponding background pixel.
391
//      (CC1,CC0,EOR)==(0,0,1):
392
//        This is the only combination where the cursor is drawn using multiple
393
//        colors, each pixel is the complement of the corresponding background
394
//        pixel.
395
//      (CC1,CC0,EOR)==(x,y,0):
396
//        This is the 'usual' configuration, cursor is drawn with a specific
397
//        color from the palette (also when bitmap rendering is not using the
398
//        palette, e.g. YJK or BD8 mode).
399
//      (CC1,CC0,EOR)==(x,y,1):
400
//        This undocumented mode draws the cursor with a single color which is
401
//        the complement of a specific palette color.
402
class CursorInfo
403
{
404
public:
405
	CursorInfo(const V9990& vdp, const V9990VRAM& vram, std::span<const int16_t, 64> palette64_32768,
406
	           unsigned attrAddr, unsigned patAddr,
407
		   int displayY, bool drawCursor)
408
	{
409
		if (!drawCursor) return;
410
411
		unsigned attrY = vram.readVRAMBx(attrAddr + 0) +
412
		                (vram.readVRAMBx(attrAddr + 2) & 1) * 256;
413
		attrY += vdp.isInterlaced() ? 2 : 1; // 1 or 2 lines later
414
		unsigned cursorLine = (displayY - attrY) & 511;
415
		if (cursorLine >= 32) return;
416
417
		uint8_t attr = vram.readVRAMBx(attrAddr + 6);
418
		if ((attr & 0x10) || ((attr & 0xe0) == 0x00)) {
419
			// don't display
420
			return;
421
		}
422
423
		pattern = (vram.readVRAMBx(patAddr + 4 * cursorLine + 0) << 24)
424
		        + (vram.readVRAMBx(patAddr + 4 * cursorLine + 1) << 16)
425
		        + (vram.readVRAMBx(patAddr + 4 * cursorLine + 2) <<  8)
426
		        + (vram.readVRAMBx(patAddr + 4 * cursorLine + 3) <<  0);
427
		if (pattern == 0) {
428
			// optimization, completely transparent line
429
			return;
430
		}
431
432
		// mark cursor visible
433
		x = vram.readVRAMBx(attrAddr + 4) + (attr & 3) * 256;
434
435
		doXor = (attr & 0xe0) == 0x20;
436
437
		auto colorIdx = vdp.getSpritePaletteOffset() + (attr >> 6);
438
		color = palette64_32768[colorIdx];
439
		if (attr & 0x20) color ^= 0x7fff;
440
	}
441
442
	[[nodiscard]] bool isVisible() const {
443
		return x != unsigned(-1);
444
	}
445
	[[nodiscard]] bool dot() const {
446
		return (x == 0) && (pattern & 0x80000000);
447
	}
448
	void shift() {
449
		if (x) {
450
			--x;
451
		} else {
452
			pattern <<= 1;
453
		}
454
	}
455
456
public:
457
	unsigned x{unsigned(-1)}; // not visible
458
	uint32_t pattern{0};
459
	int16_t color{0};
460
	bool doXor{false};
461
};
462
463
void V9990BitmapConverter::convertLine(
464
	std::span<Pixel> dst, unsigned x, unsigned y,
465
	int cursorY, bool drawCursors) const
466
{
467
	assert(dst.size() <= 1024);
468
469
	CursorInfo cursor0(vdp, vram, palette64_32768, 0x7fe00, 0x7ff00, cursorY, drawCursors);
470
	CursorInfo cursor1(vdp, vram, palette64_32768, 0x7fe08, 0x7ff80, cursorY, drawCursors);
471
472
	if (cursor0.isVisible() || cursor1.isVisible()) {
473
		// raster background into a temporary buffer
474
		std::array<uint16_t, 1024 + 3> buf; // allow to draw upto 3 pixels too many, e.g. see rasterBP2()
475
		raster(colorMode, highRes,
476
		       IndexLookup(palette64_32768, palette256_32768),
477
		       vdp, vram,
478
		       subspan(buf, 0, dst.size()), x, y);
479
480
		// draw sprites in this buffer
481
		// TODO can be optimized
482
		// TODO probably goes wrong when startX != 0
483
		// TODO investigate dual palette in B4 and higher modes
484
		// TODO check X-roll behavior
485
		for (auto i : xrange(dst.size())) {
486
			if (cursor0.dot()) {
487
				if (cursor0.doXor) {
488
					buf[i] ^= 0x7fff;
489
				} else {
490
					buf[i] = cursor0.color;
491
				}
492
			} else if (cursor1.dot()) {
493
				if (cursor1.doXor) {
494
					buf[i] ^= 0x7fff;
495
				} else {
496
					buf[i] = cursor1.color;
497
				}
498
			}
499
			cursor0.shift();
500
			cursor1.shift();
501
			if ((cursor0.pattern == 0) && (cursor1.pattern == 0)) break;
502
		}
503
504
		// copy buffer to destination, translate from V9990 to host colors
505
		for (auto i : xrange(dst.size())) {
506
			dst[i] = palette32768[buf[i]];
507
		}
508
	} else {
509
		// Optimization: no cursor(s) visible on this line, directly draw to destination
510
		raster(colorMode, highRes,
511
		       PaletteLookup<Pixel>(palette64, palette256, palette32768),
512
		       vdp, vram, dst, x, y);
513
	}
514
}
515
516
} // namespace openmsx