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