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