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