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