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