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