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 "RendererFactory.hh"
26 #include "Renderer.hh"
27 #include "SimpleDebuggable.hh"
28 #include "InfoTopic.hh"
29 #include "TclObject.hh"
30 #include "MSXMotherBoard.hh"
31 #include "Reactor.hh"
32 #include "MSXException.hh"
33 #include "CliComm.hh"
34 #include "StringOp.hh"
35 #include "array_ref.hh"
36 #include "unreachable.hh"
37 #include "memory.hh"
38 #include <algorithm>
39 #include <cstring>
40 #include <cassert>
41 
42 using std::string;
43 using std::vector;
44 
45 namespace openmsx {
46 
48 {
49 public:
50  explicit VDPRegDebug(VDP& vdp);
51  virtual byte read(unsigned address);
52  virtual void write(unsigned address, byte value, EmuTime::param time);
53 private:
54  VDP& vdp;
55 };
56 
58 {
59 public:
60  explicit VDPStatusRegDebug(VDP& vdp);
61  virtual byte read(unsigned address, EmuTime::param time);
62 private:
63  VDP& vdp;
64 };
65 
67 {
68 public:
69  explicit VDPPaletteDebug(VDP& vdp);
70  virtual byte read(unsigned address);
71  virtual void write(unsigned address, byte value, EmuTime::param time);
72 private:
73  VDP& vdp;
74 };
75 
77 {
78 public:
79  explicit VRAMPointerDebug(VDP& vdp);
80  virtual byte read(unsigned address);
81  virtual void write(unsigned address, byte value, EmuTime::param time);
82 private:
83  VDP& vdp;
84 };
85 
86 
87 class VDPInfo : public InfoTopic
88 {
89 public:
90  virtual void execute(const vector<TclObject>& /*tokens*/,
91  TclObject& result) const
92  {
93  const Schedulable& schedulable = vdp; // resolve ambiguity
94  result.setInt(calc(schedulable.getCurrentTime()));
95  }
96  virtual string help(const vector<string>& /*tokens*/) const
97  {
98  return helpText;
99  }
100  virtual int calc(const EmuTime& time) const = 0;
101 
102 protected:
103  VDPInfo(VDP& vdp_, const string& name, const string& helpText_)
104  : InfoTopic(vdp_.getMotherBoard().getMachineInfoCommand(),
105  vdp_.getName() + '_' + name)
106  , vdp(vdp_)
107  , helpText(helpText_) {}
108 
110  const string helpText;
111 };
112 
113 class FrameCountInfo : public VDPInfo
114 {
115 public:
117  : VDPInfo(vdp, "frame_count",
118  "The current frame number, starts counting at 0 "
119  "when MSX is powered up or reset.") {}
120  virtual int calc(const EmuTime& /*time*/) const
121  {
122  return vdp.frameCount;
123  }
124 };
125 
126 class CycleInFrameInfo : public VDPInfo
127 {
128 public:
130  : VDPInfo(vdp, "cycle_in_frame",
131  "The number of VDP cycles since the beginning of "
132  "the current frame. The VDP runs at 6 times the Z80 "
133  "clock frequency, so at approximately 21.5MHz.") {}
134  virtual int calc(const EmuTime& time) const
135  {
136  return vdp.getTicksThisFrame(time);
137  }
138 };
139 
140 class LineInFrameInfo : public VDPInfo
141 {
142 public:
144  : VDPInfo(vdp, "line_in_frame",
145  "The absolute line number since the beginning of "
146  "the current frame. Goes from 0 till 262 (NTSC) or "
147  "313 (PAL). Note that this number includes the "
148  "border lines, use 'msx_y_pos' to get MSX "
149  "coordinates.") {}
150  virtual int calc(const EmuTime& time) const
151  {
153  }
154 };
155 
156 class CycleInLineInfo : public VDPInfo
157 {
158 public:
160  : VDPInfo(vdp, "cycle_in_line",
161  "The number of VDP cycles since the beginning of "
162  "the current line. See also 'cycle_in_frame'."
163  "Note that this includes the cycles in the border, "
164  "use 'msx_x256_pos' or 'msx_x512_pos' to get MSX "
165  "coordinates.") {}
166  virtual int calc(const EmuTime& time) const
167  {
169  }
170 };
171 
172 class MsxYPosInfo : public VDPInfo
173 {
174 public:
176  : VDPInfo(vdp, "msx_y_pos",
177  "Similar to 'line_in_frame', but expressed in MSX "
178  "coordinates. So lines in the top border have "
179  "negative coordinates, lines in the bottom border "
180  "have coordinates bigger or equal to 192 or 212.") {}
181  virtual int calc(const EmuTime& time) const
182  {
183  return (vdp.getTicksThisFrame(time) / VDP::TICKS_PER_LINE) -
184  vdp.getLineZero();
185  }
186 };
187 
188 class MsxX256PosInfo : public VDPInfo
189 {
190 public:
192  : VDPInfo(vdp, "msx_x256_pos",
193  "Similar to 'cycle_in_frame', but expressed in MSX "
194  "coordinates. So a position in the left border has "
195  "a negative coordinate and a position in the right "
196  "border has a coordinated bigger or equal to 256. "
197  "See also 'msx_x512_pos'.") {}
198  virtual int calc(const EmuTime& time) const
199  {
200  return ((vdp.getTicksThisFrame(time) % VDP::TICKS_PER_LINE) -
201  vdp.getLeftSprites()) / 4;
202  }
203 };
204 
205 class MsxX512PosInfo : public VDPInfo
206 {
207 public:
209  : VDPInfo(vdp, "msx_x512_pos",
210  "Similar to 'cycle_in_frame', but expressed in "
211  "'narrow' (screen 7) MSX coordinates. So a position "
212  "in the left border has a negative coordinate and "
213  "a position in the right border has a coordinated "
214  "bigger or equal to 512. See also 'msx_x256_pos'.") {}
215  virtual int calc(const EmuTime& time) const
216  {
217  return ((vdp.getTicksThisFrame(time) % VDP::TICKS_PER_LINE) -
218  vdp.getLeftSprites()) / 2;
219  }
220 };
221 
222 
223 
224 VDP::VDP(const DeviceConfig& config)
225  : MSXDevice(config)
226  , Schedulable(MSXDevice::getScheduler())
227  , display(getReactor().getDisplay())
228  , vdpRegDebug (make_unique<VDPRegDebug> (*this))
229  , vdpStatusRegDebug(make_unique<VDPStatusRegDebug>(*this))
230  , vdpPaletteDebug (make_unique<VDPPaletteDebug> (*this))
231  , vramPointerDebug (make_unique<VRAMPointerDebug> (*this))
232  , frameCountInfo (make_unique<FrameCountInfo> (*this))
233  , cycleInFrameInfo (make_unique<CycleInFrameInfo> (*this))
234  , lineInFrameInfo (make_unique<LineInFrameInfo> (*this))
235  , cycleInLineInfo (make_unique<CycleInLineInfo> (*this))
236  , msxYPosInfo (make_unique<MsxYPosInfo> (*this))
237  , msxX256PosInfo (make_unique<MsxX256PosInfo> (*this))
238  , msxX512PosInfo (make_unique<MsxX512PosInfo> (*this))
239  , frameStartTime(Schedulable::getCurrentTime())
240  , irqVertical (getMotherBoard(), getName() + ".IRQvertical")
241  , irqHorizontal(getMotherBoard(), getName() + ".IRQhorizontal")
242  , displayStartSyncTime(Schedulable::getCurrentTime())
243  , vScanSyncTime(Schedulable::getCurrentTime())
244  , hScanSyncTime(Schedulable::getCurrentTime())
245  , warningPrinted(false)
246 {
247  interlaced = false;
248 
249  std::string versionString = config.getChildData("version");
250  if (versionString == "TMS99X8A") version = TMS99X8A;
251  else if (versionString == "TMS9929A") version = TMS9929A;
252  else if (versionString == "V9938") version = V9938;
253  else if (versionString == "V9958") version = V9958;
254  else throw MSXException("Unknown VDP version \"" + versionString + "\"");
255 
256  // Set up control register availability.
257  static const byte VALUE_MASKS_MSX1[32] = {
258  0x03, 0xFB, 0x0F, 0xFF, 0x07, 0x7F, 0x07, 0xFF // 00..07
259  };
260  static const byte VALUE_MASKS_MSX2[32] = {
261  0x7E, 0x7B, 0x7F, 0xFF, 0x3F, 0xFF, 0x3F, 0xFF, // 00..07
262  0xFB, 0xBF, 0x07, 0x03, 0xFF, 0xFF, 0x07, 0x0F, // 08..15
263  0x0F, 0xBF, 0xFF, 0xFF, 0x3F, 0x3F, 0x3F, 0xFF, // 16..23
264  0, 0, 0, 0, 0, 0, 0, 0, // 24..31
265  };
266  controlRegMask = (isMSX1VDP() ? 0x07 : 0x3F);
267  memcpy(controlValueMasks,
268  isMSX1VDP() ? VALUE_MASKS_MSX1 : VALUE_MASKS_MSX2,
269  sizeof(controlValueMasks));
270  if (version == V9958) {
271  // Enable V9958-specific control registers.
272  controlValueMasks[25] = 0x7F;
273  controlValueMasks[26] = 0x3F;
274  controlValueMasks[27] = 0x07;
275  }
276 
277  resetInit(); // must be done early to avoid UMRs
278 
279  // Video RAM.
281  unsigned vramSize =
282  (isMSX1VDP() ? 16 : config.getChildDataAsInt("vram"));
283  if ((vramSize != 16) && (vramSize != 64) &&
284  (vramSize != 128) && (vramSize != 192)) {
286  "VRAM size of " << vramSize << "kB is not supported!");
287  }
288  vram = make_unique<VDPVRAM>(*this, vramSize * 1024, time);
289 
290  RenderSettings& renderSettings = display.getRenderSettings();
291 
292  // Create sprite checker.
293  spriteChecker = make_unique<SpriteChecker>(*this, renderSettings, time);
294  vram->setSpriteChecker(spriteChecker.get());
295 
296  // Create command engine.
297  cmdEngine = make_unique<VDPCmdEngine>(
298  *this, renderSettings, getCommandController());
299  vram->setCmdEngine(cmdEngine.get());
300 
301  // Initialise renderer.
302  createRenderer();
303 
304  // Reset state.
305  powerUp(time);
306 
307  display.attach(*this);
308 }
309 
311 {
312  display.detach(*this);
313 }
314 
315 void VDP::preVideoSystemChange()
316 {
317  renderer.reset();
318 }
319 
320 void VDP::postVideoSystemChange()
321 {
322  createRenderer();
323 }
324 
325 void VDP::createRenderer()
326 {
327  renderer = RendererFactory::createRenderer(*this, display);
328  // TODO: Is it safe to use frameStartTime,
329  // which is most likely in the past?
330  //renderer->reset(frameStartTime.getTime());
331  vram->setRenderer(renderer.get(), frameStartTime.getTime());
332 }
333 
335 {
336  return renderer->getPostProcessor();
337 }
338 
339 void VDP::resetInit()
340 {
341  // note: vram, spriteChecker, cmdEngine, renderer may not yet be
342  // created at this point
343  for (int i = 0; i < 32; i++) {
344  controlRegs[i] = 0;
345  }
346  if (version == TMS9929A) {
347  // Boots (and remains) in PAL mode, all other VDPs boot in NTSC.
348  controlRegs[9] |= 0x02;
349  }
350  // According to page 6 of the V9938 data book the color burst registers
351  // are loaded with these values at power on.
352  controlRegs[21] = 0x3B;
353  controlRegs[22] = 0x05;
354  // Note: frameStart is the actual place palTiming is written, but it
355  // can be read before frameStart is called.
356  // TODO: Clean up initialisation sequence.
357  palTiming = true; // controlRegs[9] & 0x02;
358  displayMode.reset();
359  vramPointer = 0;
360  cpuVramData = 0;
361  dataLatch = 0;
362  cpuExtendedVram = false;
363  registerDataStored = false;
364  paletteDataStored = false;
365  blinkState = false;
366  blinkCount = 0;
367  horizontalAdjust = 7;
368 
369  // TODO: Real VDP probably resets timing as well.
370  isDisplayArea = false;
371  displayEnabled = false;
372  superimposing = nullptr;
373  externalVideo = nullptr;
374 
375  // Init status registers.
376  statusReg0 = 0x00;
377  statusReg1 = (version == V9958 ? 0x04 : 0x00);
378  statusReg2 = 0x0C;
379 
380  // Update IRQ to reflect new register values.
381  irqVertical.reset();
382  irqHorizontal.reset();
383 
384  // From appendix 8 of the V9938 data book (page 148).
385  const word V9938_PALETTE[16] = {
386  0x000, 0x000, 0x611, 0x733, 0x117, 0x327, 0x151, 0x627,
387  0x171, 0x373, 0x661, 0x664, 0x411, 0x265, 0x555, 0x777
388  };
389  // Init the palette.
390  memcpy(palette, V9938_PALETTE, sizeof(V9938_PALETTE));
391 }
392 
393 void VDP::resetMasks(EmuTime::param time)
394 {
395  updateNameBase(time);
396  updateColorBase(time);
397  updatePatternBase(time);
398  updateSpriteAttributeBase(time);
399  updateSpritePatternBase(time);
400  // TODO: It is not clear to me yet how bitmapWindow should be used.
401  // Currently it always spans 128K of VRAM.
402  //vram->bitmapWindow.setMask(~(-1 << 17), -1 << 17, time);
403 }
404 
406 {
407  vram->clear();
408  reset(time);
409 }
410 
412 {
413  removeSyncPoint(VSYNC);
414  removeSyncPoint(DISPLAY_START);
415  removeSyncPoint(VSCAN);
416  removeSyncPoint(HSCAN);
417  removeSyncPoint(HOR_ADJUST);
418  removeSyncPoint(SET_MODE);
419  removeSyncPoint(SET_BLANK);
420  removeSyncPoint(CPU_VRAM_ACCESS);
421 
422  // Reset subsystems.
423  cmdEngine->sync(time);
424  resetInit();
425  spriteChecker->reset(time);
426  cmdEngine->reset(time);
427  renderer->reInit();
428 
429  // Tell the subsystems of the new mask values.
430  resetMasks(time);
431 
432  // Init scheduling.
433  frameCount = -1;
434  frameStart(time);
435  assert(frameCount == 0);
436 }
437 
438 void VDP::executeUntil(EmuTime::param time, int userData)
439 {
440  /*
441  PRT_DEBUG("Executing VDP at time " << time
442  << ", sync type " << userData);
443  */
444  /*
445  int ticksThisFrame = getTicksThisFrame(time);
446  cout << (userData == VSYNC ? "VSYNC" :
447  (userData == VSCAN ? "VSCAN" :
448  (userData == HSCAN ? "HSCAN" : "DISPLAY_START")))
449  << " at (" << (ticksThisFrame % TICKS_PER_LINE)
450  << ',' << ((ticksThisFrame - displayStart) / TICKS_PER_LINE)
451  << "), IRQ_H = " << (int)irqHorizontal.getState()
452  << " IRQ_V = " << (int)irqVertical.getState()
453  //<< ", frame = " << frameStartTime
454  << "\n";
455  */
456 
457  // Handle the various sync types.
458  switch (userData) {
459  case VSYNC:
460  // This frame is finished.
461  // Inform VDP subcomponents.
462  // TODO: Do this via VDPVRAM?
463  renderer->frameEnd(time);
464  spriteChecker->frameEnd(time);
465  // Start next frame.
466  frameStart(time);
467  break;
468  case DISPLAY_START:
469  // Display area starts here, unless we're doing overscan and it
470  // was already active.
471  if (!isDisplayArea) {
472  if (displayEnabled) {
473  vram->updateDisplayEnabled(true, time);
474  }
475  isDisplayArea = true;
476  }
477  break;
478  case VSCAN:
479  // VSCAN is the end of display.
480  // This will generate a VBLANK IRQ. Typically MSX software will
481  // poll the keyboard/joystick on this IRQ. So now is a good
482  // time to also poll for host events.
483  getReactor().pollNow();
484 
485  if (isDisplayEnabled()) {
486  vram->updateDisplayEnabled(false, time);
487  }
488  isDisplayArea = false;
489 
490  // Vertical scanning occurs.
491  statusReg0 |= 0x80;
492  if (controlRegs[1] & 0x20) {
493  irqVertical.set();
494  }
495  break;
496  case HSCAN:
497  // Horizontal scanning occurs.
498  if (controlRegs[0] & 0x10) {
499  irqHorizontal.set();
500  }
501  break;
502  case HOR_ADJUST: {
503  int newHorAdjust = (controlRegs[18] & 0x0F) ^ 0x07;
504  if (controlRegs[25] & 0x08) {
505  newHorAdjust += 4;
506  }
507  renderer->updateHorizontalAdjust(newHorAdjust, time);
508  horizontalAdjust = newHorAdjust;
509  break;
510  }
511  case SET_MODE:
512  updateDisplayMode(
513  DisplayMode(controlRegs[0], controlRegs[1], controlRegs[25]),
514  time);
515  break;
516  case SET_BLANK: {
517  bool newDisplayEnabled = (controlRegs[1] & 0x40) != 0;
518  if (isDisplayArea) {
519  vram->updateDisplayEnabled(newDisplayEnabled, time);
520  }
521  displayEnabled = newDisplayEnabled;
522  break;
523  }
524  case CPU_VRAM_ACCESS:
525  executeCpuVramAccess(time);
526  break;
527  default:
528  UNREACHABLE;
529  }
530 }
531 
532 // TODO: This approach assumes that an overscan-like approach can be used
533 // skip display start, so that the border is rendered instead.
534 // This makes sense, but it has not been tested on real MSX yet.
535 void VDP::scheduleDisplayStart(EmuTime::param time)
536 {
537  // Remove pending DISPLAY_START sync point, if any.
538  if (displayStartSyncTime > time) {
539  removeSyncPoint(DISPLAY_START);
540  //cerr << "removing predicted DISPLAY_START sync point\n";
541  }
542 
543  // Calculate when (lines and time) display starts.
544  int verticalAdjust = (controlRegs[18] >> 4) ^ 0x07;
545  int lineZero =
546  // sync + top erase:
547  3 + 13 +
548  // top border:
549  (palTiming ? 36 : 9) +
550  (controlRegs[9] & 0x80 ? 0 : 10) +
551  verticalAdjust;
552  displayStart =
553  lineZero * TICKS_PER_LINE
554  + 100 + 102; // VR flips at start of left border
555  displayStartSyncTime = frameStartTime + displayStart;
556  //cerr << "new DISPLAY_START is " << (displayStart / TICKS_PER_LINE) << "\n";
557 
558  // Register new DISPLAY_START sync point.
559  if (displayStartSyncTime > time) {
560  setSyncPoint(displayStartSyncTime, DISPLAY_START);
561  //cerr << "inserting new DISPLAY_START sync point\n";
562  }
563 
564  // HSCAN and VSCAN are relative to display start.
565  scheduleHScan(time);
566  scheduleVScan(time);
567 }
568 
569 void VDP::scheduleVScan(EmuTime::param time)
570 {
571  /*
572  cerr << "scheduleVScan @ " << (getTicksThisFrame(time) / TICKS_PER_LINE) << "\n";
573  if (vScanSyncTime < frameStartTime) {
574  cerr << "old VSCAN was previous frame\n";
575  } else {
576  cerr << "old VSCAN was " << (frameStartTime.getTicksTill(vScanSyncTime) / TICKS_PER_LINE) << "\n";
577  }
578  */
579 
580  // Remove pending VSCAN sync point, if any.
581  if (vScanSyncTime > time) {
582  removeSyncPoint(VSCAN);
583  //cerr << "removing predicted VSCAN sync point\n";
584  }
585 
586  // Calculate moment in time display end occurs.
587  vScanSyncTime = frameStartTime +
588  (displayStart + getNumberOfLines() * TICKS_PER_LINE);
589  //cerr << "new VSCAN is " << (frameStartTime.getTicksTill(vScanSyncTime) / TICKS_PER_LINE) << "\n";
590 
591  // Register new VSCAN sync point.
592  if (vScanSyncTime > time) {
593  setSyncPoint(vScanSyncTime, VSCAN);
594  //cerr << "inserting new VSCAN sync point\n";
595  }
596 }
597 
598 void VDP::scheduleHScan(EmuTime::param time)
599 {
600  // Remove pending HSCAN sync point, if any.
601  if (hScanSyncTime > time) {
602  removeSyncPoint(HSCAN);
603  hScanSyncTime = time;
604  }
605 
606  // Calculate moment in time line match occurs.
607  horizontalScanOffset = displayStart - (100 + 102)
608  + ((controlRegs[19] - controlRegs[23]) & 0xFF) * TICKS_PER_LINE
609  + getRightBorder();
610  // Display line counter continues into the next frame.
611  // Note that this implementation is not 100% accurate, since the
612  // number of ticks of the *previous* frame should be subtracted.
613  // By switching from NTSC to PAL it may even be possible to get two
614  // HSCANs in a single frame without modifying any other setting.
615  // Fortunately, no known program relies on this.
616  int ticksPerFrame = getTicksPerFrame();
617  if (horizontalScanOffset >= ticksPerFrame) {
618  horizontalScanOffset -= ticksPerFrame;
619  // Display line counter is reset at the start of the top border.
620  // Any HSCAN that has a higher line number never occurs.
621  if (horizontalScanOffset >= LINE_COUNT_RESET_TICKS) {
622  // This is one way to say "never".
623  horizontalScanOffset = -1000 * TICKS_PER_LINE;
624  }
625  }
626 
627  // Register new HSCAN sync point if interrupt is enabled.
628  if ((controlRegs[0] & 0x10) && horizontalScanOffset >= 0) {
629  // No line interrupt will occur after bottom erase.
630  // NOT TRUE: "after next top border start" is correct.
631  // Note that line interrupt can occur in the next frame.
632  /*
633  EmuTime bottomEraseTime =
634  frameStartTime + getTicksPerFrame() - 3 * TICKS_PER_LINE;
635  */
636  hScanSyncTime = frameStartTime + horizontalScanOffset;
637  if (hScanSyncTime > time) {
638  setSyncPoint(hScanSyncTime, HSCAN);
639  }
640  }
641 }
642 
643 // TODO: inline?
644 // TODO: Is it possible to get rid of this routine and its sync point?
645 // VSYNC, HSYNC and DISPLAY_START could be scheduled for the next
646 // frame when their callback occurs.
647 // But I'm not sure how to handle the PAL/NTSC setting (which also
648 // influences the frequency at which E/O toggles).
649 void VDP::frameStart(EmuTime::param time)
650 {
651  ++frameCount;
652 
653  //cerr << "VDP::frameStart @ " << time << "\n";
654 
655  // Toggle E/O.
656  // Actually this should occur half a line earlier,
657  // but for now this is accurate enough.
658  statusReg2 ^= 0x02;
659 
660  // Settings which are fixed at start of frame.
661  // Not sure this is how real MSX does it, but close enough for now.
662  // TODO: Interlace is effectuated in border height, according to
663  // the data book. Exactly when is the fixation point?
664  palTiming = (controlRegs[9] & 0x02) != 0;
665  interlaced = (controlRegs[9] & 0x08) != 0;
666 
667  // Blinking.
668  if (blinkCount != 0) { // counter active?
669  blinkCount--;
670  if (blinkCount == 0) {
671  renderer->updateBlinkState(!blinkState, time);
672  blinkState = !blinkState;
673  blinkCount = ( blinkState
674  ? controlRegs[13] >> 4 : controlRegs[13] & 0x0F ) * 10;
675  }
676  }
677 
678  // TODO: Presumably this is done here
679  // Note that if superimposing is enabled but no external video
680  // signal is provided then the VDP stops producing a signal
681  // (at least on an MSX1, VDP(0)=1 produces "signal lost" on my
682  // monitor)
683  const RawFrame* newSuperimposing = (controlRegs[0] & 1) ? externalVideo : nullptr;
684  if (superimposing != newSuperimposing) {
685  superimposing = newSuperimposing;
686  renderer->updateSuperimposing(superimposing, time);
687  }
688 
689  // Schedule next VSYNC.
690  frameStartTime.reset(time);
691  setSyncPoint(frameStartTime + getTicksPerFrame(), VSYNC);
692  // Schedule DISPLAY_START, VSCAN and HSCAN.
693  scheduleDisplayStart(time);
694 
695  // Inform VDP subcomponents.
696  // TODO: Do this via VDPVRAM?
697  renderer->frameStart(time);
698  spriteChecker->frameStart(time);
699 
700  /*
701  cout << "--> frameStart = " << frameStartTime
702  << ", frameEnd = " << (frameStartTime + getTicksPerFrame())
703  << ", hscan = " << hScanSyncTime
704  << ", displayStart = " << displayStart
705  << ", timing: " << (palTiming ? "PAL" : "NTSC")
706  << "\n";
707  */
708 }
709 
710 // The I/O functions.
711 
712 void VDP::writeIO(word port, byte value, EmuTime::param time)
713 {
714  assert(isInsideFrame(time));
715  switch (port & 0x03) {
716  case 0: // VRAM data write
717  vramWrite(value, time);
718  registerDataStored = false;
719  break;
720  case 1: // Register or address write
721  if (registerDataStored) {
722  if (value & 0x80) {
723  if (!(value & 0x40)) {
724  // Register write.
725  changeRegister(
726  value & controlRegMask,
727  dataLatch,
728  time
729  );
730  } else {
731  // TODO what happens in this case?
732  // it's not a register write because
733  // that breaks "SNOW26" demo
734  }
735  if (isMSX1VDP()) {
736  // For these VDP's the VRAM pointer is modified when
737  // writing to VDP registers. Without this some demos won't
738  // run as on real MSX1, e.g. Planet of the Epas, Utopia and
739  // Waves 1.2. Thanks to dvik for finding this out.
740  // See also below about not using the latch on MSX1.
741  // Set read/write address.
742  vramPointer = (value << 8 | (vramPointer & 0xFF)) & 0x3FFF;
743  }
744  } else {
745  // Set read/write address.
746  vramPointer = (value << 8 | dataLatch) & 0x3FFF;
747  if (!(value & 0x40)) {
748  // Read ahead.
749  vramRead(time);
750  }
751  }
752  registerDataStored = false;
753  } else {
754  // Note: on MSX1 there seems to be no
755  // latch used, but VDP address writes
756  // are done directly.
757  // Thanks to hap for finding this out. :)
758  if (isMSX1VDP()) {
759  vramPointer = (vramPointer & 0x3F00) | value;
760  }
761  dataLatch = value;
762  registerDataStored = true;
763  }
764  break;
765  case 2: // Palette data write
766  if (paletteDataStored) {
767  int index = controlRegs[16];
768  int grb = ((value << 8) | dataLatch) & 0x777;
769  setPalette(index, grb, time);
770  controlRegs[16] = (index + 1) & 0x0F;
771  paletteDataStored = false;
772  } else {
773  dataLatch = value;
774  paletteDataStored = true;
775  }
776  break;
777  case 3: { // Indirect register write
778  dataLatch = value;
779  // TODO: What happens if reg 17 is written indirectly?
780  //fprintf(stderr, "VDP indirect register write: %02X\n", value);
781  byte regNr = controlRegs[17];
782  changeRegister(regNr & 0x3F, value, time);
783  if ((regNr & 0x80) == 0) {
784  // Auto-increment.
785  controlRegs[17] = (regNr + 1) & 0x3F;
786  }
787  break;
788  }
789  }
790 }
791 
792 void VDP::setPalette(int index, word grb, EmuTime::param time)
793 {
794  if (palette[index] != grb) {
795  renderer->updatePalette(index, grb, time);
796  palette[index] = grb;
797  }
798 }
799 
800 void VDP::vramWrite(byte value, EmuTime::param time)
801 {
802  // Tested on real V9938: 'cpuVramData' is shared between read and write.
803  // E.g. OUT (#98),A followed by IN A,(#98) returns the just written value.
804  cpuVramData = value;
805  scheduleCpuVramAccess(false, time);
806 }
807 
808 byte VDP::vramRead(EmuTime::param time)
809 {
810  scheduleCpuVramAccess(true, time); // schedule next read
811  return cpuVramData; // this is the data from the previous read
812 }
813 
814 void VDP::scheduleCpuVramAccess(bool isRead, EmuTime::param time)
815 {
816  cpuVramReqIsRead = isRead;
817  if (unlikely(cpuAccessScheduled())) {
818  // Already scheduled. Do nothing.
819  // The old request has been overwritten by the new request!
820  } else {
821  setSyncPoint(getAccessSlot(time, 16), CPU_VRAM_ACCESS);
822  }
823 }
824 
825 void VDP::executeCpuVramAccess(EmuTime::param time)
826 {
827  int addr = (controlRegs[14] << 14) | vramPointer;
828  if (displayMode.isPlanar()) {
829  // note: also extended VRAM is interleaved,
830  // because there is only 64kB it's interleaved
831  // with itself (every byte repeated twice)
832  addr = ((addr << 16) | (addr >> 1)) & 0x1FFFF;
833  }
834 
835  bool doAccess;
836  if (likely(!cpuExtendedVram)) {
837  doAccess = true;
838  } else if (likely(vram->getSize() == 192 * 1024)) {
839  addr = 0x20000 | (addr & 0xFFFF);
840  doAccess = true;
841  } else {
842  doAccess = false;
843  }
844  if (doAccess) {
845  if (cpuVramReqIsRead) {
846  cpuVramData = vram->cpuRead(addr, time);
847  } else {
848  vram->cpuWrite(addr, cpuVramData, time);
849  }
850  } else {
851  if (cpuVramReqIsRead) {
852  cpuVramData = 0xFF;
853  } else {
854  // nothing
855  }
856  }
857 
858  vramPointer = (vramPointer + 1) & 0x3FFF;
859  if (vramPointer == 0 && displayMode.isV9938Mode()) {
860  // In MSX2 video modes, pointer range is 128K.
861  controlRegs[14] = (controlRegs[14] + 1) & 0x07;
862  }
863 }
864 
866 {
867  return pendingSyncPoint(CPU_VRAM_ACCESS);
868 }
869 
870 // TODO the following 3 tables are correct for bitmap screen modes,
871 // still need to investigate character and text modes.
872 // These tables must contain at least one value that is bigger or equal
873 // to 1368+136. So we extend the data with some cyclic duplicates.
874 static const unsigned screenOff[154 + 17] = {
875  0, 8, 16, 24, 32, 40, 48, 56, 64, 72,
876  80, 88, 96, 104, 112, 120, 164, 172, 180, 188,
877  196, 204, 212, 220, 228, 236, 244, 252, 260, 268,
878  276, 292, 300, 308, 316, 324, 332, 340, 348, 356,
879  364, 372, 380, 388, 396, 404, 420, 428, 436, 444,
880  452, 460, 468, 476, 484, 492, 500, 508, 516, 524,
881  532, 548, 556, 564, 572, 580, 588, 596, 604, 612,
882  620, 628, 636, 644, 652, 660, 676, 684, 692, 700,
883  708, 716, 724, 732, 740, 748, 756, 764, 772, 780,
884  788, 804, 812, 820, 828, 836, 844, 852, 860, 868,
885  876, 884, 892, 900, 908, 916, 932, 940, 948, 956,
886  964, 972, 980, 988, 996, 1004, 1012, 1020, 1028, 1036,
887  1044, 1060, 1068, 1076, 1084, 1092, 1100, 1108, 1116, 1124,
888  1132, 1140, 1148, 1156, 1164, 1172, 1188, 1196, 1204, 1212,
889  1220, 1228, 1268, 1276, 1284, 1292, 1300, 1308, 1316, 1324,
890  1334, 1344, 1352, 1360,
891  1368+ 0, 1368+ 8, 1368+16, 1368+ 24, 1368+ 32,
892  1368+ 40, 1368+ 48, 1368+56, 1368+ 64, 1368+ 72,
893  1368+ 80, 1368+ 88, 1368+96, 1368+104, 1368+112,
894  1368+120, 1368+164
895 };
896 
897 static const unsigned spritesOff[88 + 16] = {
898  6, 14, 22, 30, 38, 46, 54, 62, 70, 78,
899  86, 94, 102, 110, 118, 162, 170, 182, 188, 214,
900  220, 246, 252, 278, 310, 316, 342, 348, 374, 380,
901  406, 438, 444, 470, 476, 502, 508, 534, 566, 572,
902  598, 604, 630, 636, 662, 694, 700, 726, 732, 758,
903  764, 790, 822, 828, 854, 860, 886, 892, 918, 950,
904  956, 982, 988, 1014, 1020, 1046, 1078, 1084, 1110, 1116,
905  1142, 1148, 1174, 1206, 1212, 1266, 1274, 1282, 1290, 1298,
906  1306, 1314, 1322, 1332, 1342, 1350, 1358, 1366,
907  1368+ 6, 1368+14, 1368+ 22, 1368+ 30, 1368+ 38,
908  1368+ 46, 1368+54, 1368+ 62, 1368+ 70, 1368+ 78,
909  1368+ 86, 1368+94, 1368+102, 1368+110, 1368+118,
910  1368+162,
911 };
912 
913 static const unsigned spritesOn[31 + 3] = {
914  28, 92, 162, 170, 188, 220, 252, 316, 348, 380,
915  444, 476, 508, 572, 604, 636, 700, 732, 764, 828,
916  860, 892, 956, 988, 1020, 1084, 1116, 1148, 1212, 1264,
917  1330,
918  1368+28, 1368+92, 1368+162,
919 };
920 
921 static array_ref<unsigned> getAccessSlots(bool display, bool sprites)
922 {
923  return display ? (sprites ? array_ref<unsigned>(spritesOn, 31)
924  : array_ref<unsigned>(spritesOff, 88))
925  : array_ref<unsigned>(screenOff, 154);
926 }
927 static array_ref<unsigned> getExtendedAccessSlots(bool display, bool sprites)
928 {
929  return display ? (sprites ? array_ref<unsigned>(spritesOn)
930  : array_ref<unsigned>(spritesOff))
931  : array_ref<unsigned>(screenOff);
932 }
933 
934 EmuTime VDP::getAccessSlot(EmuTime::param time, unsigned delta) const
935 {
936  assert(delta <= 136); // longest time between command requests
937  unsigned ticks = getTicksThisFrame(time) % TICKS_PER_LINE;
938  auto slots = getExtendedAccessSlots(
939  isDisplayEnabled(), (controlRegs[8] & 2) == 0);
940 
941  // search lowest value that is bigger or equal to ticks+delta
942  auto it = std::lower_bound(slots.begin(), slots.end(), ticks + delta);
943  assert(it != slots.end());
944  return time + VDPClock::duration(*it - ticks);
945 }
946 
948 {
949  unsigned ticks = getTicksThisFrame(time) % TICKS_PER_LINE;
950  auto slots = getAccessSlots(
951  isDisplayEnabled(), (controlRegs[8] & 2) == 0);
952  return AccessSlotCalculator(ticks, slots.data(), unsigned(slots.size()));
953 }
954 
955 byte VDP::peekStatusReg(byte reg, EmuTime::param time) const
956 {
957  switch (reg) {
958  case 0:
959  spriteChecker->sync(time);
960  return statusReg0;
961  case 1:
962  if (controlRegs[0] & 0x10) { // line int enabled
963  return statusReg1 | (irqHorizontal.getState() ? 1:0);
964  } else { // line int disabled
965  // FH goes up at the start of the right border of IL and
966  // goes down at the start of the next left border.
967  // TODO: Precalc matchLength?
968  int afterMatch =
969  getTicksThisFrame(time) - horizontalScanOffset;
970  if (afterMatch < 0) {
971  afterMatch += getTicksPerFrame();
972  // afterMatch can still be negative at this
973  // point, see scheduleHScan()
974  }
975  int matchLength = (displayMode.isTextMode() ? 87 : 59)
976  + 27 + 100 + 102;
977  return statusReg1 |
978  (0 <= afterMatch && afterMatch < matchLength);
979  }
980  case 2: {
981  // TODO: Once VDP keeps display/blanking state, keeping
982  // VR is probably part of that, so use it.
983  // --> Is isDisplayArea actually !VR?
984  int ticksThisFrame = getTicksThisFrame(time);
985  int displayEnd =
986  displayStart + getNumberOfLines() * TICKS_PER_LINE;
987  bool vr = ticksThisFrame < displayStart - TICKS_PER_LINE
988  || ticksThisFrame >= displayEnd;
989  return statusReg2
990  | (getHR(ticksThisFrame) ? 0x20 : 0x00)
991  | (vr ? 0x40 : 0x00)
992  | cmdEngine->getStatus(time);
993  }
994  case 3:
995  return byte(spriteChecker->getCollisionX(time));
996  case 4:
997  return byte(spriteChecker->getCollisionX(time) >> 8) | 0xFE;
998  case 5:
999  return byte(spriteChecker->getCollisionY(time));
1000  case 6:
1001  return byte(spriteChecker->getCollisionY(time) >> 8) | 0xFC;
1002  case 7:
1003  return cmdEngine->readColor(time);
1004  case 8:
1005  return byte(cmdEngine->getBorderX(time));
1006  case 9:
1007  return byte(cmdEngine->getBorderX(time) >> 8) | 0xFE;
1008  default: // non-existent status register
1009  return 0xFF;
1010  }
1011 }
1012 
1013 byte VDP::readStatusReg(byte reg, EmuTime::param time)
1014 {
1015  byte ret = peekStatusReg(reg, time);
1016  switch (reg) {
1017  case 0:
1018  spriteChecker->resetStatus();
1019  statusReg0 &= ~0x80;
1020  irqVertical.reset();
1021  break;
1022  case 1:
1023  if (controlRegs[0] & 0x10) { // line int enabled
1024  irqHorizontal.reset();
1025  }
1026  break;
1027  case 5:
1028  spriteChecker->resetCollision();
1029  break;
1030  case 7:
1031  cmdEngine->resetColor();
1032  break;
1033  }
1034  return ret;
1035 }
1036 
1038 {
1039  assert(isInsideFrame(time));
1040 
1041  registerDataStored = false; // Abort any port #1 writes in progress.
1042 
1043  switch (port & 0x03) {
1044  case 0: // VRAM data read
1045  return vramRead(time);
1046  case 1: // Status register read
1047  // Calculate status register contents.
1048  return readStatusReg(controlRegs[15], time);
1049  default:
1050  // These ports should not be registered for reading.
1051  UNREACHABLE; return 0xFF;
1052  }
1053 }
1054 
1055 byte VDP::peekIO(word /*port*/, EmuTime::param /*time*/) const
1056 {
1057  // TODO not implemented
1058  return 0xFF;
1059 }
1060 
1061 void VDP::changeRegister(byte reg, byte val, EmuTime::param time)
1062 {
1063  //PRT_DEBUG("VDP[" << (int)reg << "] = " << hex << (int)val << dec);
1064 
1065  if (reg >= 32) {
1066  // MXC belongs to CPU interface;
1067  // other bits in this register belong to command engine.
1068  if (reg == 45) {
1069  cpuExtendedVram = (val & 0x40) != 0;
1070  }
1071  // Pass command register writes to command engine.
1072  if (reg < 47) {
1073  cmdEngine->setCmdReg(reg - 32, val, time);
1074  }
1075  return;
1076  }
1077 
1078  // Make sure only bits that actually exist are written.
1079  val &= controlValueMasks[reg];
1080  // Determine the difference between new and old value.
1081  byte change = val ^ controlRegs[reg];
1082 
1083  // Register 13 is special because writing it resets blinking state,
1084  // even if the value in the register doesn't change.
1085  if (reg == 13) {
1086  // Switch to ON state unless ON period is zero.
1087  if (blinkState == ((val & 0xF0) == 0)) {
1088  renderer->updateBlinkState(!blinkState, time);
1089  blinkState = !blinkState;
1090  }
1091 
1092  if ((val & 0xF0) && (val & 0x0F)) {
1093  // Alternating colors, start with ON.
1094  blinkCount = (val >> 4) * 10;
1095  } else {
1096  // Stable color.
1097  blinkCount = 0;
1098  }
1099  }
1100 
1101  if (!change) return;
1102 
1103  // Perform additional tasks before new value becomes active.
1104  switch (reg) {
1105  case 0:
1106  if (change & DisplayMode::REG0_MASK) {
1107  syncAtNextLine(SET_MODE, time);
1108  }
1109  break;
1110  case 1:
1111  if (change & 0x03) {
1112  // Update sprites on size and mag changes.
1113  spriteChecker->updateSpriteSizeMag(val, time);
1114  }
1115  // TODO: Reset vertical IRQ if IE0 is reset?
1116  if (change & DisplayMode::REG1_MASK) {
1117  syncAtNextLine(SET_MODE, time);
1118  }
1119  if (change & 0x40) {
1120  syncAtNextLine(SET_BLANK, time);
1121  }
1122  break;
1123  case 2: {
1124  int base = (val << 10) | ~(-1 << 10);
1125  // TODO:
1126  // I reverted this fix.
1127  // Although the code is correct, there is also a counterpart in the
1128  // renderer that must be updated. I'm too tired now to find it.
1129  // Since name table checking is currently disabled anyway, keeping the
1130  // old code does not hurt.
1131  // Eventually this line should be re-enabled.
1132  /*
1133  if (displayMode.isPlanar()) {
1134  base = ((base << 16) | (base >> 1)) & 0x1FFFF;
1135  }
1136  */
1137  renderer->updateNameBase(base, time);
1138  break;
1139  }
1140  case 7:
1141  if (getDisplayMode().getByte() != DisplayMode::GRAPHIC7) {
1142  if (change & 0xF0) {
1143  renderer->updateForegroundColor(val >> 4, time);
1144  }
1145  if (change & 0x0F) {
1146  renderer->updateBackgroundColor(val & 0x0F, time);
1147  }
1148  } else {
1149  renderer->updateBackgroundColor(val, time);
1150  }
1151  break;
1152  case 8:
1153  if (change & 0x20) {
1154  renderer->updateTransparency((val & 0x20) == 0, time);
1155  }
1156  if (change & 0x02) {
1157  vram->updateSpritesEnabled((val & 0x02) == 0, time);
1158  }
1159  if (change & 0x08) {
1160  vram->updateVRMode((val & 0x08) != 0, time);
1161  }
1162  break;
1163  case 12:
1164  if (change & 0xF0) {
1165  renderer->updateBlinkForegroundColor(val >> 4, time);
1166  }
1167  if (change & 0x0F) {
1168  renderer->updateBlinkBackgroundColor(val & 0x0F, time);
1169  }
1170  break;
1171  case 16:
1172  // Any half-finished palette loads are aborted.
1173  paletteDataStored = false;
1174  break;
1175  case 18:
1176  if (change & 0x0F) {
1177  syncAtNextLine(HOR_ADJUST, time);
1178  }
1179  break;
1180  case 23:
1181  spriteChecker->updateVerticalScroll(val, time);
1182  renderer->updateVerticalScroll(val, time);
1183  break;
1184  case 25:
1185  if (change & DisplayMode::REG25_MASK) {
1186  updateDisplayMode(getDisplayMode().updateReg25(val),
1187  time);
1188  }
1189  if (change & 0x08) {
1190  syncAtNextLine(HOR_ADJUST, time);
1191  }
1192  if (change & 0x02) {
1193  renderer->updateBorderMask((val & 0x02) != 0, time);
1194  }
1195  if (change & 0x01) {
1196  renderer->updateMultiPage((val & 0x01) != 0, time);
1197  }
1198  break;
1199  case 26:
1200  renderer->updateHorizontalScrollHigh(val, time);
1201  break;
1202  case 27:
1203  renderer->updateHorizontalScrollLow(val, time);
1204  break;
1205  }
1206 
1207  // Commit the change.
1208  controlRegs[reg] = val;
1209 
1210  // Perform additional tasks after new value became active.
1211  // Because base masks cannot be read from the VDP, updating them after
1212  // the commit is equivalent to updating before.
1213  switch (reg) {
1214  case 0:
1215  if (change & 0x10) { // IE1
1216  if (val & 0x10) {
1217  scheduleHScan(time);
1218  } else {
1219  irqHorizontal.reset();
1220  }
1221  }
1222  break;
1223  case 1:
1224  if (change & 0x20) { // IE0
1225  if (val & 0x20) {
1226  // This behaviour is important. Without it,
1227  // the intro music in 'Andonis' is way too slow
1228  // and the intro logo of 'Zanac' is corrupted.
1229  if (statusReg0 & 0x80) {
1230  irqVertical.set();
1231  }
1232  } else {
1233  irqVertical.reset();
1234  }
1235  }
1236  if ((change & 0x80) && isMSX1VDP()) {
1237  // confirmed: VRAM remapping does not happen on a V99x8
1238  // see VDPVRAM for details on the remapping itself
1239  vram->change4k8kMapping((val & 0x80) != 0);
1240  }
1241  break;
1242  case 2:
1243  updateNameBase(time);
1244  break;
1245  case 3:
1246  case 10:
1247  updateColorBase(time);
1248  break;
1249  case 4:
1250  updatePatternBase(time);
1251  break;
1252  case 5:
1253  case 11:
1254  updateSpriteAttributeBase(time);
1255  break;
1256  case 6:
1257  updateSpritePatternBase(time);
1258  break;
1259  case 9:
1260  if ((val & 1) && ! warningPrinted) {
1261  warningPrinted = true;
1263  ("The running MSX software has set bit 0 of VDP register 9 "
1264  "(dot clock direction) to one. In an ordinary MSX, "
1265  "the screen would go black and the CPU would stop running.");
1266  // TODO: Emulate such behaviour.
1267  }
1268  if (change & 0x80) {
1269  /*
1270  cerr << "changed to " << (val & 0x80 ? 212 : 192) << " lines"
1271  << " at line " << (getTicksThisFrame(time) / TICKS_PER_LINE) << "\n";
1272  */
1273  // Display lines (192/212) determines display start and end.
1274  // TODO: Find out exactly when display start is fixed.
1275  // If it is fixed at VSYNC that would simplify things,
1276  // but I think it's more likely the current
1277  // implementation is accurate.
1278  if (time < displayStartSyncTime) {
1279  // Display start is not fixed yet.
1280  scheduleDisplayStart(time);
1281  } else {
1282  // Display start is fixed, but display end is not.
1283  scheduleVScan(time);
1284  }
1285  }
1286  break;
1287  case 19:
1288  case 23:
1289  scheduleHScan(time);
1290  break;
1291  case 25:
1292  if (change & 0x01) {
1293  updateNameBase(time);
1294  }
1295  break;
1296  }
1297 }
1298 
1299 void VDP::syncAtNextLine(SyncType type, EmuTime::param time)
1300 {
1301  int line = getTicksThisFrame(time) / TICKS_PER_LINE;
1302  int ticks = (line + 1) * TICKS_PER_LINE;
1303  EmuTime nextTime = frameStartTime + ticks;
1304  setSyncPoint(nextTime, type);
1305 }
1306 
1307 void VDP::updateNameBase(EmuTime::param time)
1308 {
1309  int base = (controlRegs[2] << 10) | ~(-1 << 10);
1310  // TODO:
1311  // I reverted this fix.
1312  // Although the code is correct, there is also a counterpart in the
1313  // renderer that must be updated. I'm too tired now to find it.
1314  // Since name table checking is currently disabled anyway, keeping the
1315  // old code does not hurt.
1316  // Eventually this line should be re-enabled.
1317  /*
1318  if (displayMode.isPlanar()) {
1319  base = ((base << 16) | (base >> 1)) & 0x1FFFF;
1320  }
1321  */
1322  int indexMask =
1323  displayMode.isBitmapMode()
1324  ? -1 << 17 // TODO: Calculate actual value; how to handle planar?
1325  : -1 << (displayMode.isTextMode() ? 12 : 10);
1326  if (controlRegs[25] & 0x01) {
1327  // Multi page scrolling. The same bit is used in character and
1328  // (non)planar-bitmap modes.
1329  // TODO test text modes
1330  indexMask &= ~0x8000;
1331  }
1332  vram->nameTable.setMask(base, indexMask, time);
1333 }
1334 
1335 void VDP::updateColorBase(EmuTime::param time)
1336 {
1337  int base = (controlRegs[10] << 14) | (controlRegs[3] << 6) | ~(-1 << 6);
1338  renderer->updateColorBase(base, time);
1339  switch (displayMode.getBase()) {
1340  case 0x09: // Text 2.
1341  // TODO: Enable this only if dual color is actually active.
1342  vram->colorTable.setMask(base, -1 << 9, time);
1343  break;
1344  case 0x00: // Graphic 1.
1345  vram->colorTable.setMask(base, -1 << 6, time);
1346  break;
1347  case 0x04: // Graphic 2.
1348  case 0x08: // Graphic 3.
1349  vram->colorTable.setMask(base, -1 << 13, time);
1350  break;
1351  default:
1352  // Other display modes do not use a color table.
1353  vram->colorTable.disable(time);
1354  }
1355 }
1356 
1357 void VDP::updatePatternBase(EmuTime::param time)
1358 {
1359  int base = (controlRegs[4] << 11) | ~(-1 << 11);
1360  renderer->updatePatternBase(base, time);
1361  switch (displayMode.getBase()) {
1362  case 0x01: // Text 1.
1363  case 0x05: // Text 1 Q.
1364  case 0x09: // Text 2.
1365  case 0x00: // Graphic 1.
1366  case 0x02: // Multicolor.
1367  case 0x06: // Multicolor Q.
1368  vram->patternTable.setMask(base, -1 << 11, time);
1369  break;
1370  case 0x04: // Graphic 2.
1371  case 0x08: // Graphic 3.
1372  vram->patternTable.setMask(base, -1 << 13, time);
1373  break;
1374  default:
1375  // Other display modes do not use a pattern table.
1376  vram->patternTable.disable(time);
1377  }
1378 }
1379 
1380 void VDP::updateSpriteAttributeBase(EmuTime::param time)
1381 {
1382  int mode = displayMode.getSpriteMode();
1383  if (mode == 0) {
1384  vram->spriteAttribTable.disable(time);
1385  return;
1386  }
1387  int baseMask = (controlRegs[11] << 15) | (controlRegs[5] << 7) | ~(-1 << 7);
1388  int indexMask = mode == 1 ? -1 << 7 : -1 << 10;
1389  if (displayMode.isPlanar()) {
1390  baseMask = ((baseMask << 16) | (baseMask >> 1)) & 0x1FFFF;
1391  indexMask = ((indexMask << 16) | ~(1 << 16)) & (indexMask >> 1);
1392  }
1393  vram->spriteAttribTable.setMask(baseMask, indexMask, time);
1394 }
1395 
1396 void VDP::updateSpritePatternBase(EmuTime::param time)
1397 {
1398  if (displayMode.getSpriteMode() == 0) {
1399  vram->spritePatternTable.disable(time);
1400  return;
1401  }
1402  int baseMask = (controlRegs[6] << 11) | ~(-1 << 11);
1403  int indexMask = -1 << 11;
1404  if (displayMode.isPlanar()) {
1405  baseMask = ((baseMask << 16) | (baseMask >> 1)) & 0x1FFFF;
1406  indexMask = ((indexMask << 16) | ~(1 << 16)) & (indexMask >> 1);
1407  }
1408  vram->spritePatternTable.setMask(baseMask, indexMask, time);
1409 }
1410 
1411 void VDP::updateDisplayMode(DisplayMode newMode, EmuTime::param time)
1412 {
1413  //PRT_DEBUG("VDP: mode " << newMode);
1414 
1415  // Synchronise subsystems.
1416  vram->updateDisplayMode(newMode, time);
1417 
1418  // TODO: Is this a useful optimisation, or doesn't it help
1419  // in practice?
1420  // What aspects have changed:
1421  // Switched from planar to nonplanar or vice versa.
1422  bool planarChange =
1423  newMode.isPlanar() != displayMode.isPlanar();
1424  // Sprite mode changed.
1425  bool spriteModeChange =
1426  newMode.getSpriteMode() != displayMode.getSpriteMode();
1427 
1428  // Commit the new display mode.
1429  displayMode = newMode;
1430 
1431  // Speed up performance of bitmap/character mode splits:
1432  // leave last used character mode active.
1433  // TODO: Disable it if not used for some time.
1434  if (!displayMode.isBitmapMode()) {
1435  updateColorBase(time);
1436  updatePatternBase(time);
1437  }
1438  if (planarChange || spriteModeChange) {
1439  updateSpritePatternBase(time);
1440  updateSpriteAttributeBase(time);
1441  }
1442  updateNameBase(time);
1443 
1444  // To be extremely accurate, reschedule hscan when changing
1445  // from/to text mode. Text mode has different border width,
1446  // which affects the moment hscan occurs.
1447  // TODO: Why didn't I implement this yet?
1448  // It's one line of code and overhead is not huge either.
1449 }
1450 
1451 void VDP::setExternalVideoSource(const RawFrame* externalSource)
1452 {
1453  externalVideo = externalSource;
1454 }
1455 
1456 // VDPRegDebug
1457 
1459  : SimpleDebuggable(vdp_.getMotherBoard(),
1460  vdp_.getName() + " regs", "VDP registers.", 0x40)
1461  , vdp(vdp_)
1462 {
1463 }
1464 
1465 byte VDPRegDebug::read(unsigned address)
1466 {
1467  if (address < 0x20) {
1468  return vdp.controlRegs[address];
1469  } else if (address < 0x2F) {
1470  return vdp.cmdEngine->peekCmdReg(address - 0x20);
1471  } else {
1472  return 0xFF;
1473  }
1474 }
1475 
1476 void VDPRegDebug::write(unsigned address, byte value, EmuTime::param time)
1477 {
1478  vdp.changeRegister(address, value, time);
1479 }
1480 
1481 
1482 // VDPStatusRegDebug
1483 
1485  : SimpleDebuggable(vdp_.getMotherBoard(),
1486  vdp_.getName() + " status regs", "VDP status registers.", 0x10)
1487  , vdp(vdp_)
1488 {
1489 }
1490 
1492 {
1493  return vdp.peekStatusReg(address, time);
1494 }
1495 
1496 
1497 // VDPPaletteDebug
1498 
1500  : SimpleDebuggable(vdp_.getMotherBoard(),
1501  vdp_.getName() + " palette", "V99x8 palette (RBG format)", 0x20)
1502  , vdp(vdp_)
1503 {
1504 }
1505 
1506 byte VDPPaletteDebug::read(unsigned address)
1507 {
1508  word grb = vdp.getPalette(address / 2);
1509  return (address & 1) ? (grb >> 8) : (grb & 0xff);
1510 }
1511 
1512 void VDPPaletteDebug::write(unsigned address, byte value, EmuTime::param time)
1513 {
1514  int index = address / 2;
1515  word grb = vdp.getPalette(index);
1516  grb = (address & 1)
1517  ? (grb & 0x0077) | ((value & 0x07) << 8)
1518  : (grb & 0x0700) | (value & 0x77);
1519  vdp.setPalette(index, grb, time);
1520 }
1521 
1522 
1523 // class VRAMPointerDebug
1524 
1526  : SimpleDebuggable(vdp_.getMotherBoard(), vdp_.getName() == "VDP" ?
1527  "VRAM pointer" : vdp_.getName() + " VRAM pointer",
1528  "VDP VRAM pointer (14 lower bits)", 2)
1529  , vdp(vdp_)
1530 {
1531 }
1532 
1533 byte VRAMPointerDebug::read(unsigned address)
1534 {
1535  if (address & 1) {
1536  return vdp.vramPointer >> 8; // TODO add read/write mode?
1537  } else {
1538  return vdp.vramPointer & 0xFF;
1539  }
1540 }
1541 
1542 void VRAMPointerDebug::write(unsigned address, byte value, EmuTime::param /*time*/)
1543 {
1544  int& ptr = vdp.vramPointer;
1545  if (address & 1) {
1546  ptr = (ptr & 0x00FF) | ((value & 0x3F) << 8);
1547  } else {
1548  ptr = (ptr & 0xFF00) | value;
1549  }
1550 }
1551 
1552 
1553 // version 1: initial version
1554 // version 2: added frameCount
1555 // version 3: removed verticalAdjust
1556 // version 4: removed lineZero
1557 // version 5: replace readAhead->cpuVramData, added cpuVramReqIsRead
1558 template<typename Archive>
1559 void VDP::serialize(Archive& ar, unsigned version)
1560 {
1561  ar.template serializeBase<MSXDevice>(*this);
1562  ar.template serializeBase<Schedulable>(*this);
1563 
1564  // not serialized
1565  // std::unique_ptr<Renderer> renderer;
1566  // VdpVersion version;
1567  // int controlRegMask;
1568  // byte controlValueMasks[32];
1569  // bool warningPrinted;
1570 
1571  ar.serialize("irqVertical", irqVertical);
1572  ar.serialize("irqHorizontal", irqHorizontal);
1573  ar.serialize("frameStartTime", frameStartTime);
1574  ar.serialize("displayStartSyncTime", displayStartSyncTime);
1575  ar.serialize("vScanSyncTime", vScanSyncTime);
1576  ar.serialize("hScanSyncTime", hScanSyncTime);
1577  ar.serialize("displayStart", displayStart);
1578  ar.serialize("horizontalScanOffset", horizontalScanOffset);
1579  ar.serialize("horizontalAdjust", horizontalAdjust);
1580  ar.serialize("registers", controlRegs);
1581  ar.serialize("blinkCount", blinkCount);
1582  ar.serialize("vramPointer", vramPointer);
1583  ar.serialize("palette", palette);
1584  ar.serialize("isDisplayArea", isDisplayArea);
1585  ar.serialize("palTiming", palTiming);
1586  ar.serialize("interlaced", interlaced);
1587  ar.serialize("statusReg0", statusReg0);
1588  ar.serialize("statusReg1", statusReg1);
1589  ar.serialize("statusReg2", statusReg2);
1590  ar.serialize("blinkState", blinkState);
1591  ar.serialize("dataLatch", dataLatch);
1592  ar.serialize("registerDataStored", registerDataStored);
1593  ar.serialize("paletteDataStored", paletteDataStored);
1594  if (ar.versionAtLeast(version, 5)) {
1595  ar.serialize("cpuVramData", cpuVramData);
1596  ar.serialize("cpuVramReqIsRead", cpuVramReqIsRead);
1597  } else {
1598  ar.serialize("readAhead", cpuVramData);
1599  }
1600  ar.serialize("cpuExtendedVram", cpuExtendedVram);
1601  ar.serialize("displayEnabled", displayEnabled);
1602  byte mode = displayMode.getByte();
1603  ar.serialize("displayMode", mode);
1604  displayMode.setByte(mode);
1605 
1606  ar.serialize("cmdEngine", *cmdEngine);
1607  ar.serialize("spriteChecker", *spriteChecker); // must come after displayMode
1608  ar.serialize("vram", *vram); // must come after controlRegs and after spriteChecker
1609 
1610  if (ar.versionAtLeast(version, 2)) {
1611  ar.serialize("frameCount", frameCount);
1612  } else {
1613  assert(ar.isLoader());
1614  // We could estimate the frameCount (assume framerate was
1615  // constant the whole time). But I think it's better to have
1616  // an obviously wrong value than an almost correct value.
1617  frameCount = 0;
1618  }
1619 
1620  // externalVideo does not need serializing. It is set on load by the
1621  // external video source (e.g. PioneerLDControl).
1622  //
1623  // TODO should superimposing be serialized? It cannot be recalculated
1624  // from register values (it depends on the register values at the start
1625  // of this frame). But it will be correct at the start of the next
1626  // frame. Probably good enough.
1627 
1628  if (ar.isLoader()) {
1629  renderer->reInit();
1630  }
1631 }
1633 REGISTER_MSXDEVICE(VDP, "VDP");
1634 
1635 } // namespace openmsx