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