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