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 "FinishFrameEvent.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 "unreachable.hh"
19 
20 namespace openmsx {
21 
23  : vdp(vdp_)
24  , eventDistributor(vdp.getReactor().getEventDistributor())
25  , realTime(vdp.getMotherBoard().getRealTime())
26  , renderSettings(vdp.getReactor().getDisplay().getRenderSettings())
27  , videoSourceSetting(vdp.getMotherBoard().getVideoSource())
28  , rasterizer(vdp.getReactor().getDisplay().
29  getVideoSystem().createV9990Rasterizer(vdp))
30 {
31  frameSkipCounter = 999; // force drawing of frame;
32  finishFrameDuration = 0;
33  drawFrame = false; // don't draw before frameStart is called
34  prevDrawFrame = false;
35 
37 
38  renderSettings.getMaxFrameSkipSetting().attach(*this);
39  renderSettings.getMinFrameSkipSetting().attach(*this);
40 }
41 
43 {
44  renderSettings.getMaxFrameSkipSetting().detach(*this);
45  renderSettings.getMinFrameSkipSetting().detach(*this);
46 }
47 
49 {
50  return rasterizer->getPostProcessor();
51 }
52 
53 void V9990PixelRenderer::reset(EmuTime::param time)
54 {
55  displayEnabled = vdp.isDisplayEnabled();
56  setDisplayMode(vdp.getDisplayMode(), time);
57  setColorMode(vdp.getColorMode(), time);
58 
59  rasterizer->reset();
60 }
61 
62 void V9990PixelRenderer::frameStart(EmuTime::param time)
63 {
64  if (!rasterizer->isActive()) {
65  frameSkipCounter = 999;
66  drawFrame = false;
67  prevDrawFrame = false;
68  return;
69  }
70  prevDrawFrame = drawFrame;
71  if (vdp.isInterlaced() && renderSettings.getDeinterlace() &&
72  vdp.getEvenOdd() && vdp.isEvenOddEnabled()) {
73  // deinterlaced odd frame, do same as even frame
74  } else {
75  if (frameSkipCounter < renderSettings.getMinFrameSkip()) {
76  ++frameSkipCounter;
77  drawFrame = false;
78  } else if (frameSkipCounter >= renderSettings.getMaxFrameSkip()) {
79  frameSkipCounter = 0;
80  drawFrame = true;
81  } else {
82  ++frameSkipCounter;
83  if (rasterizer->isRecording()) {
84  drawFrame = true;
85  } else {
86  drawFrame = realTime.timeLeft(
87  unsigned(finishFrameDuration), time);
88  }
89  if (drawFrame) {
90  frameSkipCounter = 0;
91  }
92  }
93  }
94  if (!drawFrame) return;
95 
96  accuracy = renderSettings.getAccuracy();
97  lastX = 0;
98  lastY = 0;
99  verticalOffsetA = verticalOffsetB = vdp.getTopBorder();
100 
101  // Make sure that the correct timing is used
102  setDisplayMode(vdp.getDisplayMode(), time);
103  rasterizer->frameStart();
104 }
105 
106 void V9990PixelRenderer::frameEnd(EmuTime::param time)
107 {
108  bool skipEvent = !drawFrame;
109  if (drawFrame) {
110  // Render last changes in this frame before starting a new frame
111  sync(time, true);
112 
113  auto time1 = Timer::getTime();
114  rasterizer->frameEnd(time);
115  auto time2 = Timer::getTime();
116  auto current = time2 - time1;
117  const float ALPHA = 0.2f;
118  finishFrameDuration = finishFrameDuration * (1 - ALPHA) +
119  current * ALPHA;
120 
121  if (vdp.isInterlaced() && vdp.isEvenOddEnabled() &&
122  renderSettings.getDeinterlace() &&
123  !prevDrawFrame) {
124  // dont send event in deinterlace mode when
125  // previous frame was not rendered
126  skipEvent = true;
127  }
128 
129  }
130  if (vdp.getMotherBoard().isActive() &&
131  !vdp.getMotherBoard().isFastForwarding()) {
132  eventDistributor.distributeEvent(
133  std::make_shared<FinishFrameEvent>(
134  rasterizer->getPostProcessor()->getVideoSource(),
135  videoSourceSetting.getSource(),
136  skipEvent));
137  }
138 }
139 
140 void V9990PixelRenderer::sync(EmuTime::param time, bool force)
141 {
142  if (!drawFrame) return;
143 
144  if (accuracy != RenderSettings::ACC_SCREEN || force) {
145  vdp.getVRAM().sync(time);
146  renderUntil(time);
147  }
148 }
149 
150 void V9990PixelRenderer::renderUntil(EmuTime::param time)
151 {
152  // Translate time to pixel position
153  int limitTicks = vdp.getUCTicksThisFrame(time);
154  assert(limitTicks <=
156  int toX, toY;
157  switch (accuracy) {
159  toX = limitTicks % V9990DisplayTiming::UC_TICKS_PER_LINE;
160  toY = limitTicks / V9990DisplayTiming::UC_TICKS_PER_LINE;
161  break;
164  // TODO figure out rounding point
165  toX = 0;
166  toY = (limitTicks + V9990DisplayTiming::UC_TICKS_PER_LINE - 400) /
168  break;
169  default:
170  UNREACHABLE;
171  toX = toY = 0; // avoid warning
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 
201 void 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 
230 void 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 
250 void 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 
262 void 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 }
273 void V9990PixelRenderer::updateSuperimposing(bool enabled, EmuTime::param time)
274 {
275  sync(time);
276  rasterizer->setSuperimpose(enabled);
277 }
278 void V9990PixelRenderer::setColorMode(V9990ColorMode mode, EmuTime::param time)
279 {
280  sync(time);
281  rasterizer->setColorMode(mode);
282 }
283 
284 void V9990PixelRenderer::updateBackgroundColor(int /*index*/, EmuTime::param time)
285 {
286  sync(time);
287 }
288 
289 void V9990PixelRenderer::updateScrollAX(EmuTime::param time)
290 {
291  if (displayEnabled) sync(time);
292 }
293 void V9990PixelRenderer::updateScrollBX(EmuTime::param time)
294 {
295  // TODO only in P1 mode
296  if (displayEnabled) sync(time);
297 }
298 void V9990PixelRenderer::updateScrollAYLow(EmuTime::param time)
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 }
307 void V9990PixelRenderer::updateScrollBYLow(EmuTime::param time)
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 
318 void V9990PixelRenderer::update(const Setting& setting)
319 {
320  if (&setting == &renderSettings.getMinFrameSkipSetting() ||
321  &setting == &renderSettings.getMaxFrameSkipSetting()) {
322  // Force drawing of frame
323  frameSkipCounter = 999;
324  } else {
325  UNREACHABLE;
326  }
327 }
328 
329 } // namespace openmsx
openmsx::V9990::isPalTiming
bool isPalTiming() const
Is PAL timing active? This setting is fixed at start of frame.
Definition: V9990.hh:125
VideoSystem.hh
IntegerSetting.hh
openmsx::V9990PixelRenderer::getPostProcessor
PostProcessor * getPostProcessor() const override
See V9990::getPostProcessor.
Definition: V9990PixelRenderer.cc:48
openmsx::MSXMotherBoard::getCurrentTime
EmuTime::param getCurrentTime()
Convenience method: This is the same as getScheduler().getCurrentTime().
Definition: MSXMotherBoard.cc:490
openmsx::Subject::detach
void detach(Observer< T > &observer)
Definition: Subject.hh:56
openmsx::V9990::getTopBorder
int getTopBorder() const
Definition: V9990.hh:333
Timer.hh
Display.hh
openmsx::V9990PixelRenderer::~V9990PixelRenderer
~V9990PixelRenderer() override
Definition: V9990PixelRenderer.cc:42
openmsx::RenderSettings::ACC_LINE
Definition: RenderSettings.hh:32
openmsx::V9990PixelRenderer::setDisplayMode
void setDisplayMode(V9990DisplayMode mode, EmuTime::param time) override
Set screen mode.
Definition: V9990PixelRenderer.cc:256
openmsx::V9990PixelRenderer::updateBackgroundColor
void updateBackgroundColor(int index, EmuTime::param time) override
Set background color.
Definition: V9990PixelRenderer.cc:284
openmsx::RenderSettings::getMaxFrameSkipSetting
IntegerSetting & getMaxFrameSkipSetting()
The current max frameskip.
Definition: RenderSettings.hh:59
openmsx::V9990DisplayTiming::UC_TICKS_PER_LINE
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)
Definition: V9990DisplayTiming.hh:36
openmsx::V9990::getRightBorder
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:323
openmsx::V9990ColorMode
V9990ColorMode
Definition: V9990ModeEnum.hh:11
VideoSourceSetting.hh
openmsx::Timer::getTime
uint64_t getTime()
Get current (real) time in us.
Definition: Timer.cc:7
openmsx::Subject::attach
void attach(Observer< T > &observer)
Definition: Subject.hh:50
openmsx::Setting
Definition: Setting.hh:119
openmsx::V9990PixelRenderer::updateScrollBX
void updateScrollBX(EmuTime::param time) override
Definition: V9990PixelRenderer.cc:293
openmsx::V9990::isInterlaced
bool isInterlaced() const
Get interlace status.
Definition: V9990.hh:57
openmsx::V9990PixelRenderer::frameStart
void frameStart(EmuTime::param time) override
Signal the start of a new frame.
Definition: V9990PixelRenderer.cc:62
V9990VRAM.hh
openmsx::MSXMotherBoard::isFastForwarding
bool isFastForwarding() const
Definition: MSXMotherBoard.hh:96
openmsx::RenderSettings::getDeinterlace
bool getDeinterlace() const
Deinterlacing [on, off].
Definition: RenderSettings.hh:53
RealTime.hh
Reactor.hh
openmsx::V9990PixelRenderer::updatePalette
void updatePalette(int index, byte r, byte g, byte b, bool ys, EmuTime::param time) override
Set a palette entry.
Definition: V9990PixelRenderer.cc:262
UNREACHABLE
#define UNREACHABLE
Definition: unreachable.hh:38
openmsx::V9990PixelRenderer::V9990PixelRenderer
V9990PixelRenderer(V9990 &vdp)
Definition: V9990PixelRenderer.cc:22
V9990.hh
openmsx::RenderSettings::getMinFrameSkipSetting
IntegerSetting & getMinFrameSkipSetting()
The current min frameskip.
Definition: RenderSettings.hh:63
openmsx::V9990::getDisplayMode
V9990DisplayMode getDisplayMode() const
Return the current display mode.
Definition: V9990.hh:190
openmsx::V9990PixelRenderer::updateDisplayEnabled
void updateDisplayEnabled(bool enabled, EmuTime::param time) override
Informs the renderer of a VDP display enabled change.
Definition: V9990PixelRenderer.cc:250
openmsx::V9990::getVRAM
V9990VRAM & getVRAM()
Obtain a reference to the V9990's VRAM.
Definition: V9990.hh:50
EventDistributor.hh
V9990Rasterizer.hh
PostProcessor.hh
openmsx::RenderSettings::getAccuracy
Accuracy getAccuracy() const
Accuracy [screen, line, pixel].
Definition: RenderSettings.hh:50
openmsx::MSXDevice::getMotherBoard
MSXMotherBoard & getMotherBoard() const
Get the mother board this device belongs to.
Definition: MSXDevice.cc:74
openmsx::V9990DisplayMode
V9990DisplayMode
Definition: V9990ModeEnum.hh:6
openmsx::VideoSourceSetting::getSource
int getSource() noexcept
Definition: VideoSourceSetting.cc:35
openmsx::PostProcessor
Abstract base class for post processors.
Definition: PostProcessor.hh:29
RenderSettings.hh
V9990PixelRenderer.hh
FinishFrameEvent.hh
openmsx::V9990PixelRenderer::updateSuperimposing
void updateSuperimposing(bool enabled, EmuTime::param time) override
Change superimpose status.
Definition: V9990PixelRenderer.cc:273
g
int g
Definition: ScopedAssign_test.cc:20
openmsx::V9990VRAM::sync
void sync(EmuTime::param time)
Update VRAM state to specified moment in time.
Definition: V9990VRAM.hh:33
openmsx::V9990PixelRenderer::frameEnd
void frameEnd(EmuTime::param time) override
Signal the end of the current frame.
Definition: V9990PixelRenderer.cc:106
openmsx::V9990::getEvenOdd
bool getEvenOdd() const
Is the even or odd field being displayed?
Definition: V9990.hh:71
openmsx::RenderSettings::getMinFrameSkip
int getMinFrameSkip() const
Definition: RenderSettings.hh:64
openmsx::RenderSettings::ACC_SCREEN
Definition: RenderSettings.hh:32
openmsx::MSXMotherBoard::isActive
bool isActive() const
Definition: MSXMotherBoard.hh:95
openmsx::RenderSettings::getMaxFrameSkip
int getMaxFrameSkip() const
Definition: RenderSettings.hh:60
openmsx::V9990::getUCTicksThisFrame
int getUCTicksThisFrame(EmuTime::param time) const
Get the number of elapsed UC ticks in this frame.
Definition: V9990.hh:117
V9990DisplayTiming.hh
openmsx::V9990PixelRenderer::updateScrollAYLow
void updateScrollAYLow(EmuTime::param time) override
Definition: V9990PixelRenderer.cc:298
openmsx::V9990PixelRenderer::setColorMode
void setColorMode(V9990ColorMode mode, EmuTime::param time) override
Set color mode.
Definition: V9990PixelRenderer.cc:278
openmsx::V9990::isDisplayEnabled
bool isDisplayEnabled() const
Is the display enabled? Note this is simpler than the V99x8 version.
Definition: V9990.hh:80
openmsx::V9990PixelRenderer::updateScrollBYLow
void updateScrollBYLow(EmuTime::param time) override
Definition: V9990PixelRenderer.cc:307
openmsx::V9990PixelRenderer::updateScrollAX
void updateScrollAX(EmuTime::param time) override
Set scroll register.
Definition: V9990PixelRenderer.cc:289
openmsx::V9990PixelRenderer::reset
void reset(EmuTime::param time) override
Re-initialise the V9990Renderer's state.
Definition: V9990PixelRenderer.cc:53
unreachable.hh
openmsx::V9990::getColorMode
V9990ColorMode getColorMode() const
Return the current color mode.
Definition: V9990.cc:812
openmsx::RealTime::timeLeft
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:67
openmsx::V9990::isEvenOddEnabled
bool isEvenOddEnabled() const
Get even/odd page alternation status.
Definition: V9990.hh:64
openmsx
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
MSXMotherBoard.hh
openmsx::V9990::getLeftBorder
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:316
openmsx::V9990DisplayTiming::getUCTicksPerFrame
static constexpr int getUCTicksPerFrame(bool palTiming)
Get the number of UC ticks in 1 frame.
Definition: V9990DisplayTiming.hh:75
openmsx::EventDistributor::distributeEvent
void distributeEvent(const EventPtr &event)
Schedule the given event for delivery.
Definition: EventDistributor.cc:44
openmsx::RenderSettings::ACC_PIXEL
Definition: RenderSettings.hh:32
openmsx::V9990
Implementation of the Yamaha V9990 VDP as used in the GFX9000 cartridge by Sunrise.
Definition: V9990.hh:29