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