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