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 // See comment in writeIO().
988 EmuTime time = time_;
989 if (fixedVDPIOdelayCycles > 0) {
990 time = cpu.waitCyclesZ80(time, fixedVDPIOdelayCycles);
991 }
992
993 assert(isInsideFrame(time));
994
995 registerDataStored = false; // Abort any port #1 writes in progress.
996
997 switch (port & (isMSX1VDP() ? 0x01 : 0x03)) {
998 case 0: // VRAM data read
999 return vramRead(time);
1000 case 1: // Status register read
1001 // Calculate status register contents.
1002 return readStatusReg(controlRegs[15], time);
1003 case 2:
1004 case 3:
1005 return 0xFF;
1006 default:
1008 }
1009}
1010
1011byte VDP::peekIO(word /*port*/, EmuTime::param /*time*/) const
1012{
1013 // TODO not implemented
1014 return 0xFF;
1015}
1016
1017void VDP::changeRegister(byte reg, byte val, EmuTime::param time)
1018{
1019 if (reg >= 32) {
1020 // MXC belongs to CPU interface;
1021 // other bits in this register belong to command engine.
1022 if (reg == 45) {
1023 cpuExtendedVram = (val & 0x40) != 0;
1024 }
1025 // Pass command register writes to command engine.
1026 if (reg < 47) {
1027 cmdEngine->setCmdReg(reg - 32, val, time);
1028 }
1029 return;
1030 }
1031
1032 // Make sure only bits that actually exist are written.
1033 val &= controlValueMasks[reg];
1034 // Determine the difference between new and old value.
1035 byte change = val ^ controlRegs[reg];
1036
1037 // Register 13 is special because writing it resets blinking state,
1038 // even if the value in the register doesn't change.
1039 if (reg == 13) {
1040 // Switch to ON state unless ON period is zero.
1041 if (blinkState == ((val & 0xF0) == 0)) {
1042 renderer->updateBlinkState(!blinkState, time);
1043 blinkState = !blinkState;
1044 }
1045
1046 if ((val & 0xF0) && (val & 0x0F)) {
1047 // Alternating colors, start with ON.
1048 blinkCount = (val >> 4) * 10;
1049 } else {
1050 // Stable color.
1051 blinkCount = 0;
1052 }
1053 // TODO when 'isFastBlinkEnabled()==true' the variables
1054 // 'blinkState' and 'blinkCount' represent the values at line 0.
1055 // This implementation is not correct for the partial remaining
1056 // frame after register 13 got changed.
1057 }
1058
1059 if (!change) return;
1060
1061 // Perform additional tasks before new value becomes active.
1062 switch (reg) {
1063 case 0:
1064 if (change & DisplayMode::REG0_MASK) {
1065 syncAtNextLine(syncSetMode, time);
1066 }
1067 break;
1068 case 1:
1069 if (change & 0x03) {
1070 // Update sprites on size and mag changes.
1071 spriteChecker->updateSpriteSizeMag(val, time);
1072 }
1073 // TODO: Reset vertical IRQ if IE0 is reset?
1074 if (change & DisplayMode::REG1_MASK) {
1075 syncAtNextLine(syncSetMode, time);
1076 }
1077 if (change & 0x40) {
1078 syncAtNextLine(syncSetBlank, time);
1079 }
1080 break;
1081 case 2: {
1082 unsigned base = (val << 10) | ~(~0u << 10);
1083 // TODO:
1084 // I reverted this fix.
1085 // Although the code is correct, there is also a counterpart in the
1086 // renderer that must be updated. I'm too tired now to find it.
1087 // Since name table checking is currently disabled anyway, keeping the
1088 // old code does not hurt.
1089 // Eventually this line should be re-enabled.
1090 /*
1091 if (displayMode.isPlanar()) {
1092 base = ((base << 16) | (base >> 1)) & 0x1FFFF;
1093 }
1094 */
1095 renderer->updateNameBase(base, time);
1096 break;
1097 }
1098 case 7:
1100 if (change & 0xF0) {
1101 renderer->updateForegroundColor(val >> 4, time);
1102 }
1103 if (change & 0x0F) {
1104 renderer->updateBackgroundColor(val & 0x0F, time);
1105 }
1106 } else {
1107 renderer->updateBackgroundColor(val, time);
1108 }
1109 break;
1110 case 8:
1111 if (change & 0x20) {
1112 renderer->updateTransparency((val & 0x20) == 0, time);
1113 spriteChecker->updateTransparency((val & 0x20) == 0, time);
1114 }
1115 if (change & 0x02) {
1116 syncAtNextLine(syncSetSprites, time);
1117 }
1118 if (change & 0x08) {
1119 vram->updateVRMode((val & 0x08) != 0, time);
1120 }
1121 break;
1122 case 12:
1123 if (change & 0xF0) {
1124 renderer->updateBlinkForegroundColor(val >> 4, time);
1125 }
1126 if (change & 0x0F) {
1127 renderer->updateBlinkBackgroundColor(val & 0x0F, time);
1128 }
1129 break;
1130 case 16:
1131 // Any half-finished palette loads are aborted.
1132 paletteDataStored = false;
1133 break;
1134 case 18:
1135 if (change & 0x0F) {
1136 syncAtNextLine(syncHorAdjust, time);
1137 }
1138 break;
1139 case 23:
1140 spriteChecker->updateVerticalScroll(val, time);
1141 renderer->updateVerticalScroll(val, time);
1142 break;
1143 case 25:
1144 if (change & (DisplayMode::REG25_MASK | 0x40)) {
1145 updateDisplayMode(getDisplayMode().updateReg25(val),
1146 val & 0x40,
1147 time);
1148 }
1149 if (change & 0x08) {
1150 syncAtNextLine(syncHorAdjust, time);
1151 }
1152 if (change & 0x02) {
1153 renderer->updateBorderMask((val & 0x02) != 0, time);
1154 }
1155 if (change & 0x01) {
1156 renderer->updateMultiPage((val & 0x01) != 0, time);
1157 }
1158 break;
1159 case 26:
1160 renderer->updateHorizontalScrollHigh(val, time);
1161 break;
1162 case 27:
1163 renderer->updateHorizontalScrollLow(val, time);
1164 break;
1165 }
1166
1167 // Commit the change.
1168 controlRegs[reg] = val;
1169
1170 // Perform additional tasks after new value became active.
1171 // Because base masks cannot be read from the VDP, updating them after
1172 // the commit is equivalent to updating before.
1173 switch (reg) {
1174 case 0:
1175 if (change & 0x10) { // IE1
1176 if (val & 0x10) {
1177 scheduleHScan(time);
1178 } else {
1179 irqHorizontal.reset();
1180 }
1181 }
1182 break;
1183 case 1:
1184 if (change & 0x20) { // IE0
1185 if (val & 0x20) {
1186 // This behaviour is important. Without it,
1187 // the intro music in 'Andonis' is way too slow
1188 // and the intro logo of 'Zanac' is corrupted.
1189 if (statusReg0 & 0x80) {
1190 irqVertical.set();
1191 }
1192 } else {
1193 irqVertical.reset();
1194 }
1195 }
1196 if ((change & 0x80) && isVDPwithVRAMremapping()) {
1197 // confirmed: VRAM remapping only happens on TMS99xx
1198 // see VDPVRAM for details on the remapping itself
1199 vram->change4k8kMapping((val & 0x80) != 0);
1200 }
1201 break;
1202 case 2:
1203 updateNameBase(time);
1204 break;
1205 case 3:
1206 case 10:
1207 updateColorBase(time);
1208 if (vdpHasPatColMirroring()) updatePatternBase(time);
1209 break;
1210 case 4:
1211 updatePatternBase(time);
1212 break;
1213 case 5:
1214 case 11:
1215 updateSpriteAttributeBase(time);
1216 break;
1217 case 6:
1218 updateSpritePatternBase(time);
1219 break;
1220 case 9:
1221 if ((val & 1) && ! warningPrinted) {
1222 warningPrinted = true;
1223 dotClockDirectionCallback.execute();
1224 // TODO: Emulate such behaviour.
1225 }
1226 if (change & 0x80) {
1227 /*
1228 cerr << "changed to " << (val & 0x80 ? 212 : 192) << " lines"
1229 << " at line " << (getTicksThisFrame(time) / TICKS_PER_LINE) << "\n";
1230 */
1231 // Display lines (192/212) determines display start and end.
1232 // TODO: Find out exactly when display start is fixed.
1233 // If it is fixed at VSYNC that would simplify things,
1234 // but I think it's more likely the current
1235 // implementation is accurate.
1236 if (time < displayStartSyncTime) {
1237 // Display start is not fixed yet.
1238 scheduleDisplayStart(time);
1239 } else {
1240 // Display start is fixed, but display end is not.
1241 scheduleVScan(time);
1242 }
1243 }
1244 break;
1245 case 19:
1246 case 23:
1247 scheduleHScan(time);
1248 break;
1249 case 25:
1250 if (change & 0x01) {
1251 updateNameBase(time);
1252 }
1253 break;
1254 }
1255}
1256
1257void VDP::syncAtNextLine(SyncBase& type, EmuTime::param time) const
1258{
1259 // The processing of a new line starts in the middle of the left erase,
1260 // ~144 cycles after the sync signal. Adjust affects it. See issue #1310.
1261 int offset = 144 + (horizontalAdjust - 7) * 4;
1262 int line = (getTicksThisFrame(time) + TICKS_PER_LINE - offset) / TICKS_PER_LINE;
1263 int ticks = line * TICKS_PER_LINE + offset;
1264 EmuTime nextTime = frameStartTime + ticks;
1265 type.setSyncPoint(nextTime);
1266}
1267
1268void VDP::updateNameBase(EmuTime::param time)
1269{
1270 unsigned base = (controlRegs[2] << 10) | ~(~0u << 10);
1271 // TODO:
1272 // I reverted this fix.
1273 // Although the code is correct, there is also a counterpart in the
1274 // renderer that must be updated. I'm too tired now to find it.
1275 // Since name table checking is currently disabled anyway, keeping the
1276 // old code does not hurt.
1277 // Eventually this line should be re-enabled.
1278 /*
1279 if (displayMode.isPlanar()) {
1280 base = ((base << 16) | (base >> 1)) & 0x1FFFF;
1281 }
1282 */
1283 unsigned indexMask =
1284 displayMode.isBitmapMode()
1285 ? ~0u << 17 // TODO: Calculate actual value; how to handle planar?
1286 : ~0u << (displayMode.isTextMode() ? 12 : 10);
1287 if (controlRegs[25] & 0x01) {
1288 // Multi page scrolling. The same bit is used in character and
1289 // (non)planar-bitmap modes.
1290 // TODO test text modes
1291 indexMask &= ~0x8000;
1292 }
1293 vram->nameTable.setMask(base, indexMask, time);
1294}
1295
1296void VDP::updateColorBase(EmuTime::param time)
1297{
1298 unsigned base = (controlRegs[10] << 14) | (controlRegs[3] << 6) | ~(~0u << 6);
1299 renderer->updateColorBase(base, time);
1300 switch (displayMode.getBase()) {
1301 case 0x09: // Text 2.
1302 // TODO: Enable this only if dual color is actually active.
1303 vram->colorTable.setMask(base, ~0u << 9, time);
1304 break;
1305 case 0x00: // Graphic 1.
1306 vram->colorTable.setMask(base, ~0u << 6, time);
1307 break;
1308 case 0x04: // Graphic 2.
1309 vram->colorTable.setMask(base | (vdpLacksMirroring() ? 0x1800 : 0), ~0u << 13, time);
1310 break;
1311 case 0x08: // Graphic 3.
1312 vram->colorTable.setMask(base, ~0u << 13, time);
1313 break;
1314 default:
1315 // Other display modes do not use a color table.
1316 vram->colorTable.disable(time);
1317 }
1318}
1319
1320void VDP::updatePatternBase(EmuTime::param time)
1321{
1322 unsigned base = (controlRegs[4] << 11) | ~(~0u << 11);
1323 renderer->updatePatternBase(base, time);
1324 switch (displayMode.getBase()) {
1325 case 0x01: // Text 1.
1326 case 0x05: // Text 1 Q.
1327 case 0x09: // Text 2.
1328 case 0x00: // Graphic 1.
1329 case 0x02: // Multicolor.
1330 case 0x06: // Multicolor Q.
1331 vram->patternTable.setMask(base, ~0u << 11, time);
1332 break;
1333 case 0x04: // Graphic 2.
1334 if (vdpHasPatColMirroring()) {
1335 // TMS99XX has weird pattern table behavior: some
1336 // bits of the color-base register leak into the
1337 // pattern-base. See also:
1338 // http://www.youtube.com/watch?v=XJljSJqzDR0
1339 base = (controlRegs[4] << 11)
1340 | ((controlRegs[3] & 0x1f) << 6)
1341 | ~(~0u << 6);
1342 }
1343 vram->patternTable.setMask(base | (vdpLacksMirroring() ? 0x1800 : 0), ~0u << 13, time);
1344 break;
1345 case 0x08: // Graphic 3.
1346 vram->patternTable.setMask(base, ~0u << 13, time);
1347 break;
1348 default:
1349 // Other display modes do not use a pattern table.
1350 vram->patternTable.disable(time);
1351 }
1352}
1353
1354void VDP::updateSpriteAttributeBase(EmuTime::param time)
1355{
1356 int mode = displayMode.getSpriteMode(isMSX1VDP());
1357 if (mode == 0) {
1358 vram->spriteAttribTable.disable(time);
1359 return;
1360 }
1361 unsigned baseMask = (controlRegs[11] << 15) | (controlRegs[5] << 7) | ~(~0u << 7);
1362 unsigned indexMask = mode == 1 ? ~0u << 7 : ~0u << 10;
1363 if (displayMode.isPlanar()) {
1364 baseMask = ((baseMask << 16) | (baseMask >> 1)) & 0x1FFFF;
1365 indexMask = ((indexMask << 16) | ~(1 << 16)) & (indexMask >> 1);
1366 }
1367 vram->spriteAttribTable.setMask(baseMask, indexMask, time);
1368}
1369
1370void VDP::updateSpritePatternBase(EmuTime::param time)
1371{
1372 if (displayMode.getSpriteMode(isMSX1VDP()) == 0) {
1373 vram->spritePatternTable.disable(time);
1374 return;
1375 }
1376 unsigned baseMask = (controlRegs[6] << 11) | ~(~0u << 11);
1377 unsigned indexMask = ~0u << 11;
1378 if (displayMode.isPlanar()) {
1379 baseMask = ((baseMask << 16) | (baseMask >> 1)) & 0x1FFFF;
1380 indexMask = ((indexMask << 16) | ~(1 << 16)) & (indexMask >> 1);
1381 }
1382 vram->spritePatternTable.setMask(baseMask, indexMask, time);
1383}
1384
1385void VDP::updateDisplayMode(DisplayMode newMode, bool cmdBit, EmuTime::param time)
1386{
1387 // Synchronize subsystems.
1388 vram->updateDisplayMode(newMode, cmdBit, time);
1389
1390 // TODO: Is this a useful optimisation, or doesn't it help
1391 // in practice?
1392 // What aspects have changed:
1393 // Switched from planar to non-planar or vice versa.
1394 bool planarChange =
1395 newMode.isPlanar() != displayMode.isPlanar();
1396 // Sprite mode changed.
1397 bool msx1 = isMSX1VDP();
1398 bool spriteModeChange =
1399 newMode.getSpriteMode(msx1) != displayMode.getSpriteMode(msx1);
1400
1401 // Commit the new display mode.
1402 displayMode = newMode;
1403
1404 // Speed up performance of bitmap/character mode splits:
1405 // leave last used character mode active.
1406 // TODO: Disable it if not used for some time.
1407 if (!displayMode.isBitmapMode()) {
1408 updateColorBase(time);
1409 updatePatternBase(time);
1410 }
1411 if (planarChange || spriteModeChange) {
1412 updateSpritePatternBase(time);
1413 updateSpriteAttributeBase(time);
1414 }
1415 updateNameBase(time);
1416
1417 // To be extremely accurate, reschedule hscan when changing
1418 // from/to text mode. Text mode has different border width,
1419 // which affects the moment hscan occurs.
1420 // TODO: Why didn't I implement this yet?
1421 // It's one line of code and overhead is not huge either.
1422}
1423
1424void VDP::update(const Setting& setting) noexcept
1425{
1426 assert(&setting == one_of(&cmdTiming, &tooFastAccess));
1427 (void)setting;
1428 brokenCmdTiming = cmdTiming .getEnum();
1429 allowTooFastAccess = tooFastAccess.getEnum();
1430
1431 if (allowTooFastAccess && pendingCpuAccess) [[unlikely]] {
1432 // in allowTooFastAccess-mode, don't schedule CPU-VRAM access
1433 syncCpuVramAccess.removeSyncPoint();
1434 pendingCpuAccess = false;
1435 executeCpuVramAccess(getCurrentTime());
1436 }
1437}
1438
1439/*
1440 * Roughly measured RGB values in volts.
1441 * Voltages were in range of 1.12-5.04, and had 2 digits accuracy (it seems
1442 * minimum difference was 0.04 V).
1443 * Blue component of color 5 and red component of color 9 were higher than
1444 * the components for white... There are several methods to handle this...
1445 * 1) clip to values of white
1446 * 2) scale all colors by min/max of that component (means white is not 3x 255)
1447 * 3) scale per color if components for that color are beyond those of white
1448 * 4) assume the analog values are output by a DA converter, derive the digital
1449 * values and scale that to the range 0-255 (thanks to FRS for this idea).
1450 * This also results in white not being 3x 255, of course.
1451 *
1452 * Method 4 results in the table below and seems the most accurate (so far).
1453 *
1454 * Thanks to Tiago Valença and Carlos Mansur for measuring on a T7937A.
1455 */
1456static constexpr std::array<std::array<uint8_t, 3>, 16> TOSHIBA_PALETTE = {{
1457 { 0, 0, 0 },
1458 { 0, 0, 0 },
1459 { 102, 204, 102 },
1460 { 136, 238, 136 },
1461 { 68, 68, 221 },
1462 { 119, 119, 255 },
1463 { 187, 85, 85 },
1464 { 119, 221, 221 },
1465 { 221, 102, 102 },
1466 { 255, 119, 119 },
1467 { 204, 204, 85 },
1468 { 238, 238, 136 },
1469 { 85, 170, 85 },
1470 { 187, 85, 187 },
1471 { 204, 204, 204 },
1472 { 238, 238, 238 },
1473}};
1474
1475/*
1476 * Palette for the YM2220 is a crude approximation based on the fact that the
1477 * pictures of a Yamaha AX-150 (YM2220) and a Philips NMS-8250 (V9938) have a
1478 * quite similar appearance. See first post here:
1479 *
1480 * https://www.msx.org/forum/msx-talk/hardware/unknown-vdp-yamaha-ym2220?page=3
1481 */
1482static constexpr std::array<std::array<uint8_t, 3>, 16> YM2220_PALETTE = {{
1483 { 0, 0, 0 },
1484 { 0, 0, 0 },
1485 { 36, 218, 36 },
1486 { 200, 255, 109 },
1487 { 36, 36, 255 },
1488 { 72, 109, 255 },
1489 { 182, 36, 36 },
1490 { 72, 218, 255 },
1491 { 255, 36, 36 },
1492 { 255, 175, 175 },
1493 { 230, 230, 0 },
1494 { 230, 230, 200 },
1495 { 36, 195, 36 },
1496 { 218, 72, 182 },
1497 { 182, 182, 182 },
1498 { 255, 255, 255 },
1499}};
1500
1501/*
1502How come the FM-X has a distinct palette while it clearly has a TMS9928 VDP?
1503Because it has an additional circuit that rework the palette for the same one
1504used in the Fujitsu FM-7. It's encoded in 3-bit RGB.
1505
1506This seems to be the 24-bit RGB equivalent to the palette output by the FM-X on
1507its RGB connector:
1508*/
1509static constexpr std::array<std::array<uint8_t, 3>, 16> THREE_BIT_RGB_PALETTE = {{
1510 { 0, 0, 0 },
1511 { 0, 0, 0 },
1512 { 0, 255, 0 },
1513 { 0, 255, 0 },
1514 { 0, 0, 255 },
1515 { 0, 0, 255 },
1516 { 255, 0, 0 },
1517 { 0, 255, 255 },
1518 { 255, 0, 0 },
1519 { 255, 0, 0 },
1520 { 255, 255, 0 },
1521 { 255, 255, 0 },
1522 { 0, 255, 0 },
1523 { 255, 0, 255 },
1524 { 255, 255, 255 },
1525 { 255, 255, 255 },
1526}};
1527
1528// Source: TMS9918/28/29 Data Book, page 2-17.
1529
1530static constexpr std::array<std::array<float, 3>, 16> TMS9XXXA_ANALOG_OUTPUT = {
1531 // Y R-Y B-Y voltages
1532 std::array{0.00f, 0.47f, 0.47f},
1533 std::array{0.00f, 0.47f, 0.47f},
1534 std::array{0.53f, 0.07f, 0.20f},
1535 std::array{0.67f, 0.17f, 0.27f},
1536 std::array{0.40f, 0.40f, 1.00f},
1537 std::array{0.53f, 0.43f, 0.93f},
1538 std::array{0.47f, 0.83f, 0.30f},
1539 std::array{0.73f, 0.00f, 0.70f},
1540 std::array{0.53f, 0.93f, 0.27f},
1541 std::array{0.67f, 0.93f, 0.27f},
1542 std::array{0.73f, 0.57f, 0.07f},
1543 std::array{0.80f, 0.57f, 0.17f},
1544 std::array{0.47f, 0.13f, 0.23f},
1545 std::array{0.53f, 0.73f, 0.67f},
1546 std::array{0.80f, 0.47f, 0.47f},
1547 std::array{1.00f, 0.47f, 0.47f},
1548};
1549
1550std::array<std::array<uint8_t, 3>, 16> VDP::getMSX1Palette() const
1551{
1552 assert(isMSX1VDP());
1553 if (MSXDevice::getDeviceConfig().findChild("3bitrgboutput") != nullptr) {
1554 return THREE_BIT_RGB_PALETTE;
1555 }
1556 if ((version & VM_TOSHIBA_PALETTE) != 0) {
1557 return TOSHIBA_PALETTE;
1558 }
1559 if ((version & VM_YM2220_PALETTE) != 0) {
1560 return YM2220_PALETTE;
1561 }
1562 std::array<std::array<uint8_t, 3>, 16> tmsPalette;
1563 for (auto color : xrange(16)) {
1564 // convert from analog output to YPbPr
1565 float Y = TMS9XXXA_ANALOG_OUTPUT[color][0];
1566 float Pr = TMS9XXXA_ANALOG_OUTPUT[color][1] - 0.5f;
1567 float Pb = TMS9XXXA_ANALOG_OUTPUT[color][2] - 0.5f;
1568 // apply the saturation
1569 Pr *= (narrow<float>(saturationPr) * (1.0f / 100.0f));
1570 Pb *= (narrow<float>(saturationPb) * (1.0f / 100.0f));
1571 // convert to RGB as follows:
1572 /*
1573 |R| | 1 0 1.402 | |Y |
1574 |G| = | 1 -0.344 -0.714 | x |Pb|
1575 |B| | 1 1.722 0 | |Pr|
1576 */
1577 float R = Y + 0 + 1.402f * Pr;
1578 float G = Y - 0.344f * Pb - 0.714f * Pr;
1579 float B = Y + 1.722f * Pb + 0;
1580 // blow up with factor of 255
1581 R *= 255;
1582 G *= 255;
1583 B *= 255;
1584 // the final result is that these values
1585 // are clipped in the [0:255] range.
1586 // Note: Using roundf instead of std::round because libstdc++ when
1587 // built on top of uClibc lacks std::round; uClibc does provide
1588 // roundf, but lacks other C99 math functions and that makes
1589 // libstdc++ disable all wrappers for C99 math functions.
1590 tmsPalette[color][0] = Math::clipIntToByte(narrow_cast<int>(roundf(R)));
1591 tmsPalette[color][1] = Math::clipIntToByte(narrow_cast<int>(roundf(G)));
1592 tmsPalette[color][2] = Math::clipIntToByte(narrow_cast<int>(roundf(B)));
1593 // std::cerr << color << " " << int(tmsPalette[color][0]) << " " << int(tmsPalette[color][1]) <<" " << int(tmsPalette[color][2]) << '\n';
1594 }
1595 return tmsPalette;
1596}
1597
1598// RegDebug
1599
1600VDP::RegDebug::RegDebug(const VDP& vdp_)
1601 : SimpleDebuggable(vdp_.getMotherBoard(),
1602 vdp_.getName() + " regs", "VDP registers.", 0x40)
1603{
1604}
1605
1606byte VDP::RegDebug::read(unsigned address)
1607{
1608 const auto& vdp = OUTER(VDP, vdpRegDebug);
1609 return vdp.peekRegister(address);
1610}
1611
1612void VDP::RegDebug::write(unsigned address, byte value, EmuTime::param time)
1613{
1614 auto& vdp = OUTER(VDP, vdpRegDebug);
1615 // Ignore writes to registers >= 8 on MSX1. An alternative is to only
1616 // expose 8 registers. But changing that now breaks backwards
1617 // compatibility with some existing scripts. E.g. script that queries
1618 // PAL vs NTSC in a VDP agnostic way.
1619 if ((address >= 8) && vdp.isMSX1VDP()) return;
1620 vdp.changeRegister(narrow<byte>(address), value, time);
1621}
1622
1623
1624// StatusRegDebug
1625
1626VDP::StatusRegDebug::StatusRegDebug(const VDP& vdp_)
1627 : SimpleDebuggable(vdp_.getMotherBoard(),
1628 vdp_.getName() + " status regs", "VDP status registers.", 0x10)
1629{
1630}
1631
1632byte VDP::StatusRegDebug::read(unsigned address, EmuTime::param time)
1633{
1634 const auto& vdp = OUTER(VDP, vdpStatusRegDebug);
1635 return vdp.peekStatusReg(narrow<byte>(address), time);
1636}
1637
1638
1639// PaletteDebug
1640
1641VDP::PaletteDebug::PaletteDebug(const VDP& vdp_)
1642 : SimpleDebuggable(vdp_.getMotherBoard(),
1643 vdp_.getName() + " palette", "V99x8 palette (RBG format)", 0x20)
1644{
1645}
1646
1647byte VDP::PaletteDebug::read(unsigned address)
1648{
1649 const auto& vdp = OUTER(VDP, vdpPaletteDebug);
1650 word grb = vdp.getPalette(address / 2);
1651 return (address & 1) ? narrow_cast<byte>(grb >> 8)
1652 : narrow_cast<byte>(grb & 0xff);
1653}
1654
1655void VDP::PaletteDebug::write(unsigned address, byte value, EmuTime::param time)
1656{
1657 auto& vdp = OUTER(VDP, vdpPaletteDebug);
1658 // Ignore writes on MSX1. An alternative could be to not expose the
1659 // palette at all, but allowing read-only access could be useful for
1660 // some scripts.
1661 if (vdp.isMSX1VDP()) return;
1662
1663 unsigned index = address / 2;
1664 word grb = vdp.getPalette(index);
1665 grb = (address & 1)
1666 ? word((grb & 0x0077) | ((value & 0x07) << 8))
1667 : word((grb & 0x0700) | ((value & 0x77) << 0));
1668 vdp.setPalette(index, grb, time);
1669}
1670
1671
1672// class VRAMPointerDebug
1673
1674VDP::VRAMPointerDebug::VRAMPointerDebug(const VDP& vdp_)
1675 : SimpleDebuggable(vdp_.getMotherBoard(), vdp_.getName() == "VDP" ?
1676 "VRAM pointer" : vdp_.getName() + " VRAM pointer",
1677 "VDP VRAM pointer (14 lower bits)", 2)
1678{
1679}
1680
1681byte VDP::VRAMPointerDebug::read(unsigned address)
1682{
1683 const auto& vdp = OUTER(VDP, vramPointerDebug);
1684 if (address & 1) {
1685 return narrow_cast<byte>(vdp.vramPointer >> 8); // TODO add read/write mode?
1686 } else {
1687 return narrow_cast<byte>(vdp.vramPointer & 0xFF);
1688 }
1689}
1690
1691void VDP::VRAMPointerDebug::write(unsigned address, byte value, EmuTime::param /*time*/)
1692{
1693 auto& vdp = OUTER(VDP, vramPointerDebug);
1694 int& ptr = vdp.vramPointer;
1695 if (address & 1) {
1696 ptr = (ptr & 0x00FF) | ((value & 0x3F) << 8);
1697 } else {
1698 ptr = (ptr & 0xFF00) | value;
1699 }
1700}
1701
1702// class RegisterLatchStatusDebug
1703
1704VDP::RegisterLatchStatusDebug::RegisterLatchStatusDebug(const VDP &vdp_)
1705 : SimpleDebuggable(vdp_.getMotherBoard(),
1706 vdp_.getName() + " register latch status", "V99x8 register latch status (0 = expecting a value, 1 = expecting a register)", 1)
1707{
1708}
1709
1710byte VDP::RegisterLatchStatusDebug::read(unsigned /*address*/)
1711{
1712 const auto& vdp = OUTER(VDP, registerLatchStatusDebug);
1713 return byte(vdp.registerDataStored);
1714}
1715
1716// class VramAccessStatusDebug
1717
1718VDP::VramAccessStatusDebug::VramAccessStatusDebug(const VDP &vdp_)
1719 : SimpleDebuggable(vdp_.getMotherBoard(), vdp_.getName() == "VDP" ?
1720 "VRAM access status" : vdp_.getName() + " VRAM access status",
1721 "VDP VRAM access status (0 = ready to read, 1 = ready to write)", 1)
1722{
1723}
1724
1725byte VDP::VramAccessStatusDebug::read(unsigned /*address*/)
1726{
1727 const auto& vdp = OUTER(VDP, vramAccessStatusDebug);
1728 return byte(vdp.writeAccess);
1729}
1730
1731// class PaletteLatchStatusDebug
1732
1733VDP::PaletteLatchStatusDebug::PaletteLatchStatusDebug(const VDP &vdp_)
1734 : SimpleDebuggable(vdp_.getMotherBoard(),
1735 vdp_.getName() + " palette latch status", "V99x8 palette latch status (0 = expecting red & blue, 1 = expecting green)", 1)
1736{
1737}
1738
1739byte VDP::PaletteLatchStatusDebug::read(unsigned /*address*/)
1740{
1741 const auto& vdp = OUTER(VDP, paletteLatchStatusDebug);
1742 return byte(vdp.paletteDataStored);
1743}
1744
1745// class DataLatchDebug
1746
1747VDP::DataLatchDebug::DataLatchDebug(const VDP &vdp_)
1748 : SimpleDebuggable(vdp_.getMotherBoard(),
1749 vdp_.getName() + " data latch value", "V99x8 data latch value (byte)", 1)
1750{
1751}
1752
1753byte VDP::DataLatchDebug::read(unsigned /*address*/)
1754{
1755 const auto& vdp = OUTER(VDP, dataLatchDebug);
1756 return vdp.dataLatch;
1757}
1758
1759// class Info
1760
1761VDP::Info::Info(VDP& vdp_, const std::string& name_, std::string helpText_)
1762 : InfoTopic(vdp_.getMotherBoard().getMachineInfoCommand(),
1763 strCat(vdp_.getName(), '_', name_))
1764 , vdp(vdp_)
1765 , helpText(std::move(helpText_))
1766{
1767}
1768
1769void VDP::Info::execute(std::span<const TclObject> /*tokens*/, TclObject& result) const
1770{
1771 result = calc(vdp.getCurrentTime());
1772}
1773
1774std::string VDP::Info::help(std::span<const TclObject> /*tokens*/) const
1775{
1776 return helpText;
1777}
1778
1779
1780// class FrameCountInfo
1781
1782VDP::FrameCountInfo::FrameCountInfo(VDP& vdp_)
1783 : Info(vdp_, "frame_count",
1784 "The current frame number, starts counting at 0 "
1785 "when MSX is powered up or reset.")
1786{
1787}
1788
1789int VDP::FrameCountInfo::calc(const EmuTime& /*time*/) const
1790{
1791 return vdp.frameCount;
1792}
1793
1794
1795// class CycleInFrameInfo
1796
1797VDP::CycleInFrameInfo::CycleInFrameInfo(VDP& vdp_)
1798 : Info(vdp_, "cycle_in_frame",
1799 "The number of VDP cycles since the beginning of "
1800 "the current frame. The VDP runs at 6 times the Z80 "
1801 "clock frequency, so at approximately 21.5MHz.")
1802{
1803}
1804
1805int VDP::CycleInFrameInfo::calc(const EmuTime& time) const
1806{
1807 return vdp.getTicksThisFrame(time);
1808}
1809
1810
1811// class LineInFrameInfo
1812
1813VDP::LineInFrameInfo::LineInFrameInfo(VDP& vdp_)
1814 : Info(vdp_, "line_in_frame",
1815 "The absolute line number since the beginning of "
1816 "the current frame. Goes from 0 till 262 (NTSC) or "
1817 "313 (PAL). Note that this number includes the "
1818 "border lines, use 'msx_y_pos' to get MSX "
1819 "coordinates.")
1820{
1821}
1822
1823int VDP::LineInFrameInfo::calc(const EmuTime& time) const
1824{
1825 return vdp.getTicksThisFrame(time) / VDP::TICKS_PER_LINE;
1826}
1827
1828
1829// class CycleInLineInfo
1830
1831VDP::CycleInLineInfo::CycleInLineInfo(VDP& vdp_)
1832 : Info(vdp_, "cycle_in_line",
1833 "The number of VDP cycles since the beginning of "
1834 "the current line. See also 'cycle_in_frame'."
1835 "Note that this includes the cycles in the border, "
1836 "use 'msx_x256_pos' or 'msx_x512_pos' to get MSX "
1837 "coordinates.")
1838{
1839}
1840
1841int VDP::CycleInLineInfo::calc(const EmuTime& time) const
1842{
1843 return vdp.getTicksThisFrame(time) % VDP::TICKS_PER_LINE;
1844}
1845
1846
1847// class MsxYPosInfo
1848
1849VDP::MsxYPosInfo::MsxYPosInfo(VDP& vdp_)
1850 : Info(vdp_, "msx_y_pos",
1851 "Similar to 'line_in_frame', but expressed in MSX "
1852 "coordinates. So lines in the top border have "
1853 "negative coordinates, lines in the bottom border "
1854 "have coordinates bigger or equal to 192 or 212.")
1855{
1856}
1857
1858int VDP::MsxYPosInfo::calc(const EmuTime& time) const
1859{
1860 return vdp.getMSXPos(time).y;
1861}
1862
1863
1864// class MsxX256PosInfo
1865
1866VDP::MsxX256PosInfo::MsxX256PosInfo(VDP& vdp_)
1867 : Info(vdp_, "msx_x256_pos",
1868 "Similar to 'cycle_in_frame', but expressed in MSX "
1869 "coordinates. So a position in the left border has "
1870 "a negative coordinate and a position in the right "
1871 "border has a coordinate bigger or equal to 256. "
1872 "See also 'msx_x512_pos'.")
1873{
1874}
1875
1876int VDP::MsxX256PosInfo::calc(const EmuTime& time) const
1877{
1878 return vdp.getMSXPos(time).x / 2;
1879}
1880
1881
1882// class MsxX512PosInfo
1883
1884VDP::MsxX512PosInfo::MsxX512PosInfo(VDP& vdp_)
1885 : Info(vdp_, "msx_x512_pos",
1886 "Similar to 'cycle_in_frame', but expressed in "
1887 "'narrow' (screen 7) MSX coordinates. So a position "
1888 "in the left border has a negative coordinate and "
1889 "a position in the right border has a coordinate "
1890 "bigger or equal to 512. See also 'msx_x256_pos'.")
1891{
1892}
1893
1894int VDP::MsxX512PosInfo::calc(const EmuTime& time) const
1895{
1896 return vdp.getMSXPos(time).x;
1897}
1898
1899
1900// version 1: initial version
1901// version 2: added frameCount
1902// version 3: removed verticalAdjust
1903// version 4: removed lineZero
1904// version 5: replace readAhead->cpuVramData, added cpuVramReqIsRead
1905// version 6: added cpuVramReqAddr to solve too_fast_vram_access issue
1906// version 7: removed cpuVramReqAddr again, fixed issue in a different way
1907// version 8: removed 'userData' from Schedulable
1908// version 9: update sprite-enabled-status only once per line
1909// version 10: added writeAccess
1910template<typename Archive>
1911void VDP::serialize(Archive& ar, unsigned serVersion)
1912{
1913 ar.template serializeBase<MSXDevice>(*this);
1914
1915 if (ar.versionAtLeast(serVersion, 8)) {
1916 ar.serialize("syncVSync", syncVSync,
1917 "syncDisplayStart", syncDisplayStart,
1918 "syncVScan", syncVScan,
1919 "syncHScan", syncHScan,
1920 "syncHorAdjust", syncHorAdjust,
1921 "syncSetMode", syncSetMode,
1922 "syncSetBlank", syncSetBlank,
1923 "syncCpuVramAccess", syncCpuVramAccess);
1924 // no need for syncCmdDone (only used for probe)
1925 } else {
1927 {&syncVSync, &syncDisplayStart, &syncVScan,
1928 &syncHScan, &syncHorAdjust, &syncSetMode,
1929 &syncSetBlank, &syncCpuVramAccess});
1930 }
1931
1932 // not serialized
1933 // std::unique_ptr<Renderer> renderer;
1934 // VdpVersion version;
1935 // int controlRegMask;
1936 // byte controlValueMasks[32];
1937 // bool warningPrinted;
1938
1939 ar.serialize("irqVertical", irqVertical,
1940 "irqHorizontal", irqHorizontal,
1941 "frameStartTime", frameStartTime,
1942 "displayStartSyncTime", displayStartSyncTime,
1943 "vScanSyncTime", vScanSyncTime,
1944 "hScanSyncTime", hScanSyncTime,
1945 "displayStart", displayStart,
1946 "horizontalScanOffset", horizontalScanOffset,
1947 "horizontalAdjust", horizontalAdjust,
1948 "registers", controlRegs,
1949 "blinkCount", blinkCount,
1950 "vramPointer", vramPointer,
1951 "palette", palette,
1952 "isDisplayArea", isDisplayArea,
1953 "palTiming", palTiming,
1954 "interlaced", interlaced,
1955 "statusReg0", statusReg0,
1956 "statusReg1", statusReg1,
1957 "statusReg2", statusReg2,
1958 "blinkState", blinkState,
1959 "dataLatch", dataLatch,
1960 "registerDataStored", registerDataStored,
1961 "paletteDataStored", paletteDataStored);
1962 if (ar.versionAtLeast(serVersion, 5)) {
1963 ar.serialize("cpuVramData", cpuVramData,
1964 "cpuVramReqIsRead", cpuVramReqIsRead);
1965 } else {
1966 ar.serialize("readAhead", cpuVramData);
1967 }
1968 ar.serialize("cpuExtendedVram", cpuExtendedVram,
1969 "displayEnabled", displayEnabled);
1970 byte mode = displayMode.getByte();
1971 ar.serialize("displayMode", mode);
1972 displayMode.setByte(mode);
1973
1974 ar.serialize("cmdEngine", *cmdEngine,
1975 "spriteChecker", *spriteChecker, // must come after displayMode
1976 "vram", *vram); // must come after controlRegs and after spriteChecker
1977 if constexpr (Archive::IS_LOADER) {
1978 pendingCpuAccess = syncCpuVramAccess.pendingSyncPoint();
1979 update(tooFastAccess);
1980 }
1981
1982 if (ar.versionAtLeast(serVersion, 2)) {
1983 ar.serialize("frameCount", frameCount);
1984 } else {
1985 assert(Archive::IS_LOADER);
1986 // We could estimate the frameCount (assume framerate was
1987 // constant the whole time). But I think it's better to have
1988 // an obviously wrong value than an almost correct value.
1989 frameCount = 0;
1990 }
1991
1992 if (ar.versionAtLeast(serVersion, 9)) {
1993 ar.serialize("syncSetSprites", syncSetSprites);
1994 ar.serialize("spriteEnabled", spriteEnabled);
1995 } else {
1996 assert(Archive::IS_LOADER);
1997 spriteEnabled = (controlRegs[8] & 0x02) == 0;
1998 }
1999 if (ar.versionAtLeast(serVersion, 10)) {
2000 ar.serialize("writeAccess", writeAccess);
2001 } else {
2002 writeAccess = !cpuVramReqIsRead; // best guess
2003 }
2004
2005 // externalVideo does not need serializing. It is set on load by the
2006 // external video source (e.g. PioneerLDControl).
2007 //
2008 // TODO should superimposing be serialized? It cannot be recalculated
2009 // from register values (it depends on the register values at the start
2010 // of this frame). But it will be correct at the start of the next
2011 // frame. Probably good enough.
2012
2013 if constexpr (Archive::IS_LOADER) {
2014 renderer->reInit();
2015 }
2016}
2019
2020} // 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:1017
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:1550
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:1911
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:1011
EmuTime getAccessSlot(EmuTime::param time, VDPAccessSlots::Delta delta) const
Get the earliest access slot that is at least 'delta' cycles in the future.
Definition VDP.cc: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