OXYGENHUB
◆ main openmsx / src / video / v9990 / V9990PixelRenderer.cc
8.4 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#include "V9990PixelRenderer.hh"
2
3
#include "V9990.hh"
4
#include "V9990DisplayTiming.hh"
5
#include "V9990Rasterizer.hh"
6
#include "V9990VRAM.hh"
7
8
#include "Display.hh"
9
#include "PostProcessor.hh"
10
#include "RenderSettings.hh"
11
#include "VideoSystem.hh"
12
13
#include "Event.hh"
14
#include "EventDistributor.hh"
15
#include "IntegerSetting.hh"
16
#include "MSXMotherBoard.hh"
17
#include "Reactor.hh"
18
#include "RealTime.hh"
19
#include "Timer.hh"
20
#include "VideoSourceSetting.hh"
21
22
#include "narrow.hh"
23
#include "one_of.hh"
24
#include "unreachable.hh"
25
26
#include <cassert>
27
28
namespace openmsx {
29
30
V9990PixelRenderer::V9990PixelRenderer(V9990& vdp_)
31
	: vdp(vdp_)
32
	, eventDistributor(vdp.getReactor().getEventDistributor())
33
	, realTime(vdp.getMotherBoard().getRealTime())
34
	, renderSettings(vdp.getReactor().getDisplay().getRenderSettings())
35
	, videoSourceSetting(vdp.getMotherBoard().getVideoSource())
36
	, rasterizer(vdp.getReactor().getDisplay().
37
	                getVideoSystem().createV9990Rasterizer(vdp))
38
{
39
	reset(vdp.getMotherBoard().getCurrentTime());
40
41
	renderSettings.getMaxFrameSkipSetting().attach(*this);
42
	renderSettings.getMinFrameSkipSetting().attach(*this);
43
}
44
45
V9990PixelRenderer::~V9990PixelRenderer()
46
{
47
	renderSettings.getMaxFrameSkipSetting().detach(*this);
48
	renderSettings.getMinFrameSkipSetting().detach(*this);
49
}
50
51
PostProcessor* V9990PixelRenderer::getPostProcessor() const
52
{
53
	return rasterizer->getPostProcessor();
54
}
55
56
void V9990PixelRenderer::reset(EmuTime time)
57
{
58
	displayEnabled = vdp.isDisplayEnabled();
59
	setDisplayMode(vdp.getDisplayMode(), time);
60
	setColorMode(vdp.getColorMode(), time);
61
62
	rasterizer->reset();
63
}
64
65
void V9990PixelRenderer::frameStart(EmuTime time)
66
{
67
	if (!rasterizer->isActive()) {
68
		frameSkipCounter = 999;
69
		drawFrame = false;
70
		prevDrawFrame = false;
71
		return;
72
	}
73
	prevDrawFrame = drawFrame;
74
	if (vdp.isInterlaced() && renderSettings.getDeinterlace() &&
75
	    vdp.getEvenOdd() && vdp.isEvenOddEnabled()) {
76
		// deinterlaced odd frame, do same as even frame
77
	} else {
78
		if (frameSkipCounter < renderSettings.getMinFrameSkip()) {
79
			++frameSkipCounter;
80
			drawFrame = false;
81
		} else if (frameSkipCounter >= renderSettings.getMaxFrameSkip()) {
82
			frameSkipCounter = 0;
83
			drawFrame = true;
84
		} else {
85
			++frameSkipCounter;
86
			if (rasterizer->isRecording()) {
87
				drawFrame = true;
88
			} else {
89
				drawFrame = realTime.timeLeft(
90
					unsigned(finishFrameDuration), time);
91
			}
92
			if (drawFrame) {
93
				frameSkipCounter = 0;
94
			}
95
		}
96
	}
97
	if (!drawFrame) return;
98
99
	accuracy = renderSettings.getAccuracy();
100
	lastX = 0;
101
	lastY = 0;
102
	verticalOffsetA = verticalOffsetB = vdp.getTopBorder();
103
104
	// Make sure that the correct timing is used
105
	setDisplayMode(vdp.getDisplayMode(), time);
106
	rasterizer->frameStart();
107
}
108
109
void V9990PixelRenderer::frameEnd(EmuTime time)
110
{
111
	bool skipEvent = !drawFrame;
112
	if (drawFrame) {
113
		// Render last changes in this frame before starting a new frame
114
		sync(time, true);
115
116
		auto time1 = Timer::getTime();
117
		rasterizer->frameEnd(time);
118
		auto time2 = Timer::getTime();
119
		auto current = narrow_cast<float>(time2 - time1);
120
		const float ALPHA = 0.2f;
121
		finishFrameDuration = finishFrameDuration * (1 - ALPHA) +
122
		                      current * ALPHA;
123
124
		if (vdp.isInterlaced() && vdp.isEvenOddEnabled() &&
125
		    renderSettings.getDeinterlace() &&
126
		    !prevDrawFrame) {
127
			// dont send event in deinterlace mode when
128
			// previous frame was not rendered
129
			skipEvent = true;
130
		}
131
132
	}
133
	if (vdp.getMotherBoard().isActive() &&
134
	    !vdp.getMotherBoard().isFastForwarding()) {
135
		eventDistributor.distributeEvent(FinishFrameEvent(
136
			rasterizer->getPostProcessor()->getVideoSource(),
137
			videoSourceSetting.getSource(),
138
			skipEvent));
139
	}
140
}
141
142
void V9990PixelRenderer::sync(EmuTime time, bool force)
143
{
144
	if (!drawFrame) return;
145
146
	if (accuracy != RenderSettings::Accuracy::SCREEN || force) {
147
		vdp.getVRAM().sync(time);
148
		renderUntil(time);
149
	}
150
}
151
152
void V9990PixelRenderer::renderUntil(EmuTime time)
153
{
154
	// Translate time to pixel position
155
	int limitTicks = vdp.getUCTicksThisFrame(time);
156
	assert(limitTicks <=
157
	       V9990DisplayTiming::getUCTicksPerFrame(vdp.isPalTiming()));
158
	auto [toX, toY] = [&] {
159
		switch (accuracy) {
160
		case RenderSettings::Accuracy::PIXEL:
161
			return std::pair{
162
				limitTicks % V9990DisplayTiming::UC_TICKS_PER_LINE,
163
				limitTicks / V9990DisplayTiming::UC_TICKS_PER_LINE
164
			};
165
		case RenderSettings::Accuracy::LINE:
166
		case RenderSettings::Accuracy::SCREEN:
167
			// TODO figure out rounding point
168
			return std::pair{
169
				0,
170
				(limitTicks + V9990DisplayTiming::UC_TICKS_PER_LINE - 400) /
171
				     V9990DisplayTiming::UC_TICKS_PER_LINE
172
			};
173
		default:
174
			UNREACHABLE;
175
		}
176
	}();
177
178
	if ((toX == lastX) && (toY == lastY)) return;
179
180
	// edges of the DISPLAY part of the vdp output
181
	int left       = vdp.getLeftBorder();
182
	int right      = vdp.getRightBorder();
183
	int rightEdge  = V9990DisplayTiming::UC_TICKS_PER_LINE;
184
185
	if (displayEnabled) {
186
		// Left border
187
		subdivide(lastX, lastY, toX, toY, 0, left, DRAW_BORDER);
188
		// Display area
189
		//  It's possible this draws a few pixels too many (this
190
		//  allowed to simplify the implementation of the Bx modes).
191
		//  So it's important to draw from left to right (right border
192
		//  must come _after_ display area).
193
		subdivide(lastX, lastY, toX, toY, left, right, DRAW_DISPLAY);
194
		// Right border
195
		subdivide(lastX, lastY, toX, toY, right, rightEdge, DRAW_BORDER);
196
	} else {
197
		// complete screen
198
		subdivide(lastX, lastY, toX, toY, 0, rightEdge, DRAW_BORDER);
199
	}
200
201
	lastX = toX;
202
	lastY = toY;
203
}
204
205
void V9990PixelRenderer::subdivide(int fromX, int fromY, int toX, int toY,
206
                                   int clipL, int clipR, DrawType drawType)
207
{
208
	// partial first line
209
	if (fromX > clipL) {
210
		if (fromX < clipR) {
211
			bool atEnd = (fromY != toY) || (toX >= clipR);
212
			draw(fromX, fromY, (atEnd ? clipR : toX), fromY + 1,
213
			     drawType);
214
		}
215
		if (fromY == toY) return;
216
		fromY++;
217
	}
218
219
	bool drawLast = false;
220
	if (toX >= clipR) {
221
		toY++;
222
	} else if (toX > clipL) {
223
		drawLast = true;
224
	}
225
	// full middle lines
226
	if (fromY < toY) {
227
		draw(clipL, fromY, clipR, toY, drawType);
228
	}
229
230
	// partial last line
231
	if (drawLast) draw(clipL, toY, toX, toY + 1, drawType);
232
}
233
234
void V9990PixelRenderer::draw(int fromX, int fromY, int toX, int toY,
235
                              DrawType type)
236
{
237
	if (type == DRAW_BORDER) {
238
		rasterizer->drawBorder(fromX, fromY, toX, toY);
239
240
	} else {
241
		assert(type == DRAW_DISPLAY);
242
243
		int displayX  = fromX - vdp.getLeftBorder();
244
		int displayY  = fromY - vdp.getTopBorder();
245
		int displayYA = fromY - verticalOffsetA;
246
		int displayYB = fromY - verticalOffsetB;
247
248
		rasterizer->drawDisplay(fromX, fromY, toX, toY,
249
		                        displayX,
250
		                        displayY, displayYA, displayYB);
251
	}
252
}
253
254
void V9990PixelRenderer::updateDisplayEnabled(bool enabled, EmuTime time)
255
{
256
	sync(time, true);
257
	displayEnabled = enabled;
258
}
259
260
void V9990PixelRenderer::setDisplayMode(V9990DisplayMode mode, EmuTime time)
261
{
262
	sync(time);
263
	rasterizer->setDisplayMode(mode);
264
}
265
266
void V9990PixelRenderer::updatePalette(int index, uint8_t r, uint8_t g, uint8_t b, bool ys,
267
                                       EmuTime time)
268
{
269
	if (displayEnabled) {
270
		sync(time);
271
	} else {
272
		// TODO only sync if border color changed
273
		sync(time);
274
	}
275
	rasterizer->setPalette(index, r, g, b, ys);
276
}
277
void V9990PixelRenderer::updateSuperimposing(bool enabled, EmuTime time)
278
{
279
	sync(time);
280
	rasterizer->setSuperimpose(enabled);
281
}
282
void V9990PixelRenderer::setColorMode(V9990ColorMode mode, EmuTime time)
283
{
284
	sync(time);
285
	rasterizer->setColorMode(mode);
286
}
287
288
void V9990PixelRenderer::updateBackgroundColor(int /*index*/, EmuTime time)
289
{
290
	sync(time);
291
}
292
293
void V9990PixelRenderer::updateScrollAX(EmuTime time)
294
{
295
	if (displayEnabled) sync(time);
296
}
297
void V9990PixelRenderer::updateScrollBX(EmuTime time)
298
{
299
	// TODO only in P1 mode
300
	if (displayEnabled) sync(time);
301
}
302
void V9990PixelRenderer::updateScrollAYLow(EmuTime time)
303
{
304
	if (displayEnabled) {
305
		sync(time);
306
		// happens in all display modes (verified)
307
		// TODO high byte still seems to be wrong .. need to investigate
308
		verticalOffsetA = lastY;
309
	}
310
}
311
void V9990PixelRenderer::updateScrollBYLow(EmuTime time)
312
{
313
	// TODO only in P1 mode
314
	if (displayEnabled) {
315
		sync(time);
316
		// happens in all display modes (verified)
317
		// TODO high byte still seems to be wrong .. need to investigate
318
		verticalOffsetB = lastY;
319
	}
320
}
321
322
void V9990PixelRenderer::update(const Setting& setting) noexcept
323
{
324
	assert(&setting == one_of(&renderSettings.getMinFrameSkipSetting(),
325
	                          &renderSettings.getMaxFrameSkipSetting()));
326
	(void)setting;
327
	// Force drawing of frame
328
	frameSkipCounter = 999;
329
}
330
331
} // namespace openmsx