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::ACC_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 return std::pair{0, 0}; // avoid warning
171 }
172 }();
173
174 if ((toX == lastX) && (toY == lastY)) return;
175
176 // edges of the DISPLAY part of the vdp output
177 int left = vdp.getLeftBorder();
178 int right = vdp.getRightBorder();
180
181 if (displayEnabled) {
182 // Left border
183 subdivide(lastX, lastY, toX, toY, 0, left, DRAW_BORDER);
184 // Display area
185 // It's possible this draws a few pixels too many (this
186 // allowed to simplify the implementation of the Bx modes).
187 // So it's important to draw from left to right (right border
188 // must come _after_ display area).
189 subdivide(lastX, lastY, toX, toY, left, right, DRAW_DISPLAY);
190 // Right border
191 subdivide(lastX, lastY, toX, toY, right, rightEdge, DRAW_BORDER);
192 } else {
193 // complete screen
194 subdivide(lastX, lastY, toX, toY, 0, rightEdge, DRAW_BORDER);
195 }
196
197 lastX = toX;
198 lastY = toY;
199}
200
201void V9990PixelRenderer::subdivide(int fromX, int fromY, int toX, int toY,
202 int clipL, int clipR, DrawType drawType)
203{
204 // partial first line
205 if (fromX > clipL) {
206 if (fromX < clipR) {
207 bool atEnd = (fromY != toY) || (toX >= clipR);
208 draw(fromX, fromY, (atEnd ? clipR : toX), fromY + 1,
209 drawType);
210 }
211 if (fromY == toY) return;
212 fromY++;
213 }
214
215 bool drawLast = false;
216 if (toX >= clipR) {
217 toY++;
218 } else if (toX > clipL) {
219 drawLast = true;
220 }
221 // full middle lines
222 if (fromY < toY) {
223 draw(clipL, fromY, clipR, toY, drawType);
224 }
225
226 // partial last line
227 if (drawLast) draw(clipL, toY, toX, toY + 1, drawType);
228}
229
230void V9990PixelRenderer::draw(int fromX, int fromY, int toX, int toY,
231 DrawType type)
232{
233 if (type == DRAW_BORDER) {
234 rasterizer->drawBorder(fromX, fromY, toX, toY);
235
236 } else {
237 assert(type == DRAW_DISPLAY);
238
239 int displayX = fromX - vdp.getLeftBorder();
240 int displayY = fromY - vdp.getTopBorder();
241 int displayYA = fromY - verticalOffsetA;
242 int displayYB = fromY - verticalOffsetB;
243
244 rasterizer->drawDisplay(fromX, fromY, toX, toY,
245 displayX,
246 displayY, displayYA, displayYB);
247 }
248}
249
250void V9990PixelRenderer::updateDisplayEnabled(bool enabled, EmuTime::param time)
251{
252 sync(time, true);
253 displayEnabled = enabled;
254}
255
257{
258 sync(time);
259 rasterizer->setDisplayMode(mode);
260}
261
262void V9990PixelRenderer::updatePalette(int index, byte r, byte g, byte b, bool ys,
263 EmuTime::param time)
264{
265 if (displayEnabled) {
266 sync(time);
267 } else {
268 // TODO only sync if border color changed
269 sync(time);
270 }
271 rasterizer->setPalette(index, r, g, b, ys);
272}
273void V9990PixelRenderer::updateSuperimposing(bool enabled, EmuTime::param time)
274{
275 sync(time);
276 rasterizer->setSuperimpose(enabled);
277}
279{
280 sync(time);
281 rasterizer->setColorMode(mode);
282}
283
284void V9990PixelRenderer::updateBackgroundColor(int /*index*/, EmuTime::param time)
285{
286 sync(time);
287}
288
289void V9990PixelRenderer::updateScrollAX(EmuTime::param time)
290{
291 if (displayEnabled) sync(time);
292}
293void V9990PixelRenderer::updateScrollBX(EmuTime::param time)
294{
295 // TODO only in P1 mode
296 if (displayEnabled) sync(time);
297}
299{
300 if (displayEnabled) {
301 sync(time);
302 // happens in all display modes (verified)
303 // TODO high byte still seems to be wrong .. need to investigate
304 verticalOffsetA = lastY;
305 }
306}
308{
309 // TODO only in P1 mode
310 if (displayEnabled) {
311 sync(time);
312 // happens in all display modes (verified)
313 // TODO high byte still seems to be wrong .. need to investigate
314 verticalOffsetB = lastY;
315 }
316}
317
318void V9990PixelRenderer::update(const Setting& setting) noexcept
319{
320 assert(&setting == one_of(&renderSettings.getMinFrameSkipSetting(),
321 &renderSettings.getMaxFrameSkipSetting()));
322 (void)setting;
323 // Force drawing of frame
324 frameSkipCounter = 999;
325}
326
327} // 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.
This event is send when a device (v99x8, v9990, video9000, laserdisc) reaches the end of a frame.
Definition: Event.hh:322
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().
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)
Check that there is enough real time left before we reach as certain point in emulated time.
Definition: RealTime.cc:64
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: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:802
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:9
#define UNREACHABLE
Definition: unreachable.hh:38