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  doAccess = true;
832  } else if (likely(vram->getSize() == 192 * 1024)) {
833  addr = 0x20000 | (addr & 0xFFFF);
834  doAccess = true;
835  } else {
836  doAccess = false;
837  }
838  if (doAccess) {
839  if (cpuVramReqIsRead) {
840  cpuVramData = vram->cpuRead(addr, time);
841  } else {
842  vram->cpuWrite(addr, cpuVramData, time);
843  }
844  } else {
845  if (cpuVramReqIsRead) {
846  cpuVramData = 0xFF;
847  } else {
848  // nothing
849  }
850  }
851 
852  vramPointer = (vramPointer + 1) & 0x3FFF;
853  if (vramPointer == 0 && displayMode.isV9938Mode()) {
854  // In MSX2 video modes, pointer range is 128K.
855  controlRegs[14] = (controlRegs[14] + 1) & 0x07;
856  }
857 }
858 
859 EmuTime VDP::getAccessSlot(EmuTime::param time, VDPAccessSlots::Delta delta) const
860 {
862  getFrameStartTime(), time, delta, *this);
863 }
864 
866  EmuTime::param time, EmuTime::param limit) const
867 {
869  getFrameStartTime(), time, limit, *this);
870 }
871 
872 byte VDP::peekStatusReg(byte reg, EmuTime::param time) const
873 {
874  switch (reg) {
875  case 0:
876  spriteChecker->sync(time);
877  return statusReg0;
878  case 1:
879  if (controlRegs[0] & 0x10) { // line int enabled
880  return statusReg1 | (irqHorizontal.getState() ? 1:0);
881  } else { // line int disabled
882  // FH goes up at the start of the right border of IL and
883  // goes down at the start of the next left border.
884  // TODO: Precalc matchLength?
885  int afterMatch =
886  getTicksThisFrame(time) - horizontalScanOffset;
887  if (afterMatch < 0) {
888  afterMatch += getTicksPerFrame();
889  // afterMatch can still be negative at this
890  // point, see scheduleHScan()
891  }
892  int matchLength = (displayMode.isTextMode() ? 87 : 59)
893  + 27 + 100 + 102;
894  return statusReg1 |
895  (0 <= afterMatch && afterMatch < matchLength);
896  }
897  case 2: {
898  // TODO: Once VDP keeps display/blanking state, keeping
899  // VR is probably part of that, so use it.
900  // --> Is isDisplayArea actually !VR?
901  int ticksThisFrame = getTicksThisFrame(time);
902  int displayEnd =
903  displayStart + getNumberOfLines() * TICKS_PER_LINE;
904  bool vr = ticksThisFrame < displayStart - TICKS_PER_LINE
905  || ticksThisFrame >= displayEnd;
906  return statusReg2
907  | (getHR(ticksThisFrame) ? 0x20 : 0x00)
908  | (vr ? 0x40 : 0x00)
909  | cmdEngine->getStatus(time);
910  }
911  case 3:
912  return byte(spriteChecker->getCollisionX(time));
913  case 4:
914  return byte(spriteChecker->getCollisionX(time) >> 8) | 0xFE;
915  case 5:
916  return byte(spriteChecker->getCollisionY(time));
917  case 6:
918  return byte(spriteChecker->getCollisionY(time) >> 8) | 0xFC;
919  case 7:
920  return cmdEngine->readColor(time);
921  case 8:
922  return byte(cmdEngine->getBorderX(time));
923  case 9:
924  return byte(cmdEngine->getBorderX(time) >> 8) | 0xFE;
925  default: // non-existent status register
926  return 0xFF;
927  }
928 }
929 
930 byte VDP::readStatusReg(byte reg, EmuTime::param time)
931 {
932  byte ret = peekStatusReg(reg, time);
933  switch (reg) {
934  case 0:
935  spriteChecker->resetStatus();
936  statusReg0 &= ~0x80;
937  irqVertical.reset();
938  break;
939  case 1:
940  if (controlRegs[0] & 0x10) { // line int enabled
941  irqHorizontal.reset();
942  }
943  break;
944  case 5:
945  spriteChecker->resetCollision();
946  break;
947  case 7:
948  cmdEngine->resetColor();
949  break;
950  }
951  return ret;
952 }
953 
954 byte VDP::readIO(word port, EmuTime::param time)
955 {
956  assert(isInsideFrame(time));
957 
958  registerDataStored = false; // Abort any port #1 writes in progress.
959 
960  switch (port & (isMSX1VDP() ? 0x01 : 0x03)) {
961  case 0: // VRAM data read
962  return vramRead(time);
963  case 1: // Status register read
964  // Calculate status register contents.
965  return readStatusReg(controlRegs[15], time);
966  case 2:
967  case 3:
968  return 0xFF;
969  default:
970  UNREACHABLE; return 0xFF;
971  }
972 }
973 
974 byte VDP::peekIO(word /*port*/, EmuTime::param /*time*/) const
975 {
976  // TODO not implemented
977  return 0xFF;
978 }
979 
980 void VDP::changeRegister(byte reg, byte val, EmuTime::param time)
981 {
982  if (reg >= 32) {
983  // MXC belongs to CPU interface;
984  // other bits in this register belong to command engine.
985  if (reg == 45) {
986  cpuExtendedVram = (val & 0x40) != 0;
987  }
988  // Pass command register writes to command engine.
989  if (reg < 47) {
990  cmdEngine->setCmdReg(reg - 32, val, time);
991  }
992  return;
993  }
994 
995  // Make sure only bits that actually exist are written.
996  val &= controlValueMasks[reg];
997  // Determine the difference between new and old value.
998  byte change = val ^ controlRegs[reg];
999 
1000  // Register 13 is special because writing it resets blinking state,
1001  // even if the value in the register doesn't change.
1002  if (reg == 13) {
1003  // Switch to ON state unless ON period is zero.
1004  if (blinkState == ((val & 0xF0) == 0)) {
1005  renderer->updateBlinkState(!blinkState, time);
1006  blinkState = !blinkState;
1007  }
1008 
1009  if ((val & 0xF0) && (val & 0x0F)) {
1010  // Alternating colors, start with ON.
1011  blinkCount = (val >> 4) * 10;
1012  } else {
1013  // Stable color.
1014  blinkCount = 0;
1015  }
1016  // TODO when 'isFastBlinkEnabled()==true' the variables
1017  // 'blinkState' and 'blinkCount' represent the values at line 0.
1018  // This implementation is not correct for the partial remaining
1019  // frame after register 13 got changed.
1020  }
1021 
1022  if (!change) return;
1023 
1024  // Perform additional tasks before new value becomes active.
1025  switch (reg) {
1026  case 0:
1027  if (change & DisplayMode::REG0_MASK) {
1028  syncAtNextLine(syncSetMode, time);
1029  }
1030  break;
1031  case 1:
1032  if (change & 0x03) {
1033  // Update sprites on size and mag changes.
1034  spriteChecker->updateSpriteSizeMag(val, time);
1035  }
1036  // TODO: Reset vertical IRQ if IE0 is reset?
1037  if (change & DisplayMode::REG1_MASK) {
1038  syncAtNextLine(syncSetMode, time);
1039  }
1040  if (change & 0x40) {
1041  syncAtNextLine(syncSetBlank, time);
1042  }
1043  break;
1044  case 2: {
1045  int base = (val << 10) | ~(~0u << 10);
1046  // TODO:
1047  // I reverted this fix.
1048  // Although the code is correct, there is also a counterpart in the
1049  // renderer that must be updated. I'm too tired now to find it.
1050  // Since name table checking is currently disabled anyway, keeping the
1051  // old code does not hurt.
1052  // Eventually this line should be re-enabled.
1053  /*
1054  if (displayMode.isPlanar()) {
1055  base = ((base << 16) | (base >> 1)) & 0x1FFFF;
1056  }
1057  */
1058  renderer->updateNameBase(base, time);
1059  break;
1060  }
1061  case 7:
1063  if (change & 0xF0) {
1064  renderer->updateForegroundColor(val >> 4, time);
1065  }
1066  if (change & 0x0F) {
1067  renderer->updateBackgroundColor(val & 0x0F, time);
1068  }
1069  } else {
1070  renderer->updateBackgroundColor(val, time);
1071  }
1072  break;
1073  case 8:
1074  if (change & 0x20) {
1075  renderer->updateTransparency((val & 0x20) == 0, time);
1076  spriteChecker->updateTransparency((val & 0x20) == 0, time);
1077  }
1078  if (change & 0x02) {
1079  vram->updateSpritesEnabled((val & 0x02) == 0, time);
1080  }
1081  if (change & 0x08) {
1082  vram->updateVRMode((val & 0x08) != 0, time);
1083  }
1084  break;
1085  case 12:
1086  if (change & 0xF0) {
1087  renderer->updateBlinkForegroundColor(val >> 4, time);
1088  }
1089  if (change & 0x0F) {
1090  renderer->updateBlinkBackgroundColor(val & 0x0F, time);
1091  }
1092  break;
1093  case 16:
1094  // Any half-finished palette loads are aborted.
1095  paletteDataStored = false;
1096  break;
1097  case 18:
1098  if (change & 0x0F) {
1099  syncAtNextLine(syncHorAdjust, time);
1100  }
1101  break;
1102  case 23:
1103  spriteChecker->updateVerticalScroll(val, time);
1104  renderer->updateVerticalScroll(val, time);
1105  break;
1106  case 25:
1107  if (change & (DisplayMode::REG25_MASK | 0x40)) {
1108  updateDisplayMode(getDisplayMode().updateReg25(val),
1109  val & 0x40,
1110  time);
1111  }
1112  if (change & 0x08) {
1113  syncAtNextLine(syncHorAdjust, time);
1114  }
1115  if (change & 0x02) {
1116  renderer->updateBorderMask((val & 0x02) != 0, time);
1117  }
1118  if (change & 0x01) {
1119  renderer->updateMultiPage((val & 0x01) != 0, time);
1120  }
1121  break;
1122  case 26:
1123  renderer->updateHorizontalScrollHigh(val, time);
1124  break;
1125  case 27:
1126  renderer->updateHorizontalScrollLow(val, time);
1127  break;
1128  }
1129 
1130  // Commit the change.
1131  controlRegs[reg] = val;
1132 
1133  // Perform additional tasks after new value became active.
1134  // Because base masks cannot be read from the VDP, updating them after
1135  // the commit is equivalent to updating before.
1136  switch (reg) {
1137  case 0:
1138  if (change & 0x10) { // IE1
1139  if (val & 0x10) {
1140  scheduleHScan(time);
1141  } else {
1142  irqHorizontal.reset();
1143  }
1144  }
1145  break;
1146  case 1:
1147  if (change & 0x20) { // IE0
1148  if (val & 0x20) {
1149  // This behaviour is important. Without it,
1150  // the intro music in 'Andonis' is way too slow
1151  // and the intro logo of 'Zanac' is corrupted.
1152  if (statusReg0 & 0x80) {
1153  irqVertical.set();
1154  }
1155  } else {
1156  irqVertical.reset();
1157  }
1158  }
1159  if ((change & 0x80) && isVDPwithVRAMremapping()) {
1160  // confirmed: VRAM remapping only happens on TMS99xx
1161  // see VDPVRAM for details on the remapping itself
1162  vram->change4k8kMapping((val & 0x80) != 0);
1163  }
1164  break;
1165  case 2:
1166  updateNameBase(time);
1167  break;
1168  case 3:
1169  case 10:
1170  updateColorBase(time);
1171  if (vdpHasPatColMirroring()) updatePatternBase(time);
1172  break;
1173  case 4:
1174  updatePatternBase(time);
1175  break;
1176  case 5:
1177  case 11:
1178  updateSpriteAttributeBase(time);
1179  break;
1180  case 6:
1181  updateSpritePatternBase(time);
1182  break;
1183  case 9:
1184  if ((val & 1) && ! warningPrinted) {
1185  warningPrinted = true;
1187  "The running MSX software has set bit 0 of VDP register 9 "
1188  "(dot clock direction) to one. In an ordinary MSX, "
1189  "the screen would go black and the CPU would stop running.");
1190  // TODO: Emulate such behaviour.
1191  }
1192  if (change & 0x80) {
1193  /*
1194  cerr << "changed to " << (val & 0x80 ? 212 : 192) << " lines"
1195  << " at line " << (getTicksThisFrame(time) / TICKS_PER_LINE) << "\n";
1196  */
1197  // Display lines (192/212) determines display start and end.
1198  // TODO: Find out exactly when display start is fixed.
1199  // If it is fixed at VSYNC that would simplify things,
1200  // but I think it's more likely the current
1201  // implementation is accurate.
1202  if (time < displayStartSyncTime) {
1203  // Display start is not fixed yet.
1204  scheduleDisplayStart(time);
1205  } else {
1206  // Display start is fixed, but display end is not.
1207  scheduleVScan(time);
1208  }
1209  }
1210  break;
1211  case 19:
1212  case 23:
1213  scheduleHScan(time);
1214  break;
1215  case 25:
1216  if (change & 0x01) {
1217  updateNameBase(time);
1218  }
1219  break;
1220  }
1221 }
1222 
1223 void VDP::syncAtNextLine(SyncBase& type, EmuTime::param time)
1224 {
1225  int line = getTicksThisFrame(time) / TICKS_PER_LINE;
1226  int ticks = (line + 1) * TICKS_PER_LINE;
1227  EmuTime nextTime = frameStartTime + ticks;
1228  type.setSyncPoint(nextTime);
1229 }
1230 
1231 void VDP::updateNameBase(EmuTime::param time)
1232 {
1233  int base = (controlRegs[2] << 10) | ~(~0u << 10);
1234  // TODO:
1235  // I reverted this fix.
1236  // Although the code is correct, there is also a counterpart in the
1237  // renderer that must be updated. I'm too tired now to find it.
1238  // Since name table checking is currently disabled anyway, keeping the
1239  // old code does not hurt.
1240  // Eventually this line should be re-enabled.
1241  /*
1242  if (displayMode.isPlanar()) {
1243  base = ((base << 16) | (base >> 1)) & 0x1FFFF;
1244  }
1245  */
1246  int indexMask =
1247  displayMode.isBitmapMode()
1248  ? ~0u << 17 // TODO: Calculate actual value; how to handle planar?
1249  : ~0u << (displayMode.isTextMode() ? 12 : 10);
1250  if (controlRegs[25] & 0x01) {
1251  // Multi page scrolling. The same bit is used in character and
1252  // (non)planar-bitmap modes.
1253  // TODO test text modes
1254  indexMask &= ~0x8000;
1255  }
1256  vram->nameTable.setMask(base, indexMask, time);
1257 }
1258 
1259 void VDP::updateColorBase(EmuTime::param time)
1260 {
1261  int base = (controlRegs[10] << 14) | (controlRegs[3] << 6) | ~(~0u << 6);
1262  renderer->updateColorBase(base, time);
1263  switch (displayMode.getBase()) {
1264  case 0x09: // Text 2.
1265  // TODO: Enable this only if dual color is actually active.
1266  vram->colorTable.setMask(base, ~0u << 9, time);
1267  break;
1268  case 0x00: // Graphic 1.
1269  vram->colorTable.setMask(base, ~0u << 6, time);
1270  break;
1271  case 0x04: // Graphic 2.
1272  vram->colorTable.setMask(base | (vdpLacksMirroring() ? 0x1800 : 0), ~0u << 13, time);
1273  break;
1274  case 0x08: // Graphic 3.
1275  vram->colorTable.setMask(base, ~0u << 13, time);
1276  break;
1277  default:
1278  // Other display modes do not use a color table.
1279  vram->colorTable.disable(time);
1280  }
1281 }
1282 
1283 void VDP::updatePatternBase(EmuTime::param time)
1284 {
1285  int base = (controlRegs[4] << 11) | ~(~0u << 11);
1286  renderer->updatePatternBase(base, time);
1287  switch (displayMode.getBase()) {
1288  case 0x01: // Text 1.
1289  case 0x05: // Text 1 Q.
1290  case 0x09: // Text 2.
1291  case 0x00: // Graphic 1.
1292  case 0x02: // Multicolor.
1293  case 0x06: // Multicolor Q.
1294  vram->patternTable.setMask(base, ~0u << 11, time);
1295  break;
1296  case 0x04: // Graphic 2.
1297  if (vdpHasPatColMirroring()) {
1298  // TMS99XX has weird pattern table behavior: some
1299  // bits of the color-base register leak into the
1300  // pattern-base. See also:
1301  // http://www.youtube.com/watch?v=XJljSJqzDR0
1302  base = (controlRegs[4] << 11)
1303  | ((controlRegs[3] & 0x1f) << 6)
1304  | ~(~0u << 6);
1305  }
1306  vram->patternTable.setMask(base | (vdpLacksMirroring() ? 0x1800 : 0), ~0u << 13, time);
1307  break;
1308  case 0x08: // Graphic 3.
1309  vram->patternTable.setMask(base, ~0u << 13, time);
1310  break;
1311  default:
1312  // Other display modes do not use a pattern table.
1313  vram->patternTable.disable(time);
1314  }
1315 }
1316 
1317 void VDP::updateSpriteAttributeBase(EmuTime::param time)
1318 {
1319  int mode = displayMode.getSpriteMode(isMSX1VDP());
1320  if (mode == 0) {
1321  vram->spriteAttribTable.disable(time);
1322  return;
1323  }
1324  int baseMask = (controlRegs[11] << 15) | (controlRegs[5] << 7) | ~(~0u << 7);
1325  int indexMask = mode == 1 ? ~0u << 7 : ~0u << 10;
1326  if (displayMode.isPlanar()) {
1327  baseMask = ((baseMask << 16) | (baseMask >> 1)) & 0x1FFFF;
1328  indexMask = ((indexMask << 16) | ~(1 << 16)) & (indexMask >> 1);
1329  }
1330  vram->spriteAttribTable.setMask(baseMask, indexMask, time);
1331 }
1332 
1333 void VDP::updateSpritePatternBase(EmuTime::param time)
1334 {
1335  if (displayMode.getSpriteMode(isMSX1VDP()) == 0) {
1336  vram->spritePatternTable.disable(time);
1337  return;
1338  }
1339  int baseMask = (controlRegs[6] << 11) | ~(~0u << 11);
1340  int indexMask = ~0u << 11;
1341  if (displayMode.isPlanar()) {
1342  baseMask = ((baseMask << 16) | (baseMask >> 1)) & 0x1FFFF;
1343  indexMask = ((indexMask << 16) | ~(1 << 16)) & (indexMask >> 1);
1344  }
1345  vram->spritePatternTable.setMask(baseMask, indexMask, time);
1346 }
1347 
1348 void VDP::updateDisplayMode(DisplayMode newMode, bool cmdBit, EmuTime::param time)
1349 {
1350  // Synchronise subsystems.
1351  vram->updateDisplayMode(newMode, cmdBit, time);
1352 
1353  // TODO: Is this a useful optimisation, or doesn't it help
1354  // in practice?
1355  // What aspects have changed:
1356  // Switched from planar to nonplanar or vice versa.
1357  bool planarChange =
1358  newMode.isPlanar() != displayMode.isPlanar();
1359  // Sprite mode changed.
1360  bool msx1 = isMSX1VDP();
1361  bool spriteModeChange =
1362  newMode.getSpriteMode(msx1) != displayMode.getSpriteMode(msx1);
1363 
1364  // Commit the new display mode.
1365  displayMode = newMode;
1366 
1367  // Speed up performance of bitmap/character mode splits:
1368  // leave last used character mode active.
1369  // TODO: Disable it if not used for some time.
1370  if (!displayMode.isBitmapMode()) {
1371  updateColorBase(time);
1372  updatePatternBase(time);
1373  }
1374  if (planarChange || spriteModeChange) {
1375  updateSpritePatternBase(time);
1376  updateSpriteAttributeBase(time);
1377  }
1378  updateNameBase(time);
1379 
1380  // To be extremely accurate, reschedule hscan when changing
1381  // from/to text mode. Text mode has different border width,
1382  // which affects the moment hscan occurs.
1383  // TODO: Why didn't I implement this yet?
1384  // It's one line of code and overhead is not huge either.
1385 }
1386 
1387 void VDP::update(const Setting& setting)
1388 {
1389  assert(&setting == one_of(&cmdTiming, &tooFastAccess));
1390  (void)setting;
1391  brokenCmdTiming = cmdTiming .getEnum();
1392  allowTooFastAccess = tooFastAccess.getEnum();
1393 
1394  if (unlikely(allowTooFastAccess && pendingCpuAccess)) {
1395  // in allowTooFastAccess-mode, don't schedule CPU-VRAM access
1396  syncCpuVramAccess.removeSyncPoint();
1397  pendingCpuAccess = false;
1398  executeCpuVramAccess(getCurrentTime());
1399  }
1400 }
1401 
1402 /*
1403  * Roughly measured RGB values in volts.
1404  * Voltages were in range of 1.12-5.04, and had 2 digits accuracy (it seems
1405  * minimum difference was 0.04 V).
1406  * Blue component of color 5 and red component of color 9 were higher than
1407  * the components for white... There are several methods to handle this...
1408  * 1) clip to values of white
1409  * 2) scale all colors by min/max of that component (means white is not 3x 255)
1410  * 3) scale per color if components for that color are beyond those of white
1411  * 4) assume the analog values are output by a DA converter, derive the digital
1412  * values and scale that to the range 0-255 (thanks to FRS for this idea).
1413  * This also results in white not being 3x 255, of course.
1414  *
1415  * Method 4 results in the table below and seems the most accurate (so far).
1416  *
1417  * Thanks to Tiago Valença and Carlos Mansur for measuring on a T7937A.
1418  */
1419 constexpr std::array<std::array<uint8_t, 3>, 16> TOSHIBA_PALETTE = {{
1420  { 0, 0, 0 },
1421  { 0, 0, 0 },
1422  { 102, 204, 102 },
1423  { 136, 238, 136 },
1424  { 68, 68, 221 },
1425  { 119, 119, 255 },
1426  { 187, 85, 85 },
1427  { 119, 221, 221 },
1428  { 221, 102, 102 },
1429  { 255, 119, 119 },
1430  { 204, 204, 85 },
1431  { 238, 238, 136 },
1432  { 85, 170, 85 },
1433  { 187, 85, 187 },
1434  { 204, 204, 204 },
1435  { 238, 238, 238 },
1436 }};
1437 
1438 /*
1439  * Palette for the YM2220 is a crude approximantion based on the fact that the
1440  * pictures of a Yamaha AX-150 (YM2220) and a Philips NMS-8250 (V9938) have a
1441  * quite similar appearance. See first post here:
1442  *
1443  * https://www.msx.org/forum/msx-talk/hardware/unknown-vdp-yamaha-ym2220?page=3
1444  */
1445 constexpr std::array<std::array<uint8_t, 3>, 16> YM2220_PALETTE = {{
1446  { 0, 0, 0 },
1447  { 0, 0, 0 },
1448  { 36, 218, 36 },
1449  { 200, 255, 109 },
1450  { 36, 36, 255 },
1451  { 72, 109, 255 },
1452  { 182, 36, 36 },
1453  { 72, 218, 255 },
1454  { 255, 36, 36 },
1455  { 255, 175, 175 },
1456  { 230, 230, 0 },
1457  { 230, 230, 200 },
1458  { 36, 195, 36 },
1459  { 218, 72, 182 },
1460  { 182, 182, 182 },
1461  { 255, 255, 255 },
1462 }};
1463 
1464 /*
1465 How come the FM-X has a distinct palette while it clearly has a TMS9928 VDP?
1466 Because it has an additional circuit that rework the palette for the same one
1467 used in the Fujitsu FM-7. It's encoded in 3-bit RGB.
1468 
1469 This seems to be the 24-bit RGB equivalent to the palette output by the FM-X on
1470 its RGB conector:
1471 */
1472 constexpr std::array<std::array<uint8_t, 3>, 16> THREE_BIT_RGB_PALETTE = {{
1473  { 0, 0, 0 },
1474  { 0, 0, 0 },
1475  { 0, 255, 0 },
1476  { 0, 255, 0 },
1477  { 0, 0, 255 },
1478  { 0, 0, 255 },
1479  { 255, 0, 0 },
1480  { 0, 255, 255 },
1481  { 255, 0, 0 },
1482  { 255, 0, 0 },
1483  { 255, 255, 0 },
1484  { 255, 255, 0 },
1485  { 0, 255, 0 },
1486  { 255, 0, 255 },
1487  { 255, 255, 255 },
1488  { 255, 255, 255 },
1489 }};
1490 
1491 // Source: TMS9918/28/29 Data Book, page 2-17.
1492 
1493 constexpr float TMS9XXXA_ANALOG_OUTPUT[16][3] = {
1494  // Y R-Y B-Y voltages
1495  { 0.00f, 0.47f, 0.47f },
1496  { 0.00f, 0.47f, 0.47f },
1497  { 0.53f, 0.07f, 0.20f },
1498  { 0.67f, 0.17f, 0.27f },
1499  { 0.40f, 0.40f, 1.00f },
1500  { 0.53f, 0.43f, 0.93f },
1501  { 0.47f, 0.83f, 0.30f },
1502  { 0.73f, 0.00f, 0.70f },
1503  { 0.53f, 0.93f, 0.27f },
1504  { 0.67f, 0.93f, 0.27f },
1505  { 0.73f, 0.57f, 0.07f },
1506  { 0.80f, 0.57f, 0.17f },
1507  { 0.47f, 0.13f, 0.23f },
1508  { 0.53f, 0.73f, 0.67f },
1509  { 0.80f, 0.47f, 0.47f },
1510  { 1.00f, 0.47f, 0.47f },
1511 };
1512 
1513 std::array<std::array<uint8_t, 3>, 16> VDP::getMSX1Palette() const
1514 {
1515  assert(isMSX1VDP());
1516  if (MSXDevice::getDeviceConfig().findChild("3bitrgboutput") != nullptr) {
1517  return THREE_BIT_RGB_PALETTE;
1518  }
1519  if ((version & VM_TOSHIBA_PALETTE) != 0) {
1520  return TOSHIBA_PALETTE;
1521  }
1522  if ((version & VM_YM2220_PALETTE) != 0) {
1523  return YM2220_PALETTE;
1524  }
1525  std::array<std::array<uint8_t, 3>, 16> tmsPalette;
1526  for (int color = 0; color < 16; color++) {
1527  // convert from analog output to YPbPr
1528  float Y = TMS9XXXA_ANALOG_OUTPUT[color][0];
1529  float Pr = TMS9XXXA_ANALOG_OUTPUT[color][1] - 0.5f;
1530  float Pb = TMS9XXXA_ANALOG_OUTPUT[color][2] - 0.5f;
1531  // apply the saturation
1532  Pr *= (saturationPr / 100.0f);
1533  Pb *= (saturationPb / 100.0f);
1534  // convert to RGB as follows:
1535  /*
1536  |R| | 1 0 1.402 | |Y |
1537  |G| = | 1 -0.344 -0.714 | x |Pb|
1538  |B| | 1 1.722 0 | |Pr|
1539  */
1540  float R = Y + 0 + 1.402f * Pr;
1541  float G = Y - 0.344f * Pb - 0.714f * Pr;
1542  float B = Y + 1.722f * Pb + 0;
1543  // blow up with factor of 255
1544  R *= 255;
1545  G *= 255;
1546  B *= 255;
1547  // the final result is that these values
1548  // are clipped in the [0:255] range.
1549  // Note: Using roundf instead of std::round because libstdc++ when
1550  // built on top of uClibc lacks std::round; uClibc does provide
1551  // roundf, but lacks other C99 math functions and that makes
1552  // libstdc++ disable all wrappers for C99 math functions.
1553  tmsPalette[color][0] = Math::clipIntToByte(roundf(R));
1554  tmsPalette[color][1] = Math::clipIntToByte(roundf(G));
1555  tmsPalette[color][2] = Math::clipIntToByte(roundf(B));
1556  // std::cerr << color << " " << int(tmsPalette[color][0]) << " " << int(tmsPalette[color][1]) <<" " << int(tmsPalette[color][2]) << '\n';
1557  }
1558  return tmsPalette;
1559 }
1560 
1561 // RegDebug
1562 
1563 VDP::RegDebug::RegDebug(VDP& vdp_)
1564  : SimpleDebuggable(vdp_.getMotherBoard(),
1565  vdp_.getName() + " regs", "VDP registers.", 0x40)
1566 {
1567 }
1568 
1569 byte VDP::RegDebug::read(unsigned address)
1570 {
1571  auto& vdp = OUTER(VDP, vdpRegDebug);
1572  if (address < 0x20) {
1573  return vdp.controlRegs[address];
1574  } else if (address < 0x2F) {
1575  return vdp.cmdEngine->peekCmdReg(address - 0x20);
1576  } else {
1577  return 0xFF;
1578  }
1579 }
1580 
1581 void VDP::RegDebug::write(unsigned address, byte value, EmuTime::param time)
1582 {
1583  auto& vdp = OUTER(VDP, vdpRegDebug);
1584  // Ignore writes to registers >= 8 on MSX1. An alternative is to only
1585  // expose 8 registers. But changing that now breaks backwards
1586  // compatibilty with some existing scripts. E.g. script that queries
1587  // PAL vs NTSC in a VDP agnostic way.
1588  if ((address >= 8) && vdp.isMSX1VDP()) return;
1589  vdp.changeRegister(address, value, time);
1590 }
1591 
1592 
1593 // StatusRegDebug
1594 
1595 VDP::StatusRegDebug::StatusRegDebug(VDP& vdp_)
1596  : SimpleDebuggable(vdp_.getMotherBoard(),
1597  vdp_.getName() + " status regs", "VDP status registers.", 0x10)
1598 {
1599 }
1600 
1601 byte VDP::StatusRegDebug::read(unsigned address, EmuTime::param time)
1602 {
1603  auto& vdp = OUTER(VDP, vdpStatusRegDebug);
1604  return vdp.peekStatusReg(address, time);
1605 }
1606 
1607 
1608 // PaletteDebug
1609 
1610 VDP::PaletteDebug::PaletteDebug(VDP& vdp_)
1611  : SimpleDebuggable(vdp_.getMotherBoard(),
1612  vdp_.getName() + " palette", "V99x8 palette (RBG format)", 0x20)
1613 {
1614 }
1615 
1616 byte VDP::PaletteDebug::read(unsigned address)
1617 {
1618  auto& vdp = OUTER(VDP, vdpPaletteDebug);
1619  word grb = vdp.getPalette(address / 2);
1620  return (address & 1) ? (grb >> 8) : (grb & 0xff);
1621 }
1622 
1623 void VDP::PaletteDebug::write(unsigned address, byte value, EmuTime::param time)
1624 {
1625  auto& vdp = OUTER(VDP, vdpPaletteDebug);
1626  // Ignore writes on MSX1. An alternative could be to not expose the
1627  // palette at all, but allowing read-only access could be useful for
1628  // some scripts.
1629  if (vdp.isMSX1VDP()) return;
1630 
1631  int index = address / 2;
1632  word grb = vdp.getPalette(index);
1633  grb = (address & 1)
1634  ? (grb & 0x0077) | ((value & 0x07) << 8)
1635  : (grb & 0x0700) | (value & 0x77);
1636  vdp.setPalette(index, grb, time);
1637 }
1638 
1639 
1640 // class VRAMPointerDebug
1641 
1642 VDP::VRAMPointerDebug::VRAMPointerDebug(VDP& vdp_)
1643  : SimpleDebuggable(vdp_.getMotherBoard(), vdp_.getName() == "VDP" ?
1644  "VRAM pointer" : vdp_.getName() + " VRAM pointer",
1645  "VDP VRAM pointer (14 lower bits)", 2)
1646 {
1647 }
1648 
1649 byte VDP::VRAMPointerDebug::read(unsigned address)
1650 {
1651  auto& vdp = OUTER(VDP, vramPointerDebug);
1652  if (address & 1) {
1653  return vdp.vramPointer >> 8; // TODO add read/write mode?
1654  } else {
1655  return vdp.vramPointer & 0xFF;
1656  }
1657 }
1658 
1659 void VDP::VRAMPointerDebug::write(unsigned address, byte value, EmuTime::param /*time*/)
1660 {
1661  auto& vdp = OUTER(VDP, vramPointerDebug);
1662  int& ptr = vdp.vramPointer;
1663  if (address & 1) {
1664  ptr = (ptr & 0x00FF) | ((value & 0x3F) << 8);
1665  } else {
1666  ptr = (ptr & 0xFF00) | value;
1667  }
1668 }
1669 
1670 
1671 // class Info
1672 
1673 VDP::Info::Info(VDP& vdp_, const string& name_, string helpText_)
1674  : InfoTopic(vdp_.getMotherBoard().getMachineInfoCommand(),
1675  strCat(vdp_.getName(), '_', name_))
1676  , vdp(vdp_)
1677  , helpText(std::move(helpText_))
1678 {
1679 }
1680 
1681 void VDP::Info::execute(span<const TclObject> /*tokens*/, TclObject& result) const
1682 {
1683  result = calc(vdp.getCurrentTime());
1684 }
1685 
1686 string VDP::Info::help(const vector<string>& /*tokens*/) const
1687 {
1688  return helpText;
1689 }
1690 
1691 
1692 // class FrameCountInfo
1693 
1694 VDP::FrameCountInfo::FrameCountInfo(VDP& vdp_)
1695  : Info(vdp_, "frame_count",
1696  "The current frame number, starts counting at 0 "
1697  "when MSX is powered up or reset.")
1698 {
1699 }
1700 
1701 int VDP::FrameCountInfo::calc(const EmuTime& /*time*/) const
1702 {
1703  return vdp.frameCount;
1704 }
1705 
1706 
1707 // class CycleInFrameInfo
1708 
1709 VDP::CycleInFrameInfo::CycleInFrameInfo(VDP& vdp_)
1710  : Info(vdp_, "cycle_in_frame",
1711  "The number of VDP cycles since the beginning of "
1712  "the current frame. The VDP runs at 6 times the Z80 "
1713  "clock frequency, so at approximately 21.5MHz.")
1714 {
1715 }
1716 
1717 int VDP::CycleInFrameInfo::calc(const EmuTime& time) const
1718 {
1719  return vdp.getTicksThisFrame(time);
1720 }
1721 
1722 
1723 // class LineInFrameInfo
1724 
1725 VDP::LineInFrameInfo::LineInFrameInfo(VDP& vdp_)
1726  : Info(vdp_, "line_in_frame",
1727  "The absolute line number since the beginning of "
1728  "the current frame. Goes from 0 till 262 (NTSC) or "
1729  "313 (PAL). Note that this number includes the "
1730  "border lines, use 'msx_y_pos' to get MSX "
1731  "coordinates.")
1732 {
1733 }
1734 
1735 int VDP::LineInFrameInfo::calc(const EmuTime& time) const
1736 {
1737  return vdp.getTicksThisFrame(time) / VDP::TICKS_PER_LINE;
1738 }
1739 
1740 
1741 // class CycleInLineInfo
1742 
1743 VDP::CycleInLineInfo::CycleInLineInfo(VDP& vdp_)
1744  : Info(vdp_, "cycle_in_line",
1745  "The number of VDP cycles since the beginning of "
1746  "the current line. See also 'cycle_in_frame'."
1747  "Note that this includes the cycles in the border, "
1748  "use 'msx_x256_pos' or 'msx_x512_pos' to get MSX "
1749  "coordinates.")
1750 {
1751 }
1752 
1753 int VDP::CycleInLineInfo::calc(const EmuTime& time) const
1754 {
1755  return vdp.getTicksThisFrame(time) % VDP::TICKS_PER_LINE;
1756 }
1757 
1758 
1759 // class MsxYPosInfo
1760 
1761 VDP::MsxYPosInfo::MsxYPosInfo(VDP& vdp_)
1762  : Info(vdp_, "msx_y_pos",
1763  "Similar to 'line_in_frame', but expressed in MSX "
1764  "coordinates. So lines in the top border have "
1765  "negative coordinates, lines in the bottom border "
1766  "have coordinates bigger or equal to 192 or 212.")
1767 {
1768 }
1769 
1770 int VDP::MsxYPosInfo::calc(const EmuTime& time) const
1771 {
1772  return (vdp.getTicksThisFrame(time) / VDP::TICKS_PER_LINE) -
1773  vdp.getLineZero();
1774 }
1775 
1776 
1777 // class MsxX256PosInfo
1778 
1779 VDP::MsxX256PosInfo::MsxX256PosInfo(VDP& vdp_)
1780  : Info(vdp_, "msx_x256_pos",
1781  "Similar to 'cycle_in_frame', but expressed in MSX "
1782  "coordinates. So a position in the left border has "
1783  "a negative coordinate and a position in the right "
1784  "border has a coordinated bigger or equal to 256. "
1785  "See also 'msx_x512_pos'.")
1786 {
1787 }
1788 
1789 int VDP::MsxX256PosInfo::calc(const EmuTime& time) const
1790 {
1791  return ((vdp.getTicksThisFrame(time) % VDP::TICKS_PER_LINE) -
1792  vdp.getLeftSprites()) / 4;
1793 }
1794 
1795 
1796 // class MsxX512PosInfo
1797 
1798 VDP::MsxX512PosInfo::MsxX512PosInfo(VDP& vdp_)
1799  : Info(vdp_, "msx_x512_pos",
1800  "Similar to 'cycle_in_frame', but expressed in "
1801  "'narrow' (screen 7) MSX coordinates. So a position "
1802  "in the left border has a negative coordinate and "
1803  "a position in the right border has a coordinated "
1804  "bigger or equal to 512. See also 'msx_x256_pos'.")
1805 {
1806 }
1807 
1808 int VDP::MsxX512PosInfo::calc(const EmuTime& time) const
1809 {
1810  return ((vdp.getTicksThisFrame(time) % VDP::TICKS_PER_LINE) -
1811  vdp.getLeftSprites()) / 2;
1812 }
1813 
1814 
1815 // version 1: initial version
1816 // version 2: added frameCount
1817 // version 3: removed verticalAdjust
1818 // version 4: removed lineZero
1819 // version 5: replace readAhead->cpuVramData, added cpuVramReqIsRead
1820 // version 6: added cpuVramReqAddr to solve too_fast_vram_access issue
1821 // version 7: removed cpuVramReqAddr again, fixed issue in a different way
1822 // version 8: removed 'userData' from Schedulable
1823 template<typename Archive>
1824 void VDP::serialize(Archive& ar, unsigned serVersion)
1825 {
1826  ar.template serializeBase<MSXDevice>(*this);
1827 
1828  if (ar.versionAtLeast(serVersion, 8)) {
1829  ar.serialize("syncVSync", syncVSync,
1830  "syncDisplayStart", syncDisplayStart,
1831  "syncVScan", syncVScan,
1832  "syncHScan", syncHScan,
1833  "syncHorAdjust", syncHorAdjust,
1834  "syncSetMode", syncSetMode,
1835  "syncSetBlank", syncSetBlank,
1836  "syncCpuVramAccess", syncCpuVramAccess);
1837  // no need for syncCmdDone (only used for probe)
1838  } else {
1840  {&syncVSync, &syncDisplayStart, &syncVScan,
1841  &syncHScan, &syncHorAdjust, &syncSetMode,
1842  &syncSetBlank, &syncCpuVramAccess});
1843  }
1844 
1845  // not serialized
1846  // std::unique_ptr<Renderer> renderer;
1847  // VdpVersion version;
1848  // int controlRegMask;
1849  // byte controlValueMasks[32];
1850  // bool warningPrinted;
1851 
1852  ar.serialize("irqVertical", irqVertical,
1853  "irqHorizontal", irqHorizontal,
1854  "frameStartTime", frameStartTime,
1855  "displayStartSyncTime", displayStartSyncTime,
1856  "vScanSyncTime", vScanSyncTime,
1857  "hScanSyncTime", hScanSyncTime,
1858  "displayStart", displayStart,
1859  "horizontalScanOffset", horizontalScanOffset,
1860  "horizontalAdjust", horizontalAdjust,
1861  "registers", controlRegs,
1862  "blinkCount", blinkCount,
1863  "vramPointer", vramPointer,
1864  "palette", palette,
1865  "isDisplayArea", isDisplayArea,
1866  "palTiming", palTiming,
1867  "interlaced", interlaced,
1868  "statusReg0", statusReg0,
1869  "statusReg1", statusReg1,
1870  "statusReg2", statusReg2,
1871  "blinkState", blinkState,
1872  "dataLatch", dataLatch,
1873  "registerDataStored", registerDataStored,
1874  "paletteDataStored", paletteDataStored);
1875  if (ar.versionAtLeast(serVersion, 5)) {
1876  ar.serialize("cpuVramData", cpuVramData,
1877  "cpuVramReqIsRead", cpuVramReqIsRead);
1878  } else {
1879  ar.serialize("readAhead", cpuVramData);
1880  }
1881  ar.serialize("cpuExtendedVram", cpuExtendedVram,
1882  "displayEnabled", displayEnabled);
1883  byte mode = displayMode.getByte();
1884  ar.serialize("displayMode", mode);
1885  displayMode.setByte(mode);
1886 
1887  ar.serialize("cmdEngine", *cmdEngine,
1888  "spriteChecker", *spriteChecker, // must come after displayMode
1889  "vram", *vram); // must come after controlRegs and after spriteChecker
1890  if (ar.isLoader()) {
1891  pendingCpuAccess = syncCpuVramAccess.pendingSyncPoint();
1892  update(tooFastAccess);
1893  }
1894 
1895  if (ar.versionAtLeast(serVersion, 2)) {
1896  ar.serialize("frameCount", frameCount);
1897  } else {
1898  assert(ar.isLoader());
1899  // We could estimate the frameCount (assume framerate was
1900  // constant the whole time). But I think it's better to have
1901  // an obviously wrong value than an almost correct value.
1902  frameCount = 0;
1903  }
1904 
1905  // externalVideo does not need serializing. It is set on load by the
1906  // external video source (e.g. PioneerLDControl).
1907  //
1908  // TODO should superimposing be serialized? It cannot be recalculated
1909  // from register values (it depends on the register values at the start
1910  // of this frame). But it will be correct at the start of the next
1911  // frame. Probably good enough.
1912 
1913  if (ar.isLoader()) {
1914  renderer->reInit();
1915  }
1916 }
1919 
1920 } // 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:954
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:1493
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:327
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:348
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:1513
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:865
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:34
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:740
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:1472
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:120
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:1917
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:859
openmsx::TOSHIBA_PALETTE
constexpr std::array< std::array< uint8_t, 3 >, 16 > TOSHIBA_PALETTE
Definition: VDP.cc:1419
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:1445
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:974
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:1824
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