openMSX
V9990PxConverter.cc
Go to the documentation of this file.
1#include "V9990PxConverter.hh"
2#include "V9990.hh"
3#include "V9990VRAM.hh"
4#include "ScopedAssign.hh"
5#include "build-info.hh"
6#include "components.hh"
7#include "narrow.hh"
8#include "ranges.hh"
9#include <array>
10#include <cassert>
11#include <algorithm>
12#include <cstdint>
13#include <optional>
14
15namespace openmsx {
16
17template<std::unsigned_integral Pixel>
18V9990P1Converter<Pixel>::V9990P1Converter(V9990& vdp_, std::span<const Pixel, 64> palette64_)
19 : vdp(vdp_), vram(vdp.getVRAM())
20 , palette64(palette64_)
21{
22}
23
24template<std::unsigned_integral Pixel>
25V9990P2Converter<Pixel>::V9990P2Converter(V9990& vdp_, std::span<const Pixel, 64> palette64_)
26 : vdp(vdp_), vram(vdp.getVRAM()), palette64(palette64_)
27{
28}
29
30struct P1Policy {
31 static byte readNameTable(V9990VRAM& vram, unsigned addr) {
32 return vram.readVRAMP1(addr);
33 }
34 static byte readPatternTable(V9990VRAM& vram, unsigned addr) {
35 return vram.readVRAMP1(addr);
36 }
37 static byte readSpriteAttr(V9990VRAM& vram, unsigned addr) {
38 return vram.readVRAMP1(addr);
39 }
40 static unsigned spritePatOfst(byte spriteNo, byte spriteY) {
41 return (128 * ((spriteNo & 0xF0) + spriteY))
42 + ( 8 * (spriteNo & 0x0F));
43 }
44 static constexpr unsigned SCREEN_WIDTH = 256;
45 static constexpr unsigned IMAGE_WIDTH = 2 * SCREEN_WIDTH;
46 static constexpr unsigned NAME_CHARS = IMAGE_WIDTH / 8;
47 static constexpr unsigned PATTERN_CHARS = SCREEN_WIDTH / 8;
48};
50 template<std::unsigned_integral Pixel> static void draw1(
51 std::span<const Pixel, 16> palette, Pixel* __restrict buffer,
52 byte* __restrict /*info*/, size_t p)
53 {
54 *buffer = palette[p];
55 }
56 static constexpr bool DRAW_BACKDROP = true;
57};
59 template<std::unsigned_integral Pixel> static void draw1(
60 std::span<const Pixel, 16> palette, Pixel* __restrict buffer,
61 byte* __restrict info, size_t p)
62 {
63 *info = bool(p);
64 if (p) *buffer = palette[p];
65 }
66 static constexpr bool DRAW_BACKDROP = false;
67};
68struct P2Policy {
69 static byte readNameTable(V9990VRAM& vram, unsigned addr) {
70 return vram.readVRAMDirect(addr);
71 }
72 static byte readPatternTable(V9990VRAM& vram, unsigned addr) {
73 return vram.readVRAMBx(addr);
74 }
75 static byte readSpriteAttr(V9990VRAM& vram, unsigned addr) {
76 return vram.readVRAMDirect(addr);
77 }
78 static unsigned spritePatOfst(byte spriteNo, byte spriteY) {
79 return (256 * (((spriteNo & 0xE0) >> 1) + spriteY))
80 + ( 8 * (spriteNo & 0x1F));
81 }
82 template<std::unsigned_integral Pixel> static void draw1(
83 std::span<const Pixel, 16> palette, Pixel* __restrict buffer,
84 byte* __restrict info, size_t p)
85 {
86 *info = bool(p);
87 *buffer = palette[p];
88 }
89 static constexpr bool DRAW_BACKDROP = true;
90 static constexpr unsigned SCREEN_WIDTH = 512;
91 static constexpr unsigned IMAGE_WIDTH = 2 * SCREEN_WIDTH;
92 static constexpr unsigned NAME_CHARS = IMAGE_WIDTH / 8;
93 static constexpr unsigned PATTERN_CHARS = SCREEN_WIDTH / 8;
94};
95
96template<typename Policy, bool ALIGNED>
97static unsigned getPatternAddress(
98 V9990VRAM& vram, unsigned nameAddr, unsigned patternBase, unsigned x, unsigned y)
99{
100 assert(!ALIGNED || ((x & 7) == 0));
101 unsigned patternNum = (Policy::readNameTable(vram, nameAddr + 0) +
102 Policy::readNameTable(vram, nameAddr + 1) * 256) & 0x1FFF;
103 constexpr auto PATTERN_PITCH = Policy::PATTERN_CHARS * 8 * (8 / 2);
104 unsigned x2 = (patternNum % Policy::PATTERN_CHARS) * 4 + (ALIGNED ? 0 : ((x & 7) / 2));
105 unsigned y2 = (patternNum / Policy::PATTERN_CHARS) * PATTERN_PITCH + y;
106 return patternBase + y2 + x2;
107}
108
109template<typename Policy>
110static constexpr unsigned nextNameAddr(unsigned addr)
111{
112 constexpr auto MASK = (2 * Policy::NAME_CHARS) - 1;
113 return (addr & ~MASK) | ((addr + 2) & MASK);
114}
115
116template<typename Policy, bool CHECK_WIDTH, std::unsigned_integral Pixel>
117static void draw2(
118 V9990VRAM& vram, std::span<const Pixel, 16> palette, Pixel* __restrict& buffer, byte* __restrict& info,
119 unsigned& address, int& width)
120{
121 byte data = Policy::readPatternTable(vram, address++);
122 Policy::draw1(palette, buffer + 0, info + 0, data >> 4);
123 if (!CHECK_WIDTH || (width != 1)) {
124 Policy::draw1(palette, buffer + 1, info + 1, data & 0x0F);
125 }
126 width -= 2;
127 buffer += 2;
128 info += 2;
129}
130
131template<typename Policy, std::unsigned_integral Pixel>
132static void renderPattern(
133 V9990VRAM& vram, Pixel* __restrict buffer, std::span<byte> info_,
134 Pixel bgCol, unsigned x, unsigned y,
135 unsigned nameTable, unsigned patternBase,
136 std::span<const Pixel, 16> palette0, std::span<const Pixel, 16> palette1)
137{
138 assert(x < Policy::IMAGE_WIDTH);
139 auto width = narrow<int>(info_.size());
140 if (width == 0) return;
141 byte* info = info_.data();
142
143 std::optional<ScopedAssign<Pixel>> col0, col1; // optimized away when not used
144 if constexpr (Policy::DRAW_BACKDROP) {
145 // Speedup drawing by temporarily replacing palette index 0.
146 // OK because palette0 and palette1 never partially overlap, IOW either:
147 // - palette0 == palette1 (fully overlap)
148 // - abs(palette0 - palette1) >= 16 (no overlap at all)
149 col0.emplace(*const_cast<Pixel*>(palette0.data()), bgCol);
150 col1.emplace(*const_cast<Pixel*>(palette1.data()), bgCol);
151 }
152
153 unsigned nameAddr = nameTable + (((y / 8) * Policy::NAME_CHARS + (x / 8)) * 2);
154 y = (y & 7) * Policy::NAME_CHARS * 2;
155
156 if (x & 7) {
157 unsigned address = getPatternAddress<Policy, false>(vram, nameAddr, patternBase, x, y);
158 if (x & 1) {
159 byte data = Policy::readPatternTable(vram, address);
160 Policy::draw1((address & 1) ? palette1 : palette0, buffer, info, data & 0x0F);
161 ++address;
162 ++x;
163 ++buffer;
164 ++info;
165 --width;
166 }
167 while ((x & 7) && (width > 0)) {
168 draw2<Policy, true>(vram, (address & 1) ? palette1 : palette0, buffer, info, address, width);
169 x += 2;
170 }
171 nameAddr = nextNameAddr<Policy>(nameAddr);
172 }
173 assert((x & 7) == 0 || (width <= 0));
174 while ((width & ~7) > 0) {
175 unsigned address = getPatternAddress<Policy, true>(vram, nameAddr, patternBase, x, y);
176 draw2<Policy, false>(vram, palette0, buffer, info, address, width);
177 draw2<Policy, false>(vram, palette1, buffer, info, address, width);
178 draw2<Policy, false>(vram, palette0, buffer, info, address, width);
179 draw2<Policy, false>(vram, palette1, buffer, info, address, width);
180 nameAddr = nextNameAddr<Policy>(nameAddr);
181 }
182 assert(width < 8);
183 if (width > 0) {
184 unsigned address = getPatternAddress<Policy, true>(vram, nameAddr, patternBase, x, y);
185 do {
186 draw2<Policy, true>(vram, (address & 1) ? palette1 : palette0, buffer, info, address, width);
187 } while (width > 0);
188 }
189}
190
191template<typename Policy, std::unsigned_integral Pixel> // only used for P1
192static void renderPattern2(
193 V9990VRAM& vram, Pixel* buffer, std::span<byte, 256> info, Pixel bgCol, unsigned width1, unsigned width2,
194 unsigned displayAX, unsigned displayAY, unsigned nameA, unsigned patternA, std::span<const Pixel, 16> palA,
195 unsigned displayBX, unsigned displayBY, unsigned nameB, unsigned patternB, std::span<const Pixel, 16> palB)
196{
197 renderPattern<Policy>(
198 vram, buffer, subspan(info, 0, width1), bgCol,
199 displayAX, displayAY, nameA, patternA, palA, palA);
200
201 buffer += width1;
202 width2 -= width1;
203 displayBX = (displayBX + width1) & 511;
204
205 renderPattern<Policy>(
206 vram, buffer, subspan(info, width1, width2), bgCol,
207 displayBX, displayBY, nameB, patternB, palB, palB);
208}
209
210template<typename Policy, std::unsigned_integral Pixel>
211static void renderSprites(
212 V9990VRAM& vram, unsigned spritePatternTable, std::span<const Pixel, 64> palette64,
213 Pixel* __restrict buffer, std::span<byte> info,
214 int displayX, int displayEnd, unsigned displayY)
215{
216 constexpr unsigned spriteTable = 0x3FE00;
217
218 // determine visible sprites
219 std::array<int, 16 + 1> visibleSprites;
220 int index = 0;
221 int index_max = 16;
222 for (auto sprite : xrange(125)) {
223 unsigned spriteInfo = spriteTable + 4 * sprite;
224 byte spriteY = Policy::readSpriteAttr(vram, spriteInfo) + 1;
225 byte posY = displayY - spriteY;
226 if (posY < 16) {
227 byte attr = Policy::readSpriteAttr(vram, spriteInfo + 3);
228 if (attr & 0x10) {
229 // Invisible sprites do contribute towards the
230 // 16-sprites-per-line limit.
231 index_max--;
232 } else {
233 visibleSprites[index++] = sprite;
234 }
235 if (index == index_max) break;
236 }
237 }
238 visibleSprites[index] = -1;
239
240 // actually draw sprites
241 for (unsigned sprite = 0; visibleSprites[sprite] != -1; ++sprite) {
242 unsigned addr = spriteTable + 4 * visibleSprites[sprite];
243 byte spriteAttr = Policy::readSpriteAttr(vram, addr + 3);
244 bool front = (spriteAttr & 0x20) == 0;
245 byte level = front ? 2 : 1;
246 int spriteX = Policy::readSpriteAttr(vram, addr + 2);
247 spriteX += 256 * (spriteAttr & 0x03);
248 if (spriteX > 1008) spriteX -= 1024; // hack X coord into -16..1008
249 byte spriteY = Policy::readSpriteAttr(vram, addr + 0);
250 byte spriteNo = Policy::readSpriteAttr(vram, addr + 1);
251 spriteY = displayY - (spriteY + 1);
252 unsigned patAddr = spritePatternTable + Policy::spritePatOfst(spriteNo, spriteY);
253 auto palette16 = subspan<16>(palette64, (spriteAttr >> 2) & 0x30);
254 for (int x = 0; x < 16; x +=2) {
255 auto draw = [&](int xPos, size_t p) {
256 if ((displayX <= xPos) && (xPos < displayEnd)) {
257 size_t xx = xPos - displayX;
258 if (p) {
259 if (info[xx] < level) {
260 buffer[xx] = palette16[p];
261 }
262 info[xx] = 2; // also if back-sprite is behind foreground
263 }
264 }
265 };
266 byte data = Policy::readPatternTable(vram, patAddr++);
267 draw(spriteX + x + 0, data >> 4);
268 draw(spriteX + x + 1, data & 0x0F);
269 }
270 }
271}
272
273template<std::unsigned_integral Pixel>
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<byte, 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 byte 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(P1);
322 renderSprites<P1Policy>( // uses and updates 'info'
323 vram, spritePatternTable, palette64,
324 linePtr, info, displayX, displayEnd, displayY);
325 }
326}
327
328template<std::unsigned_integral Pixel>
330 std::span<Pixel> buf, unsigned displayX, unsigned displayY,
331 unsigned displayYA, bool drawSprites)
332{
333 Pixel* __restrict linePtr = buf.data();
334 auto displayWidth = narrow<unsigned>(buf.size());
335
336 unsigned displayAX = (displayX + vdp.getScrollAX()) & 1023;
337
338 unsigned scrollY = vdp.getScrollAY();
339 unsigned rollMask = vdp.getRollMask(0x1FF);
340 unsigned scrollYBase = scrollY & ~rollMask & 0x1FF;
341 unsigned displayAY = scrollYBase + ((displayYA + scrollY) & rollMask);
342
343 unsigned displayEnd = displayX + displayWidth;
344
345 // image plane + backdrop color + fill-in 'info'
346 assert(displayWidth <= 512);
347 std::array<byte, 512> info; // filled in later: 0->background, 1->foreground, 2->sprite (front or back)
348 Pixel bgCol = palette64[vdp.getBackDropColor()];
349 byte offset = vdp.getPaletteOffset();
350 auto palette0 = subspan<16>(palette64, (offset & 0x03) << 4);
351 auto palette1 = subspan<16>(palette64, (offset & 0x0C) << 2);
352 renderPattern<P2Policy>(
353 vram, linePtr, subspan(info, 0, displayWidth), bgCol,
354 displayAX, displayAY, 0x7C000, 0x00000, palette0, palette1);
355
356 // combined back+front sprite plane
357 if (drawSprites) {
358 unsigned spritePatternTable = vdp.getSpritePatternAddress(P2);
359 renderSprites<P2Policy>(
360 vram, spritePatternTable, palette64,
361 linePtr, info, displayX, displayEnd, displayY);
362 }
363}
364
365// Force template instantiation
366#if HAVE_16BPP
367template class V9990P1Converter<uint16_t>;
368template class V9990P2Converter<uint16_t>;
369#endif
370#if HAVE_32BPP || COMPONENT_GL
371template class V9990P1Converter<uint32_t>;
372template class V9990P2Converter<uint32_t>;
373#endif
374
375} // namespace openmsx
void convertLine(std::span< Pixel > buf, unsigned displayX, unsigned displayY, unsigned displayYA, unsigned displayYB, bool drawSprites)
V9990P1Converter(V9990 &vdp, std::span< const Pixel, 64 > palette64)
void convertLine(std::span< Pixel > buf, unsigned displayX, unsigned displayY, unsigned displayYA, bool drawSprites)
V9990P2Converter(V9990 &vdp, std::span< const Pixel, 64 > palette64)
Video RAM for the V9990.
Definition: V9990VRAM.hh:16
byte readVRAMDirect(unsigned address)
Definition: V9990VRAM.hh:74
byte readVRAMP1(unsigned address)
Definition: V9990VRAM.hh:57
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:34
constexpr vecN< N, T > min(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:266
constexpr vecN< N, T > max(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:284
This file implemented 3 utility functions:
Definition: Autofire.cc:9
uint32_t Pixel
constexpr auto subspan(Range &&range, size_t offset, size_t count=std::dynamic_extent)
Definition: ranges.hh:446
static constexpr bool DRAW_BACKDROP
static void draw1(std::span< const Pixel, 16 > palette, Pixel *buffer, byte *, size_t p)
static constexpr bool DRAW_BACKDROP
static void draw1(std::span< const Pixel, 16 > palette, Pixel *buffer, byte *info, size_t p)
static byte readSpriteAttr(V9990VRAM &vram, unsigned addr)
static constexpr unsigned NAME_CHARS
static byte readNameTable(V9990VRAM &vram, unsigned addr)
static byte readPatternTable(V9990VRAM &vram, unsigned addr)
static unsigned spritePatOfst(byte spriteNo, byte spriteY)
static constexpr unsigned IMAGE_WIDTH
static constexpr unsigned PATTERN_CHARS
static constexpr unsigned SCREEN_WIDTH
static byte readNameTable(V9990VRAM &vram, unsigned addr)
static byte readSpriteAttr(V9990VRAM &vram, unsigned addr)
static byte readPatternTable(V9990VRAM &vram, unsigned addr)
static constexpr unsigned NAME_CHARS
static constexpr unsigned IMAGE_WIDTH
static unsigned spritePatOfst(byte spriteNo, byte spriteY)
static constexpr bool DRAW_BACKDROP
static void draw1(std::span< const Pixel, 16 > palette, Pixel *buffer, byte *info, size_t p)
static constexpr unsigned SCREEN_WIDTH
static constexpr unsigned PATTERN_CHARS
constexpr auto xrange(T e)
Definition: xrange.hh:133