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