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