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 "BooleanSetting.hh"
19 #include "EnumSetting.hh"
20 #include "unreachable.hh"
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.getMaxFrameSkip().attach(*this);
41  renderSettings.getMinFrameSkip().attach(*this);
42 }
43 
45 {
46  renderSettings.getMaxFrameSkip().detach(*this);
47  renderSettings.getMinFrameSkip().detach(*this);
48 }
49 
51 {
52  return rasterizer->getPostProcessor();
53 }
54 
56 {
57  displayEnabled = vdp.isDisplayEnabled();
58  setDisplayMode(vdp.getDisplayMode(), time);
59  setColorMode(vdp.getColorMode(), time);
60 
61  rasterizer->reset();
62 }
63 
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().getBoolean() &&
74  vdp.getEvenOdd() && vdp.isEvenOddEnabled()) {
75  // deinterlaced odd frame, do same as even frame
76  } else {
77  if (frameSkipCounter <
78  renderSettings.getMinFrameSkip().getInt()) {
79  ++frameSkipCounter;
80  drawFrame = false;
81  } else if (frameSkipCounter >=
82  renderSettings.getMaxFrameSkip().getInt()) {
83  frameSkipCounter = 0;
84  drawFrame = true;
85  } else {
86  ++frameSkipCounter;
87  if (rasterizer->isRecording()) {
88  drawFrame = true;
89  } else {
90  drawFrame = realTime.timeLeft(
91  unsigned(finishFrameDuration), time);
92  }
93  if (drawFrame) {
94  frameSkipCounter = 0;
95  }
96  }
97  }
98  if (!drawFrame) return;
99 
100  accuracy = renderSettings.getAccuracy().getEnum();
101  lastX = 0;
102  lastY = 0;
103  verticalOffsetA = verticalOffsetB = vdp.getTopBorder();
104 
105  // Make sure that the correct timing is used
106  setDisplayMode(vdp.getDisplayMode(), time);
107  rasterizer->frameStart();
108 }
109 
111 {
112  bool skipEvent = !drawFrame;
113  if (drawFrame) {
114  // Render last changes in this frame before starting a new frame
115  sync(time, true);
116 
117  auto time1 = Timer::getTime();
118  rasterizer->frameEnd(time);
119  auto time2 = Timer::getTime();
120  auto current = time2 - time1;
121  const double ALPHA = 0.2;
122  finishFrameDuration = finishFrameDuration * (1 - ALPHA) +
123  current * ALPHA;
124 
125  if (vdp.isInterlaced() && vdp.isEvenOddEnabled() &&
126  renderSettings.getDeinterlace().getBoolean() &&
127  !prevDrawFrame) {
128  // dont send event in deinterlace mode when
129  // previous frame was not rendered
130  skipEvent = true;
131  }
132 
133  }
134  eventDistributor.distributeEvent(
135  std::make_shared<FinishFrameEvent>(
136  rasterizer->getPostProcessor()->getVideoSource(),
137  videoSourceSetting.getSource(),
138  skipEvent));
139 }
140 
141 void V9990PixelRenderer::sync(EmuTime::param time, bool force)
142 {
143  if (!drawFrame) return;
144 
145  if (accuracy != RenderSettings::ACC_SCREEN || force) {
146  vdp.getVRAM().sync(time);
147  renderUntil(time);
148  }
149 }
150 
151 void V9990PixelRenderer::renderUntil(EmuTime::param time)
152 {
153  // Translate time to pixel position
154  int limitTicks = vdp.getUCTicksThisFrame(time);
155  assert(limitTicks <=
157  int toX, toY;
158  switch (accuracy) {
160  toX = limitTicks % V9990DisplayTiming::UC_TICKS_PER_LINE;
161  toY = limitTicks / V9990DisplayTiming::UC_TICKS_PER_LINE;
162  break;
165  // TODO figure out rounding point
166  toX = 0;
167  toY = (limitTicks + V9990DisplayTiming::UC_TICKS_PER_LINE - 400) /
169  break;
170  default:
171  UNREACHABLE;
172  toX = toY = 0; // avoid warning
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 
202 void 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 
231 void 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 
252 {
253  sync(time, true);
254  displayEnabled = enabled;
255 }
256 
258 {
259  sync(time);
260  rasterizer->setDisplayMode(mode);
261 }
262 
263 void 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 }
275 {
276  sync(time);
277  rasterizer->setSuperimpose(enabled);
278 }
280 {
281  sync(time);
282  rasterizer->setColorMode(mode);
283 }
284 
286 {
287  sync(time);
288 }
289 
291 {
292  if (displayEnabled) sync(time);
293 }
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 
319 void V9990PixelRenderer::update(const Setting& setting)
320 {
321  if (&setting == &renderSettings.getMinFrameSkip() ||
322  &setting == &renderSettings.getMaxFrameSkip()) {
323  // Force drawing of frame
324  frameSkipCounter = 999;
325  } else {
326  UNREACHABLE;
327  }
328 }
329 
330 } // namespace openmsx
Implementation of the Yamaha V9990 VDP as used in the GFX9000 cartridge by Sunrise.
Definition: V9990.hh:29
void frameStart(EmuTime::param time) override
Signal the start of a new frame.
void updateDisplayEnabled(bool enabled, EmuTime::param time) override
Informs the renderer of a VDP display enabled change.
void setDisplayMode(V9990DisplayMode mode, EmuTime::param time) override
Set screen mode.
bool isDisplayEnabled() const
Is the display enabled? Note this is simpler than the V99x8 version.
Definition: V9990.hh:82
unsigned char byte
8 bit unsigned integer
Definition: openmsx.hh:33
bool getEvenOdd() const
Is the even or odd field being displayed?
Definition: V9990.hh:73
void updateBackgroundColor(int index, EmuTime::param time) override
Set background color.
void distributeEvent(const EventPtr &event)
Schedule the given event for delivery.
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
void updateScrollAX(EmuTime::param time) override
Set scroll register.
V9990DisplayMode getDisplayMode() const
Return the current display mode.
Definition: V9990.hh:192
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
V9990ColorMode getColorMode() const
Return the current color mode.
Definition: V9990.cc:816
EnumSetting< Accuracy > & getAccuracy() const
Accuracy [screen, line, pixel].
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
int getUCTicksThisFrame(EmuTime::param time) const
Get the number of elapsed UC ticks in this frame.
Definition: V9990.hh:119
void sync(EmuTime::param time)
Update VRAM state to specified moment in time.
Definition: V9990VRAM.hh:34
void attach(Observer< T > &observer)
Definition: Subject.hh:52
IntegerSetting & getMaxFrameSkip() const
The current max frameskip.
static const int UC_TICKS_PER_LINE
The number of clockticks per line is independent of the crystal used or the display mode (NTSC/PAL) ...
int getTopBorder() const
Definition: V9990.hh:335
IntegerSetting & getMinFrameSkip() const
The current min frameskip.
MSXMotherBoard & getMotherBoard() const
Get the mother board this device belongs to.
Definition: MSXDevice.cc:78
BooleanSetting & getDeinterlace() const
Deinterlacing [on, off].
bool isEvenOddEnabled() const
Get even/odd page alternation status.
Definition: V9990.hh:66
void setColorMode(V9990ColorMode mode, EmuTime::param time) override
Set color mode.
PostProcessor * getPostProcessor() const override
See V9990::getPostProcessor.
void updateScrollBX(EmuTime::param time) override
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:7
const EmuTime & param
Definition: EmuTime.hh:20
void updateScrollAYLow(EmuTime::param time) override
bool isInterlaced() const
Get interlace status.
Definition: V9990.hh:59
void reset(EmuTime::param time) override
Re-initialise the V9990Renderer's state.
void updatePalette(int index, byte r, byte g, byte b, bool ys, EmuTime::param time) override
Set a palette entry.
void updateScrollBYLow(EmuTime::param time) override
Abstract base class for post processors.
void detach(Observer< T > &observer)
Definition: Subject.hh:58
EmuTime::param getCurrentTime()
Convenience method: This is the same as getScheduler().getCurrentTime().
static int getUCTicksPerFrame(bool palTiming)
Get the number of UC ticks in 1 frame.
uint64_t getTime()
Get current (real) time in us.
Definition: Timer.cc:24
V9990VRAM & getVRAM()
Obtain a reference to the V9990's VRAM.
Definition: V9990.hh:52
bool isPalTiming() const
Is PAL timing active? This setting is fixed at start of frame.
Definition: V9990.hh:127
void frameEnd(EmuTime::param time) override
Signal the end of the current frame.
void updateSuperimposing(bool enabled, EmuTime::param time) override
Change superimpose status.
#define UNREACHABLE
Definition: unreachable.hh:56