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