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