openMSX
V9990P1Converter.cc
Go to the documentation of this file.
1 #include "V9990P1Converter.hh"
2 #include "V9990.hh"
3 #include "V9990VRAM.hh"
4 #include "MemoryOps.hh"
5 #include "ScopedAssign.hh"
6 #include "optional.hh"
7 #include "build-info.hh"
8 #include "components.hh"
9 #include <cassert>
10 #include <algorithm>
11 #include <cstdint>
12 
13 namespace openmsx {
14 
15 template<typename Pixel>
17  : vdp(vdp_), vram(vdp.getVRAM())
18  , palette64(palette64_)
19 {
20 }
21 
22 template<bool ALIGNED>
23 static unsigned getPatternAddress(
24  V9990VRAM& vram, unsigned nameAddr, unsigned patternBase, unsigned x, unsigned y)
25 {
26  assert(!ALIGNED || ((x & 7) == 0));
27  unsigned patternNum = (vram.readVRAMP1(nameAddr + 0) +
28  vram.readVRAMP1(nameAddr + 1) * 256) & 0x1FFF;
29  unsigned x2 = (patternNum % 32) * 4 + (ALIGNED ? 0 : ((x & 7) / 2));
30  unsigned y2 = (patternNum / 32) * 1024 + y;
31  return patternBase + y2 + x2;
32 }
33 
34 static unsigned nextNameAddr(unsigned addr)
35 {
36  return (addr & ~127) | ((addr + 2) & 127);
37 }
38 
39 template<bool BACKGROUND, typename Pixel>
40 static void draw1(const Pixel* palette, Pixel* __restrict buffer, byte* __restrict info, size_t p)
41 {
42  if (BACKGROUND) {
43  *buffer = palette[p];
44  } else {
45  *info = bool(p);
46  if (p) *buffer = palette[p];
47  }
48 }
49 
50 template<bool BACKGROUND, bool CHECK_WIDTH, typename Pixel>
51 static void draw2(
52  V9990VRAM& vram, const Pixel* palette, Pixel* __restrict& buffer, byte* __restrict& info,
53  unsigned& address, int& width)
54 {
55  byte data = vram.readVRAMP1(address++);
56  draw1<BACKGROUND>(palette, buffer + 0, info + 0, data >> 4);
57  if (!CHECK_WIDTH || (width != 1)) {
58  draw1<BACKGROUND>(palette, buffer + 1, info + 1, data & 0x0F);
59  }
60  width -= 2;
61  buffer += 2;
62  info += 2;
63 }
64 
65 template<bool BACKGROUND, typename Pixel>
66 static void renderPattern2(
67  V9990VRAM& vram, Pixel* __restrict buffer, byte* __restrict info,
68  int width, unsigned x, unsigned y,
69  unsigned nameTable, unsigned patternBase, const Pixel* palette)
70 {
71  assert(x < 512);
72  if (width == 0) return;
73 
74  unsigned nameAddr = nameTable + (((y / 8) * 64 + (x / 8)) * 2);
75  y = (y & 7) * 128;
76 
77  if (x & 7) {
78  unsigned address = getPatternAddress<false>(vram, nameAddr, patternBase, x, y);
79  if (x & 1) {
80  byte data = vram.readVRAMP1(address++);
81  draw1<BACKGROUND>(palette, buffer, info, data & 0x0F);
82  ++x;
83  ++buffer;
84  --width;
85  }
86  while ((x & 7) && (width > 0)) {
87  draw2<BACKGROUND, true>(vram, palette, buffer, info, address, width);
88  x += 2;
89  }
90  nameAddr = nextNameAddr(nameAddr);
91  }
92  assert((x & 7) == 0 || (width <= 0));
93  while (width & ~7) {
94  unsigned address = getPatternAddress<true>(vram, nameAddr, patternBase, x, y);
95  draw2<BACKGROUND, false>(vram, palette, buffer, info, address, width);
96  draw2<BACKGROUND, false>(vram, palette, buffer, info, address, width);
97  draw2<BACKGROUND, false>(vram, palette, buffer, info, address, width);
98  draw2<BACKGROUND, false>(vram, palette, buffer, info, address, width);
99  nameAddr = nextNameAddr(nameAddr);
100  }
101  assert(width < 8);
102  if (width > 0) {
103  unsigned address = getPatternAddress<true>(vram, nameAddr, patternBase, x, y);
104  do {
105  draw2<BACKGROUND, true>(vram, palette, buffer, info, address, width);
106  } while (width > 0);
107  }
108 }
109 
110 template<bool BACKGROUND, typename Pixel>
111 static void renderPattern(
112  V9990VRAM& vram, Pixel* buffer, byte* info, Pixel bgcol, unsigned width1, unsigned width2,
113  unsigned displayAX, unsigned displayAY, unsigned nameA, unsigned patternA, const Pixel* palA,
114  unsigned displayBX, unsigned displayBY, unsigned nameB, unsigned patternB, const Pixel* palB)
115 {
116  optional<ScopedAssign<Pixel>> col0A, col0B; // optimized away for '!BACKGROUND'
117  if (BACKGROUND) {
118  // Speedup drawing by temporarily replacing palette index 0.
119  // Only allowed when 'palA' and 'palB' either fully overlap or are disjuct.
120  col0A.emplace(const_cast<Pixel*>(palA)[0], bgcol);
121  col0B.emplace(const_cast<Pixel*>(palB)[0], bgcol);
122  }
123 
124  renderPattern2<BACKGROUND>(
125  vram, buffer, info, width1,
126  displayAX, displayAY, nameA, patternA, palA);
127 
128  buffer += width1;
129  info += width1;
130  width2 -= width1;
131  displayBX = (displayBX + width1) & 511;
132 
133  renderPattern2<BACKGROUND>(
134  vram, buffer, info, width2,
135  displayBX, displayBY, nameB, patternB, palB);
136 }
137 
138 static void determineVisibleSprites(
139  V9990VRAM& vram, int* __restrict visibleSprites, unsigned displayY)
140 {
141  constexpr unsigned spriteTable = 0x3FE00;
142 
143  int index = 0;
144  int index_max = 16;
145  for (unsigned sprite = 0; sprite < 125; ++sprite) {
146  unsigned spriteInfo = spriteTable + 4 * sprite;
147  byte spriteY = vram.readVRAMP1(spriteInfo) + 1;
148  byte posY = displayY - spriteY;
149  if (posY < 16) {
150  byte attr = vram.readVRAMP1(spriteInfo + 3);
151  if (attr & 0x10) {
152  // Invisible sprites do contribute towards the
153  // 16-sprites-per-line limit.
154  index_max--;
155  } else {
156  visibleSprites[index++] = sprite;
157  }
158  if (index == index_max) break;
159  }
160  }
161  visibleSprites[index] = -1;
162 }
163 
164 template<typename Pixel>
165 static void renderSprites(
166  V9990VRAM& vram, unsigned spritePatternTable, const Pixel* palette64,
167  Pixel* __restrict buffer, byte* __restrict info,
168  int displayX, int displayEnd, unsigned displayY,
169  const int* __restrict visibleSprites)
170 {
171  static const unsigned spriteTable = 0x3FE00;
172 
173  for (unsigned sprite = 0; visibleSprites[sprite] != -1; ++sprite) {
174  unsigned addr = spriteTable + 4 * visibleSprites[sprite];
175  byte spriteAttr = vram.readVRAMP1(addr + 3);
176  bool front = (spriteAttr & 0x20) == 0;
177  byte level = front ? 2 : 1;
178  int spriteX = vram.readVRAMP1(addr + 2);
179  spriteX += 256 * (spriteAttr & 0x03);
180  if (spriteX > 1008) spriteX -= 1024; // hack X coord into -16..1008
181  byte spriteY = vram.readVRAMP1(addr + 0);
182  byte spriteNo = vram.readVRAMP1(addr + 1);
183  spriteY = displayY - (spriteY + 1);
184  unsigned patAddr = spritePatternTable
185  + (128 * ((spriteNo & 0xF0) + spriteY))
186  + ( 8 * (spriteNo & 0x0F));
187  const Pixel* palette = palette64 + ((spriteAttr >> 2) & 0x30);
188  for (int x = 0; x < 16; x +=2) {
189  auto draw = [&](int xPos, size_t p) {
190  if ((displayX <= xPos) && (xPos < displayEnd)) {
191  size_t xx = xPos - displayX;
192  if (p) {
193  if (info[xx] < level) {
194  buffer[xx] = palette[p];
195  }
196  info[xx] = 2; // also if back-sprite is behind foreground
197  }
198  }
199  };
200  byte data = vram.readVRAMP1(patAddr++);
201  draw(spriteX + x + 0, data >> 4);
202  draw(spriteX + x + 1, data & 0x0F);
203  }
204  }
205 }
206 
207 template<typename Pixel>
209  Pixel* linePtr, unsigned displayX, unsigned displayWidth,
210  unsigned displayY, unsigned displayYA, unsigned displayYB,
211  bool drawSprites)
212 {
213  unsigned prioX = vdp.getPriorityControlX();
214  unsigned prioY = vdp.getPriorityControlY();
215  if (displayY >= prioY) prioX = 0;
216 
217  unsigned displayAX = (displayX + vdp.getScrollAX()) & 511;
218  unsigned displayBX = (displayX + vdp.getScrollBX()) & 511;
219 
220  // Configurable 'roll' only applies to layer A.
221  // Layer B always rolls at 512 lines.
222  unsigned rollMask = vdp.getRollMask(0x1FF);
223  unsigned scrollAY = vdp.getScrollAY();
224  unsigned scrollBY = vdp.getScrollBY();
225  unsigned scrollAYBase = scrollAY & ~rollMask & 0x1FF;
226  unsigned displayAY = scrollAYBase + ((displayYA + scrollAY) & rollMask);
227  unsigned displayBY = (displayYB + scrollBY) & 0x1FF;
228 
229  unsigned displayEnd = displayX + displayWidth;
230  unsigned end1 = std::max<int>(0, std::min<int>(prioX, displayEnd) - displayX);
231 
232  // background + backdrop color
233  Pixel bgcol = palette64[vdp.getBackDropColor()];
234  byte offset = vdp.getPaletteOffset();
235  const Pixel* palA = palette64 + ((offset & 0x03) << 4);
236  const Pixel* palB = palette64 + ((offset & 0x0C) << 2);
237  renderPattern<true>(vram, linePtr, nullptr, bgcol, end1, displayWidth,
238  displayBX, displayBY, 0x7E000, 0x40000, palB,
239  displayAX, displayAY, 0x7C000, 0x00000, palA);
240 
241  // foreground + fill-in 'info'
242  assert(displayWidth <= 256);
243  byte info[256]; // left uninitialized
244  // 0->background, 1->foreground, 2->sprite (front or back)
245  renderPattern<false>(vram, linePtr, info, bgcol, end1, displayWidth,
246  displayAX, displayAY, 0x7C000, 0x00000, palA,
247  displayBX, displayBY, 0x7E000, 0x40000, palB);
248 
249  // combined back+front sprite plane
250  if (drawSprites) {
251  int visibleSprites[16 + 1];
252  determineVisibleSprites(vram, visibleSprites, displayY);
253  unsigned spritePatternTable = vdp.getSpritePatternAddress(P1);
254  renderSprites(vram, spritePatternTable, palette64,
255  linePtr, info, displayX, displayEnd, displayY,
256  visibleSprites);
257  }
258 }
259 
260 // Force template instantiation
261 #if HAVE_16BPP
262 template class V9990P1Converter<uint16_t>;
263 #endif
264 #if HAVE_32BPP || COMPONENT_GL
265 template class V9990P1Converter<uint32_t>;
266 #endif
267 
268 } // namespace openmsx
Implementation of the Yamaha V9990 VDP as used in the GFX9000 cartridge by Sunrise.
Definition: V9990.hh:29
#define ALIGNED(EXPRESSION, ALIGNMENT)
Definition: aligned.hh:13
unsigned getRollMask(unsigned maxMask) const
Returns the vertical roll mask.
Definition: V9990.hh:242
int getSpritePatternAddress(V9990DisplayMode m) const
Return the sprite pattern table base address.
Definition: V9990.hh:290
uint8_t byte
8 bit unsigned integer
Definition: openmsx.hh:26
uint32_t Pixel
byte readVRAMP1(unsigned address)
Definition: V9990VRAM.hh:57
void emplace(Args &&... args)
Definition: optional.hh:383
constexpr auto data(C &c) -> decltype(c.data())
Definition: span.hh:69
byte getPaletteOffset() const
Get palette offset.
Definition: V9990.hh:96
unsigned getPriorityControlX() const
Definition: V9990.hh:341
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
unsigned getScrollBY() const
Returns the Y scroll offset for screen B of P1 mode.
Definition: V9990.hh:236
unsigned getScrollBX() const
Returns the X scroll offset for screen B of P1 mode.
Definition: V9990.hh:230
Video RAM for the V9990.
Definition: V9990VRAM.hh:15
unsigned getScrollAX() const
Returns the X scroll offset for screen A of P1 and other modes.
Definition: V9990.hh:218
V9990P1Converter(V9990 &vdp, const Pixel *palette64)
unsigned getPriorityControlY() const
Definition: V9990.hh:345
unsigned getScrollAY() const
Returns the Y scroll offset for screen A of P1 and other modes.
Definition: V9990.hh:224
byte getBackDropColor() const
Return the current back drop color.
Definition: V9990.hh:212
void convertLine(Pixel *linePtr, unsigned displayX, unsigned displayWidth, unsigned displayY, unsigned displayYA, unsigned displayYB, bool drawSprites)