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