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 // Remove pending VSCAN sync point, if any.
496 if (vScanSyncTime > time) {
497 syncVScan.removeSyncPoint();
498 //cerr << "removing predicted VSCAN sync point\n";
499 }
500
501 // Calculate moment in time display end occurs.
502 vScanSyncTime = frameStartTime +
503 (displayStart + getNumberOfLines() * TICKS_PER_LINE);
504
505 // Register new VSCAN sync point.
506 if (vScanSyncTime > time) {
507 syncVScan.setSyncPoint(vScanSyncTime);
508 //cerr << "inserting new VSCAN sync point\n";
509 }
510}
511
512void VDP::scheduleHScan(EmuTime::param time)
513{
514 // Remove pending HSCAN sync point, if any.
515 if (hScanSyncTime > time) {
516 syncHScan.removeSyncPoint();
517 hScanSyncTime = time;
518 }
519
520 // Calculate moment in time line match occurs.
521 horizontalScanOffset = displayStart - (100 + 102)
522 + ((controlRegs[19] - controlRegs[23]) & 0xFF) * TICKS_PER_LINE
523 + getRightBorder();
524 // Display line counter continues into the next frame.
525 // Note that this implementation is not 100% accurate, since the
526 // number of ticks of the *previous* frame should be subtracted.
527 // By switching from NTSC to PAL it may even be possible to get two
528 // HSCANs in a single frame without modifying any other setting.
529 // Fortunately, no known program relies on this.
530 if (int ticksPerFrame = getTicksPerFrame();
531 horizontalScanOffset >= ticksPerFrame) {
532 horizontalScanOffset -= ticksPerFrame;
533
534 // Time at which the internal VDP display line counter is reset,
535 // expressed in ticks after vsync.
536 // I would expect the counter to reset at line 16 (for neutral
537 // set-adjust), but measurements on NMS8250 show it is one line
538 // earlier. I'm not sure whether the actual counter reset
539 // happens on line 15 or whether the VDP timing may be one line
540 // off for some reason.
541 // TODO: This is just an assumption, more measurements on real MSX
542 // are necessary to verify there is really such a thing and
543 // if so, that the value is accurate.
544 // Note: see this bug report for some measurements on a real machine:
545 // https://github.com/openMSX/openMSX/issues/1106
546 int lineCountResetTicks = (8 + getVerticalAdjust()) * TICKS_PER_LINE;
547
548 // Display line counter is reset at the start of the top border.
549 // Any HSCAN that has a higher line number never occurs.
550 if (horizontalScanOffset >= lineCountResetTicks) {
551 // This is one way to say "never".
552 horizontalScanOffset = -1000 * TICKS_PER_LINE;
553 }
554 }
555
556 // Register new HSCAN sync point if interrupt is enabled.
557 if ((controlRegs[0] & 0x10) && horizontalScanOffset >= 0) {
558 // No line interrupt will occur after bottom erase.
559 // NOT TRUE: "after next top border start" is correct.
560 // Note that line interrupt can occur in the next frame.
561 /*
562 EmuTime bottomEraseTime =
563 frameStartTime + getTicksPerFrame() - 3 * TICKS_PER_LINE;
564 */
565 hScanSyncTime = frameStartTime + horizontalScanOffset;
566 if (hScanSyncTime > time) {
567 syncHScan.setSyncPoint(hScanSyncTime);
568 }
569 }
570}
571
572// TODO: inline?
573// TODO: Is it possible to get rid of this routine and its sync point?
574// VSYNC, HSYNC and DISPLAY_START could be scheduled for the next
575// frame when their callback occurs.
576// But I'm not sure how to handle the PAL/NTSC setting (which also
577// influences the frequency at which E/O toggles).
578void VDP::frameStart(EmuTime::param time)
579{
580 ++frameCount;
581
582 // Toggle E/O.
583 // Actually this should occur half a line earlier,
584 // but for now this is accurate enough.
585 statusReg2 ^= 0x02;
586
587 // Settings which are fixed at start of frame.
588 // Not sure this is how real MSX does it, but close enough for now.
589 // TODO: Interlace is effectuated in border height, according to
590 // the data book. Exactly when is the fixation point?
591 palTiming = (controlRegs[9] & 0x02) != 0;
592 interlaced = !isFastBlinkEnabled() && ((controlRegs[9] & 0x08) != 0);
593
594 // Blinking.
595 if ((blinkCount != 0) && !isFastBlinkEnabled()) { // counter active?
596 blinkCount--;
597 if (blinkCount == 0) {
598 renderer->updateBlinkState(!blinkState, time);
599 blinkState = !blinkState;
600 blinkCount = (blinkState
601 ? controlRegs[13] >> 4 : controlRegs[13] & 0x0F) * 10;
602 }
603 }
604
605 // TODO: Presumably this is done here
606 // Note that if superimposing is enabled but no external video
607 // signal is provided then the VDP stops producing a signal
608 // (at least on an MSX1, VDP(0)=1 produces "signal lost" on my
609 // monitor)
610 if (const RawFrame* newSuperimposing = (controlRegs[0] & 1) ? externalVideo : nullptr;
611 superimposing != newSuperimposing) {
612 superimposing = newSuperimposing;
613 renderer->updateSuperimposing(superimposing, time);
614 }
615
616 // Schedule next VSYNC.
617 frameStartTime.reset(time);
618 syncVSync.setSyncPoint(frameStartTime + getTicksPerFrame());
619 // Schedule DISPLAY_START, VSCAN and HSCAN.
620 scheduleDisplayStart(time);
621
622 // Inform VDP subcomponents.
623 // TODO: Do this via VDPVRAM?
624 renderer->frameStart(time);
625 spriteChecker->frameStart(time);
626
627 /*
628 cout << "--> frameStart = " << frameStartTime
629 << ", frameEnd = " << (frameStartTime + getTicksPerFrame())
630 << ", hscan = " << hScanSyncTime
631 << ", displayStart = " << displayStart
632 << ", timing: " << (palTiming ? "PAL" : "NTSC")
633 << "\n";
634 */
635}
636
637// The I/O functions.
638
639void VDP::writeIO(word port, byte value, EmuTime::param time_)
640{
641 EmuTime time = time_;
642 // This is the (fixed) delay from
643 // https://github.com/openMSX/openMSX/issues/563 and
644 // https://github.com/openMSX/openMSX/issues/989
645 // It seems to originate from the T9769x and for x=C the delay is 1
646 // cycle and for other x it seems the delay is 2 cycles
647 if (fixedVDPIOdelayCycles > 0) {
648 time = cpu.waitCyclesZ80(time, fixedVDPIOdelayCycles);
649 }
650
651 assert(isInsideFrame(time));
652 switch (port & (isMSX1VDP() ? 0x01 : 0x03)) {
653 case 0: // VRAM data write
654 vramWrite(value, time);
655 registerDataStored = false;
656 break;
657 case 1: // Register or address write
658 if (registerDataStored) {
659 if (value & 0x80) {
660 if (!(value & 0x40) || isMSX1VDP()) {
661 // Register write.
663 value & controlRegMask,
664 dataLatch,
665 time);
666 } else {
667 // TODO what happens in this case?
668 // it's not a register write because
669 // that breaks "SNOW26" demo
670 }
671 if (isMSX1VDP()) {
672 // For these VDPs the VRAM pointer is modified when
673 // writing to VDP registers. Without this some demos won't
674 // run as on real MSX1, e.g. Planet of the Epas, Utopia and
675 // Waves 1.2. Thanks to dvik for finding this out.
676 // See also below about not using the latch on MSX1.
677 // Set read/write address.
678 vramPointer = (value << 8 | (vramPointer & 0xFF)) & 0x3FFF;
679 }
680 } else {
681 // Set read/write address.
682 writeAccess = value & 0x40;
683 vramPointer = (value << 8 | dataLatch) & 0x3FFF;
684 if (!(value & 0x40)) {
685 // Read ahead.
686 (void)vramRead(time);
687 }
688 }
689 registerDataStored = false;
690 } else {
691 // Note: on MSX1 there seems to be no
692 // latch used, but VDP address writes
693 // are done directly.
694 // Thanks to hap for finding this out. :)
695 if (isMSX1VDP()) {
696 vramPointer = (vramPointer & 0x3F00) | value;
697 }
698 dataLatch = value;
699 registerDataStored = true;
700 }
701 break;
702 case 2: // Palette data write
703 if (paletteDataStored) {
704 unsigned index = controlRegs[16];
705 word grb = ((value << 8) | dataLatch) & 0x777;
706 setPalette(index, grb, time);
707 controlRegs[16] = (index + 1) & 0x0F;
708 paletteDataStored = false;
709 } else {
710 dataLatch = value;
711 paletteDataStored = true;
712 }
713 break;
714 case 3: { // Indirect register write
715 dataLatch = value;
716 // TODO: What happens if reg 17 is written indirectly?
717 //fprintf(stderr, "VDP indirect register write: %02X\n", value);
718 byte regNr = controlRegs[17];
719 changeRegister(regNr & 0x3F, value, time);
720 if ((regNr & 0x80) == 0) {
721 // Auto-increment.
722 controlRegs[17] = (regNr + 1) & 0x3F;
723 }
724 break;
725 }
726 }
727}
728
729std::string_view VDP::getVersionString() const
730{
731 // Add VDP type from the config. An alternative is to convert the
732 // 'version' enum member into some kind of string, but we already
733 // parsed that string to become this version enum value. So whatever is
734 // in there, it makes sense. So we can just as well return that then.
735 const auto* vdpVersionString = getDeviceConfig().findChild("version");
736 assert(vdpVersionString);
737 return vdpVersionString->getData();
738}
739
741{
742 result.addDictKeyValues("version", getVersionString());
743}
744
745byte VDP::peekRegister(unsigned address) const
746{
747 if (address < 0x20) {
748 return controlRegs[address];
749 } else if (address < 0x2F) {
750 return cmdEngine->peekCmdReg(narrow<byte>(address - 0x20));
751 } else {
752 return 0xFF;
753 }
754}
755
756void VDP::setPalette(unsigned index, word grb, EmuTime::param time)
757{
758 if (palette[index] != grb) {
759 renderer->updatePalette(index, grb, time);
760 palette[index] = grb;
761 }
762}
763
764void VDP::vramWrite(byte value, EmuTime::param time)
765{
766 scheduleCpuVramAccess(false, value, time);
767}
768
769byte VDP::vramRead(EmuTime::param time)
770{
771 // Return the result from a previous read. In case
772 // allowTooFastAccess==true, the call to scheduleCpuVramAccess()
773 // already overwrites that variable, so make a local copy first.
774 byte result = cpuVramData;
775
776 byte dummy = 0;
777 scheduleCpuVramAccess(true, dummy, time); // schedule next read
778 return result;
779}
780
781void VDP::scheduleCpuVramAccess(bool isRead, byte write, EmuTime::param time)
782{
783 // Tested on real V9938: 'cpuVramData' is shared between read and write.
784 // E.g. OUT (#98),A followed by IN A,(#98) returns the just written value.
785 if (!isRead) cpuVramData = write;
786 cpuVramReqIsRead = isRead;
787 if (pendingCpuAccess) [[unlikely]] {
788 // Already scheduled. Do nothing.
789 // The old request has been overwritten by the new request!
790 assert(!allowTooFastAccess);
791 tooFastCallback.execute();
792 } else {
793 if (allowTooFastAccess) [[unlikely]] {
794 // Immediately execute request.
795 // In the past, in allowTooFastAccess-mode, we would
796 // still schedule the actual access, but process
797 // pending requests early when a new one arrives before
798 // the old one was handled. Though this would still go
799 // wrong because of the delayed update of
800 // 'vramPointer'. We could _only_ _partly_ work around
801 // that by calculating the actual vram address early
802 // (likely not what the real VDP does). But because
803 // allowTooFastAccess is anyway an artificial situation
804 // we now solve this in a simpler way: simply not
805 // schedule CPU-VRAM accesses.
806 assert(!pendingCpuAccess);
807 executeCpuVramAccess(time);
808 } else {
809 // For V99x8 there are 16 extra cycles, for details see:
810 // doc/internal/vdp-vram-timing/vdp-timing.html
811 // For TMS99x8 the situation is less clear, see
812 // doc/internal/vdp-vram-timing/vdp-timing-2.html
813 // Additional measurements(*) show that picking either 8 or 9
814 // TMS cycles (equivalent to 32 or 36 V99x8 cycles) gives the
815 // same result as on a real MSX. This corresponds to
816 // respectively 1.49us or 1.68us, the TMS documentation
817 // specifies 2us for this value.
818 // (*) In this test we did a lot of OUT operations (writes to
819 // VRAM) that are exactly N cycles apart. After the writes we
820 // detect whether all were successful by reading VRAM
821 // (slowly). We vary N and found that you get corruption for
822 // N<=26 cycles, but no corruption occurs for N>=27. This test
823 // was done in screen 2 with 4 sprites visible on one line
824 // (though the sprites did not seem to make a difference).
825 // So this test could not decide between 8 or 9 TMS cycles.
826 // To be on the safe side we picked 8.
827 //
828 // Update: 8 cycles (DELTA_32) causes corruption in
829 // 'Chase HQ', see
830 // http://www.msx.org/forum/msx-talk/openmsx/openmsx-about-release-testing-help-wanted
831 // lowering it to 7 cycles seems fine. TODO needs more
832 // investigation. (Just guessing) possibly there are
833 // other variables that influence the exact timing (7
834 // vs 8 cycles).
835 pendingCpuAccess = true;
836 auto delta = isMSX1VDP() ? VDPAccessSlots::DELTA_28
838 syncCpuVramAccess.setSyncPoint(getAccessSlot(time, delta));
839 }
840 }
841}
842
843void VDP::executeCpuVramAccess(EmuTime::param time)
844{
845 int addr = (controlRegs[14] << 14) | vramPointer;
846 if (displayMode.isPlanar()) {
847 // note: also extended VRAM is interleaved,
848 // because there is only 64kB it's interleaved
849 // with itself (every byte repeated twice)
850 addr = ((addr << 16) | (addr >> 1)) & 0x1FFFF;
851 }
852
853 bool doAccess = [&] {
854 if (!cpuExtendedVram) [[likely]] {
855 return true;
856 } else if (vram->getSize() == 192 * 1024) [[likely]] {
857 addr = 0x20000 | (addr & 0xFFFF);
858 return true;
859 } else {
860 return false;
861 }
862 }();
863 if (doAccess) {
864 if (cpuVramReqIsRead) {
865 cpuVramData = vram->cpuRead(addr, time);
866 } else {
867 vram->cpuWrite(addr, cpuVramData, time);
868 }
869 } else {
870 if (cpuVramReqIsRead) {
871 cpuVramData = 0xFF;
872 } else {
873 // nothing
874 }
875 }
876
877 vramPointer = (vramPointer + 1) & 0x3FFF;
878 if (vramPointer == 0 && displayMode.isV9938Mode()) {
879 // In MSX2 video modes, pointer range is 128K.
880 controlRegs[14] = (controlRegs[14] + 1) & 0x07;
881 }
882}
883
884EmuTime VDP::getAccessSlot(EmuTime::param time, VDPAccessSlots::Delta delta) const
885{
887 getFrameStartTime(), time, delta, *this);
888}
889
891 EmuTime::param time, EmuTime::param limit) const
892{
894 getFrameStartTime(), time, limit, *this);
895}
896
897byte VDP::peekStatusReg(byte reg, EmuTime::param time) const
898{
899 switch (reg) {
900 case 0:
901 spriteChecker->sync(time);
902 return statusReg0;
903 case 1:
904 if (controlRegs[0] & 0x10) { // line int enabled
905 return statusReg1 | (irqHorizontal.getState() ? 1:0);
906 } else { // line int disabled
907 // FH goes up at the start of the right border of IL and
908 // goes down at the start of the next left border.
909 // TODO: Precalc matchLength?
910 int afterMatch =
911 getTicksThisFrame(time) - horizontalScanOffset;
912 if (afterMatch < 0) {
913 afterMatch += getTicksPerFrame();
914 // afterMatch can still be negative at this
915 // point, see scheduleHScan()
916 }
917 int matchLength = (displayMode.isTextMode() ? 87 : 59)
918 + 27 + 100 + 102;
919 return statusReg1 |
920 (0 <= afterMatch && afterMatch < matchLength);
921 }
922 case 2: {
923 // TODO: Once VDP keeps display/blanking state, keeping
924 // VR is probably part of that, so use it.
925 // --> Is isDisplayArea actually !VR?
926 int ticksThisFrame = getTicksThisFrame(time);
927 int displayEnd =
928 displayStart + getNumberOfLines() * TICKS_PER_LINE;
929 bool vr = ticksThisFrame < displayStart - TICKS_PER_LINE
930 || ticksThisFrame >= displayEnd;
931 return statusReg2
932 | (getHR(ticksThisFrame) ? 0x20 : 0x00)
933 | (vr ? 0x40 : 0x00)
934 | cmdEngine->getStatus(time);
935 }
936 case 3:
937 return byte(spriteChecker->getCollisionX(time));
938 case 4:
939 return byte(spriteChecker->getCollisionX(time) >> 8) | 0xFE;
940 case 5:
941 return byte(spriteChecker->getCollisionY(time));
942 case 6:
943 return byte(spriteChecker->getCollisionY(time) >> 8) | 0xFC;
944 case 7:
945 return cmdEngine->readColor(time);
946 case 8:
947 return byte(cmdEngine->getBorderX(time));
948 case 9:
949 return byte(cmdEngine->getBorderX(time) >> 8) | 0xFE;
950 default: // non-existent status register
951 return 0xFF;
952 }
953}
954
955byte VDP::readStatusReg(byte reg, EmuTime::param time)
956{
957 byte ret = peekStatusReg(reg, time);
958 switch (reg) {
959 case 0:
960 spriteChecker->resetStatus();
961 statusReg0 &= ~0x80;
962 irqVertical.reset();
963 break;
964 case 1:
965 if (controlRegs[0] & 0x10) { // line int enabled
966 irqHorizontal.reset();
967 }
968 break;
969 case 5:
970 spriteChecker->resetCollision();
971 break;
972 case 7:
973 cmdEngine->resetColor();
974 break;
975 }
976 return ret;
977}
978
979byte VDP::readIO(word port, EmuTime::param time)
980{
981 assert(isInsideFrame(time));
982
983 registerDataStored = false; // Abort any port #1 writes in progress.
984
985 switch (port & (isMSX1VDP() ? 0x01 : 0x03)) {
986 case 0: // VRAM data read
987 return vramRead(time);
988 case 1: // Status register read
989 // Calculate status register contents.
990 return readStatusReg(controlRegs[15], time);
991 case 2:
992 case 3:
993 return 0xFF;
994 default:
996 }
997}
998
999byte VDP::peekIO(word /*port*/, EmuTime::param /*time*/) const
1000{
1001 // TODO not implemented
1002 return 0xFF;
1003}
1004
1005void VDP::changeRegister(byte reg, byte val, EmuTime::param time)
1006{
1007 if (reg >= 32) {
1008 // MXC belongs to CPU interface;
1009 // other bits in this register belong to command engine.
1010 if (reg == 45) {
1011 cpuExtendedVram = (val & 0x40) != 0;
1012 }
1013 // Pass command register writes to command engine.
1014 if (reg < 47) {
1015 cmdEngine->setCmdReg(reg - 32, val, time);
1016 }
1017 return;
1018 }
1019
1020 // Make sure only bits that actually exist are written.
1021 val &= controlValueMasks[reg];
1022 // Determine the difference between new and old value.
1023 byte change = val ^ controlRegs[reg];
1024
1025 // Register 13 is special because writing it resets blinking state,
1026 // even if the value in the register doesn't change.
1027 if (reg == 13) {
1028 // Switch to ON state unless ON period is zero.
1029 if (blinkState == ((val & 0xF0) == 0)) {
1030 renderer->updateBlinkState(!blinkState, time);
1031 blinkState = !blinkState;
1032 }
1033
1034 if ((val & 0xF0) && (val & 0x0F)) {
1035 // Alternating colors, start with ON.
1036 blinkCount = (val >> 4) * 10;
1037 } else {
1038 // Stable color.
1039 blinkCount = 0;
1040 }
1041 // TODO when 'isFastBlinkEnabled()==true' the variables
1042 // 'blinkState' and 'blinkCount' represent the values at line 0.
1043 // This implementation is not correct for the partial remaining
1044 // frame after register 13 got changed.
1045 }
1046
1047 if (!change) return;
1048
1049 // Perform additional tasks before new value becomes active.
1050 switch (reg) {
1051 case 0:
1052 if (change & DisplayMode::REG0_MASK) {
1053 syncAtNextLine(syncSetMode, time);
1054 }
1055 break;
1056 case 1:
1057 if (change & 0x03) {
1058 // Update sprites on size and mag changes.
1059 spriteChecker->updateSpriteSizeMag(val, time);
1060 }
1061 // TODO: Reset vertical IRQ if IE0 is reset?
1062 if (change & DisplayMode::REG1_MASK) {
1063 syncAtNextLine(syncSetMode, time);
1064 }
1065 if (change & 0x40) {
1066 syncAtNextLine(syncSetBlank, time);
1067 }
1068 break;
1069 case 2: {
1070 unsigned base = (val << 10) | ~(~0u << 10);
1071 // TODO:
1072 // I reverted this fix.
1073 // Although the code is correct, there is also a counterpart in the
1074 // renderer that must be updated. I'm too tired now to find it.
1075 // Since name table checking is currently disabled anyway, keeping the
1076 // old code does not hurt.
1077 // Eventually this line should be re-enabled.
1078 /*
1079 if (displayMode.isPlanar()) {
1080 base = ((base << 16) | (base >> 1)) & 0x1FFFF;
1081 }
1082 */
1083 renderer->updateNameBase(base, time);
1084 break;
1085 }
1086 case 7:
1088 if (change & 0xF0) {
1089 renderer->updateForegroundColor(val >> 4, time);
1090 }
1091 if (change & 0x0F) {
1092 renderer->updateBackgroundColor(val & 0x0F, time);
1093 }
1094 } else {
1095 renderer->updateBackgroundColor(val, time);
1096 }
1097 break;
1098 case 8:
1099 if (change & 0x20) {
1100 renderer->updateTransparency((val & 0x20) == 0, time);
1101 spriteChecker->updateTransparency((val & 0x20) == 0, time);
1102 }
1103 if (change & 0x02) {
1104 syncAtNextLine(syncSetSprites, time);
1105 }
1106 if (change & 0x08) {
1107 vram->updateVRMode((val & 0x08) != 0, time);
1108 }
1109 break;
1110 case 12:
1111 if (change & 0xF0) {
1112 renderer->updateBlinkForegroundColor(val >> 4, time);
1113 }
1114 if (change & 0x0F) {
1115 renderer->updateBlinkBackgroundColor(val & 0x0F, time);
1116 }
1117 break;
1118 case 16:
1119 // Any half-finished palette loads are aborted.
1120 paletteDataStored = false;
1121 break;
1122 case 18:
1123 if (change & 0x0F) {
1124 syncAtNextLine(syncHorAdjust, time);
1125 }
1126 break;
1127 case 23:
1128 spriteChecker->updateVerticalScroll(val, time);
1129 renderer->updateVerticalScroll(val, time);
1130 break;
1131 case 25:
1132 if (change & (DisplayMode::REG25_MASK | 0x40)) {
1133 updateDisplayMode(getDisplayMode().updateReg25(val),
1134 val & 0x40,
1135 time);
1136 }
1137 if (change & 0x08) {
1138 syncAtNextLine(syncHorAdjust, time);
1139 }
1140 if (change & 0x02) {
1141 renderer->updateBorderMask((val & 0x02) != 0, time);
1142 }
1143 if (change & 0x01) {
1144 renderer->updateMultiPage((val & 0x01) != 0, time);
1145 }
1146 break;
1147 case 26:
1148 renderer->updateHorizontalScrollHigh(val, time);
1149 break;
1150 case 27:
1151 renderer->updateHorizontalScrollLow(val, time);
1152 break;
1153 }
1154
1155 // Commit the change.
1156 controlRegs[reg] = val;
1157
1158 // Perform additional tasks after new value became active.
1159 // Because base masks cannot be read from the VDP, updating them after
1160 // the commit is equivalent to updating before.
1161 switch (reg) {
1162 case 0:
1163 if (change & 0x10) { // IE1
1164 if (val & 0x10) {
1165 scheduleHScan(time);
1166 } else {
1167 irqHorizontal.reset();
1168 }
1169 }
1170 break;
1171 case 1:
1172 if (change & 0x20) { // IE0
1173 if (val & 0x20) {
1174 // This behaviour is important. Without it,
1175 // the intro music in 'Andonis' is way too slow
1176 // and the intro logo of 'Zanac' is corrupted.
1177 if (statusReg0 & 0x80) {
1178 irqVertical.set();
1179 }
1180 } else {
1181 irqVertical.reset();
1182 }
1183 }
1184 if ((change & 0x80) && isVDPwithVRAMremapping()) {
1185 // confirmed: VRAM remapping only happens on TMS99xx
1186 // see VDPVRAM for details on the remapping itself
1187 vram->change4k8kMapping((val & 0x80) != 0);
1188 }
1189 break;
1190 case 2:
1191 updateNameBase(time);
1192 break;
1193 case 3:
1194 case 10:
1195 updateColorBase(time);
1196 if (vdpHasPatColMirroring()) updatePatternBase(time);
1197 break;
1198 case 4:
1199 updatePatternBase(time);
1200 break;
1201 case 5:
1202 case 11:
1203 updateSpriteAttributeBase(time);
1204 break;
1205 case 6:
1206 updateSpritePatternBase(time);
1207 break;
1208 case 9:
1209 if ((val & 1) && ! warningPrinted) {
1210 warningPrinted = true;
1212 "The running MSX software has set bit 0 of VDP register 9 "
1213 "(dot clock direction) to one. In an ordinary MSX, "
1214 "the screen would go black and the CPU would stop running.");
1215 // TODO: Emulate such behaviour.
1216 }
1217 if (change & 0x80) {
1218 /*
1219 cerr << "changed to " << (val & 0x80 ? 212 : 192) << " lines"
1220 << " at line " << (getTicksThisFrame(time) / TICKS_PER_LINE) << "\n";
1221 */
1222 // Display lines (192/212) determines display start and end.
1223 // TODO: Find out exactly when display start is fixed.
1224 // If it is fixed at VSYNC that would simplify things,
1225 // but I think it's more likely the current
1226 // implementation is accurate.
1227 if (time < displayStartSyncTime) {
1228 // Display start is not fixed yet.
1229 scheduleDisplayStart(time);
1230 } else {
1231 // Display start is fixed, but display end is not.
1232 scheduleVScan(time);
1233 }
1234 }
1235 break;
1236 case 19:
1237 case 23:
1238 scheduleHScan(time);
1239 break;
1240 case 25:
1241 if (change & 0x01) {
1242 updateNameBase(time);
1243 }
1244 break;
1245 }
1246}
1247
1248void VDP::syncAtNextLine(SyncBase& type, EmuTime::param time) const
1249{
1250 // The processing of a new line starts in the middle of the left erase,
1251 // ~144 cycles after the sync signal. Adjust affects it. See issue #1310.
1252 int offset = 144 + (horizontalAdjust - 7) * 4;
1253 int line = (getTicksThisFrame(time) + TICKS_PER_LINE - offset) / TICKS_PER_LINE;
1254 int ticks = line * TICKS_PER_LINE + offset;
1255 EmuTime nextTime = frameStartTime + ticks;
1256 type.setSyncPoint(nextTime);
1257}
1258
1259void VDP::updateNameBase(EmuTime::param time)
1260{
1261 unsigned base = (controlRegs[2] << 10) | ~(~0u << 10);
1262 // TODO:
1263 // I reverted this fix.
1264 // Although the code is correct, there is also a counterpart in the
1265 // renderer that must be updated. I'm too tired now to find it.
1266 // Since name table checking is currently disabled anyway, keeping the
1267 // old code does not hurt.
1268 // Eventually this line should be re-enabled.
1269 /*
1270 if (displayMode.isPlanar()) {
1271 base = ((base << 16) | (base >> 1)) & 0x1FFFF;
1272 }
1273 */
1274 unsigned indexMask =
1275 displayMode.isBitmapMode()
1276 ? ~0u << 17 // TODO: Calculate actual value; how to handle planar?
1277 : ~0u << (displayMode.isTextMode() ? 12 : 10);
1278 if (controlRegs[25] & 0x01) {
1279 // Multi page scrolling. The same bit is used in character and
1280 // (non)planar-bitmap modes.
1281 // TODO test text modes
1282 indexMask &= ~0x8000;
1283 }
1284 vram->nameTable.setMask(base, indexMask, time);
1285}
1286
1287void VDP::updateColorBase(EmuTime::param time)
1288{
1289 unsigned base = (controlRegs[10] << 14) | (controlRegs[3] << 6) | ~(~0u << 6);
1290 renderer->updateColorBase(base, time);
1291 switch (displayMode.getBase()) {
1292 case 0x09: // Text 2.
1293 // TODO: Enable this only if dual color is actually active.
1294 vram->colorTable.setMask(base, ~0u << 9, time);
1295 break;
1296 case 0x00: // Graphic 1.
1297 vram->colorTable.setMask(base, ~0u << 6, time);
1298 break;
1299 case 0x04: // Graphic 2.
1300 vram->colorTable.setMask(base | (vdpLacksMirroring() ? 0x1800 : 0), ~0u << 13, time);
1301 break;
1302 case 0x08: // Graphic 3.
1303 vram->colorTable.setMask(base, ~0u << 13, time);
1304 break;
1305 default:
1306 // Other display modes do not use a color table.
1307 vram->colorTable.disable(time);
1308 }
1309}
1310
1311void VDP::updatePatternBase(EmuTime::param time)
1312{
1313 unsigned base = (controlRegs[4] << 11) | ~(~0u << 11);
1314 renderer->updatePatternBase(base, time);
1315 switch (displayMode.getBase()) {
1316 case 0x01: // Text 1.
1317 case 0x05: // Text 1 Q.
1318 case 0x09: // Text 2.
1319 case 0x00: // Graphic 1.
1320 case 0x02: // Multicolor.
1321 case 0x06: // Multicolor Q.
1322 vram->patternTable.setMask(base, ~0u << 11, time);
1323 break;
1324 case 0x04: // Graphic 2.
1325 if (vdpHasPatColMirroring()) {
1326 // TMS99XX has weird pattern table behavior: some
1327 // bits of the color-base register leak into the
1328 // pattern-base. See also:
1329 // http://www.youtube.com/watch?v=XJljSJqzDR0
1330 base = (controlRegs[4] << 11)
1331 | ((controlRegs[3] & 0x1f) << 6)
1332 | ~(~0u << 6);
1333 }
1334 vram->patternTable.setMask(base | (vdpLacksMirroring() ? 0x1800 : 0), ~0u << 13, time);
1335 break;
1336 case 0x08: // Graphic 3.
1337 vram->patternTable.setMask(base, ~0u << 13, time);
1338 break;
1339 default:
1340 // Other display modes do not use a pattern table.
1341 vram->patternTable.disable(time);
1342 }
1343}
1344
1345void VDP::updateSpriteAttributeBase(EmuTime::param time)
1346{
1347 int mode = displayMode.getSpriteMode(isMSX1VDP());
1348 if (mode == 0) {
1349 vram->spriteAttribTable.disable(time);
1350 return;
1351 }
1352 unsigned baseMask = (controlRegs[11] << 15) | (controlRegs[5] << 7) | ~(~0u << 7);
1353 unsigned indexMask = mode == 1 ? ~0u << 7 : ~0u << 10;
1354 if (displayMode.isPlanar()) {
1355 baseMask = ((baseMask << 16) | (baseMask >> 1)) & 0x1FFFF;
1356 indexMask = ((indexMask << 16) | ~(1 << 16)) & (indexMask >> 1);
1357 }
1358 vram->spriteAttribTable.setMask(baseMask, indexMask, time);
1359}
1360
1361void VDP::updateSpritePatternBase(EmuTime::param time)
1362{
1363 if (displayMode.getSpriteMode(isMSX1VDP()) == 0) {
1364 vram->spritePatternTable.disable(time);
1365 return;
1366 }
1367 unsigned baseMask = (controlRegs[6] << 11) | ~(~0u << 11);
1368 unsigned indexMask = ~0u << 11;
1369 if (displayMode.isPlanar()) {
1370 baseMask = ((baseMask << 16) | (baseMask >> 1)) & 0x1FFFF;
1371 indexMask = ((indexMask << 16) | ~(1 << 16)) & (indexMask >> 1);
1372 }
1373 vram->spritePatternTable.setMask(baseMask, indexMask, time);
1374}
1375
1376void VDP::updateDisplayMode(DisplayMode newMode, bool cmdBit, EmuTime::param time)
1377{
1378 // Synchronize subsystems.
1379 vram->updateDisplayMode(newMode, cmdBit, time);
1380
1381 // TODO: Is this a useful optimisation, or doesn't it help
1382 // in practice?
1383 // What aspects have changed:
1384 // Switched from planar to non-planar or vice versa.
1385 bool planarChange =
1386 newMode.isPlanar() != displayMode.isPlanar();
1387 // Sprite mode changed.
1388 bool msx1 = isMSX1VDP();
1389 bool spriteModeChange =
1390 newMode.getSpriteMode(msx1) != displayMode.getSpriteMode(msx1);
1391
1392 // Commit the new display mode.
1393 displayMode = newMode;
1394
1395 // Speed up performance of bitmap/character mode splits:
1396 // leave last used character mode active.
1397 // TODO: Disable it if not used for some time.
1398 if (!displayMode.isBitmapMode()) {
1399 updateColorBase(time);
1400 updatePatternBase(time);
1401 }
1402 if (planarChange || spriteModeChange) {
1403 updateSpritePatternBase(time);
1404 updateSpriteAttributeBase(time);
1405 }
1406 updateNameBase(time);
1407
1408 // To be extremely accurate, reschedule hscan when changing
1409 // from/to text mode. Text mode has different border width,
1410 // which affects the moment hscan occurs.
1411 // TODO: Why didn't I implement this yet?
1412 // It's one line of code and overhead is not huge either.
1413}
1414
1415void VDP::update(const Setting& setting) noexcept
1416{
1417 assert(&setting == one_of(&cmdTiming, &tooFastAccess));
1418 (void)setting;
1419 brokenCmdTiming = cmdTiming .getEnum();
1420 allowTooFastAccess = tooFastAccess.getEnum();
1421
1422 if (allowTooFastAccess && pendingCpuAccess) [[unlikely]] {
1423 // in allowTooFastAccess-mode, don't schedule CPU-VRAM access
1424 syncCpuVramAccess.removeSyncPoint();
1425 pendingCpuAccess = false;
1426 executeCpuVramAccess(getCurrentTime());
1427 }
1428}
1429
1430/*
1431 * Roughly measured RGB values in volts.
1432 * Voltages were in range of 1.12-5.04, and had 2 digits accuracy (it seems
1433 * minimum difference was 0.04 V).
1434 * Blue component of color 5 and red component of color 9 were higher than
1435 * the components for white... There are several methods to handle this...
1436 * 1) clip to values of white
1437 * 2) scale all colors by min/max of that component (means white is not 3x 255)
1438 * 3) scale per color if components for that color are beyond those of white
1439 * 4) assume the analog values are output by a DA converter, derive the digital
1440 * values and scale that to the range 0-255 (thanks to FRS for this idea).
1441 * This also results in white not being 3x 255, of course.
1442 *
1443 * Method 4 results in the table below and seems the most accurate (so far).
1444 *
1445 * Thanks to Tiago Valença and Carlos Mansur for measuring on a T7937A.
1446 */
1447static constexpr std::array<std::array<uint8_t, 3>, 16> TOSHIBA_PALETTE = {{
1448 { 0, 0, 0 },
1449 { 0, 0, 0 },
1450 { 102, 204, 102 },
1451 { 136, 238, 136 },
1452 { 68, 68, 221 },
1453 { 119, 119, 255 },
1454 { 187, 85, 85 },
1455 { 119, 221, 221 },
1456 { 221, 102, 102 },
1457 { 255, 119, 119 },
1458 { 204, 204, 85 },
1459 { 238, 238, 136 },
1460 { 85, 170, 85 },
1461 { 187, 85, 187 },
1462 { 204, 204, 204 },
1463 { 238, 238, 238 },
1464}};
1465
1466/*
1467 * Palette for the YM2220 is a crude approximation based on the fact that the
1468 * pictures of a Yamaha AX-150 (YM2220) and a Philips NMS-8250 (V9938) have a
1469 * quite similar appearance. See first post here:
1470 *
1471 * https://www.msx.org/forum/msx-talk/hardware/unknown-vdp-yamaha-ym2220?page=3
1472 */
1473static constexpr std::array<std::array<uint8_t, 3>, 16> YM2220_PALETTE = {{
1474 { 0, 0, 0 },
1475 { 0, 0, 0 },
1476 { 36, 218, 36 },
1477 { 200, 255, 109 },
1478 { 36, 36, 255 },
1479 { 72, 109, 255 },
1480 { 182, 36, 36 },
1481 { 72, 218, 255 },
1482 { 255, 36, 36 },
1483 { 255, 175, 175 },
1484 { 230, 230, 0 },
1485 { 230, 230, 200 },
1486 { 36, 195, 36 },
1487 { 218, 72, 182 },
1488 { 182, 182, 182 },
1489 { 255, 255, 255 },
1490}};
1491
1492/*
1493How come the FM-X has a distinct palette while it clearly has a TMS9928 VDP?
1494Because it has an additional circuit that rework the palette for the same one
1495used in the Fujitsu FM-7. It's encoded in 3-bit RGB.
1496
1497This seems to be the 24-bit RGB equivalent to the palette output by the FM-X on
1498its RGB connector:
1499*/
1500static constexpr std::array<std::array<uint8_t, 3>, 16> THREE_BIT_RGB_PALETTE = {{
1501 { 0, 0, 0 },
1502 { 0, 0, 0 },
1503 { 0, 255, 0 },
1504 { 0, 255, 0 },
1505 { 0, 0, 255 },
1506 { 0, 0, 255 },
1507 { 255, 0, 0 },
1508 { 0, 255, 255 },
1509 { 255, 0, 0 },
1510 { 255, 0, 0 },
1511 { 255, 255, 0 },
1512 { 255, 255, 0 },
1513 { 0, 255, 0 },
1514 { 255, 0, 255 },
1515 { 255, 255, 255 },
1516 { 255, 255, 255 },
1517}};
1518
1519// Source: TMS9918/28/29 Data Book, page 2-17.
1520
1521static constexpr std::array<std::array<float, 3>, 16> TMS9XXXA_ANALOG_OUTPUT = {
1522 // Y R-Y B-Y voltages
1523 std::array{0.00f, 0.47f, 0.47f},
1524 std::array{0.00f, 0.47f, 0.47f},
1525 std::array{0.53f, 0.07f, 0.20f},
1526 std::array{0.67f, 0.17f, 0.27f},
1527 std::array{0.40f, 0.40f, 1.00f},
1528 std::array{0.53f, 0.43f, 0.93f},
1529 std::array{0.47f, 0.83f, 0.30f},
1530 std::array{0.73f, 0.00f, 0.70f},
1531 std::array{0.53f, 0.93f, 0.27f},
1532 std::array{0.67f, 0.93f, 0.27f},
1533 std::array{0.73f, 0.57f, 0.07f},
1534 std::array{0.80f, 0.57f, 0.17f},
1535 std::array{0.47f, 0.13f, 0.23f},
1536 std::array{0.53f, 0.73f, 0.67f},
1537 std::array{0.80f, 0.47f, 0.47f},
1538 std::array{1.00f, 0.47f, 0.47f},
1539};
1540
1541std::array<std::array<uint8_t, 3>, 16> VDP::getMSX1Palette() const
1542{
1543 assert(isMSX1VDP());
1544 if (MSXDevice::getDeviceConfig().findChild("3bitrgboutput") != nullptr) {
1545 return THREE_BIT_RGB_PALETTE;
1546 }
1547 if ((version & VM_TOSHIBA_PALETTE) != 0) {
1548 return TOSHIBA_PALETTE;
1549 }
1550 if ((version & VM_YM2220_PALETTE) != 0) {
1551 return YM2220_PALETTE;
1552 }
1553 std::array<std::array<uint8_t, 3>, 16> tmsPalette;
1554 for (auto color : xrange(16)) {
1555 // convert from analog output to YPbPr
1556 float Y = TMS9XXXA_ANALOG_OUTPUT[color][0];
1557 float Pr = TMS9XXXA_ANALOG_OUTPUT[color][1] - 0.5f;
1558 float Pb = TMS9XXXA_ANALOG_OUTPUT[color][2] - 0.5f;
1559 // apply the saturation
1560 Pr *= (narrow<float>(saturationPr) * (1.0f / 100.0f));
1561 Pb *= (narrow<float>(saturationPb) * (1.0f / 100.0f));
1562 // convert to RGB as follows:
1563 /*
1564 |R| | 1 0 1.402 | |Y |
1565 |G| = | 1 -0.344 -0.714 | x |Pb|
1566 |B| | 1 1.722 0 | |Pr|
1567 */
1568 float R = Y + 0 + 1.402f * Pr;
1569 float G = Y - 0.344f * Pb - 0.714f * Pr;
1570 float B = Y + 1.722f * Pb + 0;
1571 // blow up with factor of 255
1572 R *= 255;
1573 G *= 255;
1574 B *= 255;
1575 // the final result is that these values
1576 // are clipped in the [0:255] range.
1577 // Note: Using roundf instead of std::round because libstdc++ when
1578 // built on top of uClibc lacks std::round; uClibc does provide
1579 // roundf, but lacks other C99 math functions and that makes
1580 // libstdc++ disable all wrappers for C99 math functions.
1581 tmsPalette[color][0] = Math::clipIntToByte(narrow_cast<int>(roundf(R)));
1582 tmsPalette[color][1] = Math::clipIntToByte(narrow_cast<int>(roundf(G)));
1583 tmsPalette[color][2] = Math::clipIntToByte(narrow_cast<int>(roundf(B)));
1584 // std::cerr << color << " " << int(tmsPalette[color][0]) << " " << int(tmsPalette[color][1]) <<" " << int(tmsPalette[color][2]) << '\n';
1585 }
1586 return tmsPalette;
1587}
1588
1589// RegDebug
1590
1591VDP::RegDebug::RegDebug(const VDP& vdp_)
1592 : SimpleDebuggable(vdp_.getMotherBoard(),
1593 vdp_.getName() + " regs", "VDP registers.", 0x40)
1594{
1595}
1596
1597byte VDP::RegDebug::read(unsigned address)
1598{
1599 auto& vdp = OUTER(VDP, vdpRegDebug);
1600 return vdp.peekRegister(address);
1601}
1602
1603void VDP::RegDebug::write(unsigned address, byte value, EmuTime::param time)
1604{
1605 auto& vdp = OUTER(VDP, vdpRegDebug);
1606 // Ignore writes to registers >= 8 on MSX1. An alternative is to only
1607 // expose 8 registers. But changing that now breaks backwards
1608 // compatibility with some existing scripts. E.g. script that queries
1609 // PAL vs NTSC in a VDP agnostic way.
1610 if ((address >= 8) && vdp.isMSX1VDP()) return;
1611 vdp.changeRegister(narrow<byte>(address), value, time);
1612}
1613
1614
1615// StatusRegDebug
1616
1617VDP::StatusRegDebug::StatusRegDebug(const VDP& vdp_)
1618 : SimpleDebuggable(vdp_.getMotherBoard(),
1619 vdp_.getName() + " status regs", "VDP status registers.", 0x10)
1620{
1621}
1622
1623byte VDP::StatusRegDebug::read(unsigned address, EmuTime::param time)
1624{
1625 auto& vdp = OUTER(VDP, vdpStatusRegDebug);
1626 return vdp.peekStatusReg(narrow<byte>(address), time);
1627}
1628
1629
1630// PaletteDebug
1631
1632VDP::PaletteDebug::PaletteDebug(const VDP& vdp_)
1633 : SimpleDebuggable(vdp_.getMotherBoard(),
1634 vdp_.getName() + " palette", "V99x8 palette (RBG format)", 0x20)
1635{
1636}
1637
1638byte VDP::PaletteDebug::read(unsigned address)
1639{
1640 auto& vdp = OUTER(VDP, vdpPaletteDebug);
1641 word grb = vdp.getPalette(address / 2);
1642 return (address & 1) ? narrow_cast<byte>(grb >> 8)
1643 : narrow_cast<byte>(grb & 0xff);
1644}
1645
1646void VDP::PaletteDebug::write(unsigned address, byte value, EmuTime::param time)
1647{
1648 auto& vdp = OUTER(VDP, vdpPaletteDebug);
1649 // Ignore writes on MSX1. An alternative could be to not expose the
1650 // palette at all, but allowing read-only access could be useful for
1651 // some scripts.
1652 if (vdp.isMSX1VDP()) return;
1653
1654 unsigned index = address / 2;
1655 word grb = vdp.getPalette(index);
1656 grb = (address & 1)
1657 ? word((grb & 0x0077) | ((value & 0x07) << 8))
1658 : word((grb & 0x0700) | ((value & 0x77) << 0));
1659 vdp.setPalette(index, grb, time);
1660}
1661
1662
1663// class VRAMPointerDebug
1664
1665VDP::VRAMPointerDebug::VRAMPointerDebug(const VDP& vdp_)
1666 : SimpleDebuggable(vdp_.getMotherBoard(), vdp_.getName() == "VDP" ?
1667 "VRAM pointer" : vdp_.getName() + " VRAM pointer",
1668 "VDP VRAM pointer (14 lower bits)", 2)
1669{
1670}
1671
1672byte VDP::VRAMPointerDebug::read(unsigned address)
1673{
1674 auto& vdp = OUTER(VDP, vramPointerDebug);
1675 if (address & 1) {
1676 return narrow_cast<byte>(vdp.vramPointer >> 8); // TODO add read/write mode?
1677 } else {
1678 return narrow_cast<byte>(vdp.vramPointer & 0xFF);
1679 }
1680}
1681
1682void VDP::VRAMPointerDebug::write(unsigned address, byte value, EmuTime::param /*time*/)
1683{
1684 auto& vdp = OUTER(VDP, vramPointerDebug);
1685 int& ptr = vdp.vramPointer;
1686 if (address & 1) {
1687 ptr = (ptr & 0x00FF) | ((value & 0x3F) << 8);
1688 } else {
1689 ptr = (ptr & 0xFF00) | value;
1690 }
1691}
1692
1693// class RegisterLatchStatusDebug
1694
1695VDP::RegisterLatchStatusDebug::RegisterLatchStatusDebug(const VDP &vdp_)
1696 : SimpleDebuggable(vdp_.getMotherBoard(),
1697 vdp_.getName() + " register latch status", "V99x8 register latch status (0 = expecting a value, 1 = expecting a register)", 1)
1698{
1699}
1700
1701byte VDP::RegisterLatchStatusDebug::read(unsigned /*address*/)
1702{
1703 auto& vdp = OUTER(VDP, registerLatchStatusDebug);
1704 return byte(vdp.registerDataStored);
1705}
1706
1707// class VramAccessStatusDebug
1708
1709VDP::VramAccessStatusDebug::VramAccessStatusDebug(const VDP &vdp_)
1710 : SimpleDebuggable(vdp_.getMotherBoard(), vdp_.getName() == "VDP" ?
1711 "VRAM access status" : vdp_.getName() + " VRAM access status",
1712 "VDP VRAM access status (0 = ready to read, 1 = ready to write)", 1)
1713{
1714}
1715
1716byte VDP::VramAccessStatusDebug::read(unsigned /*address*/)
1717{
1718 auto& vdp = OUTER(VDP, vramAccessStatusDebug);
1719 return byte(vdp.writeAccess);
1720}
1721
1722// class PaletteLatchStatusDebug
1723
1724VDP::PaletteLatchStatusDebug::PaletteLatchStatusDebug(const VDP &vdp_)
1725 : SimpleDebuggable(vdp_.getMotherBoard(),
1726 vdp_.getName() + " palette latch status", "V99x8 palette latch status (0 = expecting red & blue, 1 = expecting green)", 1)
1727{
1728}
1729
1730byte VDP::PaletteLatchStatusDebug::read(unsigned /*address*/)
1731{
1732 auto& vdp = OUTER(VDP, paletteLatchStatusDebug);
1733 return byte(vdp.paletteDataStored);
1734}
1735
1736// class DataLatchDebug
1737
1738VDP::DataLatchDebug::DataLatchDebug(const VDP &vdp_)
1739 : SimpleDebuggable(vdp_.getMotherBoard(),
1740 vdp_.getName() + " data latch value", "V99x8 data latch value (byte)", 1)
1741{
1742}
1743
1744byte VDP::DataLatchDebug::read(unsigned /*address*/)
1745{
1746 auto& vdp = OUTER(VDP, dataLatchDebug);
1747 return vdp.dataLatch;
1748}
1749
1750// class Info
1751
1752VDP::Info::Info(VDP& vdp_, const std::string& name_, std::string helpText_)
1753 : InfoTopic(vdp_.getMotherBoard().getMachineInfoCommand(),
1754 strCat(vdp_.getName(), '_', name_))
1755 , vdp(vdp_)
1756 , helpText(std::move(helpText_))
1757{
1758}
1759
1760void VDP::Info::execute(std::span<const TclObject> /*tokens*/, TclObject& result) const
1761{
1762 result = calc(vdp.getCurrentTime());
1763}
1764
1765std::string VDP::Info::help(std::span<const TclObject> /*tokens*/) const
1766{
1767 return helpText;
1768}
1769
1770
1771// class FrameCountInfo
1772
1773VDP::FrameCountInfo::FrameCountInfo(VDP& vdp_)
1774 : Info(vdp_, "frame_count",
1775 "The current frame number, starts counting at 0 "
1776 "when MSX is powered up or reset.")
1777{
1778}
1779
1780int VDP::FrameCountInfo::calc(const EmuTime& /*time*/) const
1781{
1782 return vdp.frameCount;
1783}
1784
1785
1786// class CycleInFrameInfo
1787
1788VDP::CycleInFrameInfo::CycleInFrameInfo(VDP& vdp_)
1789 : Info(vdp_, "cycle_in_frame",
1790 "The number of VDP cycles since the beginning of "
1791 "the current frame. The VDP runs at 6 times the Z80 "
1792 "clock frequency, so at approximately 21.5MHz.")
1793{
1794}
1795
1796int VDP::CycleInFrameInfo::calc(const EmuTime& time) const
1797{
1798 return vdp.getTicksThisFrame(time);
1799}
1800
1801
1802// class LineInFrameInfo
1803
1804VDP::LineInFrameInfo::LineInFrameInfo(VDP& vdp_)
1805 : Info(vdp_, "line_in_frame",
1806 "The absolute line number since the beginning of "
1807 "the current frame. Goes from 0 till 262 (NTSC) or "
1808 "313 (PAL). Note that this number includes the "
1809 "border lines, use 'msx_y_pos' to get MSX "
1810 "coordinates.")
1811{
1812}
1813
1814int VDP::LineInFrameInfo::calc(const EmuTime& time) const
1815{
1816 return vdp.getTicksThisFrame(time) / VDP::TICKS_PER_LINE;
1817}
1818
1819
1820// class CycleInLineInfo
1821
1822VDP::CycleInLineInfo::CycleInLineInfo(VDP& vdp_)
1823 : Info(vdp_, "cycle_in_line",
1824 "The number of VDP cycles since the beginning of "
1825 "the current line. See also 'cycle_in_frame'."
1826 "Note that this includes the cycles in the border, "
1827 "use 'msx_x256_pos' or 'msx_x512_pos' to get MSX "
1828 "coordinates.")
1829{
1830}
1831
1832int VDP::CycleInLineInfo::calc(const EmuTime& time) const
1833{
1834 return vdp.getTicksThisFrame(time) % VDP::TICKS_PER_LINE;
1835}
1836
1837
1838// class MsxYPosInfo
1839
1840VDP::MsxYPosInfo::MsxYPosInfo(VDP& vdp_)
1841 : Info(vdp_, "msx_y_pos",
1842 "Similar to 'line_in_frame', but expressed in MSX "
1843 "coordinates. So lines in the top border have "
1844 "negative coordinates, lines in the bottom border "
1845 "have coordinates bigger or equal to 192 or 212.")
1846{
1847}
1848
1849int VDP::MsxYPosInfo::calc(const EmuTime& time) const
1850{
1851 return (vdp.getTicksThisFrame(time) / VDP::TICKS_PER_LINE) -
1852 vdp.getLineZero();
1853}
1854
1855
1856// class MsxX256PosInfo
1857
1858VDP::MsxX256PosInfo::MsxX256PosInfo(VDP& vdp_)
1859 : Info(vdp_, "msx_x256_pos",
1860 "Similar to 'cycle_in_frame', but expressed in MSX "
1861 "coordinates. So a position in the left border has "
1862 "a negative coordinate and a position in the right "
1863 "border has a coordinated bigger or equal to 256. "
1864 "See also 'msx_x512_pos'.")
1865{
1866}
1867
1868int VDP::MsxX256PosInfo::calc(const EmuTime& time) const
1869{
1870 return ((vdp.getTicksThisFrame(time) % VDP::TICKS_PER_LINE) -
1871 vdp.getLeftSprites()) / 4;
1872}
1873
1874
1875// class MsxX512PosInfo
1876
1877VDP::MsxX512PosInfo::MsxX512PosInfo(VDP& vdp_)
1878 : Info(vdp_, "msx_x512_pos",
1879 "Similar to 'cycle_in_frame', but expressed in "
1880 "'narrow' (screen 7) MSX coordinates. So a position "
1881 "in the left border has a negative coordinate and "
1882 "a position in the right border has a coordinated "
1883 "bigger or equal to 512. See also 'msx_x256_pos'.")
1884{
1885}
1886
1887int VDP::MsxX512PosInfo::calc(const EmuTime& time) const
1888{
1889 return ((vdp.getTicksThisFrame(time) % VDP::TICKS_PER_LINE) -
1890 vdp.getLeftSprites()) / 2;
1891}
1892
1893
1894// version 1: initial version
1895// version 2: added frameCount
1896// version 3: removed verticalAdjust
1897// version 4: removed lineZero
1898// version 5: replace readAhead->cpuVramData, added cpuVramReqIsRead
1899// version 6: added cpuVramReqAddr to solve too_fast_vram_access issue
1900// version 7: removed cpuVramReqAddr again, fixed issue in a different way
1901// version 8: removed 'userData' from Schedulable
1902// version 9: update sprite-enabled-status only once per line
1903// version 10: added writeAccess
1904template<typename Archive>
1905void VDP::serialize(Archive& ar, unsigned serVersion)
1906{
1907 ar.template serializeBase<MSXDevice>(*this);
1908
1909 if (ar.versionAtLeast(serVersion, 8)) {
1910 ar.serialize("syncVSync", syncVSync,
1911 "syncDisplayStart", syncDisplayStart,
1912 "syncVScan", syncVScan,
1913 "syncHScan", syncHScan,
1914 "syncHorAdjust", syncHorAdjust,
1915 "syncSetMode", syncSetMode,
1916 "syncSetBlank", syncSetBlank,
1917 "syncCpuVramAccess", syncCpuVramAccess);
1918 // no need for syncCmdDone (only used for probe)
1919 } else {
1921 {&syncVSync, &syncDisplayStart, &syncVScan,
1922 &syncHScan, &syncHorAdjust, &syncSetMode,
1923 &syncSetBlank, &syncCpuVramAccess});
1924 }
1925
1926 // not serialized
1927 // std::unique_ptr<Renderer> renderer;
1928 // VdpVersion version;
1929 // int controlRegMask;
1930 // byte controlValueMasks[32];
1931 // bool warningPrinted;
1932
1933 ar.serialize("irqVertical", irqVertical,
1934 "irqHorizontal", irqHorizontal,
1935 "frameStartTime", frameStartTime,
1936 "displayStartSyncTime", displayStartSyncTime,
1937 "vScanSyncTime", vScanSyncTime,
1938 "hScanSyncTime", hScanSyncTime,
1939 "displayStart", displayStart,
1940 "horizontalScanOffset", horizontalScanOffset,
1941 "horizontalAdjust", horizontalAdjust,
1942 "registers", controlRegs,
1943 "blinkCount", blinkCount,
1944 "vramPointer", vramPointer,
1945 "palette", palette,
1946 "isDisplayArea", isDisplayArea,
1947 "palTiming", palTiming,
1948 "interlaced", interlaced,
1949 "statusReg0", statusReg0,
1950 "statusReg1", statusReg1,
1951 "statusReg2", statusReg2,
1952 "blinkState", blinkState,
1953 "dataLatch", dataLatch,
1954 "registerDataStored", registerDataStored,
1955 "paletteDataStored", paletteDataStored);
1956 if (ar.versionAtLeast(serVersion, 5)) {
1957 ar.serialize("cpuVramData", cpuVramData,
1958 "cpuVramReqIsRead", cpuVramReqIsRead);
1959 } else {
1960 ar.serialize("readAhead", cpuVramData);
1961 }
1962 ar.serialize("cpuExtendedVram", cpuExtendedVram,
1963 "displayEnabled", displayEnabled);
1964 byte mode = displayMode.getByte();
1965 ar.serialize("displayMode", mode);
1966 displayMode.setByte(mode);
1967
1968 ar.serialize("cmdEngine", *cmdEngine,
1969 "spriteChecker", *spriteChecker, // must come after displayMode
1970 "vram", *vram); // must come after controlRegs and after spriteChecker
1971 if constexpr (Archive::IS_LOADER) {
1972 pendingCpuAccess = syncCpuVramAccess.pendingSyncPoint();
1973 update(tooFastAccess);
1974 }
1975
1976 if (ar.versionAtLeast(serVersion, 2)) {
1977 ar.serialize("frameCount", frameCount);
1978 } else {
1979 assert(Archive::IS_LOADER);
1980 // We could estimate the frameCount (assume framerate was
1981 // constant the whole time). But I think it's better to have
1982 // an obviously wrong value than an almost correct value.
1983 frameCount = 0;
1984 }
1985
1986 if (ar.versionAtLeast(serVersion, 9)) {
1987 ar.serialize("syncSetSprites", syncSetSprites);
1988 ar.serialize("spriteEnabled", spriteEnabled);
1989 } else {
1990 assert(Archive::IS_LOADER);
1991 spriteEnabled = (controlRegs[8] & 0x02) == 0;
1992 }
1993 if (ar.versionAtLeast(serVersion, 10)) {
1994 ar.serialize("writeAccess", writeAccess);
1995 } else {
1996 writeAccess = !cpuVramReqIsRead; // best guess
1997 }
1998
1999 // externalVideo does not need serializing. It is set on load by the
2000 // external video source (e.g. PioneerLDControl).
2001 //
2002 // TODO should superimposing be serialized? It cannot be recalculated
2003 // from register values (it depends on the register values at the start
2004 // of this frame). But it will be correct at the start of the next
2005 // frame. Probably good enough.
2006
2007 if constexpr (Archive::IS_LOADER) {
2008 renderer->reInit();
2009 }
2010}
2013
2014} // namespace openmsx
BaseSetting * setting
#define REGISTER_MSXDEVICE(CLASS, NAME)
Definition MSXDevice.hh:356
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:121
RenderSettings & getRenderSettings()
Definition Display.hh:47
void attach(VideoSystemChangeListener &listener)
Definition Display.cc:115
void set()
Set the interrupt request on the bus.
Definition IRQHelper.hh:76
void reset()
Reset the interrupt request on the bus.
Definition IRQHelper.hh:85
bool getState() const
Get the interrupt state.
Definition IRQHelper.hh:105
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:236
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:60
void attach(Observer< T > &observer)
Definition Subject.hh:54
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:639
void changeRegister(byte reg, byte val, EmuTime::param time)
VDP control register has changed, work out the consequences.
Definition VDP.cc:1005
byte peekRegister(unsigned address) const
Definition VDP.cc:745
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:1541
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:740
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:890
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:756
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:1905
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:999
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:884
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:979
std::string_view getVersionString() const
Definition VDP.cc:729
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:897
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