openMSX
CharacterConverter.cc
Go to the documentation of this file.
1/*
2TODO:
3- Clean up renderGraphics2, it is currently very hard to understand
4 with all the masks and quarters etc.
5- Correctly implement vertical scroll in text modes.
6 Can be implemented by reordering blitting, but uses a smaller
7 wrap than GFX modes: 8 lines instead of 256 lines.
8*/
9
10#include "CharacterConverter.hh"
11
12#include "VDP.hh"
13#include "VDPVRAM.hh"
14
15#include "ranges.hh"
16#include "xrange.hh"
17
18#include <bit>
19#include <cstdint>
20
21#ifdef __SSE2__
22#include "emmintrin.h" // SSE2
23#endif
24
25namespace openmsx {
26
28
30 VDP& vdp_, std::span<const Pixel, 16> palFg_, std::span<const Pixel, 16> palBg_)
31 : vdp(vdp_), vram(vdp.getVRAM()), palFg(palFg_), palBg(palBg_)
32{
33}
34
36{
37 modeBase = mode.getBase();
38 assert(modeBase < 0x0C);
39}
40
41void CharacterConverter::convertLine(std::span<Pixel> buf, int line) const
42{
43 // TODO: Support YJK on modes other than Graphic 6/7.
44 switch (modeBase) {
45 case DisplayMode::GRAPHIC1: // screen 1
46 renderGraphic1(subspan<256>(buf), line);
47 break;
48 case DisplayMode::TEXT1: // screen 0, width 40
49 renderText1(subspan<256>(buf), line);
50 break;
51 case DisplayMode::MULTICOLOR: // screen 3
52 renderMulti(subspan<256>(buf), line);
53 break;
54 case DisplayMode::GRAPHIC2: // screen 2
55 renderGraphic2(subspan<256>(buf), line);
56 break;
57 case DisplayMode::GRAPHIC3: // screen 4
58 renderGraphic2(subspan<256>(buf), line); // graphic3, actually
59 break;
60 case DisplayMode::TEXT2: // screen 0, width 80
61 renderText2(subspan<512>(buf), line);
62 break;
63 case DisplayMode::TEXT1Q: // TMSxxxx only
64 if (vdp.isMSX1VDP()) {
65 renderText1Q(subspan<256>(buf), line);
66 } else {
67 renderBlank (subspan<256>(buf));
68 }
69 break;
70 case DisplayMode::MULTIQ: // TMSxxxx only
71 if (vdp.isMSX1VDP()) {
72 renderMultiQ(subspan<256>(buf), line);
73 } else {
74 renderBlank (subspan<256>(buf));
75 }
76 break;
77 default: // remaining (non-bitmap) modes
78 if (vdp.isMSX1VDP()) {
79 renderBogus(subspan<256>(buf));
80 } else {
81 renderBlank(subspan<256>(buf));
82 }
83 }
84}
85
86#ifdef __SSE2__
87// Copied from Scale2xScaler.cc, TODO move to common location?
88static inline __m128i select(__m128i a0, __m128i a1, __m128i mask)
89{
90 return _mm_xor_si128(_mm_and_si128(_mm_xor_si128(a0, a1), mask), a0);
91}
92#endif
93
94static inline void draw6(
95 Pixel* __restrict & pixelPtr, Pixel fg, Pixel bg, byte pattern)
96{
97 pixelPtr[0] = (pattern & 0x80) ? fg : bg;
98 pixelPtr[1] = (pattern & 0x40) ? fg : bg;
99 pixelPtr[2] = (pattern & 0x20) ? fg : bg;
100 pixelPtr[3] = (pattern & 0x10) ? fg : bg;
101 pixelPtr[4] = (pattern & 0x08) ? fg : bg;
102 pixelPtr[5] = (pattern & 0x04) ? fg : bg;
103 pixelPtr += 6;
104}
105
106static inline void draw8(
107 Pixel* __restrict & pixelPtr, Pixel fg, Pixel bg, byte pattern)
108{
109#ifdef __SSE2__
110 // SSE2 version, 32bpp
111 const __m128i m74 = _mm_set_epi32(0x10, 0x20, 0x40, 0x80);
112 const __m128i m30 = _mm_set_epi32(0x01, 0x02, 0x04, 0x08);
113 const __m128i zero = _mm_setzero_si128();
114
115 __m128i fg4 = _mm_set1_epi32(fg);
116 __m128i bg4 = _mm_set1_epi32(bg);
117 __m128i pat = _mm_set1_epi32(pattern);
118
119 __m128i b74 = _mm_cmpeq_epi32(_mm_and_si128(pat, m74), zero);
120 __m128i b30 = _mm_cmpeq_epi32(_mm_and_si128(pat, m30), zero);
121
122 auto* out = std::bit_cast<__m128i*>(pixelPtr);
123 _mm_storeu_si128(out + 0, select(fg4, bg4, b74));
124 _mm_storeu_si128(out + 1, select(fg4, bg4, b30));
125 pixelPtr += 8;
126 return;
127#endif
128
129 // C++ version
130 pixelPtr[0] = (pattern & 0x80) ? fg : bg;
131 pixelPtr[1] = (pattern & 0x40) ? fg : bg;
132 pixelPtr[2] = (pattern & 0x20) ? fg : bg;
133 pixelPtr[3] = (pattern & 0x10) ? fg : bg;
134 pixelPtr[4] = (pattern & 0x08) ? fg : bg;
135 pixelPtr[5] = (pattern & 0x04) ? fg : bg;
136 pixelPtr[6] = (pattern & 0x02) ? fg : bg;
137 pixelPtr[7] = (pattern & 0x01) ? fg : bg;
138 pixelPtr += 8;
139}
140
141void CharacterConverter::renderText1(std::span<Pixel, 256> buf, int line) const
142{
143 Pixel fg = palFg[vdp.getForegroundColor()];
144 Pixel bg = palFg[vdp.getBackgroundColor()];
145
146 // 8 * 256 is small enough to always be contiguous
147 auto patternArea = vram.patternTable.getReadArea<256 * 8>(0);
148 auto l = (line + vdp.getVerticalScroll()) & 7;
149
150 // Note: Because line width is not a power of two, reading an entire line
151 // from a VRAM pointer returned by readArea will not wrap the index
152 // correctly. Therefore we read one character at a time.
153 unsigned nameStart = (line / 8) * 40;
154 unsigned nameEnd = nameStart + 40;
155 Pixel* __restrict pixelPtr = buf.data();
156 for (auto name : xrange(nameStart, nameEnd)) {
157 unsigned charCode = vram.nameTable.readNP((name + 0xC00) | (~0u << 12));
158 auto pattern = patternArea[l + charCode * 8];
159 draw6(pixelPtr, fg, bg, pattern);
160 }
161}
162
163void CharacterConverter::renderText1Q(std::span<Pixel, 256> buf, int line) const
164{
165 Pixel fg = palFg[vdp.getForegroundColor()];
166 Pixel bg = palFg[vdp.getBackgroundColor()];
167
168 unsigned patternBaseLine = (~0u << 13) | ((line + vdp.getVerticalScroll()) & 7);
169
170 // Note: Because line width is not a power of two, reading an entire line
171 // from a VRAM pointer returned by readArea will not wrap the index
172 // correctly. Therefore we read one character at a time.
173 unsigned nameStart = (line / 8) * 40;
174 unsigned nameEnd = nameStart + 40;
175 unsigned patternQuarter = (line & 0xC0) << 2;
176 Pixel* __restrict pixelPtr = buf.data();
177 for (auto name : xrange(nameStart, nameEnd)) {
178 unsigned charCode = vram.nameTable.readNP((name + 0xC00) | (~0u << 12));
179 unsigned patternNr = patternQuarter | charCode;
180 auto pattern = vram.patternTable.readNP(
181 patternBaseLine | (patternNr * 8));
182 draw6(pixelPtr, fg, bg, pattern);
183 }
184}
185
186void CharacterConverter::renderText2(std::span<Pixel, 512> buf, int line) const
187{
188 Pixel plainFg = palFg[vdp.getForegroundColor()];
189 Pixel plainBg = palFg[vdp.getBackgroundColor()];
190 Pixel blinkFg, blinkBg;
191 if (vdp.getBlinkState()) {
192 int fg = vdp.getBlinkForegroundColor();
193 blinkFg = palBg[fg ? fg : vdp.getBlinkBackgroundColor()];
194 blinkBg = palBg[vdp.getBlinkBackgroundColor()];
195 } else {
196 blinkFg = plainFg;
197 blinkBg = plainBg;
198 }
199
200 // 8 * 256 is small enough to always be contiguous
201 auto patternArea = vram.patternTable.getReadArea<256 * 8>(0);
202 auto l = (line + vdp.getVerticalScroll()) & 7;
203
204 unsigned colorStart = (line / 8) * (80 / 8);
205 unsigned nameStart = (line / 8) * 80;
206 Pixel* __restrict pixelPtr = buf.data();
207 for (auto i : xrange(80 / 8)) {
208 unsigned colorPattern = vram.colorTable.readNP(
209 (colorStart + i) | (~0u << 9));
210 auto nameArea = vram.nameTable.getReadArea<8>(
211 (nameStart + 8 * i) | (~0u << 12));
212 draw6(pixelPtr,
213 (colorPattern & 0x80) ? blinkFg : plainFg,
214 (colorPattern & 0x80) ? blinkBg : plainBg,
215 patternArea[l + nameArea[0] * 8]);
216 draw6(pixelPtr,
217 (colorPattern & 0x40) ? blinkFg : plainFg,
218 (colorPattern & 0x40) ? blinkBg : plainBg,
219 patternArea[l + nameArea[1] * 8]);
220 draw6(pixelPtr,
221 (colorPattern & 0x20) ? blinkFg : plainFg,
222 (colorPattern & 0x20) ? blinkBg : plainBg,
223 patternArea[l + nameArea[2] * 8]);
224 draw6(pixelPtr,
225 (colorPattern & 0x10) ? blinkFg : plainFg,
226 (colorPattern & 0x10) ? blinkBg : plainBg,
227 patternArea[l + nameArea[3] * 8]);
228 draw6(pixelPtr,
229 (colorPattern & 0x08) ? blinkFg : plainFg,
230 (colorPattern & 0x08) ? blinkBg : plainBg,
231 patternArea[l + nameArea[4] * 8]);
232 draw6(pixelPtr,
233 (colorPattern & 0x04) ? blinkFg : plainFg,
234 (colorPattern & 0x04) ? blinkBg : plainBg,
235 patternArea[l + nameArea[5] * 8]);
236 draw6(pixelPtr,
237 (colorPattern & 0x02) ? blinkFg : plainFg,
238 (colorPattern & 0x02) ? blinkBg : plainBg,
239 patternArea[l + nameArea[6] * 8]);
240 draw6(pixelPtr,
241 (colorPattern & 0x01) ? blinkFg : plainFg,
242 (colorPattern & 0x01) ? blinkBg : plainBg,
243 patternArea[l + nameArea[7] * 8]);
244 }
245}
246
247std::span<const byte, 32> CharacterConverter::getNamePtr(int line, int scroll) const
248{
249 // no need to test whether multi-page scrolling is enabled,
250 // indexMask in the nameTable already takes care of it
251 return vram.nameTable.getReadArea<32>(
252 ((line / 8) * 32) | ((scroll & 0x20) ? 0x8000 : 0));
253}
254void CharacterConverter::renderGraphic1(std::span<Pixel, 256> buf, int line) const
255{
256 auto patternArea = vram.patternTable.getReadArea<256 * 8>(0);
257 auto l = line & 7;
258 auto colorArea = vram.colorTable.getReadArea<256 / 8>(0);
259
260 int scroll = vdp.getHorizontalScrollHigh();
261 auto namePtr = getNamePtr(line, scroll);
262 Pixel* __restrict pixelPtr = buf.data();
263 repeat(32, [&] {
264 auto charCode = namePtr[scroll & 0x1F];
265 auto pattern = patternArea[l + charCode * 8];
266 auto color = colorArea[charCode / 8];
267 Pixel fg = palFg[color >> 4];
268 Pixel bg = palFg[color & 0x0F];
269 draw8(pixelPtr, fg, bg, pattern);
270 if (!(++scroll & 0x1F)) namePtr = getNamePtr(line, scroll);
271 });
272}
273
274void CharacterConverter::renderGraphic2(std::span<Pixel, 256> buf, int line) const
275{
276 int quarter8 = (((line / 8) * 32) & ~0xFF) * 8;
277 int line7 = line & 7;
278 int scroll = vdp.getHorizontalScrollHigh();
279 auto namePtr = getNamePtr(line, scroll);
280
281 Pixel* __restrict pixelPtr = buf.data();
282 if (vram.colorTable .isContinuous((8 * 256) - 1) &&
283 vram.patternTable.isContinuous((8 * 256) - 1) &&
284 ((scroll & 0x1f) == 0)) {
285 // Both color and pattern table can be accessed contiguously
286 // (no mirroring) and there's no v9958 horizontal scrolling.
287 // This is very common, so make an optimized version for this.
288 auto patternArea = vram.patternTable.getReadArea<256 * 8>(quarter8);
289 auto colorArea = vram.colorTable .getReadArea<256 * 8>(quarter8);
290 for (auto n : xrange(32)) {
291 auto charCode8 = namePtr[n] * 8;
292 auto pattern = patternArea[line7 + charCode8];
293 auto color = colorArea [line7 + charCode8];
294 Pixel fg = palFg[color >> 4];
295 Pixel bg = palFg[color & 0x0F];
296 draw8(pixelPtr, fg, bg, pattern);
297 }
298 } else {
299 // Slower variant, also works when:
300 // - there is mirroring in the color table
301 // - there is mirroring in the pattern table (TMS9929)
302 // - V9958 horizontal scroll feature is used
303 unsigned baseLine = (~0u << 13) | quarter8 | line7;
304 repeat(32, [&] {
305 unsigned charCode8 = namePtr[scroll & 0x1F] * 8;
306 unsigned index = charCode8 | baseLine;
307 auto pattern = vram.patternTable.readNP(index);
308 auto color = vram.colorTable .readNP(index);
309 Pixel fg = palFg[color >> 4];
310 Pixel bg = palFg[color & 0x0F];
311 draw8(pixelPtr, fg, bg, pattern);
312 if (!(++scroll & 0x1F)) namePtr = getNamePtr(line, scroll);
313 });
314 }
315}
316
317void CharacterConverter::renderMultiHelper(
318 Pixel* __restrict pixelPtr, int line,
319 unsigned mask, unsigned patternQuarter) const
320{
321 unsigned baseLine = mask | ((line / 4) & 7);
322 unsigned scroll = vdp.getHorizontalScrollHigh();
323 auto namePtr = getNamePtr(line, scroll);
324 repeat(32, [&] {
325 unsigned patternNr = patternQuarter | namePtr[scroll & 0x1F];
326 unsigned color = vram.patternTable.readNP((patternNr * 8) | baseLine);
327 Pixel cl = palFg[color >> 4];
328 Pixel cr = palFg[color & 0x0F];
329 pixelPtr[0] = cl; pixelPtr[1] = cl;
330 pixelPtr[2] = cl; pixelPtr[3] = cl;
331 pixelPtr[4] = cr; pixelPtr[5] = cr;
332 pixelPtr[6] = cr; pixelPtr[7] = cr;
333 pixelPtr += 8;
334 if (!(++scroll & 0x1F)) namePtr = getNamePtr(line, scroll);
335 });
336}
337void CharacterConverter::renderMulti(std::span<Pixel, 256> buf, int line) const
338{
339 unsigned mask = (~0u << 11);
340 renderMultiHelper(buf.data(), line, mask, 0);
341}
342
343void CharacterConverter::renderMultiQ(
344 std::span<Pixel, 256> buf, int line) const
345{
346 unsigned mask = (~0u << 13);
347 unsigned patternQuarter = (line * 4) & ~0xFF; // (line / 8) * 32
348 renderMultiHelper(buf.data(), line, mask, patternQuarter);
349}
350
351void CharacterConverter::renderBogus(std::span<Pixel, 256> buf) const
352{
353 Pixel* __restrict pixelPtr = buf.data();
354 Pixel fg = palFg[vdp.getForegroundColor()];
355 Pixel bg = palFg[vdp.getBackgroundColor()];
356 auto draw = [&](int n, Pixel col) {
357 pixelPtr = std::fill_n(pixelPtr, n, col);
358 };
359 draw(8, bg);
360 repeat(40, [&] {
361 draw(4, fg);
362 draw(2, bg);
363 });
364 draw(8, bg);
365}
366
367void CharacterConverter::renderBlank(std::span<Pixel, 256> buf) const
368{
369 // when this is in effect, the VRAM is not refreshed anymore, but that
370 // is not emulated
371 ranges::fill(buf, palFg[15]);
372}
373
374} // namespace openmsx
void setDisplayMode(DisplayMode mode)
Select the display mode to use for scanline conversion.
CharacterConverter(VDP &vdp, std::span< const Pixel, 16 > palFg, std::span< const Pixel, 16 > palBg)
Create a new bitmap scanline converter.
void convertLine(std::span< Pixel > buf, int line) const
Convert a line of V9938 VRAM to 256 or 512 host pixels.
Represents a VDP display mode.
constexpr byte getBase() const
Get the base display mode as an integer: M5..M1 combined.
VRAMWindow colorTable
Definition VDPVRAM.hh:690
VRAMWindow patternTable
Definition VDPVRAM.hh:691
VRAMWindow nameTable
Definition VDPVRAM.hh:689
Unified implementation of MSX Video Display Processors (VDPs).
Definition VDP.hh:66
int getForegroundColor() const
Gets the current foreground color.
Definition VDP.hh:206
int getBlinkBackgroundColor() const
Gets the current blinking color for blinking text.
Definition VDP.hh:237
byte getHorizontalScrollHigh() const
Gets the current horizontal scroll higher bits.
Definition VDP.hh:348
bool getBlinkState() const
Gets the current blink state.
Definition VDP.hh:244
int getBlinkForegroundColor() const
Gets the current blinking color for blinking text.
Definition VDP.hh:230
bool isMSX1VDP() const
Is this an MSX1 VDP?
Definition VDP.hh:107
byte getBackgroundColor() const
Gets the current background color.
Definition VDP.hh:218
byte getVerticalScroll() const
Gets the current vertical scroll (line displayed at Y=0).
Definition VDP.hh:330
bool isContinuous(unsigned index, unsigned size) const
Is the given index range continuous in VRAM (iow there's no mirroring) Only if the range is continuou...
Definition VDPVRAM.hh:200
byte readNP(unsigned index) const
Reads a byte from VRAM in its current state.
Definition VDPVRAM.hh:265
std::span< const byte, size > getReadArea(unsigned index) const
Gets a span of a contiguous part of the VRAM.
Definition VDPVRAM.hh:228
This file implemented 3 utility functions:
Definition Autofire.cc:11
CharacterConverter::Pixel Pixel
constexpr void fill(ForwardRange &&range, const T &value)
Definition ranges.hh:305
constexpr void repeat(T n, Op op)
Repeat the given operation 'op' 'n' times.
Definition xrange.hh:147
constexpr auto xrange(T e)
Definition xrange.hh:132