openMSX
VDP.cc
Go to the documentation of this file.
1 /*
2 TODO:
3 - Run more measurements on real MSX to find out how horizontal
4  scanning interrupt really works.
5  Finish model and implement it.
6  Especially test this scenario:
7  * IE1 enabled, interrupt occurs
8  * wait until matching line is passed
9  * disable IE1
10  * read FH
11  * read FH
12  Current implementation would return FH=0 both times.
13 - Check how Z80 should treat interrupts occurring during DI.
14 - Bottom erase suspends display even on overscan.
15  However, it shows black, not border color.
16  How to handle this? Currently it is treated as "overscan" which
17  falls outside of the rendered screen area.
18 */
19 
20 #include "VDP.hh"
21 #include "VDPVRAM.hh"
22 #include "VDPCmdEngine.hh"
23 #include "SpriteChecker.hh"
24 #include "Display.hh"
25 #include "HardwareConfig.hh"
26 #include "RendererFactory.hh"
27 #include "Renderer.hh"
28 #include "RenderSettings.hh"
29 #include "EnumSetting.hh"
30 #include "TclObject.hh"
31 #include "MSXCPU.hh"
32 #include "MSXMotherBoard.hh"
33 #include "Reactor.hh"
34 #include "MSXException.hh"
35 #include "CliComm.hh"
36 #include "ranges.hh"
37 #include "unreachable.hh"
38 #include <cstring>
39 #include <cassert>
40 #include <memory>
41 
42 using std::string;
43 using std::vector;
44 
45 namespace openmsx {
46 
47 static byte getDelayCycles(const XMLElement& devices) {
48  byte cycles = 0;
49  const XMLElement* t9769Dev = devices.findChild("T9769");
50  if (t9769Dev) {
51  if (t9769Dev->getChildData("subtype") == "C") {
52  cycles = 1;
53  } else {
54  cycles = 2;
55  }
56  } else if (devices.findChild("S1990")) {
57  // this case is purely there for backwards compatibility for
58  // turboR configs which do not have the T9769 tag yet.
59  cycles = 1;
60  }
61  return cycles;
62 }
63 
64 VDP::VDP(const DeviceConfig& config)
65  : MSXDevice(config)
66  , syncVSync(*this)
67  , syncDisplayStart(*this)
68  , syncVScan(*this)
69  , syncHScan(*this)
70  , syncHorAdjust(*this)
71  , syncSetMode(*this)
72  , syncSetBlank(*this)
73  , syncCpuVramAccess(*this)
74  , display(getReactor().getDisplay())
75  , cmdTiming (display.getRenderSettings().getCmdTimingSetting())
76  , tooFastAccess(display.getRenderSettings().getTooFastAccessSetting())
77  , vdpRegDebug (*this)
78  , vdpStatusRegDebug(*this)
79  , vdpPaletteDebug (*this)
80  , vramPointerDebug (*this)
81  , frameCountInfo (*this)
82  , cycleInFrameInfo (*this)
83  , lineInFrameInfo (*this)
84  , cycleInLineInfo (*this)
85  , msxYPosInfo (*this)
86  , msxX256PosInfo (*this)
87  , msxX512PosInfo (*this)
88  , frameStartTime(getCurrentTime())
89  , irqVertical (getMotherBoard(), getName() + ".IRQvertical", config)
90  , irqHorizontal(getMotherBoard(), getName() + ".IRQhorizontal", config)
91  , displayStartSyncTime(getCurrentTime())
92  , vScanSyncTime(getCurrentTime())
93  , hScanSyncTime(getCurrentTime())
94  , tooFastCallback(
95  getCommandController(),
96  getName() + ".too_fast_vram_access_callback",
97  "Tcl proc called when the VRAM is read or written too fast")
98  , warningPrinted(false)
99  , cpu(getCPU()) // used frequently, so cache it
100  , fixedVDPIOdelayCycles(getDelayCycles(getMotherBoard().getMachineConfig()->getConfig().getChild("devices")))
101 {
102  interlaced = false;
103 
104  // Current general defaults for saturation:
105  // - Any MSX with a TMS9x18 VDP: SatPr=SatPb=100%
106  // - Other machines with a TMS9x2x VDP and RGB output:
107  // SatPr=SatPr=100%, until someone reports a better value
108  // - Other machines with a TMS9x2x VDP and CVBS only output:
109  // SatPr=SatPb=54%, until someone reports a better value
110  // At this point we don't know anything about the connector here, so
111  // only the first point can be implemented. The default default is
112  // still 54, which is very similar to the old palette implementation
113 
114  int defaultSaturation = 54;
115 
116  std::string versionString = config.getChildData("version");
117  if (versionString == "TMS99X8A") version = TMS99X8A;
118  else if (versionString == "TMS9918A") {
119  version = TMS99X8A;
120  defaultSaturation = 100;
121  } else if (versionString == "TMS9928A") version = TMS99X8A;
122  else if (versionString == "T6950PAL") version = T6950PAL;
123  else if (versionString == "T6950NTSC") version = T6950NTSC;
124  else if (versionString == "T7937APAL") version = T7937APAL;
125  else if (versionString == "T7937ANTSC") version = T7937ANTSC;
126  else if (versionString == "TMS91X8") version = TMS91X8;
127  else if (versionString == "TMS9118") {
128  version = TMS91X8;
129  defaultSaturation = 100;
130  } else if (versionString == "TMS9128") version = TMS91X8;
131  else if (versionString == "TMS9929A") version = TMS9929A;
132  else if (versionString == "TMS9129") version = TMS9129;
133  else if (versionString == "V9938") version = V9938;
134  else if (versionString == "V9958") version = V9958;
135  else throw MSXException("Unknown VDP version \"", versionString, '"');
136 
137  // saturation parameters only make sense when using TMS VDP's
138  if ((versionString.find("TMS") != 0) && ((config.findChild("saturationPr") != nullptr) || (config.findChild("saturationPb") != nullptr) || (config.findChild("saturation") != nullptr))) {
139  throw MSXException("Specifying saturation parameters only makes sense for TMS VDP's");
140  }
141 
142  int saturation = config.getChildDataAsInt("saturation", defaultSaturation);
143  if (!((0 <= saturation) && (saturation <= 100))) {
144  throw MSXException(
145  "Saturation percentage is not in range 0..100: ", saturationPr);
146  }
147  saturationPr = config.getChildDataAsInt("saturationPr", saturation);
148  if (!((0 <= saturationPr) && (saturationPr <= 100))) {
149  throw MSXException(
150  "Saturation percentage for Pr component is not in range 0..100: ",
151  saturationPr);
152  }
153  saturationPb = config.getChildDataAsInt("saturationPb", saturation);
154  if (!((0 <= saturationPb) && (saturationPb <= 100))) {
155  throw MSXException(
156  "Saturation percentage for Pb component is not in range 0..100: ",
157  saturationPr);
158  }
159 
160  // Set up control register availability.
161  static const byte VALUE_MASKS_MSX1[32] = {
162  0x03, 0xFB, 0x0F, 0xFF, 0x07, 0x7F, 0x07, 0xFF // 00..07
163  };
164  static const byte VALUE_MASKS_MSX2[32] = {
165  0x7E, 0x7B, 0x7F, 0xFF, 0x3F, 0xFF, 0x3F, 0xFF, // 00..07
166  0xFB, 0xBF, 0x07, 0x03, 0xFF, 0xFF, 0x07, 0x0F, // 08..15
167  0x0F, 0xBF, 0xFF, 0xFF, 0x3F, 0x3F, 0x3F, 0xFF, // 16..23
168  0, 0, 0, 0, 0, 0, 0, 0, // 24..31
169  };
170  controlRegMask = (isMSX1VDP() ? 0x07 : 0x3F);
171  memcpy(controlValueMasks,
172  isMSX1VDP() ? VALUE_MASKS_MSX1 : VALUE_MASKS_MSX2,
173  sizeof(controlValueMasks));
174  if (version == V9958) {
175  // Enable V9958-specific control registers.
176  controlValueMasks[25] = 0x7F;
177  controlValueMasks[26] = 0x3F;
178  controlValueMasks[27] = 0x07;
179  }
180 
181  resetInit(); // must be done early to avoid UMRs
182 
183  // Video RAM.
184  EmuTime::param time = getCurrentTime();
185  unsigned vramSize =
186  (isMSX1VDP() ? 16 : config.getChildDataAsInt("vram"));
187  if ((vramSize != 16) && (vramSize != 64) &&
188  (vramSize != 128) && (vramSize != 192)) {
189  throw MSXException(
190  "VRAM size of ", vramSize, "kB is not supported!");
191  }
192  vram = std::make_unique<VDPVRAM>(*this, vramSize * 1024, time);
193 
194  // Create sprite checker.
195  auto& renderSettings = display.getRenderSettings();
196  spriteChecker = std::make_unique<SpriteChecker>(*this, renderSettings, time);
197  vram->setSpriteChecker(spriteChecker.get());
198 
199  // Create command engine.
200  cmdEngine = std::make_unique<VDPCmdEngine>(*this, getCommandController());
201  vram->setCmdEngine(cmdEngine.get());
202 
203  // Initialise renderer.
204  createRenderer();
205 
206  // Reset state.
207  powerUp(time);
208 
209  display .attach(*this);
210  cmdTiming .attach(*this);
211  tooFastAccess.attach(*this);
212  update(tooFastAccess); // handles both cmdTiming and tooFastAccess
213 }
214 
216 {
217  tooFastAccess.detach(*this);
218  cmdTiming .detach(*this);
219  display .detach(*this);
220 }
221 
222 void VDP::preVideoSystemChange()
223 {
224  renderer.reset();
225 }
226 
227 void VDP::postVideoSystemChange()
228 {
229  createRenderer();
230 }
231 
232 void VDP::createRenderer()
233 {
234  renderer = RendererFactory::createRenderer(*this, display);
235  // TODO: Is it safe to use frameStartTime,
236  // which is most likely in the past?
237  //renderer->reset(frameStartTime.getTime());
238  vram->setRenderer(renderer.get(), frameStartTime.getTime());
239 }
240 
242 {
243  return renderer->getPostProcessor();
244 }
245 
246 void VDP::resetInit()
247 {
248  // note: vram, spriteChecker, cmdEngine, renderer may not yet be
249  // created at this point
250  ranges::fill(controlRegs, 0);
251  if (isVDPwithPALonly()) {
252  // Boots (and remains) in PAL mode, all other VDPs boot in NTSC.
253  controlRegs[9] |= 0x02;
254  }
255  // According to page 6 of the V9938 data book the color burst registers
256  // are loaded with these values at power on.
257  controlRegs[21] = 0x3B;
258  controlRegs[22] = 0x05;
259  // Note: frameStart is the actual place palTiming is written, but it
260  // can be read before frameStart is called.
261  // TODO: Clean up initialisation sequence.
262  palTiming = true; // controlRegs[9] & 0x02;
263  displayMode.reset();
264  vramPointer = 0;
265  cpuVramData = 0;
266  dataLatch = 0;
267  cpuExtendedVram = false;
268  registerDataStored = false;
269  paletteDataStored = false;
270  blinkState = false;
271  blinkCount = 0;
272  horizontalAdjust = 7;
273 
274  // TODO: Real VDP probably resets timing as well.
275  isDisplayArea = false;
276  displayEnabled = false;
277  superimposing = nullptr;
278  externalVideo = nullptr;
279 
280  // Init status registers.
281  statusReg0 = 0x00;
282  statusReg1 = (version == V9958 ? 0x04 : 0x00);
283  statusReg2 = 0x0C;
284 
285  // Update IRQ to reflect new register values.
286  irqVertical.reset();
287  irqHorizontal.reset();
288 
289  // From appendix 8 of the V9938 data book (page 148).
290  const word V9938_PALETTE[16] = {
291  0x000, 0x000, 0x611, 0x733, 0x117, 0x327, 0x151, 0x627,
292  0x171, 0x373, 0x661, 0x664, 0x411, 0x265, 0x555, 0x777
293  };
294  // Init the palette.
295  memcpy(palette, V9938_PALETTE, sizeof(V9938_PALETTE));
296 }
297 
298 void VDP::resetMasks(EmuTime::param time)
299 {
300  updateNameBase(time);
301  updateColorBase(time);
302  updatePatternBase(time);
303  updateSpriteAttributeBase(time);
304  updateSpritePatternBase(time);
305  // TODO: It is not clear to me yet how bitmapWindow should be used.
306  // Currently it always spans 128K of VRAM.
307  //vram->bitmapWindow.setMask(~(~0u << 17), ~0u << 17, time);
308 }
309 
310 void VDP::powerUp(EmuTime::param time)
311 {
312  vram->clear();
313  reset(time);
314 }
315 
316 void VDP::reset(EmuTime::param time)
317 {
318  syncVSync .removeSyncPoint();
319  syncDisplayStart .removeSyncPoint();
320  syncVScan .removeSyncPoint();
321  syncHScan .removeSyncPoint();
322  syncHorAdjust .removeSyncPoint();
323  syncSetMode .removeSyncPoint();
324  syncSetBlank .removeSyncPoint();
325  syncCpuVramAccess.removeSyncPoint();
326  pendingCpuAccess = false;
327 
328  // Reset subsystems.
329  cmdEngine->sync(time);
330  resetInit();
331  spriteChecker->reset(time);
332  cmdEngine->reset(time);
333  renderer->reInit();
334 
335  // Tell the subsystems of the new mask values.
336  resetMasks(time);
337 
338  // Init scheduling.
339  frameCount = -1;
340  frameStart(time);
341  assert(frameCount == 0);
342 }
343 
344 void VDP::execVSync(EmuTime::param time)
345 {
346  // This frame is finished.
347  // Inform VDP subcomponents.
348  // TODO: Do this via VDPVRAM?
349  renderer->frameEnd(time);
350  spriteChecker->frameEnd(time);
351  // Start next frame.
352  frameStart(time);
353 }
354 
355 void VDP::execDisplayStart(EmuTime::param time)
356 {
357  // Display area starts here, unless we're doing overscan and it
358  // was already active.
359  if (!isDisplayArea) {
360  if (displayEnabled) {
361  vram->updateDisplayEnabled(true, time);
362  }
363  isDisplayArea = true;
364  }
365 }
366 
367 void VDP::execVScan(EmuTime::param time)
368 {
369  // VSCAN is the end of display.
370  // This will generate a VBLANK IRQ. Typically MSX software will
371  // poll the keyboard/joystick on this IRQ. So now is a good
372  // time to also poll for host events.
374 
375  if (isDisplayEnabled()) {
376  vram->updateDisplayEnabled(false, time);
377  }
378  isDisplayArea = false;
379 
380  // Vertical scanning occurs.
381  statusReg0 |= 0x80;
382  if (controlRegs[1] & 0x20) {
383  irqVertical.set();
384  }
385 }
386 
387 void VDP::execHScan()
388 {
389  // Horizontal scanning occurs.
390  if (controlRegs[0] & 0x10) {
391  irqHorizontal.set();
392  }
393 }
394 
395 void VDP::execHorAdjust(EmuTime::param time)
396 {
397  int newHorAdjust = (controlRegs[18] & 0x0F) ^ 0x07;
398  if (controlRegs[25] & 0x08) {
399  newHorAdjust += 4;
400  }
401  renderer->updateHorizontalAdjust(newHorAdjust, time);
402  horizontalAdjust = newHorAdjust;
403 }
404 
405 void VDP::execSetMode(EmuTime::param time)
406 {
407  updateDisplayMode(
408  DisplayMode(controlRegs[0], controlRegs[1], controlRegs[25]),
409  getCmdBit(),
410  time);
411 }
412 
413 void VDP::execSetBlank(EmuTime::param time)
414 {
415  bool newDisplayEnabled = (controlRegs[1] & 0x40) != 0;
416  if (isDisplayArea) {
417  vram->updateDisplayEnabled(newDisplayEnabled, time);
418  }
419  displayEnabled = newDisplayEnabled;
420 }
421 
422 void VDP::execCpuVramAccess(EmuTime::param time)
423 {
424  assert(!allowTooFastAccess);
425  pendingCpuAccess = false;
426  executeCpuVramAccess(time);
427 }
428 
429 // TODO: This approach assumes that an overscan-like approach can be used
430 // skip display start, so that the border is rendered instead.
431 // This makes sense, but it has not been tested on real MSX yet.
432 void VDP::scheduleDisplayStart(EmuTime::param time)
433 {
434  // Remove pending DISPLAY_START sync point, if any.
435  if (displayStartSyncTime > time) {
436  syncDisplayStart.removeSyncPoint();
437  //cerr << "removing predicted DISPLAY_START sync point\n";
438  }
439 
440  // Calculate when (lines and time) display starts.
441  int lineZero =
442  // sync + top erase:
443  3 + 13 +
444  // top border:
445  (palTiming ? 36 : 9) +
446  (controlRegs[9] & 0x80 ? 0 : 10) +
447  getVerticalAdjust(); // 0..15
448  displayStart =
449  lineZero * TICKS_PER_LINE
450  + 100 + 102; // VR flips at start of left border
451  displayStartSyncTime = frameStartTime + displayStart;
452  //cerr << "new DISPLAY_START is " << (displayStart / TICKS_PER_LINE) << "\n";
453 
454  // Register new DISPLAY_START sync point.
455  if (displayStartSyncTime > time) {
456  syncDisplayStart.setSyncPoint(displayStartSyncTime);
457  //cerr << "inserting new DISPLAY_START sync point\n";
458  }
459 
460  // HSCAN and VSCAN are relative to display start.
461  scheduleHScan(time);
462  scheduleVScan(time);
463 }
464 
465 void VDP::scheduleVScan(EmuTime::param time)
466 {
467  /*
468  cerr << "scheduleVScan @ " << (getTicksThisFrame(time) / TICKS_PER_LINE) << "\n";
469  if (vScanSyncTime < frameStartTime) {
470  cerr << "old VSCAN was previous frame\n";
471  } else {
472  cerr << "old VSCAN was " << (frameStartTime.getTicksTill(vScanSyncTime) / TICKS_PER_LINE) << "\n";
473  }
474  */
475 
476  // Remove pending VSCAN sync point, if any.
477  if (vScanSyncTime > time) {
478  syncVScan.removeSyncPoint();
479  //cerr << "removing predicted VSCAN sync point\n";
480  }
481 
482  // Calculate moment in time display end occurs.
483  vScanSyncTime = frameStartTime +
484  (displayStart + getNumberOfLines() * TICKS_PER_LINE);
485  //cerr << "new VSCAN is " << (frameStartTime.getTicksTill(vScanSyncTime) / TICKS_PER_LINE) << "\n";
486 
487  // Register new VSCAN sync point.
488  if (vScanSyncTime > time) {
489  syncVScan.setSyncPoint(vScanSyncTime);
490  //cerr << "inserting new VSCAN sync point\n";
491  }
492 }
493 
494 void VDP::scheduleHScan(EmuTime::param time)
495 {
496  // Remove pending HSCAN sync point, if any.
497  if (hScanSyncTime > time) {
498  syncHScan.removeSyncPoint();
499  hScanSyncTime = time;
500  }
501 
502  // Calculate moment in time line match occurs.
503  horizontalScanOffset = displayStart - (100 + 102)
504  + ((controlRegs[19] - controlRegs[23]) & 0xFF) * TICKS_PER_LINE
505  + getRightBorder();
506  // Display line counter continues into the next frame.
507  // Note that this implementation is not 100% accurate, since the
508  // number of ticks of the *previous* frame should be subtracted.
509  // By switching from NTSC to PAL it may even be possible to get two
510  // HSCANs in a single frame without modifying any other setting.
511  // Fortunately, no known program relies on this.
512  int ticksPerFrame = getTicksPerFrame();
513  if (horizontalScanOffset >= ticksPerFrame) {
514  horizontalScanOffset -= ticksPerFrame;
515 
516  // Time at which the internal VDP display line counter is reset,
517  // expressed in ticks after vsync.
518  // I would expect the counter to reset at line 16 (for neutral
519  // set-adjust), but measurements on NMS8250 show it is one line
520  // earlier. I'm not sure whether the actual counter reset
521  // happens on line 15 or whether the VDP timing may be one line
522  // off for some reason.
523  // TODO: This is just an assumption, more measurements on real MSX
524  // are necessary to verify there is really such a thing and
525  // if so, that the value is accurate.
526  // Note: see this bug report for some measurements on a real machine:
527  // https://github.com/openMSX/openMSX/issues/1106
528  int lineCountResetTicks = (8 + getVerticalAdjust()) * TICKS_PER_LINE;
529 
530  // Display line counter is reset at the start of the top border.
531  // Any HSCAN that has a higher line number never occurs.
532  if (horizontalScanOffset >= lineCountResetTicks) {
533  // This is one way to say "never".
534  horizontalScanOffset = -1000 * TICKS_PER_LINE;
535  }
536  }
537 
538  // Register new HSCAN sync point if interrupt is enabled.
539  if ((controlRegs[0] & 0x10) && horizontalScanOffset >= 0) {
540  // No line interrupt will occur after bottom erase.
541  // NOT TRUE: "after next top border start" is correct.
542  // Note that line interrupt can occur in the next frame.
543  /*
544  EmuTime bottomEraseTime =
545  frameStartTime + getTicksPerFrame() - 3 * TICKS_PER_LINE;
546  */
547  hScanSyncTime = frameStartTime + horizontalScanOffset;
548  if (hScanSyncTime > time) {
549  syncHScan.setSyncPoint(hScanSyncTime);
550  }
551  }
552 }
553 
554 // TODO: inline?
555 // TODO: Is it possible to get rid of this routine and its sync point?
556 // VSYNC, HSYNC and DISPLAY_START could be scheduled for the next
557 // frame when their callback occurs.
558 // But I'm not sure how to handle the PAL/NTSC setting (which also
559 // influences the frequency at which E/O toggles).
560 void VDP::frameStart(EmuTime::param time)
561 {
562  ++frameCount;
563 
564  //cerr << "VDP::frameStart @ " << time << "\n";
565 
566  // Toggle E/O.
567  // Actually this should occur half a line earlier,
568  // but for now this is accurate enough.
569  statusReg2 ^= 0x02;
570 
571  // Settings which are fixed at start of frame.
572  // Not sure this is how real MSX does it, but close enough for now.
573  // TODO: Interlace is effectuated in border height, according to
574  // the data book. Exactly when is the fixation point?
575  palTiming = (controlRegs[9] & 0x02) != 0;
576  interlaced = (controlRegs[9] & 0x08) != 0;
577 
578  // Blinking.
579  if (blinkCount != 0) { // counter active?
580  blinkCount--;
581  if (blinkCount == 0) {
582  renderer->updateBlinkState(!blinkState, time);
583  blinkState = !blinkState;
584  blinkCount = ( blinkState
585  ? controlRegs[13] >> 4 : controlRegs[13] & 0x0F ) * 10;
586  }
587  }
588 
589  // TODO: Presumably this is done here
590  // Note that if superimposing is enabled but no external video
591  // signal is provided then the VDP stops producing a signal
592  // (at least on an MSX1, VDP(0)=1 produces "signal lost" on my
593  // monitor)
594  const RawFrame* newSuperimposing = (controlRegs[0] & 1) ? externalVideo : nullptr;
595  if (superimposing != newSuperimposing) {
596  superimposing = newSuperimposing;
597  renderer->updateSuperimposing(superimposing, time);
598  }
599 
600  // Schedule next VSYNC.
601  frameStartTime.reset(time);
602  syncVSync.setSyncPoint(frameStartTime + getTicksPerFrame());
603  // Schedule DISPLAY_START, VSCAN and HSCAN.
604  scheduleDisplayStart(time);
605 
606  // Inform VDP subcomponents.
607  // TODO: Do this via VDPVRAM?
608  renderer->frameStart(time);
609  spriteChecker->frameStart(time);
610 
611  /*
612  cout << "--> frameStart = " << frameStartTime
613  << ", frameEnd = " << (frameStartTime + getTicksPerFrame())
614  << ", hscan = " << hScanSyncTime
615  << ", displayStart = " << displayStart
616  << ", timing: " << (palTiming ? "PAL" : "NTSC")
617  << "\n";
618  */
619 }
620 
621 // The I/O functions.
622 
623 void VDP::writeIO(word port, byte value, EmuTime::param time_)
624 {
625  EmuTime time = time_;
626  // This is the (fixed) delay from
627  // https://github.com/openMSX/openMSX/issues/563 and
628  // https://github.com/openMSX/openMSX/issues/989
629  // It seems to originate from the T9769x and for x=C the delay is 1
630  // cycle and for other x it seems the delay is 2 cycles
631  if (fixedVDPIOdelayCycles > 0) {
632  time = cpu.waitCycles(time, fixedVDPIOdelayCycles);
633  }
634 
635  assert(isInsideFrame(time));
636  switch (port & (isMSX1VDP() ? 0x01 : 0x03)) {
637  case 0: // VRAM data write
638  vramWrite(value, time);
639  registerDataStored = false;
640  break;
641  case 1: // Register or address write
642  if (registerDataStored) {
643  if (value & 0x80) {
644  if (!(value & 0x40) || isMSX1VDP()) {
645  // Register write.
646  changeRegister(
647  value & controlRegMask,
648  dataLatch,
649  time
650  );
651  } else {
652  // TODO what happens in this case?
653  // it's not a register write because
654  // that breaks "SNOW26" demo
655  }
656  if (isMSX1VDP()) {
657  // For these VDP's the VRAM pointer is modified when
658  // writing to VDP registers. Without this some demos won't
659  // run as on real MSX1, e.g. Planet of the Epas, Utopia and
660  // Waves 1.2. Thanks to dvik for finding this out.
661  // See also below about not using the latch on MSX1.
662  // Set read/write address.
663  vramPointer = (value << 8 | (vramPointer & 0xFF)) & 0x3FFF;
664  }
665  } else {
666  // Set read/write address.
667  vramPointer = (value << 8 | dataLatch) & 0x3FFF;
668  if (!(value & 0x40)) {
669  // Read ahead.
670  vramRead(time);
671  }
672  }
673  registerDataStored = false;
674  } else {
675  // Note: on MSX1 there seems to be no
676  // latch used, but VDP address writes
677  // are done directly.
678  // Thanks to hap for finding this out. :)
679  if (isMSX1VDP()) {
680  vramPointer = (vramPointer & 0x3F00) | value;
681  }
682  dataLatch = value;
683  registerDataStored = true;
684  }
685  break;
686  case 2: // Palette data write
687  if (paletteDataStored) {
688  int index = controlRegs[16];
689  int grb = ((value << 8) | dataLatch) & 0x777;
690  setPalette(index, grb, time);
691  controlRegs[16] = (index + 1) & 0x0F;
692  paletteDataStored = false;
693  } else {
694  dataLatch = value;
695  paletteDataStored = true;
696  }
697  break;
698  case 3: { // Indirect register write
699  dataLatch = value;
700  // TODO: What happens if reg 17 is written indirectly?
701  //fprintf(stderr, "VDP indirect register write: %02X\n", value);
702  byte regNr = controlRegs[17];
703  changeRegister(regNr & 0x3F, value, time);
704  if ((regNr & 0x80) == 0) {
705  // Auto-increment.
706  controlRegs[17] = (regNr + 1) & 0x3F;
707  }
708  break;
709  }
710  }
711 }
712 
713 void VDP::setPalette(int index, word grb, EmuTime::param time)
714 {
715  if (palette[index] != grb) {
716  renderer->updatePalette(index, grb, time);
717  palette[index] = grb;
718  }
719 }
720 
721 void VDP::vramWrite(byte value, EmuTime::param time)
722 {
723  scheduleCpuVramAccess(false, value, time);
724 }
725 
726 byte VDP::vramRead(EmuTime::param time)
727 {
728  // Return the result from a previous read. In case
729  // allowTooFastAccess==true, the call to scheduleCpuVramAccess()
730  // already overwrites that variable, so make a local copy first.
731  byte result = cpuVramData;
732 
733  byte dummy = 0;
734  scheduleCpuVramAccess(true, dummy, time); // schedule next read
735  return result;
736 }
737 
738 void VDP::scheduleCpuVramAccess(bool isRead, byte write, EmuTime::param time)
739 {
740  // Tested on real V9938: 'cpuVramData' is shared between read and write.
741  // E.g. OUT (#98),A followed by IN A,(#98) returns the just written value.
742  if (!isRead) cpuVramData = write;
743  cpuVramReqIsRead = isRead;
744  if (unlikely(pendingCpuAccess)) {
745  // Already scheduled. Do nothing.
746  // The old request has been overwritten by the new request!
747  assert(!allowTooFastAccess);
748  tooFastCallback.execute();
749  } else {
750  if (unlikely(allowTooFastAccess)) {
751  // Immediately execute request.
752  // In the past, in allowTooFastAccess-mode, we would
753  // still schedule the actual access, but process
754  // pending requests early when a new one arrives before
755  // the old one was handled. Though this would still go
756  // wrong because of the delayed update of
757  // 'vramPointer'. We could _only_ _partly_ work around
758  // that by calculating the actual vram address early
759  // (likely not what the real VDP does). But because
760  // allowTooFastAccess is anyway an artifical situation
761  // we now solve this in a simpler way: simply not
762  // schedule CPU-VRAM accesses.
763  assert(!pendingCpuAccess);
764  executeCpuVramAccess(time);
765  } else {
766  // For V99x8 there are 16 extra cycles, for details see:
767  // doc/internal/vdp-vram-timing/vdp-timing.html
768  // For TMS99x8 the situation is less clear, see
769  // doc/internal/vdp-vram-timing/vdp-timing-2.html
770  // Additional measurements(*) show that picking either 8 or 9
771  // TMS cycles (equivalent to 32 or 36 V99x8 cycles) gives the
772  // same result as on a real MSX. This corresponds to
773  // respectively 1.49us or 1.68us, the TMS documentation
774  // specifies 2us for this value.
775  // (*) In this test we did a lot of OUT operations (writes to
776  // VRAM) that are exactly N cycles apart. After the writes we
777  // detect whether all were successful by reading VRAM
778  // (slowly). We vary N and found that you get corruption for
779  // N<=26 cycles, but no corruption occurs for N>=27. This test
780  // was done in screen 2 with 4 sprites visible on one line
781  // (though the sprites did not seem to make a difference).
782  // So this test could not decide between 8 or 9 TMS cycles.
783  // To be on the safe side we picked 8.
784  //
785  // Update: 8 cycles (DELTA_32) causes corruption in
786  // 'Chase HQ', see
787  // http://www.msx.org/forum/msx-talk/openmsx/openmsx-about-release-testing-help-wanted
788  // lowering it to 7 cycles seems fine. TODO needs more
789  // investigation. (Just guessing) possibly there are
790  // other variables that influence the exact timing (7
791  // vs 8 cycles).
792  pendingCpuAccess = true;
793  auto delta = isMSX1VDP() ? VDPAccessSlots::DELTA_28
795  syncCpuVramAccess.setSyncPoint(getAccessSlot(time, delta));
796  }
797  }
798 }
799 
800 void VDP::executeCpuVramAccess(EmuTime::param time)
801 {
802  int addr = (controlRegs[14] << 14) | vramPointer;
803  if (displayMode.isPlanar()) {
804  // note: also extended VRAM is interleaved,
805  // because there is only 64kB it's interleaved
806  // with itself (every byte repeated twice)
807  addr = ((addr << 16) | (addr >> 1)) & 0x1FFFF;
808  }
809 
810  bool doAccess;
811  if (likely(!cpuExtendedVram)) {
812  doAccess = true;
813  } else if (likely(vram->getSize() == 192 * 1024)) {
814  addr = 0x20000 | (addr & 0xFFFF);
815  doAccess = true;
816  } else {
817  doAccess = false;
818  }
819  if (doAccess) {
820  if (cpuVramReqIsRead) {
821  cpuVramData = vram->cpuRead(addr, time);
822  } else {
823  vram->cpuWrite(addr, cpuVramData, time);
824  }
825  } else {
826  if (cpuVramReqIsRead) {
827  cpuVramData = 0xFF;
828  } else {
829  // nothing
830  }
831  }
832 
833  vramPointer = (vramPointer + 1) & 0x3FFF;
834  if (vramPointer == 0 && displayMode.isV9938Mode()) {
835  // In MSX2 video modes, pointer range is 128K.
836  controlRegs[14] = (controlRegs[14] + 1) & 0x07;
837  }
838 }
839 
840 EmuTime VDP::getAccessSlot(EmuTime::param time, VDPAccessSlots::Delta delta) const
841 {
843  getFrameStartTime(), time, delta, *this);
844 }
845 
847  EmuTime::param time, EmuTime::param limit) const
848 {
850  getFrameStartTime(), time, limit, *this);
851 }
852 
853 byte VDP::peekStatusReg(byte reg, EmuTime::param time) const
854 {
855  switch (reg) {
856  case 0:
857  spriteChecker->sync(time);
858  return statusReg0;
859  case 1:
860  if (controlRegs[0] & 0x10) { // line int enabled
861  return statusReg1 | (irqHorizontal.getState() ? 1:0);
862  } else { // line int disabled
863  // FH goes up at the start of the right border of IL and
864  // goes down at the start of the next left border.
865  // TODO: Precalc matchLength?
866  int afterMatch =
867  getTicksThisFrame(time) - horizontalScanOffset;
868  if (afterMatch < 0) {
869  afterMatch += getTicksPerFrame();
870  // afterMatch can still be negative at this
871  // point, see scheduleHScan()
872  }
873  int matchLength = (displayMode.isTextMode() ? 87 : 59)
874  + 27 + 100 + 102;
875  return statusReg1 |
876  (0 <= afterMatch && afterMatch < matchLength);
877  }
878  case 2: {
879  // TODO: Once VDP keeps display/blanking state, keeping
880  // VR is probably part of that, so use it.
881  // --> Is isDisplayArea actually !VR?
882  int ticksThisFrame = getTicksThisFrame(time);
883  int displayEnd =
884  displayStart + getNumberOfLines() * TICKS_PER_LINE;
885  bool vr = ticksThisFrame < displayStart - TICKS_PER_LINE
886  || ticksThisFrame >= displayEnd;
887  return statusReg2
888  | (getHR(ticksThisFrame) ? 0x20 : 0x00)
889  | (vr ? 0x40 : 0x00)
890  | cmdEngine->getStatus(time);
891  }
892  case 3:
893  return byte(spriteChecker->getCollisionX(time));
894  case 4:
895  return byte(spriteChecker->getCollisionX(time) >> 8) | 0xFE;
896  case 5:
897  return byte(spriteChecker->getCollisionY(time));
898  case 6:
899  return byte(spriteChecker->getCollisionY(time) >> 8) | 0xFC;
900  case 7:
901  return cmdEngine->readColor(time);
902  case 8:
903  return byte(cmdEngine->getBorderX(time));
904  case 9:
905  return byte(cmdEngine->getBorderX(time) >> 8) | 0xFE;
906  default: // non-existent status register
907  return 0xFF;
908  }
909 }
910 
911 byte VDP::readStatusReg(byte reg, EmuTime::param time)
912 {
913  byte ret = peekStatusReg(reg, time);
914  switch (reg) {
915  case 0:
916  spriteChecker->resetStatus();
917  statusReg0 &= ~0x80;
918  irqVertical.reset();
919  break;
920  case 1:
921  if (controlRegs[0] & 0x10) { // line int enabled
922  irqHorizontal.reset();
923  }
924  break;
925  case 5:
926  spriteChecker->resetCollision();
927  break;
928  case 7:
929  cmdEngine->resetColor();
930  break;
931  }
932  return ret;
933 }
934 
935 byte VDP::readIO(word port, EmuTime::param time)
936 {
937  assert(isInsideFrame(time));
938 
939  registerDataStored = false; // Abort any port #1 writes in progress.
940 
941  switch (port & (isMSX1VDP() ? 0x01 : 0x03)) {
942  case 0: // VRAM data read
943  return vramRead(time);
944  case 1: // Status register read
945  // Calculate status register contents.
946  return readStatusReg(controlRegs[15], time);
947  case 2:
948  case 3:
949  return 0xFF;
950  default:
951  UNREACHABLE; return 0xFF;
952  }
953 }
954 
955 byte VDP::peekIO(word /*port*/, EmuTime::param /*time*/) const
956 {
957  // TODO not implemented
958  return 0xFF;
959 }
960 
961 void VDP::changeRegister(byte reg, byte val, EmuTime::param time)
962 {
963  if (reg >= 32) {
964  // MXC belongs to CPU interface;
965  // other bits in this register belong to command engine.
966  if (reg == 45) {
967  cpuExtendedVram = (val & 0x40) != 0;
968  }
969  // Pass command register writes to command engine.
970  if (reg < 47) {
971  cmdEngine->setCmdReg(reg - 32, val, time);
972  }
973  return;
974  }
975 
976  // Make sure only bits that actually exist are written.
977  val &= controlValueMasks[reg];
978  // Determine the difference between new and old value.
979  byte change = val ^ controlRegs[reg];
980 
981  // Register 13 is special because writing it resets blinking state,
982  // even if the value in the register doesn't change.
983  if (reg == 13) {
984  // Switch to ON state unless ON period is zero.
985  if (blinkState == ((val & 0xF0) == 0)) {
986  renderer->updateBlinkState(!blinkState, time);
987  blinkState = !blinkState;
988  }
989 
990  if ((val & 0xF0) && (val & 0x0F)) {
991  // Alternating colors, start with ON.
992  blinkCount = (val >> 4) * 10;
993  } else {
994  // Stable color.
995  blinkCount = 0;
996  }
997  }
998 
999  if (!change) return;
1000 
1001  // Perform additional tasks before new value becomes active.
1002  switch (reg) {
1003  case 0:
1004  if (change & DisplayMode::REG0_MASK) {
1005  syncAtNextLine(syncSetMode, time);
1006  }
1007  break;
1008  case 1:
1009  if (change & 0x03) {
1010  // Update sprites on size and mag changes.
1011  spriteChecker->updateSpriteSizeMag(val, time);
1012  }
1013  // TODO: Reset vertical IRQ if IE0 is reset?
1014  if (change & DisplayMode::REG1_MASK) {
1015  syncAtNextLine(syncSetMode, time);
1016  }
1017  if (change & 0x40) {
1018  syncAtNextLine(syncSetBlank, time);
1019  }
1020  break;
1021  case 2: {
1022  int base = (val << 10) | ~(~0u << 10);
1023  // TODO:
1024  // I reverted this fix.
1025  // Although the code is correct, there is also a counterpart in the
1026  // renderer that must be updated. I'm too tired now to find it.
1027  // Since name table checking is currently disabled anyway, keeping the
1028  // old code does not hurt.
1029  // Eventually this line should be re-enabled.
1030  /*
1031  if (displayMode.isPlanar()) {
1032  base = ((base << 16) | (base >> 1)) & 0x1FFFF;
1033  }
1034  */
1035  renderer->updateNameBase(base, time);
1036  break;
1037  }
1038  case 7:
1039  if (getDisplayMode().getByte() != DisplayMode::GRAPHIC7) {
1040  if (change & 0xF0) {
1041  renderer->updateForegroundColor(val >> 4, time);
1042  }
1043  if (change & 0x0F) {
1044  renderer->updateBackgroundColor(val & 0x0F, time);
1045  }
1046  } else {
1047  renderer->updateBackgroundColor(val, time);
1048  }
1049  break;
1050  case 8:
1051  if (change & 0x20) {
1052  renderer->updateTransparency((val & 0x20) == 0, time);
1053  }
1054  if (change & 0x02) {
1055  vram->updateSpritesEnabled((val & 0x02) == 0, time);
1056  }
1057  if (change & 0x08) {
1058  vram->updateVRMode((val & 0x08) != 0, time);
1059  }
1060  break;
1061  case 12:
1062  if (change & 0xF0) {
1063  renderer->updateBlinkForegroundColor(val >> 4, time);
1064  }
1065  if (change & 0x0F) {
1066  renderer->updateBlinkBackgroundColor(val & 0x0F, time);
1067  }
1068  break;
1069  case 16:
1070  // Any half-finished palette loads are aborted.
1071  paletteDataStored = false;
1072  break;
1073  case 18:
1074  if (change & 0x0F) {
1075  syncAtNextLine(syncHorAdjust, time);
1076  }
1077  break;
1078  case 23:
1079  spriteChecker->updateVerticalScroll(val, time);
1080  renderer->updateVerticalScroll(val, time);
1081  break;
1082  case 25:
1083  if (change & (DisplayMode::REG25_MASK | 0x40)) {
1084  updateDisplayMode(getDisplayMode().updateReg25(val),
1085  val & 0x40,
1086  time);
1087  }
1088  if (change & 0x08) {
1089  syncAtNextLine(syncHorAdjust, time);
1090  }
1091  if (change & 0x02) {
1092  renderer->updateBorderMask((val & 0x02) != 0, time);
1093  }
1094  if (change & 0x01) {
1095  renderer->updateMultiPage((val & 0x01) != 0, time);
1096  }
1097  break;
1098  case 26:
1099  renderer->updateHorizontalScrollHigh(val, time);
1100  break;
1101  case 27:
1102  renderer->updateHorizontalScrollLow(val, time);
1103  break;
1104  }
1105 
1106  // Commit the change.
1107  controlRegs[reg] = val;
1108 
1109  // Perform additional tasks after new value became active.
1110  // Because base masks cannot be read from the VDP, updating them after
1111  // the commit is equivalent to updating before.
1112  switch (reg) {
1113  case 0:
1114  if (change & 0x10) { // IE1
1115  if (val & 0x10) {
1116  scheduleHScan(time);
1117  } else {
1118  irqHorizontal.reset();
1119  }
1120  }
1121  break;
1122  case 1:
1123  if (change & 0x20) { // IE0
1124  if (val & 0x20) {
1125  // This behaviour is important. Without it,
1126  // the intro music in 'Andonis' is way too slow
1127  // and the intro logo of 'Zanac' is corrupted.
1128  if (statusReg0 & 0x80) {
1129  irqVertical.set();
1130  }
1131  } else {
1132  irqVertical.reset();
1133  }
1134  }
1135  if ((change & 0x80) && isVDPwithVRAMremapping()) {
1136  // confirmed: VRAM remapping only happens on TMS99xx
1137  // see VDPVRAM for details on the remapping itself
1138  vram->change4k8kMapping((val & 0x80) != 0);
1139  }
1140  break;
1141  case 2:
1142  updateNameBase(time);
1143  break;
1144  case 3:
1145  case 10:
1146  updateColorBase(time);
1147  if (vdpHasPatColMirroring()) updatePatternBase(time);
1148  break;
1149  case 4:
1150  updatePatternBase(time);
1151  break;
1152  case 5:
1153  case 11:
1154  updateSpriteAttributeBase(time);
1155  break;
1156  case 6:
1157  updateSpritePatternBase(time);
1158  break;
1159  case 9:
1160  if ((val & 1) && ! warningPrinted) {
1161  warningPrinted = true;
1163  "The running MSX software has set bit 0 of VDP register 9 "
1164  "(dot clock direction) to one. In an ordinary MSX, "
1165  "the screen would go black and the CPU would stop running.");
1166  // TODO: Emulate such behaviour.
1167  }
1168  if (change & 0x80) {
1169  /*
1170  cerr << "changed to " << (val & 0x80 ? 212 : 192) << " lines"
1171  << " at line " << (getTicksThisFrame(time) / TICKS_PER_LINE) << "\n";
1172  */
1173  // Display lines (192/212) determines display start and end.
1174  // TODO: Find out exactly when display start is fixed.
1175  // If it is fixed at VSYNC that would simplify things,
1176  // but I think it's more likely the current
1177  // implementation is accurate.
1178  if (time < displayStartSyncTime) {
1179  // Display start is not fixed yet.
1180  scheduleDisplayStart(time);
1181  } else {
1182  // Display start is fixed, but display end is not.
1183  scheduleVScan(time);
1184  }
1185  }
1186  break;
1187  case 19:
1188  case 23:
1189  scheduleHScan(time);
1190  break;
1191  case 25:
1192  if (change & 0x01) {
1193  updateNameBase(time);
1194  }
1195  break;
1196  }
1197 }
1198 
1199 void VDP::syncAtNextLine(SyncBase& type, EmuTime::param time)
1200 {
1201  int line = getTicksThisFrame(time) / TICKS_PER_LINE;
1202  int ticks = (line + 1) * TICKS_PER_LINE;
1203  EmuTime nextTime = frameStartTime + ticks;
1204  type.setSyncPoint(nextTime);
1205 }
1206 
1207 void VDP::updateNameBase(EmuTime::param time)
1208 {
1209  int base = (controlRegs[2] << 10) | ~(~0u << 10);
1210  // TODO:
1211  // I reverted this fix.
1212  // Although the code is correct, there is also a counterpart in the
1213  // renderer that must be updated. I'm too tired now to find it.
1214  // Since name table checking is currently disabled anyway, keeping the
1215  // old code does not hurt.
1216  // Eventually this line should be re-enabled.
1217  /*
1218  if (displayMode.isPlanar()) {
1219  base = ((base << 16) | (base >> 1)) & 0x1FFFF;
1220  }
1221  */
1222  int indexMask =
1223  displayMode.isBitmapMode()
1224  ? ~0u << 17 // TODO: Calculate actual value; how to handle planar?
1225  : ~0u << (displayMode.isTextMode() ? 12 : 10);
1226  if (controlRegs[25] & 0x01) {
1227  // Multi page scrolling. The same bit is used in character and
1228  // (non)planar-bitmap modes.
1229  // TODO test text modes
1230  indexMask &= ~0x8000;
1231  }
1232  vram->nameTable.setMask(base, indexMask, time);
1233 }
1234 
1235 void VDP::updateColorBase(EmuTime::param time)
1236 {
1237  int base = (controlRegs[10] << 14) | (controlRegs[3] << 6) | ~(~0u << 6);
1238  renderer->updateColorBase(base, time);
1239  switch (displayMode.getBase()) {
1240  case 0x09: // Text 2.
1241  // TODO: Enable this only if dual color is actually active.
1242  vram->colorTable.setMask(base, ~0u << 9, time);
1243  break;
1244  case 0x00: // Graphic 1.
1245  vram->colorTable.setMask(base, ~0u << 6, time);
1246  break;
1247  case 0x04: // Graphic 2.
1248  vram->colorTable.setMask(base | (vdpLacksMirroring() ? 0x1800 : 0), ~0u << 13, time);
1249  break;
1250  case 0x08: // Graphic 3.
1251  vram->colorTable.setMask(base, ~0u << 13, time);
1252  break;
1253  default:
1254  // Other display modes do not use a color table.
1255  vram->colorTable.disable(time);
1256  }
1257 }
1258 
1259 void VDP::updatePatternBase(EmuTime::param time)
1260 {
1261  int base = (controlRegs[4] << 11) | ~(~0u << 11);
1262  renderer->updatePatternBase(base, time);
1263  switch (displayMode.getBase()) {
1264  case 0x01: // Text 1.
1265  case 0x05: // Text 1 Q.
1266  case 0x09: // Text 2.
1267  case 0x00: // Graphic 1.
1268  case 0x02: // Multicolor.
1269  case 0x06: // Multicolor Q.
1270  vram->patternTable.setMask(base, ~0u << 11, time);
1271  break;
1272  case 0x04: // Graphic 2.
1273  if (vdpHasPatColMirroring()) {
1274  // TMS99XX has weird pattern table behavior: some
1275  // bits of the color-base register leak into the
1276  // pattern-base. See also:
1277  // http://www.youtube.com/watch?v=XJljSJqzDR0
1278  base = (controlRegs[4] << 11)
1279  | ((controlRegs[3] & 0x1f) << 6)
1280  | ~(~0u << 6);
1281  }
1282  vram->patternTable.setMask(base | (vdpLacksMirroring() ? 0x1800 : 0), ~0u << 13, time);
1283  break;
1284  case 0x08: // Graphic 3.
1285  vram->patternTable.setMask(base, ~0u << 13, time);
1286  break;
1287  default:
1288  // Other display modes do not use a pattern table.
1289  vram->patternTable.disable(time);
1290  }
1291 }
1292 
1293 void VDP::updateSpriteAttributeBase(EmuTime::param time)
1294 {
1295  int mode = displayMode.getSpriteMode(isMSX1VDP());
1296  if (mode == 0) {
1297  vram->spriteAttribTable.disable(time);
1298  return;
1299  }
1300  int baseMask = (controlRegs[11] << 15) | (controlRegs[5] << 7) | ~(~0u << 7);
1301  int indexMask = mode == 1 ? ~0u << 7 : ~0u << 10;
1302  if (displayMode.isPlanar()) {
1303  baseMask = ((baseMask << 16) | (baseMask >> 1)) & 0x1FFFF;
1304  indexMask = ((indexMask << 16) | ~(1 << 16)) & (indexMask >> 1);
1305  }
1306  vram->spriteAttribTable.setMask(baseMask, indexMask, time);
1307 }
1308 
1309 void VDP::updateSpritePatternBase(EmuTime::param time)
1310 {
1311  if (displayMode.getSpriteMode(isMSX1VDP()) == 0) {
1312  vram->spritePatternTable.disable(time);
1313  return;
1314  }
1315  int baseMask = (controlRegs[6] << 11) | ~(~0u << 11);
1316  int indexMask = ~0u << 11;
1317  if (displayMode.isPlanar()) {
1318  baseMask = ((baseMask << 16) | (baseMask >> 1)) & 0x1FFFF;
1319  indexMask = ((indexMask << 16) | ~(1 << 16)) & (indexMask >> 1);
1320  }
1321  vram->spritePatternTable.setMask(baseMask, indexMask, time);
1322 }
1323 
1324 void VDP::updateDisplayMode(DisplayMode newMode, bool cmdBit, EmuTime::param time)
1325 {
1326  // Synchronise subsystems.
1327  vram->updateDisplayMode(newMode, cmdBit, time);
1328 
1329  // TODO: Is this a useful optimisation, or doesn't it help
1330  // in practice?
1331  // What aspects have changed:
1332  // Switched from planar to nonplanar or vice versa.
1333  bool planarChange =
1334  newMode.isPlanar() != displayMode.isPlanar();
1335  // Sprite mode changed.
1336  bool msx1 = isMSX1VDP();
1337  bool spriteModeChange =
1338  newMode.getSpriteMode(msx1) != displayMode.getSpriteMode(msx1);
1339 
1340  // Commit the new display mode.
1341  displayMode = newMode;
1342 
1343  // Speed up performance of bitmap/character mode splits:
1344  // leave last used character mode active.
1345  // TODO: Disable it if not used for some time.
1346  if (!displayMode.isBitmapMode()) {
1347  updateColorBase(time);
1348  updatePatternBase(time);
1349  }
1350  if (planarChange || spriteModeChange) {
1351  updateSpritePatternBase(time);
1352  updateSpriteAttributeBase(time);
1353  }
1354  updateNameBase(time);
1355 
1356  // To be extremely accurate, reschedule hscan when changing
1357  // from/to text mode. Text mode has different border width,
1358  // which affects the moment hscan occurs.
1359  // TODO: Why didn't I implement this yet?
1360  // It's one line of code and overhead is not huge either.
1361 }
1362 
1363 void VDP::update(const Setting& setting)
1364 {
1365  assert((&setting == &cmdTiming) ||
1366  (&setting == &tooFastAccess));
1367  (void)setting;
1368  brokenCmdTiming = cmdTiming .getEnum();
1369  allowTooFastAccess = tooFastAccess.getEnum();
1370 
1371  if (unlikely(allowTooFastAccess && pendingCpuAccess)) {
1372  // in allowTooFastAccess-mode, don't schedule CPU-VRAM access
1373  syncCpuVramAccess.removeSyncPoint();
1374  pendingCpuAccess = false;
1375  executeCpuVramAccess(getCurrentTime());
1376  }
1377 }
1378 
1379 /*
1380  * Roughly measured RGB values in volts.
1381  * Voltages were in range of 1.12-5.04, and had 2 digits accuracy (it seems
1382  * minimum difference was 0.04 V).
1383  * Blue component of color 5 and red component of color 9 were higher than
1384  * the components for white... There are several methods to handle this...
1385  * 1) clip to values of white
1386  * 2) scale all colors by min/max of that component (means white is not 3x 255)
1387  * 3) scale per color if components for that color are beyond those of white
1388  * 4) assume the analog values are output by a DA converter, derive the digital
1389  * values and scale that to the range 0-255 (thanks to FRS for this idea).
1390  * This also results in white not being 3x 255, of course.
1391  *
1392  * Method 4 results in the table below and seems the most accurate (so far).
1393  *
1394  * Thanks to Tiago Valença and Carlos Mansur for measuring on a T7937A.
1395  */
1396 static const std::array<std::array<uint8_t,3>,16> TOSHIBA_PALETTE = {{
1397  { 0, 0, 0 },
1398  { 0, 0, 0 },
1399  { 102, 204, 102 },
1400  { 136, 238, 136 },
1401  { 68, 68, 221 },
1402  { 119, 119, 255 },
1403  { 187, 85, 85 },
1404  { 119, 221, 221 },
1405  { 221, 102, 102 },
1406  { 255, 119, 119 },
1407  { 204, 204, 85 },
1408  { 238, 238, 136 },
1409  { 85, 170, 85 },
1410  { 187, 85, 187 },
1411  { 204, 204, 204 },
1412  { 238, 238, 238 },
1413 }};
1414 
1415 /*
1416 How come the FM-X has a distinct palette while it clearly has a TMS9928 VDP?
1417 Because it has an additional circuit that rework the palette for the same one
1418 used in the Fujitsu FM-7. It's encoded in 3-bit RGB.
1419 
1420 This seems to be the 24-bit RGB equivalent to the palette output by the FM-X on
1421 its RGB conector:
1422 */
1423 static const std::array<std::array<uint8_t,3>,16> THREE_BIT_RGB_PALETTE = {{
1424  { 0, 0, 0 },
1425  { 0, 0, 0 },
1426  { 0, 255, 0 },
1427  { 0, 255, 0 },
1428  { 0, 0, 255 },
1429  { 0, 0, 255 },
1430  { 255, 0, 0 },
1431  { 0, 255, 255 },
1432  { 255, 0, 0 },
1433  { 255, 0, 0 },
1434  { 255, 255, 0 },
1435  { 255, 255, 0 },
1436  { 0, 255, 0 },
1437  { 255, 0, 255 },
1438  { 255, 255, 255 },
1439  { 255, 255, 255 },
1440 }};
1441 
1442 // Source: TMS9918/28/29 Data Book, page 2-17.
1443 
1444 const float TMS9XXXA_ANALOG_OUTPUT[16][3] = {
1445  // Y R-Y B-Y voltages
1446  { 0.00f, 0.47f, 0.47f },
1447  { 0.00f, 0.47f, 0.47f },
1448  { 0.53f, 0.07f, 0.20f },
1449  { 0.67f, 0.17f, 0.27f },
1450  { 0.40f, 0.40f, 1.00f },
1451  { 0.53f, 0.43f, 0.93f },
1452  { 0.47f, 0.83f, 0.30f },
1453  { 0.73f, 0.00f, 0.70f },
1454  { 0.53f, 0.93f, 0.27f },
1455  { 0.67f, 0.93f, 0.27f },
1456  { 0.73f, 0.57f, 0.07f },
1457  { 0.80f, 0.57f, 0.17f },
1458  { 0.47f, 0.13f, 0.23f },
1459  { 0.53f, 0.73f, 0.67f },
1460  { 0.80f, 0.47f, 0.47f },
1461  { 1.00f, 0.47f, 0.47f },
1462 };
1463 
1464 std::array<std::array<uint8_t, 3>, 16> VDP::getMSX1Palette() const
1465 {
1466  assert(isMSX1VDP());
1467  if (MSXDevice::getDeviceConfig().findChild("3bitrgboutput") != nullptr) {
1468  return THREE_BIT_RGB_PALETTE;
1469  }
1470  if ((version & VM_TOSHIBA_PALETTE) != 0) {
1471  return TOSHIBA_PALETTE;
1472  }
1473  std::array<std::array<uint8_t, 3>, 16> tmsPalette;
1474  for (int color = 0; color < 16; color++) {
1475  // convert from analog output to YPbPr
1476  float Y = TMS9XXXA_ANALOG_OUTPUT[color][0];
1477  float Pr = TMS9XXXA_ANALOG_OUTPUT[color][1] - 0.5f;
1478  float Pb = TMS9XXXA_ANALOG_OUTPUT[color][2] - 0.5f;
1479  // apply the saturation
1480  Pr *= (saturationPr / 100.0f);
1481  Pb *= (saturationPb / 100.0f);
1482  // convert to RGB as follows:
1483  /*
1484  |R| | 1 0 1.402 | |Y |
1485  |G| = | 1 -0.344 -0.714 | x |Pb|
1486  |B| | 1 1.722 0 | |Pr|
1487  */
1488  float R = Y + 0 + 1.402f * Pr;
1489  float G = Y - 0.344f * Pb - 0.714f * Pr;
1490  float B = Y + 1.722f * Pb + 0;
1491  // blow up with factor of 255
1492  R *= 255;
1493  G *= 255;
1494  B *= 255;
1495  // the final result is that these values
1496  // are clipped in the [0:255] range.
1497  // Note: Using roundf instead of std::round because libstdc++ when
1498  // built on top of uClibc lacks std::round; uClibc does provide
1499  // roundf, but lacks other C99 math functions and that makes
1500  // libstdc++ disable all wrappers for C99 math functions.
1501  tmsPalette[color][0] = Math::clipIntToByte(roundf(R));
1502  tmsPalette[color][1] = Math::clipIntToByte(roundf(G));
1503  tmsPalette[color][2] = Math::clipIntToByte(roundf(B));
1504  // std::cerr << color << " " << int(tmsPalette[color][0]) << " " << int(tmsPalette[color][1]) <<" " << int(tmsPalette[color][2]) << '\n';
1505  }
1506  return tmsPalette;
1507 }
1508 
1509 // RegDebug
1510 
1511 VDP::RegDebug::RegDebug(VDP& vdp_)
1513  vdp_.getName() + " regs", "VDP registers.", 0x40)
1514 {
1515 }
1516 
1517 byte VDP::RegDebug::read(unsigned address)
1518 {
1519  auto& vdp = OUTER(VDP, vdpRegDebug);
1520  if (address < 0x20) {
1521  return vdp.controlRegs[address];
1522  } else if (address < 0x2F) {
1523  return vdp.cmdEngine->peekCmdReg(address - 0x20);
1524  } else {
1525  return 0xFF;
1526  }
1527 }
1528 
1529 void VDP::RegDebug::write(unsigned address, byte value, EmuTime::param time)
1530 {
1531  auto& vdp = OUTER(VDP, vdpRegDebug);
1532  vdp.changeRegister(address, value, time);
1533 }
1534 
1535 
1536 // StatusRegDebug
1537 
1538 VDP::StatusRegDebug::StatusRegDebug(VDP& vdp_)
1540  vdp_.getName() + " status regs", "VDP status registers.", 0x10)
1541 {
1542 }
1543 
1544 byte VDP::StatusRegDebug::read(unsigned address, EmuTime::param time)
1545 {
1546  auto& vdp = OUTER(VDP, vdpStatusRegDebug);
1547  return vdp.peekStatusReg(address, time);
1548 }
1549 
1550 
1551 // PaletteDebug
1552 
1553 VDP::PaletteDebug::PaletteDebug(VDP& vdp_)
1555  vdp_.getName() + " palette", "V99x8 palette (RBG format)", 0x20)
1556 {
1557 }
1558 
1559 byte VDP::PaletteDebug::read(unsigned address)
1560 {
1561  auto& vdp = OUTER(VDP, vdpPaletteDebug);
1562  word grb = vdp.getPalette(address / 2);
1563  return (address & 1) ? (grb >> 8) : (grb & 0xff);
1564 }
1565 
1566 void VDP::PaletteDebug::write(unsigned address, byte value, EmuTime::param time)
1567 {
1568  auto& vdp = OUTER(VDP, vdpPaletteDebug);
1569  int index = address / 2;
1570  word grb = vdp.getPalette(index);
1571  grb = (address & 1)
1572  ? (grb & 0x0077) | ((value & 0x07) << 8)
1573  : (grb & 0x0700) | (value & 0x77);
1574  vdp.setPalette(index, grb, time);
1575 }
1576 
1577 
1578 // class VRAMPointerDebug
1579 
1580 VDP::VRAMPointerDebug::VRAMPointerDebug(VDP& vdp_)
1581  : SimpleDebuggable(vdp_.getMotherBoard(), vdp_.getName() == "VDP" ?
1582  "VRAM pointer" : vdp_.getName() + " VRAM pointer",
1583  "VDP VRAM pointer (14 lower bits)", 2)
1584 {
1585 }
1586 
1587 byte VDP::VRAMPointerDebug::read(unsigned address)
1588 {
1589  auto& vdp = OUTER(VDP, vramPointerDebug);
1590  if (address & 1) {
1591  return vdp.vramPointer >> 8; // TODO add read/write mode?
1592  } else {
1593  return vdp.vramPointer & 0xFF;
1594  }
1595 }
1596 
1597 void VDP::VRAMPointerDebug::write(unsigned address, byte value, EmuTime::param /*time*/)
1598 {
1599  auto& vdp = OUTER(VDP, vramPointerDebug);
1600  int& ptr = vdp.vramPointer;
1601  if (address & 1) {
1602  ptr = (ptr & 0x00FF) | ((value & 0x3F) << 8);
1603  } else {
1604  ptr = (ptr & 0xFF00) | value;
1605  }
1606 }
1607 
1608 
1609 // class Info
1610 
1611 VDP::Info::Info(VDP& vdp_, const string& name_, string helpText_)
1613  strCat(vdp_.getName(), '_', name_))
1614  , vdp(vdp_)
1615  , helpText(std::move(helpText_))
1616 {
1617 }
1618 
1619 void VDP::Info::execute(span<const TclObject> /*tokens*/, TclObject& result) const
1620 {
1621  result = calc(vdp.getCurrentTime());
1622 }
1623 
1624 string VDP::Info::help(const vector<string>& /*tokens*/) const
1625 {
1626  return helpText;
1627 }
1628 
1629 
1630 // class FrameCountInfo
1631 
1632 VDP::FrameCountInfo::FrameCountInfo(VDP& vdp_)
1633  : Info(vdp_, "frame_count",
1634  "The current frame number, starts counting at 0 "
1635  "when MSX is powered up or reset.")
1636 {
1637 }
1638 
1639 int VDP::FrameCountInfo::calc(const EmuTime& /*time*/) const
1640 {
1641  return vdp.frameCount;
1642 }
1643 
1644 
1645 // class CycleInFrameInfo
1646 
1647 VDP::CycleInFrameInfo::CycleInFrameInfo(VDP& vdp_)
1648  : Info(vdp_, "cycle_in_frame",
1649  "The number of VDP cycles since the beginning of "
1650  "the current frame. The VDP runs at 6 times the Z80 "
1651  "clock frequency, so at approximately 21.5MHz.")
1652 {
1653 }
1654 
1655 int VDP::CycleInFrameInfo::calc(const EmuTime& time) const
1656 {
1657  return vdp.getTicksThisFrame(time);
1658 }
1659 
1660 
1661 // class LineInFrameInfo
1662 
1663 VDP::LineInFrameInfo::LineInFrameInfo(VDP& vdp_)
1664  : Info(vdp_, "line_in_frame",
1665  "The absolute line number since the beginning of "
1666  "the current frame. Goes from 0 till 262 (NTSC) or "
1667  "313 (PAL). Note that this number includes the "
1668  "border lines, use 'msx_y_pos' to get MSX "
1669  "coordinates.")
1670 {
1671 }
1672 
1673 int VDP::LineInFrameInfo::calc(const EmuTime& time) const
1674 {
1675  return vdp.getTicksThisFrame(time) / VDP::TICKS_PER_LINE;
1676 }
1677 
1678 
1679 // class CycleInLineInfo
1680 
1681 VDP::CycleInLineInfo::CycleInLineInfo(VDP& vdp_)
1682  : Info(vdp_, "cycle_in_line",
1683  "The number of VDP cycles since the beginning of "
1684  "the current line. See also 'cycle_in_frame'."
1685  "Note that this includes the cycles in the border, "
1686  "use 'msx_x256_pos' or 'msx_x512_pos' to get MSX "
1687  "coordinates.")
1688 {
1689 }
1690 
1691 int VDP::CycleInLineInfo::calc(const EmuTime& time) const
1692 {
1693  return vdp.getTicksThisFrame(time) % VDP::TICKS_PER_LINE;
1694 }
1695 
1696 
1697 // class MsxYPosInfo
1698 
1699 VDP::MsxYPosInfo::MsxYPosInfo(VDP& vdp_)
1700  : Info(vdp_, "msx_y_pos",
1701  "Similar to 'line_in_frame', but expressed in MSX "
1702  "coordinates. So lines in the top border have "
1703  "negative coordinates, lines in the bottom border "
1704  "have coordinates bigger or equal to 192 or 212.")
1705 {
1706 }
1707 
1708 int VDP::MsxYPosInfo::calc(const EmuTime& time) const
1709 {
1710  return (vdp.getTicksThisFrame(time) / VDP::TICKS_PER_LINE) -
1711  vdp.getLineZero();
1712 }
1713 
1714 
1715 // class MsxX256PosInfo
1716 
1717 VDP::MsxX256PosInfo::MsxX256PosInfo(VDP& vdp_)
1718  : Info(vdp_, "msx_x256_pos",
1719  "Similar to 'cycle_in_frame', but expressed in MSX "
1720  "coordinates. So a position in the left border has "
1721  "a negative coordinate and a position in the right "
1722  "border has a coordinated bigger or equal to 256. "
1723  "See also 'msx_x512_pos'.")
1724 {
1725 }
1726 
1727 int VDP::MsxX256PosInfo::calc(const EmuTime& time) const
1728 {
1729  return ((vdp.getTicksThisFrame(time) % VDP::TICKS_PER_LINE) -
1730  vdp.getLeftSprites()) / 4;
1731 }
1732 
1733 
1734 // class MsxX512PosInfo
1735 
1736 VDP::MsxX512PosInfo::MsxX512PosInfo(VDP& vdp_)
1737  : Info(vdp_, "msx_x512_pos",
1738  "Similar to 'cycle_in_frame', but expressed in "
1739  "'narrow' (screen 7) MSX coordinates. So a position "
1740  "in the left border has a negative coordinate and "
1741  "a position in the right border has a coordinated "
1742  "bigger or equal to 512. See also 'msx_x256_pos'.")
1743 {
1744 }
1745 
1746 int VDP::MsxX512PosInfo::calc(const EmuTime& time) const
1747 {
1748  return ((vdp.getTicksThisFrame(time) % VDP::TICKS_PER_LINE) -
1749  vdp.getLeftSprites()) / 2;
1750 }
1751 
1752 
1753 // version 1: initial version
1754 // version 2: added frameCount
1755 // version 3: removed verticalAdjust
1756 // version 4: removed lineZero
1757 // version 5: replace readAhead->cpuVramData, added cpuVramReqIsRead
1758 // version 6: added cpuVramReqAddr to solve too_fast_vram_access issue
1759 // version 7: removed cpuVramReqAddr again, fixed issue in a different way
1760 // version 8: removed 'userData' from Schedulable
1761 template<typename Archive>
1762 void VDP::serialize(Archive& ar, unsigned serVersion)
1763 {
1764  ar.template serializeBase<MSXDevice>(*this);
1765 
1766  if (ar.versionAtLeast(serVersion, 8)) {
1767  ar.serialize("syncVSync", syncVSync);
1768  ar.serialize("syncDisplayStart", syncDisplayStart);
1769  ar.serialize("syncVScan", syncVScan);
1770  ar.serialize("syncHScan", syncHScan);
1771  ar.serialize("syncHorAdjust", syncHorAdjust);
1772  ar.serialize("syncSetMode", syncSetMode);
1773  ar.serialize("syncSetBlank", syncSetBlank);
1774  ar.serialize("syncCpuVramAccess", syncCpuVramAccess);
1775  } else {
1777  {&syncVSync, &syncDisplayStart, &syncVScan,
1778  &syncHScan, &syncHorAdjust, &syncSetMode,
1779  &syncSetBlank, &syncCpuVramAccess});
1780  }
1781 
1782  // not serialized
1783  // std::unique_ptr<Renderer> renderer;
1784  // VdpVersion version;
1785  // int controlRegMask;
1786  // byte controlValueMasks[32];
1787  // bool warningPrinted;
1788 
1789  ar.serialize("irqVertical", irqVertical);
1790  ar.serialize("irqHorizontal", irqHorizontal);
1791  ar.serialize("frameStartTime", frameStartTime);
1792  ar.serialize("displayStartSyncTime", displayStartSyncTime);
1793  ar.serialize("vScanSyncTime", vScanSyncTime);
1794  ar.serialize("hScanSyncTime", hScanSyncTime);
1795  ar.serialize("displayStart", displayStart);
1796  ar.serialize("horizontalScanOffset", horizontalScanOffset);
1797  ar.serialize("horizontalAdjust", horizontalAdjust);
1798  ar.serialize("registers", controlRegs);
1799  ar.serialize("blinkCount", blinkCount);
1800  ar.serialize("vramPointer", vramPointer);
1801  ar.serialize("palette", palette);
1802  ar.serialize("isDisplayArea", isDisplayArea);
1803  ar.serialize("palTiming", palTiming);
1804  ar.serialize("interlaced", interlaced);
1805  ar.serialize("statusReg0", statusReg0);
1806  ar.serialize("statusReg1", statusReg1);
1807  ar.serialize("statusReg2", statusReg2);
1808  ar.serialize("blinkState", blinkState);
1809  ar.serialize("dataLatch", dataLatch);
1810  ar.serialize("registerDataStored", registerDataStored);
1811  ar.serialize("paletteDataStored", paletteDataStored);
1812  if (ar.versionAtLeast(serVersion, 5)) {
1813  ar.serialize("cpuVramData", cpuVramData);
1814  ar.serialize("cpuVramReqIsRead", cpuVramReqIsRead);
1815  } else {
1816  ar.serialize("readAhead", cpuVramData);
1817  }
1818  ar.serialize("cpuExtendedVram", cpuExtendedVram);
1819  ar.serialize("displayEnabled", displayEnabled);
1820  byte mode = displayMode.getByte();
1821  ar.serialize("displayMode", mode);
1822  displayMode.setByte(mode);
1823 
1824  ar.serialize("cmdEngine", *cmdEngine);
1825  ar.serialize("spriteChecker", *spriteChecker); // must come after displayMode
1826  ar.serialize("vram", *vram); // must come after controlRegs and after spriteChecker
1827  if (ar.isLoader()) {
1828  pendingCpuAccess = syncCpuVramAccess.pendingSyncPoint();
1829  update(tooFastAccess);
1830  }
1831 
1832  if (ar.versionAtLeast(serVersion, 2)) {
1833  ar.serialize("frameCount", frameCount);
1834  } else {
1835  assert(ar.isLoader());
1836  // We could estimate the frameCount (assume framerate was
1837  // constant the whole time). But I think it's better to have
1838  // an obviously wrong value than an almost correct value.
1839  frameCount = 0;
1840  }
1841 
1842  // externalVideo does not need serializing. It is set on load by the
1843  // external video source (e.g. PioneerLDControl).
1844  //
1845  // TODO should superimposing be serialized? It cannot be recalculated
1846  // from register values (it depends on the register values at the start
1847  // of this frame). But it will be correct at the start of the next
1848  // frame. Probably good enough.
1849 
1850  if (ar.isLoader()) {
1851  renderer->reInit();
1852  }
1853 }
1855 REGISTER_MSXDEVICE(VDP, "VDP");
1856 
1857 } // namespace openmsx
int getChildDataAsInt(string_view name, int defaultValue=0) const
Definition: DeviceConfig.cc:52
static const byte REG25_MASK
Bits of VDP register 25 that encode part of the display mode.
Definition: DisplayMode.hh:47
const std::string & getChildData(string_view name) const
Definition: DeviceConfig.cc:43
Calculator getCalculator(EmuTime::param frame, EmuTime::param time, EmuTime::param limit, const VDP &vdp)
When many calls to getAccessSlot() are needed, it&#39;s more efficient to instead use this function...
void powerUp(EmuTime::param time) override
This method is called when MSX is powered up.
Definition: VDP.cc:310
EmuTime::param getFrameStartTime() const
Definition: VDP.hh:382
static const int TICKS_PER_LINE
Number of VDP clock ticks per line.
Definition: VDP.hh:72
byte getByte() const
Get the dispay mode as a byte: YAE YJK M5..M1 combined.
Definition: DisplayMode.hh:102
#define unlikely(x)
Definition: likely.hh:15
CliComm & getCliComm() const
Definition: MSXDevice.cc:149
bool isV9938Mode() const
Was this mode introduced by the V9938?
Definition: DisplayMode.hh:123
void reset(EmuTime::param e)
Reset the clock to start ticking at the given time.
Definition: Clock.hh:102
const XMLElement * findChild(string_view name) const
Definition: DeviceConfig.cc:61
Definition: span.hh:34
uint8_t byte
8 bit unsigned integer
Definition: openmsx.hh:26
TclObject execute()
Definition: TclCallback.cc:40
DisplayMode getDisplayMode() const
Get the display mode the VDP is in.
Definition: VDP.hh:141
EmuTime getAccessSlot(EmuTime::param frame_, EmuTime::param time, Delta delta, const VDP &vdp)
Return the time of the next available access slot that is at least &#39;delta&#39; cycles later than &#39;time&#39;...
RenderSettings & getRenderSettings()
Definition: Display.hh:44
bool isVDPwithVRAMremapping() const
Does this VDP have VRAM remapping when switching from 4k to 8/16k mode?
Definition: VDP.hh:121
virtual std::string getName() const
Returns a human-readable name for this device.
Definition: MSXDevice.cc:374
Represents a VDP display mode.
Definition: DisplayMode.hh:14
VDPAccessSlots::Calculator getAccessSlotCalculator(EmuTime::param time, EmuTime::param limit) const
Same as getAccessSlot(), but it can be much faster for repeated calls, e.g.
Definition: VDP.cc:846
REGISTER_MSXDEVICE(ChakkariCopy, "ChakkariCopy")
bool isTextMode() const
Is the current mode a text mode? Text1 and Text2 are text modes.
Definition: DisplayMode.hh:131
void writeIO(word port, byte value, EmuTime::param time) override
Write a byte to a given IO port at a certain time to this device.
Definition: VDP.cc:623
void serialize(Archive &ar, unsigned version)
Definition: VDP.cc:1762
void fill(ForwardRange &&range, const T &value)
Definition: ranges.hh:179
byte readIO(word port, EmuTime::param time) override
Read a byte from an IO port at a certain time from this device.
Definition: VDP.cc:935
const float TMS9XXXA_ANALOG_OUTPUT[16][3]
Definition: VDP.cc:1444
int getSpriteMode(bool isMSX1) const
Get the sprite mode of this display mode.
Definition: DisplayMode.hh:170
static void restoreOld(Archive &ar, std::vector< Schedulable *> schedulables)
Definition: Schedulable.hh:77
void attach(Observer< T > &observer)
Definition: Subject.hh:45
PostProcessor * getPostProcessor() const
Used by Video9000 to be able to couple the VDP and V9990 output.
Definition: VDP.cc:241
EmuTime getAccessSlot(EmuTime::param time, VDPAccessSlots::Delta delta) const
Get the earliest access slot that is at least &#39;delta&#39; cycles in the future.
Definition: VDP.cc:840
bool isVDPwithPALonly() const
Is this a VDP only capable of PAL?
Definition: VDP.hh:100
A video frame as output by the VDP scanline conversion unit, before any postprocessing filters are ap...
Definition: RawFrame.hh:25
InfoCommand & getMachineInfoCommand()
unique_ptr< Renderer > createRenderer(VDP &vdp, Display &display)
Create the Renderer selected by the current renderer setting.
void detach(VideoSystemChangeListener &listener)
Definition: Display.cc:142
bool vdpLacksMirroring() const
Is this a VDP that lacks mirroring?
Definition: VDP.hh:107
static const byte REG1_MASK
Bits of VDP register 1 that encode part of the display mode.
Definition: DisplayMode.hh:44
bool isPlanar() const
Is VRAM "planar" in the current display mode? Graphic 6 and 7 spread their bytes over two VRAM ICs...
Definition: DisplayMode.hh:152
bool vdpHasPatColMirroring() const
Is this a VDP that has pattern/colortable mirroring?
Definition: VDP.hh:114
int getTicksPerFrame() const
Gets the number of VDP clockticks (21MHz) per frame.
Definition: VDP.hh:407
EmuTime::param getCurrentTime() const
Definition: MSXDevice.cc:133
EmuTime waitCycles(EmuTime::param time, unsigned cycles)
Definition: MSXCPU.cc:191
void setByte(byte mode_)
Used for de-serialization.
Definition: DisplayMode.hh:107
static const byte REG0_MASK
Bits of VDP register 0 that encode part of the display mode.
Definition: DisplayMode.hh:41
bool isInsideFrame(EmuTime::param time) const
Is the given timestamp inside the current frame? Mainly useful for debugging, because relevant timest...
Definition: VDP.hh:420
bool isMSX1VDP() const
Is this an MSX1 VDP?
Definition: VDP.hh:93
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
bool getState() const
Get the interrupt state.
Definition: IRQHelper.hh:116
An MSXDevice is an emulated hardware component connected to the bus of the emulated MSX...
Definition: MSXDevice.hh:31
MSXMotherBoard & getMotherBoard() const
Get the mother board this device belongs to.
Definition: MSXDevice.cc:77
VDP(const DeviceConfig &config)
Definition: VDP.cc:64
void reset()
Reset the interrupt request on the bus.
Definition: IRQHelper.hh:96
VDP-VRAM access slot calculator, meant to be used in the inner loops of the VDPCmdEngine commands...
uint16_t word
16 bit unsigned integer
Definition: openmsx.hh:29
CommandController & getCommandController() const
Definition: MSXDevice.cc:157
void attach(VideoSystemChangeListener &listener)
Definition: Display.cc:136
void set()
Set the interrupt request on the bus.
Definition: IRQHelper.hh:87
Unified implementation of MSX Video Display Processors (VDPs).
Definition: VDP.hh:61
Abstract base class for post processors.
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:840
string getName(KeyCode keyCode)
Translate key code to key name.
Definition: Keys.cc:589
std::array< std::array< uint8_t, 3 >, 16 > getMSX1Palette() const
Get the (fixed) palette for this MSX1 VDP.
Definition: VDP.cc:1464
void enterMainLoop()
Definition: Reactor.cc:477
~VDP() override
Definition: VDP.cc:215
#define likely(x)
Definition: likely.hh:14
byte peekIO(word port, EmuTime::param time) const override
Read a byte from a given IO port.
Definition: VDP.cc:955
void detach(Observer< T > &observer)
Definition: Subject.hh:51
int getTicksThisFrame(EmuTime::param time) const
Gets the number of VDP clock ticks (21MHz) elapsed between a given time and the start of this frame...
Definition: VDP.hh:378
uint8_t clipIntToByte(int x)
Clip x to range [0,255].
Definition: Math.hh:66
std::string strCat(Ts &&...ts)
Definition: strCat.hh:577
const XMLElement & getDeviceConfig() const
Get the configuration section for this device.
Definition: MSXDevice.hh:218
byte getBase() const
Get the base dispay mode as an integer: M5..M1 combined.
Definition: DisplayMode.hh:116
bool getCmdBit() const
Are commands possible in non Graphic modes? (V9958 only)
Definition: VDP.hh:401
bool isBitmapMode() const
Is the current mode a bitmap mode? Graphic4 and higher are bitmap modes.
Definition: DisplayMode.hh:142
#define OUTER(type, member)
Definition: outer.hh:38
bool isDisplayEnabled() const
Is the display enabled? Both the regular border and forced blanking by clearing the display enable bi...
Definition: VDP.hh:237
void reset()
Bring the display mode to its initial state.
Definition: DisplayMode.hh:82
EmuTime::param getTime() const
Gets the time at which the last clock tick occurred.
Definition: Clock.hh:46
int getRightBorder() const
Gets the number of VDP clockticks between start of line and the start of the right border...
Definition: VDP.hh:457
void printWarning(string_view message)
Definition: CliComm.cc:20
void reset(EmuTime::param time) override
This method is called on reset.
Definition: VDP.cc:316
Reactor & getReactor() const
Definition: MSXDevice.cc:153
#define UNREACHABLE
Definition: unreachable.hh:38