openMSX
BitmapConverter.cc
Go to the documentation of this file.
1#include "BitmapConverter.hh"
2
3#include "endian.hh"
4#include "narrow.hh"
5#include "ranges.hh"
6#include "unreachable.hh"
7#include "xrange.hh"
8
9#include <algorithm>
10#include <bit>
11#include <tuple>
12
13namespace openmsx {
14
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
25void BitmapConverter::calcDPalette()
26{
27 dPaletteValid = true;
28 unsigned bits = sizeof(Pixel) * 8;
29 for (auto i : xrange(16)) {
30 DPixel p0 = palette16[i];
31 for (auto j : xrange(16)) {
32 DPixel p1 = palette16[j];
33 DPixel dp = Endian::BIG ? (p0 << bits) | p1
34 : (p1 << bits) | p0;
35 dPalette[16 * i + j] = dp;
36 }
37 }
38}
39
40void BitmapConverter::convertLine(std::span<Pixel> buf, std::span<const byte, 128> vramPtr)
41{
42 switch (mode.getByte()) {
43 case DisplayMode::GRAPHIC4: // screen 5
45 renderGraphic4(subspan<256>(buf), vramPtr);
46 break;
47 case DisplayMode::GRAPHIC5: // screen 6
49 renderGraphic5(subspan<512>(buf), vramPtr);
50 break;
51 // These are handled in convertLinePlanar().
61 // TODO: Support YJK on modes other than Graphic 6/7.
66 default:
67 renderBogus(subspan<256>(buf));
68 break;
69 }
70}
71
73 std::span<Pixel> buf, std::span<const byte, 128> vramPtr0, std::span<const byte, 128> vramPtr1)
74{
75 switch (mode.getByte()) {
76 case DisplayMode::GRAPHIC6: // screen 7
78 renderGraphic6(subspan<512>(buf), vramPtr0, vramPtr1);
79 break;
80 case DisplayMode::GRAPHIC7: // screen 8
82 renderGraphic7(subspan<256>(buf), vramPtr0, vramPtr1);
83 break;
84 case DisplayMode::GRAPHIC6 | DisplayMode::YJK: // screen 12
86 renderYJK(subspan<256>(buf), vramPtr0, vramPtr1);
87 break;
90 renderYAE(subspan<256>(buf), vramPtr0, vramPtr1);
91 break;
92 // These are handled in convertLine().
102 default:
103 renderBogus(subspan<256>(buf));
104 break;
105 }
106}
107
108void BitmapConverter::renderGraphic4(
109 std::span<Pixel, 256> buf,
110 std::span<const byte, 128> vramPtr0)
111{
112 /*for (unsigned i = 0; i < 128; i += 2) {
113 unsigned data0 = vramPtr0[i + 0];
114 unsigned data1 = vramPtr0[i + 1];
115 buf[2 * i + 0] = palette16[data0 >> 4];
116 buf[2 * i + 1] = palette16[data0 & 15];
117 buf[2 * i + 2] = palette16[data1 >> 4];
118 buf[2 * i + 3] = palette16[data1 & 15];
119 }*/
120
121 if (!dPaletteValid) [[unlikely]] {
122 calcDPalette();
123 }
124
125 Pixel* __restrict pixelPtr = buf.data();
126 auto* out = std::bit_cast<DPixel*>(pixelPtr);
127 const auto* in = std::bit_cast<const unsigned*>(vramPtr0.data());
128 for (auto i : xrange(256 / 8)) {
129 // 8 pixels per iteration
130 unsigned data = in[i];
131 if constexpr (Endian::BIG) {
132 out[4 * i + 0] = dPalette[(data >> 24) & 0xFF];
133 out[4 * i + 1] = dPalette[(data >> 16) & 0xFF];
134 out[4 * i + 2] = dPalette[(data >> 8) & 0xFF];
135 out[4 * i + 3] = dPalette[(data >> 0) & 0xFF];
136 } else {
137 out[4 * i + 0] = dPalette[(data >> 0) & 0xFF];
138 out[4 * i + 1] = dPalette[(data >> 8) & 0xFF];
139 out[4 * i + 2] = dPalette[(data >> 16) & 0xFF];
140 out[4 * i + 3] = dPalette[(data >> 24) & 0xFF];
141 }
142 }
143}
144
145void BitmapConverter::renderGraphic5(
146 std::span<Pixel, 512> buf,
147 std::span<const byte, 128> vramPtr0) const
148{
149 Pixel* __restrict pixelPtr = buf.data();
150 for (auto i : xrange(128)) {
151 unsigned data = vramPtr0[i];
152 pixelPtr[4 * i + 0] = palette16[ 0 + (data >> 6) ];
153 pixelPtr[4 * i + 1] = palette16[16 + ((data >> 4) & 3)];
154 pixelPtr[4 * i + 2] = palette16[ 0 + ((data >> 2) & 3)];
155 pixelPtr[4 * i + 3] = palette16[16 + ((data >> 0) & 3)];
156 }
157}
158
159void BitmapConverter::renderGraphic6(
160 std::span<Pixel, 512> buf,
161 std::span<const byte, 128> vramPtr0,
162 std::span<const byte, 128> vramPtr1)
163{
164 Pixel* __restrict pixelPtr = buf.data();
165 /*for (auto i : xrange(128)) {
166 unsigned data0 = vramPtr0[i];
167 unsigned data1 = vramPtr1[i];
168 pixelPtr[4 * i + 0] = palette16[data0 >> 4];
169 pixelPtr[4 * i + 1] = palette16[data0 & 15];
170 pixelPtr[4 * i + 2] = palette16[data1 >> 4];
171 pixelPtr[4 * i + 3] = palette16[data1 & 15];
172 }*/
173 if (!dPaletteValid) [[unlikely]] {
174 calcDPalette();
175 }
176 auto* out = std::bit_cast<DPixel*>(pixelPtr);
177 const auto* in0 = std::bit_cast<const unsigned*>(vramPtr0.data());
178 const auto* in1 = std::bit_cast<const unsigned*>(vramPtr1.data());
179 for (auto i : xrange(512 / 16)) {
180 // 16 pixels per iteration
181 unsigned data0 = in0[i];
182 unsigned data1 = in1[i];
183 if constexpr (Endian::BIG) {
184 out[8 * i + 0] = dPalette[(data0 >> 24) & 0xFF];
185 out[8 * i + 1] = dPalette[(data1 >> 24) & 0xFF];
186 out[8 * i + 2] = dPalette[(data0 >> 16) & 0xFF];
187 out[8 * i + 3] = dPalette[(data1 >> 16) & 0xFF];
188 out[8 * i + 4] = dPalette[(data0 >> 8) & 0xFF];
189 out[8 * i + 5] = dPalette[(data1 >> 8) & 0xFF];
190 out[8 * i + 6] = dPalette[(data0 >> 0) & 0xFF];
191 out[8 * i + 7] = dPalette[(data1 >> 0) & 0xFF];
192 } else {
193 out[8 * i + 0] = dPalette[(data0 >> 0) & 0xFF];
194 out[8 * i + 1] = dPalette[(data1 >> 0) & 0xFF];
195 out[8 * i + 2] = dPalette[(data0 >> 8) & 0xFF];
196 out[8 * i + 3] = dPalette[(data1 >> 8) & 0xFF];
197 out[8 * i + 4] = dPalette[(data0 >> 16) & 0xFF];
198 out[8 * i + 5] = dPalette[(data1 >> 16) & 0xFF];
199 out[8 * i + 6] = dPalette[(data0 >> 24) & 0xFF];
200 out[8 * i + 7] = dPalette[(data1 >> 24) & 0xFF];
201 }
202 }
203}
204
205void BitmapConverter::renderGraphic7(
206 std::span<Pixel, 256> buf,
207 std::span<const byte, 128> vramPtr0,
208 std::span<const byte, 128> vramPtr1) const
209{
210 Pixel* __restrict pixelPtr = buf.data();
211 for (auto i : xrange(128)) {
212 pixelPtr[2 * i + 0] = palette256[vramPtr0[i]];
213 pixelPtr[2 * i + 1] = palette256[vramPtr1[i]];
214 }
215}
216
217static constexpr std::tuple<int, int, int> yjk2rgb(int y, int j, int k)
218{
219 // Note the formula for 'blue' differs from the 'traditional' formula
220 // (e.g. as specified in the V9958 datasheet) in the rounding behavior.
221 // Confirmed on real turbor machine. For details see:
222 // https://github.com/openMSX/openMSX/issues/1394
223 // https://twitter.com/mdpc___/status/1480432007180341251?s=20
224 int r = std::clamp(y + j, 0, 31);
225 int g = std::clamp(y + k, 0, 31);
226 int b = std::clamp((5 * y - 2 * j - k + 2) / 4, 0, 31);
227 return {r, g, b};
228}
229
230void BitmapConverter::renderYJK(
231 std::span<Pixel, 256> buf,
232 std::span<const byte, 128> vramPtr0,
233 std::span<const byte, 128> vramPtr1) const
234{
235 Pixel* __restrict pixelPtr = buf.data();
236 for (auto i : xrange(64)) {
237 std::array<unsigned, 4> p = {
238 vramPtr0[2 * i + 0],
239 vramPtr1[2 * i + 0],
240 vramPtr0[2 * i + 1],
241 vramPtr1[2 * i + 1],
242 };
243 int j = narrow<int>((p[2] & 7) + ((p[3] & 3) << 3)) - narrow<int>((p[3] & 4) << 3);
244 int k = narrow<int>((p[0] & 7) + ((p[1] & 3) << 3)) - narrow<int>((p[1] & 4) << 3);
245
246 for (auto n : xrange(4)) {
247 int y = narrow<int>(p[n] >> 3);
248 auto [r, g, b] = yjk2rgb(y, j, k);
249 int col = (r << 10) + (g << 5) + b;
250 pixelPtr[4 * i + n] = palette32768[col];
251 }
252 }
253}
254
255void BitmapConverter::renderYAE(
256 std::span<Pixel, 256> buf,
257 std::span<const byte, 128> vramPtr0,
258 std::span<const byte, 128> vramPtr1) const
259{
260 Pixel* __restrict pixelPtr = buf.data();
261 for (auto i : xrange(64)) {
262 std::array<unsigned, 4> p = {
263 vramPtr0[2 * i + 0],
264 vramPtr1[2 * i + 0],
265 vramPtr0[2 * i + 1],
266 vramPtr1[2 * i + 1],
267 };
268 int j = narrow<int>((p[2] & 7) + ((p[3] & 3) << 3)) - narrow<int>((p[3] & 4) << 3);
269 int k = narrow<int>((p[0] & 7) + ((p[1] & 3) << 3)) - narrow<int>((p[1] & 4) << 3);
270
271 for (auto n : xrange(4)) {
272 Pixel pix;
273 if (p[n] & 0x08) {
274 // YAE
275 pix = palette16[p[n] >> 4];
276 } else {
277 // YJK
278 int y = narrow<int>(p[n] >> 3);
279 auto [r, g, b] = yjk2rgb(y, j, k);
280 pix = palette32768[(r << 10) + (g << 5) + b];
281 }
282 pixelPtr[4 * i + n] = pix;
283 }
284 }
285}
286
287void BitmapConverter::renderBogus(std::span<Pixel, 256> buf) const
288{
289 // Verified on real V9958: all bogus modes behave like this, always
290 // show palette color 15.
291 // When this is in effect, the VRAM is not refreshed anymore, but that
292 // is not emulated.
293 ranges::fill(buf, palette16[15]);
294}
295
296} // namespace openmsx
int g
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.
void convertLine(std::span< Pixel > buf, std::span< const byte, 128 > vramPtr)
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.
static constexpr byte YJK
Encoding of YJK flag.
constexpr byte getByte() const
Get the display mode as a byte: YAE YJK M5..M1 combined.
constexpr bool BIG
Definition endian.hh:16
This file implemented 3 utility functions:
Definition Autofire.cc:11
constexpr void fill(ForwardRange &&range, const T &value)
Definition ranges.hh:305
#define UNREACHABLE
constexpr auto xrange(T e)
Definition xrange.hh:132