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