openMSX
V9990PixelRenderer.cc
Go to the documentation of this file.
2#include "V9990.hh"
3#include "V9990VRAM.hh"
5#include "V9990Rasterizer.hh"
6#include "PostProcessor.hh"
7#include "Display.hh"
8#include "VideoSystem.hh"
10#include "Event.hh"
11#include "RealTime.hh"
12#include "Timer.hh"
13#include "EventDistributor.hh"
14#include "MSXMotherBoard.hh"
15#include "Reactor.hh"
16#include "RenderSettings.hh"
17#include "IntegerSetting.hh"
18#include "narrow.hh"
19#include "one_of.hh"
20#include "unreachable.hh"
21#include <cassert>
22
23namespace openmsx {
24
26 : vdp(vdp_)
27 , eventDistributor(vdp.getReactor().getEventDistributor())
28 , realTime(vdp.getMotherBoard().getRealTime())
29 , renderSettings(vdp.getReactor().getDisplay().getRenderSettings())
30 , videoSourceSetting(vdp.getMotherBoard().getVideoSource())
31 , rasterizer(vdp.getReactor().getDisplay().
32 getVideoSystem().createV9990Rasterizer(vdp))
33{
35
36 renderSettings.getMaxFrameSkipSetting().attach(*this);
37 renderSettings.getMinFrameSkipSetting().attach(*this);
38}
39
41{
42 renderSettings.getMaxFrameSkipSetting().detach(*this);
43 renderSettings.getMinFrameSkipSetting().detach(*this);
44}
45
47{
48 return rasterizer->getPostProcessor();
49}
50
51void V9990PixelRenderer::reset(EmuTime::param time)
52{
53 displayEnabled = vdp.isDisplayEnabled();
54 setDisplayMode(vdp.getDisplayMode(), time);
55 setColorMode(vdp.getColorMode(), time);
56
57 rasterizer->reset();
58}
59
60void V9990PixelRenderer::frameStart(EmuTime::param time)
61{
62 if (!rasterizer->isActive()) {
63 frameSkipCounter = 999;
64 drawFrame = false;
65 prevDrawFrame = false;
66 return;
67 }
68 prevDrawFrame = drawFrame;
69 if (vdp.isInterlaced() && renderSettings.getDeinterlace() &&
70 vdp.getEvenOdd() && vdp.isEvenOddEnabled()) {
71 // deinterlaced odd frame, do same as even frame
72 } else {
73 if (frameSkipCounter < renderSettings.getMinFrameSkip()) {
74 ++frameSkipCounter;
75 drawFrame = false;
76 } else if (frameSkipCounter >= renderSettings.getMaxFrameSkip()) {
77 frameSkipCounter = 0;
78 drawFrame = true;
79 } else {
80 ++frameSkipCounter;
81 if (rasterizer->isRecording()) {
82 drawFrame = true;
83 } else {
84 drawFrame = realTime.timeLeft(
85 unsigned(finishFrameDuration), time);
86 }
87 if (drawFrame) {
88 frameSkipCounter = 0;
89 }
90 }
91 }
92 if (!drawFrame) return;
93
94 accuracy = renderSettings.getAccuracy();
95 lastX = 0;
96 lastY = 0;
97 verticalOffsetA = verticalOffsetB = vdp.getTopBorder();
98
99 // Make sure that the correct timing is used
100 setDisplayMode(vdp.getDisplayMode(), time);
101 rasterizer->frameStart();
102}
103
104void V9990PixelRenderer::frameEnd(EmuTime::param time)
105{
106 bool skipEvent = !drawFrame;
107 if (drawFrame) {
108 // Render last changes in this frame before starting a new frame
109 sync(time, true);
110
111 auto time1 = Timer::getTime();
112 rasterizer->frameEnd(time);
113 auto time2 = Timer::getTime();
114 auto current = narrow_cast<float>(time2 - time1);
115 const float ALPHA = 0.2f;
116 finishFrameDuration = finishFrameDuration * (1 - ALPHA) +
117 current * ALPHA;
118
119 if (vdp.isInterlaced() && vdp.isEvenOddEnabled() &&
120 renderSettings.getDeinterlace() &&
121 !prevDrawFrame) {
122 // dont send event in deinterlace mode when
123 // previous frame was not rendered
124 skipEvent = true;
125 }
126
127 }
128 if (vdp.getMotherBoard().isActive() &&
130 eventDistributor.distributeEvent(FinishFrameEvent(
131 rasterizer->getPostProcessor()->getVideoSource(),
132 videoSourceSetting.getSource(),
133 skipEvent));
134 }
135}
136
137void V9990PixelRenderer::sync(EmuTime::param time, bool force)
138{
139 if (!drawFrame) return;
140
141 if (accuracy != RenderSettings::Accuracy::SCREEN || force) {
142 vdp.getVRAM().sync(time);
143 renderUntil(time);
144 }
145}
146
147void V9990PixelRenderer::renderUntil(EmuTime::param time)
148{
149 // Translate time to pixel position
150 int limitTicks = vdp.getUCTicksThisFrame(time);
151 assert(limitTicks <=
153 auto [toX, toY] = [&] {
154 switch (accuracy) {
156 return std::pair{
159 };
162 // TODO figure out rounding point
163 return std::pair{
164 0,
165 (limitTicks + V9990DisplayTiming::UC_TICKS_PER_LINE - 400) /
167 };
168 default:
170 }
171 }();
172
173 if ((toX == lastX) && (toY == lastY)) return;
174
175 // edges of the DISPLAY part of the vdp output
176 int left = vdp.getLeftBorder();
177 int right = vdp.getRightBorder();
179
180 if (displayEnabled) {
181 // Left border
182 subdivide(lastX, lastY, toX, toY, 0, left, DRAW_BORDER);
183 // Display area
184 // It's possible this draws a few pixels too many (this
185 // allowed to simplify the implementation of the Bx modes).
186 // So it's important to draw from left to right (right border
187 // must come _after_ display area).
188 subdivide(lastX, lastY, toX, toY, left, right, DRAW_DISPLAY);
189 // Right border
190 subdivide(lastX, lastY, toX, toY, right, rightEdge, DRAW_BORDER);
191 } else {
192 // complete screen
193 subdivide(lastX, lastY, toX, toY, 0, rightEdge, DRAW_BORDER);
194 }
195
196 lastX = toX;
197 lastY = toY;
198}
199
200void V9990PixelRenderer::subdivide(int fromX, int fromY, int toX, int toY,
201 int clipL, int clipR, DrawType drawType)
202{
203 // partial first line
204 if (fromX > clipL) {
205 if (fromX < clipR) {
206 bool atEnd = (fromY != toY) || (toX >= clipR);
207 draw(fromX, fromY, (atEnd ? clipR : toX), fromY + 1,
208 drawType);
209 }
210 if (fromY == toY) return;
211 fromY++;
212 }
213
214 bool drawLast = false;
215 if (toX >= clipR) {
216 toY++;
217 } else if (toX > clipL) {
218 drawLast = true;
219 }
220 // full middle lines
221 if (fromY < toY) {
222 draw(clipL, fromY, clipR, toY, drawType);
223 }
224
225 // partial last line
226 if (drawLast) draw(clipL, toY, toX, toY + 1, drawType);
227}
228
229void V9990PixelRenderer::draw(int fromX, int fromY, int toX, int toY,
230 DrawType type)
231{
232 if (type == DRAW_BORDER) {
233 rasterizer->drawBorder(fromX, fromY, toX, toY);
234
235 } else {
236 assert(type == DRAW_DISPLAY);
237
238 int displayX = fromX - vdp.getLeftBorder();
239 int displayY = fromY - vdp.getTopBorder();
240 int displayYA = fromY - verticalOffsetA;
241 int displayYB = fromY - verticalOffsetB;
242
243 rasterizer->drawDisplay(fromX, fromY, toX, toY,
244 displayX,
245 displayY, displayYA, displayYB);
246 }
247}
248
249void V9990PixelRenderer::updateDisplayEnabled(bool enabled, EmuTime::param time)
250{
251 sync(time, true);
252 displayEnabled = enabled;
253}
254
256{
257 sync(time);
258 rasterizer->setDisplayMode(mode);
259}
260
261void V9990PixelRenderer::updatePalette(int index, byte r, byte g, byte b, bool ys,
262 EmuTime::param time)
263{
264 if (displayEnabled) {
265 sync(time);
266 } else {
267 // TODO only sync if border color changed
268 sync(time);
269 }
270 rasterizer->setPalette(index, r, g, b, ys);
271}
272void V9990PixelRenderer::updateSuperimposing(bool enabled, EmuTime::param time)
273{
274 sync(time);
275 rasterizer->setSuperimpose(enabled);
276}
278{
279 sync(time);
280 rasterizer->setColorMode(mode);
281}
282
283void V9990PixelRenderer::updateBackgroundColor(int /*index*/, EmuTime::param time)
284{
285 sync(time);
286}
287
288void V9990PixelRenderer::updateScrollAX(EmuTime::param time)
289{
290 if (displayEnabled) sync(time);
291}
292void V9990PixelRenderer::updateScrollBX(EmuTime::param time)
293{
294 // TODO only in P1 mode
295 if (displayEnabled) sync(time);
296}
298{
299 if (displayEnabled) {
300 sync(time);
301 // happens in all display modes (verified)
302 // TODO high byte still seems to be wrong .. need to investigate
303 verticalOffsetA = lastY;
304 }
305}
307{
308 // TODO only in P1 mode
309 if (displayEnabled) {
310 sync(time);
311 // happens in all display modes (verified)
312 // TODO high byte still seems to be wrong .. need to investigate
313 verticalOffsetB = lastY;
314 }
315}
316
317void V9990PixelRenderer::update(const Setting& setting) noexcept
318{
319 assert(&setting == one_of(&renderSettings.getMinFrameSkipSetting(),
320 &renderSettings.getMaxFrameSkipSetting()));
321 (void)setting;
322 // Force drawing of frame
323 frameSkipCounter = 999;
324}
325
326} // namespace openmsx
BaseSetting * setting
int g
void distributeEvent(Event &&event)
Schedule the given event for delivery.
This event is send when a device (v99x8, v9990, video9000, laserdisc) reaches the end of a frame.
Definition Event.hh:315
MSXMotherBoard & getMotherBoard() const
Get the mother board this device belongs to.
Definition MSXDevice.cc:70
EmuTime::param getCurrentTime() const
Convenience method: This is the same as getScheduler().getCurrentTime().
A post processor builds the frame that is displayed from the MSX frame, while applying effects such a...
bool timeLeft(uint64_t us, EmuTime::param time) const
Check that there is enough real time left before we reach as certain point in emulated time.
Definition RealTime.cc:67
IntegerSetting & getMinFrameSkipSetting()
The current min frameskip.
Accuracy getAccuracy() const
Accuracy [screen, line, pixel].
IntegerSetting & getMaxFrameSkipSetting()
The current max frameskip.
void detach(Observer< T > &observer)
Definition Subject.hh:60
void attach(Observer< T > &observer)
Definition Subject.hh:54
static constexpr int UC_TICKS_PER_LINE
The number of clock ticks per line is independent of the crystal used or the display mode (NTSC/PAL)
static constexpr int getUCTicksPerFrame(bool palTiming)
Get the number of UC ticks in 1 frame.
void updateScrollBX(EmuTime::param time) override
void updatePalette(int index, byte r, byte g, byte b, bool ys, EmuTime::param time) override
Set a palette entry.
void setDisplayMode(V9990DisplayMode mode, EmuTime::param time) override
Set screen mode.
void updateScrollAYLow(EmuTime::param time) override
void reset(EmuTime::param time) override
Re-initialise the V9990Renderer's state.
void updateScrollAX(EmuTime::param time) override
Set scroll register.
void updateSuperimposing(bool enabled, EmuTime::param time) override
Change superimpose status.
PostProcessor * getPostProcessor() const override
See V9990::getPostProcessor.
void updateScrollBYLow(EmuTime::param time) override
void frameEnd(EmuTime::param time) override
Signal the end of the current frame.
void updateBackgroundColor(int index, EmuTime::param time) override
Set background color.
void updateDisplayEnabled(bool enabled, EmuTime::param time) override
Informs the renderer of a VDP display enabled change.
void frameStart(EmuTime::param time) override
Signal the start of a new frame.
void setColorMode(V9990ColorMode mode, EmuTime::param time) override
Set color mode.
void sync(EmuTime::param time)
Update VRAM state to specified moment in time.
Definition V9990VRAM.hh:34
Implementation of the Yamaha V9990 VDP as used in the GFX9000 cartridge by Sunrise.
Definition V9990.hh:35
int getTopBorder() const
Definition V9990.hh:338
bool isPalTiming() const
Is PAL timing active? This setting is fixed at start of frame.
Definition V9990.hh:130
int getUCTicksThisFrame(EmuTime::param time) const
Get the number of elapsed UC ticks in this frame.
Definition V9990.hh:122
V9990VRAM & getVRAM()
Obtain a reference to the V9990's VRAM.
Definition V9990.hh:55
bool isInterlaced() const
Get interlace status.
Definition V9990.hh:62
bool isEvenOddEnabled() const
Get even/odd page alternation status.
Definition V9990.hh:69
V9990DisplayMode getDisplayMode() const
Return the current display mode.
Definition V9990.hh:195
int getLeftBorder() const
Get the number of VDP clock-ticks between the start of the line and the end of the left border.
Definition V9990.hh:321
bool isDisplayEnabled() const
Is the display enabled? Note this is simpler than the V99x8 version.
Definition V9990.hh:85
V9990ColorMode getColorMode() const
Return the current color mode.
Definition V9990.cc:801
int getRightBorder() const
Get the number of VDP clock-ticks between the start of the line and the end of the right border.
Definition V9990.hh:328
bool getEvenOdd() const
Is the even or odd field being displayed?
Definition V9990.hh:76
uint64_t getTime()
Get current (real) time in us.
Definition Timer.cc:7
This file implemented 3 utility functions:
Definition Autofire.cc:11
#define UNREACHABLE