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(
131 Event::create<FinishFrameEvent>(
132 rasterizer->getPostProcessor()->getVideoSource(),
133 videoSourceSetting.getSource(),
134 skipEvent));
135 }
136}
137
138void V9990PixelRenderer::sync(EmuTime::param time, bool force)
139{
140 if (!drawFrame) return;
141
142 if (accuracy != RenderSettings::ACC_SCREEN || force) {
143 vdp.getVRAM().sync(time);
144 renderUntil(time);
145 }
146}
147
148void V9990PixelRenderer::renderUntil(EmuTime::param time)
149{
150 // Translate time to pixel position
151 int limitTicks = vdp.getUCTicksThisFrame(time);
152 assert(limitTicks <=
154 auto [toX, toY] = [&] {
155 switch (accuracy) {
157 return std::pair{
160 };
163 // TODO figure out rounding point
164 return std::pair{
165 0,
166 (limitTicks + V9990DisplayTiming::UC_TICKS_PER_LINE - 400) /
168 };
169 default:
171 return std::pair{0, 0}; // avoid warning
172 }
173 }();
174
175 if ((toX == lastX) && (toY == lastY)) return;
176
177 // edges of the DISPLAY part of the vdp output
178 int left = vdp.getLeftBorder();
179 int right = vdp.getRightBorder();
181
182 if (displayEnabled) {
183 // Left border
184 subdivide(lastX, lastY, toX, toY, 0, left, DRAW_BORDER);
185 // Display area
186 // It's possible this draws a few pixels too many (this
187 // allowed to simplify the implementation of the Bx modes).
188 // So it's important to draw from left to right (right border
189 // must come _after_ display area).
190 subdivide(lastX, lastY, toX, toY, left, right, DRAW_DISPLAY);
191 // Right border
192 subdivide(lastX, lastY, toX, toY, right, rightEdge, DRAW_BORDER);
193 } else {
194 // complete screen
195 subdivide(lastX, lastY, toX, toY, 0, rightEdge, DRAW_BORDER);
196 }
197
198 lastX = toX;
199 lastY = toY;
200}
201
202void V9990PixelRenderer::subdivide(int fromX, int fromY, int toX, int toY,
203 int clipL, int clipR, DrawType drawType)
204{
205 // partial first line
206 if (fromX > clipL) {
207 if (fromX < clipR) {
208 bool atEnd = (fromY != toY) || (toX >= clipR);
209 draw(fromX, fromY, (atEnd ? clipR : toX), fromY + 1,
210 drawType);
211 }
212 if (fromY == toY) return;
213 fromY++;
214 }
215
216 bool drawLast = false;
217 if (toX >= clipR) {
218 toY++;
219 } else if (toX > clipL) {
220 drawLast = true;
221 }
222 // full middle lines
223 if (fromY < toY) {
224 draw(clipL, fromY, clipR, toY, drawType);
225 }
226
227 // partial last line
228 if (drawLast) draw(clipL, toY, toX, toY + 1, drawType);
229}
230
231void V9990PixelRenderer::draw(int fromX, int fromY, int toX, int toY,
232 DrawType type)
233{
234 if (type == DRAW_BORDER) {
235 rasterizer->drawBorder(fromX, fromY, toX, toY);
236
237 } else {
238 assert(type == DRAW_DISPLAY);
239
240 int displayX = fromX - vdp.getLeftBorder();
241 int displayY = fromY - vdp.getTopBorder();
242 int displayYA = fromY - verticalOffsetA;
243 int displayYB = fromY - verticalOffsetB;
244
245 rasterizer->drawDisplay(fromX, fromY, toX, toY,
246 displayX,
247 displayY, displayYA, displayYB);
248 }
249}
250
251void V9990PixelRenderer::updateDisplayEnabled(bool enabled, EmuTime::param time)
252{
253 sync(time, true);
254 displayEnabled = enabled;
255}
256
258{
259 sync(time);
260 rasterizer->setDisplayMode(mode);
261}
262
263void V9990PixelRenderer::updatePalette(int index, byte r, byte g, byte b, bool ys,
264 EmuTime::param time)
265{
266 if (displayEnabled) {
267 sync(time);
268 } else {
269 // TODO only sync if border color changed
270 sync(time);
271 }
272 rasterizer->setPalette(index, r, g, b, ys);
273}
274void V9990PixelRenderer::updateSuperimposing(bool enabled, EmuTime::param time)
275{
276 sync(time);
277 rasterizer->setSuperimpose(enabled);
278}
280{
281 sync(time);
282 rasterizer->setColorMode(mode);
283}
284
285void V9990PixelRenderer::updateBackgroundColor(int /*index*/, EmuTime::param time)
286{
287 sync(time);
288}
289
290void V9990PixelRenderer::updateScrollAX(EmuTime::param time)
291{
292 if (displayEnabled) sync(time);
293}
294void V9990PixelRenderer::updateScrollBX(EmuTime::param time)
295{
296 // TODO only in P1 mode
297 if (displayEnabled) sync(time);
298}
300{
301 if (displayEnabled) {
302 sync(time);
303 // happens in all display modes (verified)
304 // TODO high byte still seems to be wrong .. need to investigate
305 verticalOffsetA = lastY;
306 }
307}
309{
310 // TODO only in P1 mode
311 if (displayEnabled) {
312 sync(time);
313 // happens in all display modes (verified)
314 // TODO high byte still seems to be wrong .. need to investigate
315 verticalOffsetB = lastY;
316 }
317}
318
319void V9990PixelRenderer::update(const Setting& setting) noexcept
320{
321 assert(&setting == one_of(&renderSettings.getMinFrameSkipSetting(),
322 &renderSettings.getMaxFrameSkipSetting()));
323 (void)setting;
324 // Force drawing of frame
325 frameSkipCounter = 999;
326}
327
328} // namespace openmsx
BaseSetting * setting
Definition: Interpreter.cc:28
int g
Definition: one_of.hh:7
void distributeEvent(Event &&event)
Schedule the given event for delivery.
MSXMotherBoard & getMotherBoard() const
Get the mother board this device belongs to.
Definition: MSXDevice.cc:70
EmuTime::param getCurrentTime()
Convenience method: This is the same as getScheduler().getCurrentTime().
Abstract base class for post processors.
bool timeLeft(uint64_t us, EmuTime::param time)
Check that there is enough real time left before we reach as certain point in emulated time.
Definition: RealTime.cc:64
bool getDeinterlace() const
Deinterlacing [on, off].
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:56
void attach(Observer< T > &observer)
Definition: Subject.hh:50
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:33
Implementation of the Yamaha V9990 VDP as used in the GFX9000 cartridge by Sunrise.
Definition: V9990.hh:34
int getTopBorder() const
Definition: V9990.hh:337
bool isPalTiming() const
Is PAL timing active? This setting is fixed at start of frame.
Definition: V9990.hh:129
int getUCTicksThisFrame(EmuTime::param time) const
Get the number of elapsed UC ticks in this frame.
Definition: V9990.hh:121
V9990VRAM & getVRAM()
Obtain a reference to the V9990's VRAM.
Definition: V9990.hh:54
bool isInterlaced() const
Get interlace status.
Definition: V9990.hh:61
bool isEvenOddEnabled() const
Get even/odd page alternation status.
Definition: V9990.hh:68
V9990DisplayMode getDisplayMode() const
Return the current display mode.
Definition: V9990.hh:194
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:320
bool isDisplayEnabled() const
Is the display enabled? Note this is simpler than the V99x8 version.
Definition: V9990.hh:84
V9990ColorMode getColorMode() const
Return the current color mode.
Definition: V9990.cc:789
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:327
bool getEvenOdd() const
Is the even or odd field being displayed?
Definition: V9990.hh:75
uint64_t getTime()
Get current (real) time in us.
Definition: Timer.cc:7
This file implemented 3 utility functions:
Definition: Autofire.cc:9
#define UNREACHABLE
Definition: unreachable.hh:38