openMSX
BitmapConverter.cc
Go to the documentation of this file.
1#include "BitmapConverter.hh"
2#include "endian.hh"
3#include "narrow.hh"
4#include "ranges.hh"
5#include "unreachable.hh"
6#include "xrange.hh"
7#include "components.hh"
8#include <algorithm>
9#include <cstdint>
10#include <tuple>
11
12namespace openmsx {
13
14template<std::unsigned_integral Pixel>
16 std::span<const Pixel, 16 * 2> palette16_,
17 std::span<const Pixel, 256> palette256_,
18 std::span<const Pixel, 32768> palette32768_)
19 : palette16(palette16_)
20 , palette256(palette256_)
21 , palette32768(palette32768_)
22{
23}
24
25template<std::unsigned_integral Pixel>
27{
28 dPaletteValid = true;
29 unsigned bits = sizeof(Pixel) * 8;
30 for (auto i : xrange(16)) {
31 DPixel p0 = palette16[i];
32 for (auto j : xrange(16)) {
33 DPixel p1 = palette16[j];
34 DPixel dp = Endian::BIG ? (p0 << bits) | p1
35 : (p1 << bits) | p0;
36 dPalette[16 * i + j] = dp;
37 }
38 }
39}
40
41template<std::unsigned_integral Pixel>
42void BitmapConverter<Pixel>::convertLine(std::span<Pixel> buf, std::span<const byte, 128> vramPtr)
43{
44 switch (mode.getByte()) {
45 case DisplayMode::GRAPHIC4: // screen 5
47 renderGraphic4(subspan<256>(buf), vramPtr);
48 break;
49 case DisplayMode::GRAPHIC5: // screen 6
51 renderGraphic5(subspan<512>(buf), vramPtr);
52 break;
53 // These are handled in convertLinePlanar().
63 // TODO: Support YJK on modes other than Graphic 6/7.
68 default:
69 renderBogus(subspan<256>(buf));
70 break;
71 }
72}
73
74template<std::unsigned_integral Pixel>
76 std::span<Pixel> buf, std::span<const byte, 128> vramPtr0, std::span<const byte, 128> vramPtr1)
77{
78 switch (mode.getByte()) {
79 case DisplayMode::GRAPHIC6: // screen 7
81 renderGraphic6(subspan<512>(buf), vramPtr0, vramPtr1);
82 break;
83 case DisplayMode::GRAPHIC7: // screen 8
85 renderGraphic7(subspan<256>(buf), vramPtr0, vramPtr1);
86 break;
87 case DisplayMode::GRAPHIC6 | DisplayMode::YJK: // screen 12
89 renderYJK(subspan<256>(buf), vramPtr0, vramPtr1);
90 break;
93 renderYAE(subspan<256>(buf), vramPtr0, vramPtr1);
94 break;
95 // These are handled in convertLine().
105 default:
106 renderBogus(subspan<256>(buf));
107 break;
108 }
109}
110
111template<std::unsigned_integral Pixel>
113 std::span<Pixel, 256> buf,
114 std::span<const byte, 128> vramPtr0)
115{
116 /*for (unsigned i = 0; i < 128; i += 2) {
117 unsigned data0 = vramPtr0[i + 0];
118 unsigned data1 = vramPtr0[i + 1];
119 buf[2 * i + 0] = palette16[data0 >> 4];
120 buf[2 * i + 1] = palette16[data0 & 15];
121 buf[2 * i + 2] = palette16[data1 >> 4];
122 buf[2 * i + 3] = palette16[data1 & 15];
123 }*/
124
125 if (!dPaletteValid) [[unlikely]] {
126 calcDPalette();
127 }
128
129 Pixel* __restrict pixelPtr = buf.data();
130 if ((sizeof(Pixel) == 2) && ((uintptr_t(pixelPtr) & 1) == 1)) {
131 // Its 16 bit destination but currently not aligned on a word boundary
132 // First write one pixel to get aligned
133 // Then write double pixels in a loop with 4 double pixels (is 8 single pixels) per iteration
134 // Finally write the last pixel unaligned
135 const auto* in = reinterpret_cast<const unsigned*>(vramPtr0.data());
136 unsigned data = in[0];
137 if constexpr (Endian::BIG) {
138 pixelPtr[0] = palette16[(data >> 28) & 0x0F];
139 data <<=4;
140 } else {
141 pixelPtr[0] = palette16[(data >> 0) & 0x0F];
142 data >>=4;
143 }
144
145 pixelPtr += 1; // Move to next pixel, which is on word boundary
146 auto out = reinterpret_cast<DPixel*>(pixelPtr);
147 for (auto i : xrange(256 / 8)) {
148 // 8 pixels per iteration
149 if constexpr (Endian::BIG) {
150 out[4 * i + 0] = dPalette[(data >> 24) & 0xFF];
151 out[4 * i + 1] = dPalette[(data >> 16) & 0xFF];
152 out[4 * i + 2] = dPalette[(data >> 8) & 0xFF];
153 if (i == (256-8) / 8) {
154 // Last pixel in last iteration must be written individually
155 pixelPtr[254] = palette16[(data >> 0) & 0x0F];
156 } else {
157 // Last double-pixel must be composed of
158 // remaining 4 bits in (previous) data
159 // and first 4 bits from (next) data
160 unsigned prevData = data;
161 data = in[i+1];
162 out[4 * i + 3] = dPalette[(prevData & 0xF0) | ((data >> 28) & 0x0F)];
163 data <<= 4;
164 }
165 } else {
166 out[4 * i + 0] = dPalette[(data >> 0) & 0xFF];
167 out[4 * i + 1] = dPalette[(data >> 8) & 0xFF];
168 out[4 * i + 2] = dPalette[(data >> 16) & 0xFF];
169 if (i != (256-8) / 8) {
170 // Last pixel in last iteration must be written individually
171 pixelPtr[254] = palette16[(data >> 24) & 0x0F];
172 } else {
173 // Last double-pixel must be composed of
174 // remaining 4 bits in (previous) data
175 // and first 4 bits from (next) data
176 unsigned prevData = data;
177 data = in[i+1];
178 out[4 * i + 3] = dPalette[((prevData >> 24) & 0x0F) | ((data & 0x0F)<<4)];
179 data >>=4;
180 }
181 }
182 }
183 return;
184 }
185
186 auto* out = reinterpret_cast<DPixel*>(pixelPtr);
187 const auto* in = reinterpret_cast<const unsigned*>(vramPtr0.data());
188 for (auto i : xrange(256 / 8)) {
189 // 8 pixels per iteration
190 unsigned data = in[i];
191 if constexpr (Endian::BIG) {
192 out[4 * i + 0] = dPalette[(data >> 24) & 0xFF];
193 out[4 * i + 1] = dPalette[(data >> 16) & 0xFF];
194 out[4 * i + 2] = dPalette[(data >> 8) & 0xFF];
195 out[4 * i + 3] = dPalette[(data >> 0) & 0xFF];
196 } else {
197 out[4 * i + 0] = dPalette[(data >> 0) & 0xFF];
198 out[4 * i + 1] = dPalette[(data >> 8) & 0xFF];
199 out[4 * i + 2] = dPalette[(data >> 16) & 0xFF];
200 out[4 * i + 3] = dPalette[(data >> 24) & 0xFF];
201 }
202 }
203}
204
205template<std::unsigned_integral Pixel>
206void BitmapConverter<Pixel>::renderGraphic5(
207 std::span<Pixel, 512> buf,
208 std::span<const byte, 128> vramPtr0)
209{
210 Pixel* __restrict pixelPtr = buf.data();
211 for (auto i : xrange(128)) {
212 unsigned data = vramPtr0[i];
213 pixelPtr[4 * i + 0] = palette16[ 0 + (data >> 6) ];
214 pixelPtr[4 * i + 1] = palette16[16 + ((data >> 4) & 3)];
215 pixelPtr[4 * i + 2] = palette16[ 0 + ((data >> 2) & 3)];
216 pixelPtr[4 * i + 3] = palette16[16 + ((data >> 0) & 3)];
217 }
218}
219
220template<std::unsigned_integral Pixel>
221void BitmapConverter<Pixel>::renderGraphic6(
222 std::span<Pixel, 512> buf,
223 std::span<const byte, 128> vramPtr0,
224 std::span<const byte, 128> vramPtr1)
225{
226 Pixel* __restrict pixelPtr = buf.data();
227 /*for (auto i : xrange(128)) {
228 unsigned data0 = vramPtr0[i];
229 unsigned data1 = vramPtr1[i];
230 pixelPtr[4 * i + 0] = palette16[data0 >> 4];
231 pixelPtr[4 * i + 1] = palette16[data0 & 15];
232 pixelPtr[4 * i + 2] = palette16[data1 >> 4];
233 pixelPtr[4 * i + 3] = palette16[data1 & 15];
234 }*/
235 if (!dPaletteValid) [[unlikely]] {
236 calcDPalette();
237 }
238 auto* out = reinterpret_cast<DPixel*>(pixelPtr);
239 const auto* in0 = reinterpret_cast<const unsigned*>(vramPtr0.data());
240 const auto* in1 = reinterpret_cast<const unsigned*>(vramPtr1.data());
241 for (auto i : xrange(512 / 16)) {
242 // 16 pixels per iteration
243 unsigned data0 = in0[i];
244 unsigned data1 = in1[i];
245 if constexpr (Endian::BIG) {
246 out[8 * i + 0] = dPalette[(data0 >> 24) & 0xFF];
247 out[8 * i + 1] = dPalette[(data1 >> 24) & 0xFF];
248 out[8 * i + 2] = dPalette[(data0 >> 16) & 0xFF];
249 out[8 * i + 3] = dPalette[(data1 >> 16) & 0xFF];
250 out[8 * i + 4] = dPalette[(data0 >> 8) & 0xFF];
251 out[8 * i + 5] = dPalette[(data1 >> 8) & 0xFF];
252 out[8 * i + 6] = dPalette[(data0 >> 0) & 0xFF];
253 out[8 * i + 7] = dPalette[(data1 >> 0) & 0xFF];
254 } else {
255 out[8 * i + 0] = dPalette[(data0 >> 0) & 0xFF];
256 out[8 * i + 1] = dPalette[(data1 >> 0) & 0xFF];
257 out[8 * i + 2] = dPalette[(data0 >> 8) & 0xFF];
258 out[8 * i + 3] = dPalette[(data1 >> 8) & 0xFF];
259 out[8 * i + 4] = dPalette[(data0 >> 16) & 0xFF];
260 out[8 * i + 5] = dPalette[(data1 >> 16) & 0xFF];
261 out[8 * i + 6] = dPalette[(data0 >> 24) & 0xFF];
262 out[8 * i + 7] = dPalette[(data1 >> 24) & 0xFF];
263 }
264 }
265}
266
267template<std::unsigned_integral Pixel>
268void BitmapConverter<Pixel>::renderGraphic7(
269 std::span<Pixel, 256> buf,
270 std::span<const byte, 128> vramPtr0,
271 std::span<const byte, 128> vramPtr1)
272{
273 Pixel* __restrict pixelPtr = buf.data();
274 for (auto i : xrange(128)) {
275 pixelPtr[2 * i + 0] = palette256[vramPtr0[i]];
276 pixelPtr[2 * i + 1] = palette256[vramPtr1[i]];
277 }
278}
279
280static constexpr std::tuple<int, int, int> yjk2rgb(int y, int j, int k)
281{
282 // Note the formula for 'blue' differs from the 'traditional' formula
283 // (e.g. as specified in the V9958 datasheet) in the rounding behavior.
284 // Confirmed on real turbor machine. For details see:
285 // https://github.com/openMSX/openMSX/issues/1394
286 // https://twitter.com/mdpc___/status/1480432007180341251?s=20
287 int r = std::clamp(y + j, 0, 31);
288 int g = std::clamp(y + k, 0, 31);
289 int b = std::clamp((5 * y - 2 * j - k + 2) / 4, 0, 31);
290 return {r, g, b};
291}
292
293template<std::unsigned_integral Pixel>
294void BitmapConverter<Pixel>::renderYJK(
295 std::span<Pixel, 256> buf,
296 std::span<const byte, 128> vramPtr0,
297 std::span<const byte, 128> vramPtr1)
298{
299 Pixel* __restrict pixelPtr = buf.data();
300 for (auto i : xrange(64)) {
301 std::array<unsigned, 4> p = {
302 vramPtr0[2 * i + 0],
303 vramPtr1[2 * i + 0],
304 vramPtr0[2 * i + 1],
305 vramPtr1[2 * i + 1],
306 };
307 int j = narrow<int>((p[2] & 7) + ((p[3] & 3) << 3)) - narrow<int>((p[3] & 4) << 3);
308 int k = narrow<int>((p[0] & 7) + ((p[1] & 3) << 3)) - narrow<int>((p[1] & 4) << 3);
309
310 for (auto n : xrange(4)) {
311 int y = narrow<int>(p[n] >> 3);
312 auto [r, g, b] = yjk2rgb(y, j, k);
313 int col = (r << 10) + (g << 5) + b;
314 pixelPtr[4 * i + n] = palette32768[col];
315 }
316 }
317}
318
319template<std::unsigned_integral Pixel>
320void BitmapConverter<Pixel>::renderYAE(
321 std::span<Pixel, 256> buf,
322 std::span<const byte, 128> vramPtr0,
323 std::span<const byte, 128> vramPtr1)
324{
325 Pixel* __restrict pixelPtr = buf.data();
326 for (auto i : xrange(64)) {
327 std::array<unsigned, 4> p = {
328 vramPtr0[2 * i + 0],
329 vramPtr1[2 * i + 0],
330 vramPtr0[2 * i + 1],
331 vramPtr1[2 * i + 1],
332 };
333 int j = narrow<int>((p[2] & 7) + ((p[3] & 3) << 3)) - narrow<int>((p[3] & 4) << 3);
334 int k = narrow<int>((p[0] & 7) + ((p[1] & 3) << 3)) - narrow<int>((p[1] & 4) << 3);
335
336 for (auto n : xrange(4)) {
337 Pixel pix;
338 if (p[n] & 0x08) {
339 // YAE
340 pix = palette16[p[n] >> 4];
341 } else {
342 // YJK
343 int y = narrow<int>(p[n] >> 3);
344 auto [r, g, b] = yjk2rgb(y, j, k);
345 pix = palette32768[(r << 10) + (g << 5) + b];
346 }
347 pixelPtr[4 * i + n] = pix;
348 }
349 }
350}
351
352template<std::unsigned_integral Pixel>
353void BitmapConverter<Pixel>::renderBogus(std::span<Pixel, 256> buf)
354{
355 // Verified on real V9958: all bogus modes behave like this, always
356 // show palette color 15.
357 // When this is in effect, the VRAM is not refreshed anymore, but that
358 // is not emulated.
359 ranges::fill(buf, palette16[15]);
360}
361
362// Force template instantiation.
363#if HAVE_16BPP
364template class BitmapConverter<uint16_t>;
365#endif
366#if HAVE_32BPP || COMPONENT_GL
367template class BitmapConverter<uint32_t>;
368#endif
369
370} // namespace openmsx
int g
Utility class for converting VRAM contents to host pixels.
void convertLine(std::span< Pixel > buf, std::span< const byte, 128 > vramPtr)
Convert a line of V9938 VRAM to 256 or 512 host pixels.
void convertLinePlanar(std::span< Pixel > buf, std::span< const byte, 128 > vramPtr0, std::span< const byte, 128 > vramPtr1)
Convert a line of V9938 VRAM to 256 or 512 host pixels.
BitmapConverter(std::span< const Pixel, 16 *2 > palette16, std::span< const Pixel, 256 > palette256, std::span< const Pixel, 32768 > palette32768)
Create a new bitmap scanline converter.
static constexpr byte YAE
Encoding of YAE flag.
Definition: DisplayMode.hh:54
static constexpr byte YJK
Encoding of YJK flag.
Definition: DisplayMode.hh:51
constexpr bool BIG
Definition: endian.hh:15
constexpr vecN< N, T > clamp(const vecN< N, T > &x, const vecN< N, T > &minVal, const vecN< N, T > &maxVal)
Definition: gl_vec.hh:294
This file implemented 3 utility functions:
Definition: Autofire.cc:9
uint32_t Pixel
constexpr void fill(ForwardRange &&range, const T &value)
Definition: ranges.hh:287
#define UNREACHABLE
Definition: unreachable.hh:38
constexpr auto xrange(T e)
Definition: xrange.hh:132