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