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