OXYGENHUB
◆ main openmsx / src / video / SDLRasterizer.cc
20.1 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#include "SDLRasterizer.hh"
2
3
#include "Display.hh"
4
#include "OutputSurface.hh"
5
#include "PostProcessor.hh"
6
#include "RawFrame.hh"
7
#include "RenderSettings.hh"
8
#include "Renderer.hh"
9
#include "VDP.hh"
10
#include "VDPVRAM.hh"
11
12
#include "MemoryOps.hh"
13
#include "enumerate.hh"
14
#include "one_of.hh"
15
#include "xrange.hh"
16
17
#include <algorithm>
18
#include <array>
19
#include <cassert>
20
#include <cstdint>
21
#include <memory>
22
23
using namespace gl;
24
25
namespace openmsx {
26
27
using Pixel = SDLRasterizer::Pixel;
28
29
/** VDP ticks between start of line and start of left border.
30
  */
31
static constexpr int TICKS_LEFT_BORDER = 100 + 102;
32
static constexpr int TICKS_RIGHT_BORDER = 100 + 102 + 56 + 1024 + 59;
33
34
/** The middle of the visible (display + borders) part of a line,
35
  * expressed in VDP ticks since the start of the line.
36
  * TODO: Move this to a central location?
37
  */
38
static constexpr int TICKS_VISIBLE_MIDDLE =
39
	TICKS_LEFT_BORDER + (VDP::TICKS_PER_LINE - TICKS_LEFT_BORDER - 27) / 2;
40
41
/** Translate from absolute VDP coordinates to screen coordinates:
42
  * Note: In reality, there are only 569.5 visible pixels on a line.
43
  *       Because it looks better, the borders are extended to 640.
44
  * @param absoluteX Absolute VDP coordinate.
45
  * @param narrow Is this a narrow (512 pixels wide) display mode?
46
  */
47
static constexpr int translateX(int absoluteX, bool narrow)
48
{
49
	int maxX = narrow ? 640 : 320;
50
51
	// Clip positions outside left/right VDP border to left/right "extended"
52
	// border ("extended" because we map 569.5 VDP pixels to 640 host pixels).
53
	// This prevents "inventing" new pixels that the real VDP would not
54
	// render (e.g. by rapidly changing the border-color register). So we
55
	// only extend actual VDP pixels.
56
	//
57
	// This was also needed to prevent a UMR:
58
	// * The VDP switches to a new display mode at a specific moment in the
59
	//   line, for details see VDP::syncAtNextLine(). Summary: (with
60
	//   set-adjust=0) that is 144 cycles past the (start of the) sync
61
	//   signal.
62
	// * Without this check, for 'absoluteX=144', this function would return
63
	//   a non-zero value.
64
	// * Suppose we're switching from a 256 to a 512 pixel wide mode. This
65
	//   switch happens at cycle=144, but that meant the first few pixels
66
	//   are still renderer at width=256 and the rest of the line at
67
	//   width=512. But, without this check, that meant some pixels in the
68
	//   640-pixel wide host buffer are never written, resulting in a UMR.
69
	// * Clipping to TICKS_{LEFT,RIGHT}_BORDER prevents this.
70
	if (absoluteX < TICKS_LEFT_BORDER) return 0;
71
	if (absoluteX > TICKS_RIGHT_BORDER) return maxX;
72
73
	// Note: The origin is forced to a pixel boundary, otherwise rounding
74
	//       errors will occur. It must be the same boundary the display area
75
	//       sits on: getLeftBackground() is 2 (mod 4), so the origin is too,
76
	//       else mid-line border-color changes land half a pixel off it.
77
	static_assert((TICKS_VISIBLE_MIDDLE % 4) == 3);
78
	const int origin = TICKS_VISIBLE_MIDDLE - 1;
79
	return ((absoluteX - origin)
80
		>> (narrow ? 1 : 2))
81
		+ maxX / 2;
82
}
83
84
inline void SDLRasterizer::renderBitmapLine(std::span<Pixel> buf, unsigned vramLine)
85
{
86
	if (vdp.getDisplayMode().isPlanar()) {
87
		auto [vramPtr0, vramPtr1] =
88
			vram.bitmapCacheWindow.getReadAreaPlanar<256>(vramLine * 256);
89
		bitmapConverter.convertLinePlanar(buf, vramPtr0, vramPtr1);
90
	} else {
91
		auto vramPtr =
92
			vram.bitmapCacheWindow.getReadArea<128>(vramLine * 128);
93
		bitmapConverter.convertLine(buf, vramPtr);
94
	}
95
}
96
97
SDLRasterizer::SDLRasterizer(
98
		VDP& vdp_, Display& display, OutputSurface& screen_,
99
		std::unique_ptr<PostProcessor> postProcessor_)
100
	: vdp(vdp_), vram(vdp.getVRAM())
101
	, screen(screen_)
102
	, postProcessor(std::move(postProcessor_))
103
	, workFrame(std::make_unique<RawFrame>(640, 240))
104
	, renderSettings(display.getRenderSettings())
105
	, characterConverter(vdp, subspan<16>(palFg), palBg)
106
	, bitmapConverter(palFg, PALETTE256, V9958_COLORS)
107
	, spriteConverter(vdp.getSpriteChecker(), palBg)
108
{
109
	// Init the palette.
110
	precalcPalette();
111
112
	// Initialize palette (avoid UMR)
113
	if (!vdp.isMSX1VDP()) {
114
		for (auto i : xrange(16)) {
115
			palFg[i] = palFg[i + 16] = palBg[i] =
116
				V9938_COLORS[0][0][0];
117
		}
118
	}
119
120
	renderSettings.getGammaSetting()      .attach(*this);
121
	renderSettings.getBrightnessSetting() .attach(*this);
122
	renderSettings.getContrastSetting()   .attach(*this);
123
	renderSettings.getColorMatrixSetting().attach(*this);
124
}
125
126
SDLRasterizer::~SDLRasterizer()
127
{
128
	renderSettings.getColorMatrixSetting().detach(*this);
129
	renderSettings.getGammaSetting()      .detach(*this);
130
	renderSettings.getBrightnessSetting() .detach(*this);
131
	renderSettings.getContrastSetting()   .detach(*this);
132
}
133
134
PostProcessor* SDLRasterizer::getPostProcessor() const
135
{
136
	return postProcessor.get();
137
}
138
139
const RawFrame* SDLRasterizer::getWorkingFrame() const
140
{
141
	return workFrame.get();
142
}
143
144
const RawFrame* SDLRasterizer::getLastFrame() const
145
{
146
	return postProcessor->getLastRawFrame();
147
}
148
149
bool SDLRasterizer::isActive()
150
{
151
	return postProcessor->needRender() &&
152
	       vdp.getMotherBoard().isActive() &&
153
	       !vdp.getMotherBoard().isFastForwarding();
154
}
155
156
void SDLRasterizer::reset()
157
{
158
	// Init renderer state.
159
	setDisplayMode(vdp.getDisplayMode());
160
	spriteConverter.setTransparency(vdp.getTransparency());
161
162
	resetPalette();
163
}
164
165
void SDLRasterizer::resetPalette()
166
{
167
	if (!vdp.isMSX1VDP()) {
168
		// Reset the palette.
169
		for (auto i : xrange(16)) {
170
			setPalette(i, vdp.getPalette(i));
171
		}
172
	}
173
}
174
175
void SDLRasterizer::setSuperimposeVideoFrame(const RawFrame* videoSource)
176
{
177
	postProcessor->setSuperimposeVideoFrame(videoSource);
178
	precalcColorIndex0(vdp.getDisplayMode(), vdp.getTransparency(),
179
	                   videoSource, vdp.getBackgroundColor());
180
}
181
182
void SDLRasterizer::frameStart(EmuTime time)
183
{
184
	workFrame = postProcessor->rotateFrames(std::move(workFrame), time);
185
	workFrame->init(
186
	    vdp.isInterlaced() ? (vdp.getEvenOdd() ? FrameSource::FieldType::ODD
187
	                                           : FrameSource::FieldType::EVEN)
188
	                       : FrameSource::FieldType::NONINTERLACED);
189
190
	// Calculate line to render at top of screen.
191
	// Make sure the display area is centered.
192
	// 240 - 212 = 28 lines available for top/bottom border; 14 each.
193
	// NTSC: display at [32..244),
194
	// PAL:  display at [59..271).
195
	lineRenderTop = vdp.isPalTiming() ? 59 - 14 : 32 - 14;
196
}
197
198
void SDLRasterizer::frameEnd()
199
{
200
}
201
202
void SDLRasterizer::setDisplayMode(DisplayMode mode)
203
{
204
	if (mode.isBitmapMode()) {
205
		bitmapConverter.setDisplayMode(mode);
206
	} else {
207
		characterConverter.setDisplayMode(mode);
208
	}
209
	precalcColorIndex0(mode, vdp.getTransparency(),
210
	                   vdp.isSuperimposing(), vdp.getRawBackgroundColor());
211
	spriteConverter.setDisplayMode(mode);
212
	spriteConverter.setPalette(mode.getByte() == DisplayMode::GRAPHIC7
213
	                           ? palGraphic7Sprites : palBg);
214
215
}
216
217
void SDLRasterizer::setPalette(unsigned index, int grb)
218
{
219
	// Update SDL colors in palette.
220
	Pixel newColor = V9938_COLORS[(grb >> 4) & 7][grb >> 8][grb & 7];
221
	palFg[index     ] = newColor;
222
	palFg[index + 16] = newColor;
223
	palBg[index     ] = newColor;
224
	bitmapConverter.palette16Changed();
225
226
	precalcColorIndex0(vdp.getDisplayMode(), vdp.getTransparency(),
227
	                   vdp.isSuperimposing(), vdp.getBackgroundColor());
228
}
229
230
void SDLRasterizer::setBackgroundColor(uint8_t index)
231
{
232
	if (vdp.getDisplayMode().getByte() != DisplayMode::GRAPHIC7) {
233
		precalcColorIndex0(vdp.getDisplayMode(), vdp.getTransparency(),
234
				   vdp.isSuperimposing(), index);
235
	}
236
}
237
238
void SDLRasterizer::setHorizontalAdjust(int /*adjust*/)
239
{
240
}
241
242
void SDLRasterizer::setHorizontalScrollLow(uint8_t /*scroll*/)
243
{
244
}
245
246
void SDLRasterizer::setBorderMask(bool /*masked*/)
247
{
248
}
249
250
void SDLRasterizer::setTransparency(bool enabled)
251
{
252
	spriteConverter.setTransparency(enabled);
253
	precalcColorIndex0(vdp.getDisplayMode(), enabled,
254
	                   vdp.isSuperimposing(), vdp.getBackgroundColor());
255
}
256
257
void SDLRasterizer::precalcPalette()
258
{
259
	if (vdp.isMSX1VDP()) {
260
		// Fixed palette.
261
		const auto palette = vdp.getMSX1Palette();
262
		for (auto i : xrange(16)) {
263
			const auto rgb = palette[i];
264
			palFg[i] = palFg[i + 16] = palBg[i] =
265
				screen.mapRGB(
266
					renderSettings.transformRGB(
267
						vec3(rgb[0], rgb[1], rgb[2]) * (1.0f / 255.0f)));
268
		}
269
	} else {
270
		if (vdp.hasYJK()) {
271
			// Precalculate palette for V9958 colors.
272
			if (renderSettings.isColorMatrixIdentity()) {
273
				// Most users use the "normal" monitor type; making this a
274
				// special case speeds up palette precalculation a lot.
275
				std::array<int, 32> intensity;
276
				for (auto [i, r] : enumerate(intensity)) {
277
					r = narrow_cast<int>(255.0f * renderSettings.transformComponent(narrow<float>(i) * (1.0f / 31.0f)));
278
				}
279
				for (auto [rgb, col] : enumerate(V9958_COLORS)) {
280
					col = screen.mapRGB255(ivec3(
281
						intensity[(rgb >> 10) & 31],
282
						intensity[(rgb >>  5) & 31],
283
						intensity[(rgb >>  0) & 31]));
284
				}
285
			} else {
286
				for (auto r : xrange(32)) {
287
					for (auto g : xrange(32)) {
288
						for (auto b : xrange(32)) {
289
							vec3 rgb{narrow<float>(r),
290
							         narrow<float>(g),
291
							         narrow<float>(b)};
292
							V9958_COLORS[(r << 10) + (g << 5) + b] =
293
								screen.mapRGB(
294
									renderSettings.transformRGB(rgb * (1.0f / 31.0f)));
295
						}
296
					}
297
				}
298
			}
299
			// Precalculate palette for V9938 colors.
300
			// Based on comparing red and green gradients, using palette and
301
			// YJK, in SCREEN11 on a real turbo R.
302
			for (auto r3 : xrange(8)) {
303
				int r5 = (r3 << 2) | (r3 >> 1);
304
				for (auto g3 : xrange(8)) {
305
					int g5 = (g3 << 2) | (g3 >> 1);
306
					for (auto b3 : xrange(8)) {
307
						int b5 = (b3 << 2) | (b3 >> 1);
308
						V9938_COLORS[r3][g3][b3] =
309
							V9958_COLORS[(r5 << 10) + (g5 << 5) + b5];
310
					}
311
				}
312
			}
313
		} else {
314
			// Precalculate palette for V9938 colors.
315
			if (renderSettings.isColorMatrixIdentity()) {
316
				std::array<int, 8> intensity;
317
				for (auto [i, r] : enumerate(intensity)) {
318
					r = narrow_cast<int>(255.0f * renderSettings.transformComponent(narrow<float>(i) * (1.0f / 7.0f)));
319
				}
320
				for (auto r : xrange(8)) {
321
					for (auto g : xrange(8)) {
322
						for (auto b : xrange(8)) {
323
							V9938_COLORS[r][g][b] =
324
								screen.mapRGB255(ivec3(
325
									intensity[r],
326
									intensity[g],
327
									intensity[b]));
328
						}
329
					}
330
				}
331
			} else {
332
				for (auto r : xrange(8)) {
333
					for (auto g : xrange(8)) {
334
						for (auto b : xrange(8)) {
335
							vec3 rgb{narrow<float>(r),
336
							         narrow<float>(g),
337
							         narrow<float>(b)};
338
							V9938_COLORS[r][g][b] =
339
								screen.mapRGB(
340
									renderSettings.transformRGB(rgb * (1.0f / 7.0f)));
341
						}
342
					}
343
				}
344
			}
345
		}
346
		// Precalculate Graphic 7 bitmap palette.
347
		for (auto i : xrange(256)) {
348
			PALETTE256[i] = V9938_COLORS
349
				[(i & 0x1C) >> 2]
350
				[(i & 0xE0) >> 5]
351
				[(i & 0x03) == 3 ? 7 : (i & 0x03) * 2];
352
		}
353
		// Precalculate Graphic 7 sprite palette.
354
		for (auto i : xrange(16)) {
355
			uint16_t grb = Renderer::GRAPHIC7_SPRITE_PALETTE[i];
356
			palGraphic7Sprites[i] =
357
				V9938_COLORS[(grb >> 4) & 7][grb >> 8][grb & 7];
358
		}
359
	}
360
}
361
362
void SDLRasterizer::precalcColorIndex0(DisplayMode mode,
363
		bool transparency, const RawFrame* superimposing, uint8_t bgColorIndex)
364
{
365
	// Graphic7 mode doesn't use transparency.
366
	if (mode.getByte() == DisplayMode::GRAPHIC7) {
367
		transparency = false;
368
	} else {
369
		bgColorIndex &= 0x0f;
370
	}
371
372
	int tpIndex = transparency ? bgColorIndex : 0;
373
	if (mode.getBase() != DisplayMode::GRAPHIC5) {
374
		Pixel c = (superimposing && (bgColorIndex == 0))
375
		        ? screen.getKeyColor()
376
		        : palBg[tpIndex];
377
378
		if (palFg[0] != c) {
379
			palFg[0] = c;
380
			bitmapConverter.palette16Changed();
381
		}
382
	} else {
383
		// TODO: superimposing
384
		if ((palFg[ 0] != palBg[tpIndex >> 2]) ||
385
		    (palFg[16] != palBg[tpIndex &  3])) {
386
			palFg[ 0] = palBg[tpIndex >> 2];
387
			palFg[16] = palBg[tpIndex &  3];
388
			bitmapConverter.palette16Changed();
389
		}
390
	}
391
}
392
393
std::pair<Pixel, Pixel> SDLRasterizer::getBorderColors()
394
{
395
	DisplayMode mode = vdp.getDisplayMode();
396
	int bgColor = vdp.getBackgroundColor();
397
	if (mode.getBase() == DisplayMode::GRAPHIC5) {
398
		// border in SCREEN6 has separate color for even and odd pixels.
399
		// TODO odd/even swapped?
400
		return {palBg[(bgColor & 0x0C) >> 2],
401
		        palBg[(bgColor & 0x03) >> 0]};
402
	}
403
	Pixel col = [&] { // other modes only have a single border color
404
		if (mode.getByte() == DisplayMode::GRAPHIC7) {
405
			return PALETTE256[bgColor];
406
		} else {
407
			if (!bgColor && vdp.isSuperimposing()) {
408
				return screen.getKeyColor();
409
			} else {
410
				return palBg[bgColor];
411
			}
412
		}
413
	}();
414
	return {col, col};
415
}
416
417
void SDLRasterizer::drawBorder(
418
	int fromX, int fromY, int limitX, int limitY)
419
{
420
	auto [border0, border1] = getBorderColors();
421
422
	int startY = std::max(fromY - lineRenderTop, 0);
423
	int endY = std::min(limitY - lineRenderTop, 240);
424
	if ((fromX == 0) && (limitX == VDP::TICKS_PER_LINE) &&
425
	    (border0 == border1)) {
426
		// complete lines, non striped
427
		for (auto y : xrange(startY, endY)) {
428
			workFrame->setBlank(y, border0);
429
			// setBlank() implies this line is not suitable
430
			// for left/right border optimization in a later
431
			// frame.
432
		}
433
	} else {
434
		unsigned lineWidth = vdp.getDisplayMode().getLineWidth();
435
		unsigned x = translateX(fromX, (lineWidth == 512));
436
		unsigned num = translateX(limitX, (lineWidth == 512)) - x;
437
		unsigned width = (lineWidth == 512) ? 640 : 320;
438
		for (auto y : xrange(startY, endY)) {
439
			MemoryOps::fill_2(workFrame->getLineDirect(y).subspan(x, num),
440
			                  border0, border1);
441
			if (limitX == VDP::TICKS_PER_LINE) {
442
				// Only set line width at the end (right
443
				// border) of the line. This ensures we can
444
				// keep testing the width of the previous
445
				// version of this line for all (partial)
446
				// updates of this line.
447
				workFrame->setLineWidth(y, width);
448
			}
449
		}
450
	}
451
}
452
453
void SDLRasterizer::drawDisplay(
454
	int /*fromX*/, int fromY,
455
	int displayX, int displayY,
456
	int displayWidth, int displayHeight)
457
{
458
	// Note: we don't call workFrame->setLineWidth() because that's done in
459
	// drawBorder() (for the right border). And the value we set there is
460
	// anyway the same as the one we would set here.
461
	DisplayMode mode = vdp.getDisplayMode();
462
	unsigned lineWidth = mode.getLineWidth();
463
	if (lineWidth == 256) {
464
		int endX = displayX + displayWidth;
465
		displayX /= 2;
466
		displayWidth = endX / 2 - displayX;
467
	}
468
469
	// Clip to screen area.
470
	int screenLimitY = std::min(
471
		fromY + displayHeight - lineRenderTop,
472
		240);
473
	int screenY = fromY - lineRenderTop;
474
	if (screenY < 0) {
475
		displayY -= screenY;
476
		//fromY = lineRenderTop;
477
		screenY = 0;
478
	}
479
	displayHeight = screenLimitY - screenY;
480
	if (displayHeight <= 0) return;
481
482
	int leftBackground =
483
		translateX(vdp.getLeftBackground(), lineWidth == 512);
484
	// TODO: Find out why this causes 1-pixel jitter:
485
	//dest.x = translateX(fromX);
486
	unsigned hScroll =
487
		  mode.isTextMode()
488
		? 0
489
		: 8 * (lineWidth / 256) * (vdp.getHorizontalScrollHigh() & 0x1F);
490
491
	// Page border is display X coordinate where to stop drawing current page.
492
	// This is either the multi page split point, or the right edge of the
493
	// rectangle to draw, whichever comes first.
494
	// Note that it is possible for pageBorder to be to the left of displayX,
495
	// in that case only the second page should be drawn.
496
	int pageBorder = displayX + displayWidth;
497
	auto [scrollPage1, scrollPage2] = [&] -> std::pair<int, int> {
498
		if (vdp.isMultiPageScrolling()) {
499
			int p1 = vdp.getHorizontalScrollHigh() >> 5;
500
			int p2 = p1 ^ 1;
501
			return {p1, p2};
502
		} else {
503
			return {0, 0};
504
		}
505
	}();
506
	// Because SDL blits do not wrap, unlike GL textures, the pageBorder is
507
	// also used if multi page is disabled.
508
	int pageSplit = narrow<int>(lineWidth - hScroll);
509
	pageBorder = std::min(pageBorder, pageSplit);
510
511
	if (mode.isBitmapMode()) {
512
		for (auto y : xrange(screenY, screenLimitY)) {
513
			// Which bits in the name mask determine the page?
514
			// TODO optimize this?
515
			//   Calculating pageMaskOdd/Even is a non-trivial amount
516
			//   of work. We used to do this per frame (more or less)
517
			//   but now do it per line. Per-line is actually only
518
			//   needed when vdp.isFastBlinkEnabled() is true.
519
			//   Idea: can be cheaply calculated incrementally.
520
			unsigned pageMaskOdd = (mode.isPlanar() ? 0x000 : 0x200) |
521
				vdp.getEvenOddMask(y);
522
			unsigned pageMaskEven = vdp.isMultiPageScrolling()
523
				? (pageMaskOdd & ~0x100)
524
				: pageMaskOdd;
525
			const std::array<unsigned, 2> vramLine = {
526
				(vram.nameTable.getMask() >> 7) & (pageMaskEven | displayY),
527
				(vram.nameTable.getMask() >> 7) & (pageMaskOdd  | displayY)
528
			};
529
530
			std::array<Pixel, 512> buf;
531
			auto lineInBuf = unsigned(-1); // buffer data not valid
532
			auto dst = workFrame->getLineDirect(y).subspan(leftBackground + displayX);
533
			int firstPageWidth = pageBorder - displayX;
534
			if (firstPageWidth > 0) {
535
				if (((displayX + hScroll) == 0) &&
536
				    (firstPageWidth == narrow<int>(lineWidth))) {
537
					// fast-path, directly render to destination
538
					renderBitmapLine(dst, vramLine[scrollPage1]);
539
				} else {
540
					lineInBuf = vramLine[scrollPage1];
541
					renderBitmapLine(buf, vramLine[scrollPage1]);
542
					auto src = subspan(buf, displayX + hScroll, firstPageWidth);
543
					copy_to_range(src, dst);
544
				}
545
			} else {
546
				firstPageWidth = 0;
547
			}
548
			if (firstPageWidth < displayWidth) {
549
				if (lineInBuf != vramLine[scrollPage2]) {
550
					renderBitmapLine(buf, vramLine[scrollPage2]);
551
				}
552
				unsigned x = displayX < pageBorder
553
					   ? 0 : displayX + hScroll - lineWidth;
554
				copy_to_range(subspan(buf, x, displayWidth - firstPageWidth),
555
				              subspan(dst, firstPageWidth));
556
			}
557
558
			displayY = (displayY + 1) & 255;
559
		}
560
	} else {
561
		// horizontal scroll (high) is implemented in CharacterConverter
562
		for (auto y : xrange(screenY, screenLimitY)) {
563
			assert(!vdp.isMSX1VDP() || displayY < 192);
564
565
			auto dst = workFrame->getLineDirect(y).subspan(leftBackground + displayX);
566
			if ((displayX == 0) && (displayWidth == narrow<int>(lineWidth))){
567
				characterConverter.convertLine(dst, displayY);
568
			} else {
569
				std::array<Pixel, 512> buf;
570
				characterConverter.convertLine(buf, displayY);
571
				auto src = subspan(buf, displayX, displayWidth);
572
				copy_to_range(src, dst);
573
			}
574
575
			displayY = (displayY + 1) & 255;
576
		}
577
	}
578
}
579
580
void SDLRasterizer::drawSprites(
581
	int /*fromX*/, int fromY,
582
	int displayX, int /*displayY*/,
583
	int displayWidth, int displayHeight)
584
{
585
	// Clip to screen area.
586
	// TODO: Code duplicated from drawDisplay.
587
	int screenLimitY = std::min(
588
		fromY + displayHeight - lineRenderTop,
589
		240);
590
	int screenY = fromY - lineRenderTop;
591
	if (screenY < 0) {
592
		fromY = lineRenderTop;
593
		screenY = 0;
594
	}
595
	displayHeight = screenLimitY - screenY;
596
	if (displayHeight <= 0) return;
597
598
	// Render sprites.
599
	// TODO: Call different SpriteConverter methods depending on narrow/wide
600
	//       pixels in this display mode?
601
	int spriteMode = vdp.getDisplayMode().getSpriteMode(vdp.isMSX1VDP());
602
	int displayLimitX = displayX + displayWidth;
603
	int limitY = fromY + displayHeight;
604
	int screenX = translateX(
605
		vdp.getLeftSprites(),
606
		vdp.getDisplayMode().getLineWidth() == 512);
607
	if (spriteMode == 1) {
608
		for (int y = fromY; y < limitY; y++, screenY++) {
609
			auto dst = workFrame->getLineDirect(screenY).subspan(screenX);
610
			spriteConverter.drawMode1(y, displayX, displayLimitX, dst);
611
		}
612
	} else {
613
		uint8_t mode = vdp.getDisplayMode().getByte();
614
		if (mode == DisplayMode::GRAPHIC5) {
615
			for (int y = fromY; y < limitY; y++, screenY++) {
616
				auto dst = workFrame->getLineDirect(screenY).subspan(screenX);
617
				spriteConverter.template drawMode2<DisplayMode::GRAPHIC5>(
618
					y, displayX, displayLimitX, dst);
619
			}
620
		} else if (mode == DisplayMode::GRAPHIC6) {
621
			for (int y = fromY; y < limitY; y++, screenY++) {
622
				auto dst = workFrame->getLineDirect(screenY).subspan(screenX);
623
				spriteConverter.template drawMode2<DisplayMode::GRAPHIC6>(
624
					y, displayX, displayLimitX, dst);
625
			}
626
		} else {
627
			for (int y = fromY; y < limitY; y++, screenY++) {
628
				auto dst = workFrame->getLineDirect(screenY).subspan(screenX);
629
				spriteConverter.template drawMode2<DisplayMode::GRAPHIC4>(
630
					y, displayX, displayLimitX, dst);
631
			}
632
		}
633
	}
634
}
635
636
bool SDLRasterizer::isRecording() const
637
{
638
	return postProcessor->isRecording();
639
}
640
641
void SDLRasterizer::update(const Setting& setting) noexcept
642
{
643
	if (&setting == one_of(&renderSettings.getGammaSetting(),
644
	                       &renderSettings.getBrightnessSetting(),
645
	                       &renderSettings.getContrastSetting(),
646
	                       &renderSettings.getColorMatrixSetting())) {
647
		precalcPalette();
648
		resetPalette();
649
	}
650
}
651
652
} // namespace openmsx