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