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