50static byte getDelayCycles(
const XMLElement& devices) {
52 if (
const auto* t9769Dev = devices.findChild(
"T9769")) {
53 if (t9769Dev->getChildData(
"subtype") ==
"C") {
58 }
else if (devices.findChild(
"S1990")) {
69 , syncDisplayStart(*this)
72 , syncHorAdjust(*this)
75 , syncSetSprites(*this)
76 , syncCpuVramAccess(*this)
78 , display(getReactor().getDisplay())
79 , cmdTiming (display.getRenderSettings().getCmdTimingSetting())
80 , tooFastAccess(display.getRenderSettings().getTooFastAccessSetting())
82 , vdpStatusRegDebug(*this)
83 , vdpPaletteDebug (*this)
84 , vramPointerDebug (*this)
85 , registerLatchStatusDebug(*this)
86 , vramAccessStatusDebug(*this)
87 , paletteLatchStatusDebug(*this)
88 , dataLatchDebug (*this)
89 , frameCountInfo (*this)
90 , cycleInFrameInfo (*this)
91 , lineInFrameInfo (*this)
92 , cycleInLineInfo (*this)
94 , msxX256PosInfo (*this)
95 , msxX512PosInfo (*this)
96 , frameStartTime(getCurrentTime())
97 , irqVertical (getMotherBoard(), getName() +
".IRQvertical", config)
98 , irqHorizontal(getMotherBoard(), getName() +
".IRQhorizontal", config)
99 , displayStartSyncTime(getCurrentTime())
100 , vScanSyncTime(getCurrentTime())
101 , hScanSyncTime(getCurrentTime())
103 getCommandController(),
104 getName() +
".too_fast_vram_access_callback",
105 "Tcl proc called when the VRAM is read or written too fast",
109 , fixedVDPIOdelayCycles(getDelayCycles(getMotherBoard().getMachineConfig()->getConfig().getChild(
"devices")))
121 int defaultSaturation = 54;
123 const auto& versionString = config.
getChildData(
"version");
124 if (versionString ==
"TMS99X8A") version = TMS99X8A;
125 else if (versionString ==
"TMS9918A") {
127 defaultSaturation = 100;
128 }
else if (versionString ==
"TMS9928A") version = TMS99X8A;
129 else if (versionString ==
"T6950PAL") version = T6950PAL;
130 else if (versionString ==
"T6950NTSC") version = T6950NTSC;
131 else if (versionString ==
"T7937APAL") version = T7937APAL;
132 else if (versionString ==
"T7937ANTSC") version = T7937ANTSC;
133 else if (versionString ==
"TMS91X8") version = TMS91X8;
134 else if (versionString ==
"TMS9118") {
136 defaultSaturation = 100;
137 }
else if (versionString ==
"TMS9128") version = TMS91X8;
138 else if (versionString ==
"TMS9929A") version = TMS9929A;
139 else if (versionString ==
"TMS9129") version = TMS9129;
140 else if (versionString ==
"V9938") version = V9938;
141 else if (versionString ==
"V9958") version = V9958;
142 else if (versionString ==
"YM2220PAL") version = YM2220PAL;
143 else if (versionString ==
"YM2220NTSC") version = YM2220NTSC;
144 else throw MSXException(
"Unknown VDP version \"", versionString,
'"');
147 if ((versionString.find(
"TMS") != 0) && ((config.
findChild(
"saturationPr") !=
nullptr) || (config.
findChild(
"saturationPb") !=
nullptr) || (config.
findChild(
"saturation") !=
nullptr))) {
148 throw MSXException(
"Specifying saturation parameters only makes sense for TMS VDPs");
151 auto getPercentage = [&](std::string_view name, std::string_view extra,
int defaultValue) {
153 if ((result < 0) || (result > 100)) {
155 "Saturation percentage ", extra,
"is not in range 0..100: ", result);
159 int saturation = getPercentage(
"saturation",
"", defaultSaturation);
160 saturationPr = getPercentage(
"saturationPr",
"for Pr component ", saturation);
161 saturationPb = getPercentage(
"saturationPb",
"for Pb component ", saturation);
164 static constexpr std::array<byte, 32> VALUE_MASKS_MSX1 = {
165 0x03, 0xFB, 0x0F, 0xFF, 0x07, 0x7F, 0x07, 0xFF
167 static constexpr std::array<byte, 32> VALUE_MASKS_MSX2 = {
168 0x7E, 0x7F, 0x7F, 0xFF, 0x3F, 0xFF, 0x3F, 0xFF,
169 0xFB, 0xBF, 0x07, 0x03, 0xFF, 0xFF, 0x07, 0x0F,
170 0x0F, 0xBF, 0xFF, 0xFF, 0x3F, 0x3F, 0x3F, 0xFF,
171 0, 0, 0, 0, 0, 0, 0, 0,
173 controlRegMask =
isMSX1VDP() ? 0x07 : 0x3F;
174 controlValueMasks =
isMSX1VDP() ? VALUE_MASKS_MSX1 : VALUE_MASKS_MSX2;
175 if (version == V9958) {
177 controlValueMasks[25] = 0x7F;
178 controlValueMasks[26] = 0x3F;
179 controlValueMasks[27] = 0x07;
188 if (vramSize !=
one_of(16u, 64u, 128u, 192u)) {
190 "VRAM size of ", vramSize,
"kB is not supported!");
192 vram = std::make_unique<VDPVRAM>(*
this, vramSize * 1024, time);
196 spriteChecker = std::make_unique<SpriteChecker>(*
this, renderSettings, time);
197 vram->setSpriteChecker(spriteChecker.get());
201 vram->setCmdEngine(cmdEngine.get());
211 tooFastAccess.
attach(*
this);
212 update(tooFastAccess);
217 tooFastAccess.
detach(*
this);
222void VDP::preVideoSystemChange() noexcept
227void VDP::postVideoSystemChange() noexcept
232void VDP::createRenderer()
238 vram->setRenderer(renderer.get(), frameStartTime.
getTime());
243 return renderer->getPostProcessor();
253 controlRegs[9] |= 0x02;
257 controlRegs[21] = 0x3B;
258 controlRegs[22] = 0x05;
266 cpuVramReqIsRead =
false;
268 cpuExtendedVram =
false;
269 registerDataStored =
false;
271 paletteDataStored =
false;
274 horizontalAdjust = 7;
277 isDisplayArea =
false;
278 displayEnabled =
false;
279 spriteEnabled =
true;
280 superimposing =
nullptr;
281 externalVideo =
nullptr;
285 statusReg1 = (version == V9958 ? 0x04 : 0x00);
290 irqHorizontal.
reset();
293 const std::array<uint16_t, 16> V9938_PALETTE = {
294 0x000, 0x000, 0x611, 0x733, 0x117, 0x327, 0x151, 0x627,
295 0x171, 0x373, 0x661, 0x664, 0x411, 0x265, 0x555, 0x777
298 palette = V9938_PALETTE;
301void VDP::resetMasks(EmuTime::param time)
303 updateNameBase(time);
304 updateColorBase(time);
305 updatePatternBase(time);
306 updateSpriteAttributeBase(time);
307 updateSpritePatternBase(time);
321 syncVSync .removeSyncPoint();
322 syncDisplayStart .removeSyncPoint();
323 syncVScan .removeSyncPoint();
324 syncHScan .removeSyncPoint();
325 syncHorAdjust .removeSyncPoint();
326 syncSetMode .removeSyncPoint();
327 syncSetBlank .removeSyncPoint();
328 syncSetSprites .removeSyncPoint();
329 syncCpuVramAccess.removeSyncPoint();
330 syncCmdDone .removeSyncPoint();
331 pendingCpuAccess =
false;
334 cmdEngine->sync(time);
336 spriteChecker->reset(time);
337 cmdEngine->reset(time);
346 assert(frameCount == 0);
349void VDP::execVSync(EmuTime::param time)
354 renderer->frameEnd(time);
355 spriteChecker->frameEnd(time);
360 blinkState = next.state;
361 blinkCount = next.count;
365 cmdEngine->sync(time);
371void VDP::execDisplayStart(EmuTime::param time)
375 if (!isDisplayArea) {
376 if (displayEnabled) {
377 vram->updateDisplayEnabled(
true, time);
379 isDisplayArea =
true;
383void VDP::execVScan(EmuTime::param time)
392 vram->updateDisplayEnabled(
false, time);
394 isDisplayArea =
false;
398 if (controlRegs[1] & 0x20) {
406 if (controlRegs[0] & 0x10) {
411void VDP::execHorAdjust(EmuTime::param time)
413 int newHorAdjust = (controlRegs[18] & 0x0F) ^ 0x07;
414 if (controlRegs[25] & 0x08) {
417 renderer->updateHorizontalAdjust(newHorAdjust, time);
418 horizontalAdjust = newHorAdjust;
421void VDP::execSetMode(EmuTime::param time)
424 DisplayMode(controlRegs[0], controlRegs[1], controlRegs[25]),
429void VDP::execSetBlank(EmuTime::param time)
431 bool newDisplayEnabled = (controlRegs[1] & 0x40) != 0;
433 vram->updateDisplayEnabled(newDisplayEnabled, time);
435 displayEnabled = newDisplayEnabled;
438void VDP::execSetSprites(EmuTime::param time)
440 bool newSpriteEnabled = (controlRegs[8] & 0x02) == 0;
441 vram->updateSpritesEnabled(newSpriteEnabled, time);
442 spriteEnabled = newSpriteEnabled;
445void VDP::execCpuVramAccess(EmuTime::param time)
447 assert(!allowTooFastAccess);
448 pendingCpuAccess =
false;
449 executeCpuVramAccess(time);
452void VDP::execSyncCmdDone(EmuTime::param time)
454 cmdEngine->sync(time);
460void VDP::scheduleDisplayStart(EmuTime::param time)
463 if (displayStartSyncTime > time) {
464 syncDisplayStart.removeSyncPoint();
473 (palTiming ? 36 : 9) +
474 (controlRegs[9] & 0x80 ? 0 : 10) +
479 displayStartSyncTime = frameStartTime + displayStart;
483 if (displayStartSyncTime > time) {
484 syncDisplayStart.setSyncPoint(displayStartSyncTime);
493void VDP::scheduleVScan(EmuTime::param time)
496 if (vScanSyncTime > time) {
497 syncVScan.removeSyncPoint();
502 vScanSyncTime = frameStartTime +
506 if (vScanSyncTime > time) {
507 syncVScan.setSyncPoint(vScanSyncTime);
512void VDP::scheduleHScan(EmuTime::param time)
515 if (hScanSyncTime > time) {
516 syncHScan.removeSyncPoint();
517 hScanSyncTime = time;
521 horizontalScanOffset = displayStart - (100 + 102)
531 horizontalScanOffset >= ticksPerFrame) {
532 horizontalScanOffset -= ticksPerFrame;
546 int lineCountResetTicks = (8 + getVerticalAdjust()) *
TICKS_PER_LINE;
550 if (horizontalScanOffset >= lineCountResetTicks) {
557 if ((controlRegs[0] & 0x10) && horizontalScanOffset >= 0) {
565 hScanSyncTime = frameStartTime + horizontalScanOffset;
566 if (hScanSyncTime > time) {
567 syncHScan.setSyncPoint(hScanSyncTime);
578void VDP::frameStart(EmuTime::param time)
591 palTiming = (controlRegs[9] & 0x02) != 0;
597 if (blinkCount == 0) {
598 renderer->updateBlinkState(!blinkState, time);
599 blinkState = !blinkState;
600 blinkCount = (blinkState
601 ? controlRegs[13] >> 4 : controlRegs[13] & 0x0F) * 10;
610 if (
const RawFrame* newSuperimposing = (controlRegs[0] & 1) ? externalVideo :
nullptr;
611 superimposing != newSuperimposing) {
612 superimposing = newSuperimposing;
613 renderer->updateSuperimposing(superimposing, time);
617 frameStartTime.
reset(time);
620 scheduleDisplayStart(time);
624 renderer->frameStart(time);
625 spriteChecker->frameStart(time);
641 EmuTime time = time_;
647 if (fixedVDPIOdelayCycles > 0) {
652 switch (port & (
isMSX1VDP() ? 0x01 : 0x03)) {
654 vramWrite(value, time);
655 registerDataStored =
false;
658 if (registerDataStored) {
663 value & controlRegMask,
678 vramPointer = (value << 8 | (vramPointer & 0xFF)) & 0x3FFF;
682 writeAccess = value & 0x40;
683 vramPointer = (value << 8 | dataLatch) & 0x3FFF;
684 if (!(value & 0x40)) {
686 (void)vramRead(time);
689 registerDataStored =
false;
696 vramPointer = (vramPointer & 0x3F00) | value;
699 registerDataStored =
true;
703 if (paletteDataStored) {
704 unsigned index = controlRegs[16];
705 word grb = ((value << 8) | dataLatch) & 0x777;
707 controlRegs[16] = (index + 1) & 0x0F;
708 paletteDataStored =
false;
711 paletteDataStored =
true;
718 byte regNr = controlRegs[17];
720 if ((regNr & 0x80) == 0) {
722 controlRegs[17] = (regNr + 1) & 0x3F;
736 assert(vdpVersionString);
737 return vdpVersionString->getData();
747 if (address < 0x20) {
748 return controlRegs[address];
749 }
else if (address < 0x2F) {
750 return cmdEngine->peekCmdReg(narrow<byte>(address - 0x20));
758 if (palette[index] != grb) {
759 renderer->updatePalette(index, grb, time);
760 palette[index] = grb;
764void VDP::vramWrite(
byte value, EmuTime::param time)
766 scheduleCpuVramAccess(
false, value, time);
769byte VDP::vramRead(EmuTime::param time)
774 byte result = cpuVramData;
777 scheduleCpuVramAccess(
true, dummy, time);
781void VDP::scheduleCpuVramAccess(
bool isRead,
byte write, EmuTime::param time)
785 if (!isRead) cpuVramData = write;
786 cpuVramReqIsRead = isRead;
787 if (pendingCpuAccess) [[unlikely]] {
790 assert(!allowTooFastAccess);
793 if (allowTooFastAccess) [[unlikely]] {
806 assert(!pendingCpuAccess);
807 executeCpuVramAccess(time);
835 pendingCpuAccess =
true;
843void VDP::executeCpuVramAccess(EmuTime::param time)
845 int addr = (controlRegs[14] << 14) | vramPointer;
850 addr = ((addr << 16) | (addr >> 1)) & 0x1FFFF;
853 bool doAccess = [&] {
854 if (!cpuExtendedVram) [[likely]] {
856 }
else if (vram->getSize() == 192 * 1024) [[likely]] {
857 addr = 0x20000 | (addr & 0xFFFF);
864 if (cpuVramReqIsRead) {
865 cpuVramData = vram->cpuRead(addr, time);
867 vram->cpuWrite(addr, cpuVramData, time);
870 if (cpuVramReqIsRead) {
877 vramPointer = (vramPointer + 1) & 0x3FFF;
878 if (vramPointer == 0 && displayMode.
isV9938Mode()) {
880 controlRegs[14] = (controlRegs[14] + 1) & 0x07;
891 EmuTime::param time, EmuTime::param limit)
const
901 spriteChecker->sync(time);
904 if (controlRegs[0] & 0x10) {
905 return statusReg1 | (irqHorizontal.
getState() ? 1:0);
912 if (afterMatch < 0) {
917 int matchLength = (displayMode.
isTextMode() ? 87 : 59)
920 (0 <= afterMatch && afterMatch < matchLength);
930 || ticksThisFrame >= displayEnd;
932 | (getHR(ticksThisFrame) ? 0x20 : 0x00)
934 | cmdEngine->getStatus(time);
937 return byte(spriteChecker->getCollisionX(time));
939 return byte(spriteChecker->getCollisionX(time) >> 8) | 0xFE;
941 return byte(spriteChecker->getCollisionY(time));
943 return byte(spriteChecker->getCollisionY(time) >> 8) | 0xFC;
945 return cmdEngine->readColor(time);
947 return byte(cmdEngine->getBorderX(time));
949 return byte(cmdEngine->getBorderX(time) >> 8) | 0xFE;
955byte VDP::readStatusReg(
byte reg, EmuTime::param time)
960 spriteChecker->resetStatus();
965 if (controlRegs[0] & 0x10) {
966 irqHorizontal.
reset();
970 spriteChecker->resetCollision();
973 cmdEngine->resetColor();
983 registerDataStored =
false;
985 switch (port & (
isMSX1VDP() ? 0x01 : 0x03)) {
987 return vramRead(time);
990 return readStatusReg(controlRegs[15], time);
1011 cpuExtendedVram = (val & 0x40) != 0;
1015 cmdEngine->setCmdReg(reg - 32, val, time);
1021 val &= controlValueMasks[reg];
1023 byte change = val ^ controlRegs[reg];
1029 if (blinkState == ((val & 0xF0) == 0)) {
1030 renderer->updateBlinkState(!blinkState, time);
1031 blinkState = !blinkState;
1034 if ((val & 0xF0) && (val & 0x0F)) {
1036 blinkCount = (val >> 4) * 10;
1047 if (!change)
return;
1053 syncAtNextLine(syncSetMode, time);
1057 if (change & 0x03) {
1059 spriteChecker->updateSpriteSizeMag(val, time);
1063 syncAtNextLine(syncSetMode, time);
1065 if (change & 0x40) {
1066 syncAtNextLine(syncSetBlank, time);
1070 unsigned base = (val << 10) | ~(~0u << 10);
1083 renderer->updateNameBase(base, time);
1088 if (change & 0xF0) {
1089 renderer->updateForegroundColor(val >> 4, time);
1091 if (change & 0x0F) {
1092 renderer->updateBackgroundColor(val & 0x0F, time);
1095 renderer->updateBackgroundColor(val, time);
1099 if (change & 0x20) {
1100 renderer->updateTransparency((val & 0x20) == 0, time);
1101 spriteChecker->updateTransparency((val & 0x20) == 0, time);
1103 if (change & 0x02) {
1104 syncAtNextLine(syncSetSprites, time);
1106 if (change & 0x08) {
1107 vram->updateVRMode((val & 0x08) != 0, time);
1111 if (change & 0xF0) {
1112 renderer->updateBlinkForegroundColor(val >> 4, time);
1114 if (change & 0x0F) {
1115 renderer->updateBlinkBackgroundColor(val & 0x0F, time);
1120 paletteDataStored =
false;
1123 if (change & 0x0F) {
1124 syncAtNextLine(syncHorAdjust, time);
1128 spriteChecker->updateVerticalScroll(val, time);
1129 renderer->updateVerticalScroll(val, time);
1137 if (change & 0x08) {
1138 syncAtNextLine(syncHorAdjust, time);
1140 if (change & 0x02) {
1141 renderer->updateBorderMask((val & 0x02) != 0, time);
1143 if (change & 0x01) {
1144 renderer->updateMultiPage((val & 0x01) != 0, time);
1148 renderer->updateHorizontalScrollHigh(val, time);
1151 renderer->updateHorizontalScrollLow(val, time);
1156 controlRegs[reg] = val;
1163 if (change & 0x10) {
1165 scheduleHScan(time);
1167 irqHorizontal.
reset();
1172 if (change & 0x20) {
1177 if (statusReg0 & 0x80) {
1181 irqVertical.
reset();
1187 vram->change4k8kMapping((val & 0x80) != 0);
1191 updateNameBase(time);
1195 updateColorBase(time);
1199 updatePatternBase(time);
1203 updateSpriteAttributeBase(time);
1206 updateSpritePatternBase(time);
1209 if ((val & 1) && ! warningPrinted) {
1210 warningPrinted =
true;
1212 "The running MSX software has set bit 0 of VDP register 9 "
1213 "(dot clock direction) to one. In an ordinary MSX, "
1214 "the screen would go black and the CPU would stop running.");
1217 if (change & 0x80) {
1227 if (time < displayStartSyncTime) {
1229 scheduleDisplayStart(time);
1232 scheduleVScan(time);
1238 scheduleHScan(time);
1241 if (change & 0x01) {
1242 updateNameBase(time);
1248void VDP::syncAtNextLine(SyncBase& type, EmuTime::param time)
const
1252 int offset = 144 + (horizontalAdjust - 7) * 4;
1255 EmuTime nextTime = frameStartTime + ticks;
1256 type.setSyncPoint(nextTime);
1259void VDP::updateNameBase(EmuTime::param time)
1261 unsigned base = (controlRegs[2] << 10) | ~(~0u << 10);
1274 unsigned indexMask =
1277 : ~0u << (displayMode.
isTextMode() ? 12 : 10);
1278 if (controlRegs[25] & 0x01) {
1282 indexMask &= ~0x8000;
1284 vram->nameTable.setMask(base, indexMask, time);
1287void VDP::updateColorBase(EmuTime::param time)
1289 unsigned base = (controlRegs[10] << 14) | (controlRegs[3] << 6) | ~(~0u << 6);
1290 renderer->updateColorBase(base, time);
1291 switch (displayMode.
getBase()) {
1294 vram->colorTable.setMask(base, ~0u << 9, time);
1297 vram->colorTable.setMask(base, ~0u << 6, time);
1300 vram->colorTable.setMask(base | (
vdpLacksMirroring() ? 0x1800 : 0), ~0u << 13, time);
1303 vram->colorTable.setMask(base, ~0u << 13, time);
1307 vram->colorTable.disable(time);
1311void VDP::updatePatternBase(EmuTime::param time)
1313 unsigned base = (controlRegs[4] << 11) | ~(~0u << 11);
1314 renderer->updatePatternBase(base, time);
1315 switch (displayMode.
getBase()) {
1322 vram->patternTable.setMask(base, ~0u << 11, time);
1330 base = (controlRegs[4] << 11)
1331 | ((controlRegs[3] & 0x1f) << 6)
1334 vram->patternTable.setMask(base | (
vdpLacksMirroring() ? 0x1800 : 0), ~0u << 13, time);
1337 vram->patternTable.setMask(base, ~0u << 13, time);
1341 vram->patternTable.disable(time);
1345void VDP::updateSpriteAttributeBase(EmuTime::param time)
1349 vram->spriteAttribTable.disable(time);
1352 unsigned baseMask = (controlRegs[11] << 15) | (controlRegs[5] << 7) | ~(~0u << 7);
1353 unsigned indexMask = mode == 1 ? ~0u << 7 : ~0u << 10;
1355 baseMask = ((baseMask << 16) | (baseMask >> 1)) & 0x1FFFF;
1356 indexMask = ((indexMask << 16) | ~(1 << 16)) & (indexMask >> 1);
1358 vram->spriteAttribTable.setMask(baseMask, indexMask, time);
1361void VDP::updateSpritePatternBase(EmuTime::param time)
1364 vram->spritePatternTable.disable(time);
1367 unsigned baseMask = (controlRegs[6] << 11) | ~(~0u << 11);
1368 unsigned indexMask = ~0u << 11;
1370 baseMask = ((baseMask << 16) | (baseMask >> 1)) & 0x1FFFF;
1371 indexMask = ((indexMask << 16) | ~(1 << 16)) & (indexMask >> 1);
1373 vram->spritePatternTable.setMask(baseMask, indexMask, time);
1376void VDP::updateDisplayMode(DisplayMode newMode,
bool cmdBit, EmuTime::param time)
1379 vram->updateDisplayMode(newMode, cmdBit, time);
1386 newMode.isPlanar() != displayMode.
isPlanar();
1389 bool spriteModeChange =
1390 newMode.getSpriteMode(msx1) != displayMode.
getSpriteMode(msx1);
1393 displayMode = newMode;
1399 updateColorBase(time);
1400 updatePatternBase(time);
1402 if (planarChange || spriteModeChange) {
1403 updateSpritePatternBase(time);
1404 updateSpriteAttributeBase(time);
1406 updateNameBase(time);
1415void VDP::update(
const Setting&
setting)
noexcept
1419 brokenCmdTiming = cmdTiming .getEnum();
1420 allowTooFastAccess = tooFastAccess.getEnum();
1422 if (allowTooFastAccess && pendingCpuAccess) [[unlikely]] {
1424 syncCpuVramAccess.removeSyncPoint();
1425 pendingCpuAccess =
false;
1426 executeCpuVramAccess(getCurrentTime());
1447static constexpr std::array<std::array<uint8_t, 3>, 16> TOSHIBA_PALETTE = {{
1473static constexpr std::array<std::array<uint8_t, 3>, 16> YM2220_PALETTE = {{
1500static constexpr std::array<std::array<uint8_t, 3>, 16> THREE_BIT_RGB_PALETTE = {{
1521static constexpr std::array<std::array<float, 3>, 16> TMS9XXXA_ANALOG_OUTPUT = {
1523 std::array{0.00f, 0.47f, 0.47f},
1524 std::array{0.00f, 0.47f, 0.47f},
1525 std::array{0.53f, 0.07f, 0.20f},
1526 std::array{0.67f, 0.17f, 0.27f},
1527 std::array{0.40f, 0.40f, 1.00f},
1528 std::array{0.53f, 0.43f, 0.93f},
1529 std::array{0.47f, 0.83f, 0.30f},
1530 std::array{0.73f, 0.00f, 0.70f},
1531 std::array{0.53f, 0.93f, 0.27f},
1532 std::array{0.67f, 0.93f, 0.27f},
1533 std::array{0.73f, 0.57f, 0.07f},
1534 std::array{0.80f, 0.57f, 0.17f},
1535 std::array{0.47f, 0.13f, 0.23f},
1536 std::array{0.53f, 0.73f, 0.67f},
1537 std::array{0.80f, 0.47f, 0.47f},
1538 std::array{1.00f, 0.47f, 0.47f},
1545 return THREE_BIT_RGB_PALETTE;
1547 if ((version & VM_TOSHIBA_PALETTE) != 0) {
1548 return TOSHIBA_PALETTE;
1550 if ((version & VM_YM2220_PALETTE) != 0) {
1551 return YM2220_PALETTE;
1553 std::array<std::array<uint8_t, 3>, 16> tmsPalette;
1554 for (
auto color :
xrange(16)) {
1556 float Y = TMS9XXXA_ANALOG_OUTPUT[color][0];
1557 float Pr = TMS9XXXA_ANALOG_OUTPUT[color][1] - 0.5f;
1558 float Pb = TMS9XXXA_ANALOG_OUTPUT[color][2] - 0.5f;
1560 Pr *= (narrow<float>(saturationPr) * (1.0f / 100.0f));
1561 Pb *= (narrow<float>(saturationPb) * (1.0f / 100.0f));
1568 float R = Y + 0 + 1.402f * Pr;
1569 float G = Y - 0.344f * Pb - 0.714f * Pr;
1570 float B = Y + 1.722f * Pb + 0;
1591VDP::RegDebug::RegDebug(
const VDP& vdp_)
1593 vdp_.getName() +
" regs",
"VDP registers.", 0x40)
1597byte VDP::RegDebug::read(
unsigned address)
1599 auto& vdp =
OUTER(VDP, vdpRegDebug);
1600 return vdp.peekRegister(address);
1603void VDP::RegDebug::write(
unsigned address,
byte value, EmuTime::param time)
1605 auto& vdp =
OUTER(VDP, vdpRegDebug);
1610 if ((address >= 8) && vdp.isMSX1VDP())
return;
1611 vdp.changeRegister(narrow<byte>(address), value, time);
1617VDP::StatusRegDebug::StatusRegDebug(
const VDP& vdp_)
1618 : SimpleDebuggable(vdp_.getMotherBoard(),
1619 vdp_.getName() +
" status regs",
"VDP status registers.", 0x10)
1623byte VDP::StatusRegDebug::read(
unsigned address, EmuTime::param time)
1625 auto& vdp =
OUTER(VDP, vdpStatusRegDebug);
1626 return vdp.peekStatusReg(narrow<byte>(address), time);
1632VDP::PaletteDebug::PaletteDebug(
const VDP& vdp_)
1633 : SimpleDebuggable(vdp_.getMotherBoard(),
1634 vdp_.getName() +
" palette",
"V99x8 palette (RBG format)", 0x20)
1638byte VDP::PaletteDebug::read(
unsigned address)
1640 auto& vdp =
OUTER(VDP, vdpPaletteDebug);
1641 word grb = vdp.getPalette(address / 2);
1642 return (address & 1) ? narrow_cast<byte>(grb >> 8)
1646void VDP::PaletteDebug::write(
unsigned address,
byte value, EmuTime::param time)
1648 auto& vdp =
OUTER(VDP, vdpPaletteDebug);
1652 if (vdp.isMSX1VDP())
return;
1654 unsigned index = address / 2;
1655 word grb = vdp.getPalette(index);
1657 ?
word((grb & 0x0077) | ((value & 0x07) << 8))
1658 :
word((grb & 0x0700) | ((value & 0x77) << 0));
1659 vdp.setPalette(index, grb, time);
1665VDP::VRAMPointerDebug::VRAMPointerDebug(
const VDP& vdp_)
1666 : SimpleDebuggable(vdp_.getMotherBoard(), vdp_.getName() ==
"VDP" ?
1667 "VRAM pointer" : vdp_.getName() +
" VRAM pointer",
1668 "VDP VRAM pointer (14 lower bits)", 2)
1672byte VDP::VRAMPointerDebug::read(
unsigned address)
1674 auto& vdp =
OUTER(VDP, vramPointerDebug);
1676 return narrow_cast<byte>(vdp.vramPointer >> 8);
1678 return narrow_cast<byte>(vdp.vramPointer & 0xFF);
1682void VDP::VRAMPointerDebug::write(
unsigned address,
byte value, EmuTime::param )
1684 auto& vdp =
OUTER(VDP, vramPointerDebug);
1685 int& ptr = vdp.vramPointer;
1687 ptr = (ptr & 0x00FF) | ((value & 0x3F) << 8);
1689 ptr = (ptr & 0xFF00) | value;
1695VDP::RegisterLatchStatusDebug::RegisterLatchStatusDebug(
const VDP &vdp_)
1696 : SimpleDebuggable(vdp_.getMotherBoard(),
1697 vdp_.getName() +
" register latch status",
"V99x8 register latch status (0 = expecting a value, 1 = expecting a register)", 1)
1701byte VDP::RegisterLatchStatusDebug::read(
unsigned )
1703 auto& vdp =
OUTER(VDP, registerLatchStatusDebug);
1704 return byte(vdp.registerDataStored);
1709VDP::VramAccessStatusDebug::VramAccessStatusDebug(
const VDP &vdp_)
1710 : SimpleDebuggable(vdp_.getMotherBoard(), vdp_.getName() ==
"VDP" ?
1711 "VRAM access status" : vdp_.getName() +
" VRAM access status",
1712 "VDP VRAM access status (0 = ready to read, 1 = ready to write)", 1)
1716byte VDP::VramAccessStatusDebug::read(
unsigned )
1718 auto& vdp =
OUTER(VDP, vramAccessStatusDebug);
1719 return byte(vdp.writeAccess);
1724VDP::PaletteLatchStatusDebug::PaletteLatchStatusDebug(
const VDP &vdp_)
1725 : SimpleDebuggable(vdp_.getMotherBoard(),
1726 vdp_.getName() +
" palette latch status",
"V99x8 palette latch status (0 = expecting red & blue, 1 = expecting green)", 1)
1730byte VDP::PaletteLatchStatusDebug::read(
unsigned )
1732 auto& vdp =
OUTER(VDP, paletteLatchStatusDebug);
1733 return byte(vdp.paletteDataStored);
1738VDP::DataLatchDebug::DataLatchDebug(
const VDP &vdp_)
1739 : SimpleDebuggable(vdp_.getMotherBoard(),
1740 vdp_.getName() +
" data latch value",
"V99x8 data latch value (byte)", 1)
1744byte VDP::DataLatchDebug::read(
unsigned )
1746 auto& vdp =
OUTER(VDP, dataLatchDebug);
1747 return vdp.dataLatch;
1752VDP::Info::Info(VDP& vdp_,
const std::string& name_, std::string helpText_)
1753 : InfoTopic(vdp_.getMotherBoard().getMachineInfoCommand(),
1754 strCat(vdp_.getName(),
'_', name_))
1756 , helpText(
std::move(helpText_))
1760void VDP::Info::execute(std::span<const TclObject> , TclObject& result)
const
1762 result = calc(vdp.getCurrentTime());
1765std::string VDP::Info::help(std::span<const TclObject> )
const
1773VDP::FrameCountInfo::FrameCountInfo(VDP& vdp_)
1774 :
Info(vdp_,
"frame_count",
1775 "The current frame number, starts counting at 0 "
1776 "when MSX is powered up or reset.")
1780int VDP::FrameCountInfo::calc(
const EmuTime& )
const
1782 return vdp.frameCount;
1788VDP::CycleInFrameInfo::CycleInFrameInfo(VDP& vdp_)
1789 :
Info(vdp_,
"cycle_in_frame",
1790 "The number of VDP cycles since the beginning of "
1791 "the current frame. The VDP runs at 6 times the Z80 "
1792 "clock frequency, so at approximately 21.5MHz.")
1796int VDP::CycleInFrameInfo::calc(
const EmuTime& time)
const
1798 return vdp.getTicksThisFrame(time);
1804VDP::LineInFrameInfo::LineInFrameInfo(VDP& vdp_)
1805 :
Info(vdp_,
"line_in_frame",
1806 "The absolute line number since the beginning of "
1807 "the current frame. Goes from 0 till 262 (NTSC) or "
1808 "313 (PAL). Note that this number includes the "
1809 "border lines, use 'msx_y_pos' to get MSX "
1814int VDP::LineInFrameInfo::calc(
const EmuTime& time)
const
1822VDP::CycleInLineInfo::CycleInLineInfo(VDP& vdp_)
1823 :
Info(vdp_,
"cycle_in_line",
1824 "The number of VDP cycles since the beginning of "
1825 "the current line. See also 'cycle_in_frame'."
1826 "Note that this includes the cycles in the border, "
1827 "use 'msx_x256_pos' or 'msx_x512_pos' to get MSX "
1832int VDP::CycleInLineInfo::calc(
const EmuTime& time)
const
1840VDP::MsxYPosInfo::MsxYPosInfo(VDP& vdp_)
1841 :
Info(vdp_,
"msx_y_pos",
1842 "Similar to 'line_in_frame', but expressed in MSX "
1843 "coordinates. So lines in the top border have "
1844 "negative coordinates, lines in the bottom border "
1845 "have coordinates bigger or equal to 192 or 212.")
1849int VDP::MsxYPosInfo::calc(
const EmuTime& time)
const
1858VDP::MsxX256PosInfo::MsxX256PosInfo(VDP& vdp_)
1859 :
Info(vdp_,
"msx_x256_pos",
1860 "Similar to 'cycle_in_frame', but expressed in MSX "
1861 "coordinates. So a position in the left border has "
1862 "a negative coordinate and a position in the right "
1863 "border has a coordinated bigger or equal to 256. "
1864 "See also 'msx_x512_pos'.")
1868int VDP::MsxX256PosInfo::calc(
const EmuTime& time)
const
1871 vdp.getLeftSprites()) / 4;
1877VDP::MsxX512PosInfo::MsxX512PosInfo(VDP& vdp_)
1878 :
Info(vdp_,
"msx_x512_pos",
1879 "Similar to 'cycle_in_frame', but expressed in "
1880 "'narrow' (screen 7) MSX coordinates. So a position "
1881 "in the left border has a negative coordinate and "
1882 "a position in the right border has a coordinated "
1883 "bigger or equal to 512. See also 'msx_x256_pos'.")
1887int VDP::MsxX512PosInfo::calc(
const EmuTime& time)
const
1890 vdp.getLeftSprites()) / 2;
1904template<
typename Archive>
1907 ar.template serializeBase<MSXDevice>(*
this);
1909 if (ar.versionAtLeast(serVersion, 8)) {
1910 ar.serialize(
"syncVSync", syncVSync,
1911 "syncDisplayStart", syncDisplayStart,
1912 "syncVScan", syncVScan,
1913 "syncHScan", syncHScan,
1914 "syncHorAdjust", syncHorAdjust,
1915 "syncSetMode", syncSetMode,
1916 "syncSetBlank", syncSetBlank,
1917 "syncCpuVramAccess", syncCpuVramAccess);
1921 {&syncVSync, &syncDisplayStart, &syncVScan,
1922 &syncHScan, &syncHorAdjust, &syncSetMode,
1923 &syncSetBlank, &syncCpuVramAccess});
1933 ar.serialize(
"irqVertical", irqVertical,
1934 "irqHorizontal", irqHorizontal,
1935 "frameStartTime", frameStartTime,
1936 "displayStartSyncTime", displayStartSyncTime,
1937 "vScanSyncTime", vScanSyncTime,
1938 "hScanSyncTime", hScanSyncTime,
1939 "displayStart", displayStart,
1940 "horizontalScanOffset", horizontalScanOffset,
1941 "horizontalAdjust", horizontalAdjust,
1942 "registers", controlRegs,
1943 "blinkCount", blinkCount,
1944 "vramPointer", vramPointer,
1946 "isDisplayArea", isDisplayArea,
1947 "palTiming", palTiming,
1948 "interlaced", interlaced,
1949 "statusReg0", statusReg0,
1950 "statusReg1", statusReg1,
1951 "statusReg2", statusReg2,
1952 "blinkState", blinkState,
1953 "dataLatch", dataLatch,
1954 "registerDataStored", registerDataStored,
1955 "paletteDataStored", paletteDataStored);
1956 if (ar.versionAtLeast(serVersion, 5)) {
1957 ar.serialize(
"cpuVramData", cpuVramData,
1958 "cpuVramReqIsRead", cpuVramReqIsRead);
1960 ar.serialize(
"readAhead", cpuVramData);
1962 ar.serialize(
"cpuExtendedVram", cpuExtendedVram,
1963 "displayEnabled", displayEnabled);
1964 byte mode = displayMode.
getByte();
1965 ar.serialize(
"displayMode", mode);
1968 ar.serialize(
"cmdEngine", *cmdEngine,
1969 "spriteChecker", *spriteChecker,
1971 if constexpr (Archive::IS_LOADER) {
1972 pendingCpuAccess = syncCpuVramAccess.pendingSyncPoint();
1973 update(tooFastAccess);
1976 if (ar.versionAtLeast(serVersion, 2)) {
1977 ar.serialize(
"frameCount", frameCount);
1979 assert(Archive::IS_LOADER);
1986 if (ar.versionAtLeast(serVersion, 9)) {
1987 ar.serialize(
"syncSetSprites", syncSetSprites);
1988 ar.serialize(
"spriteEnabled", spriteEnabled);
1990 assert(Archive::IS_LOADER);
1991 spriteEnabled = (controlRegs[8] & 0x02) == 0;
1993 if (ar.versionAtLeast(serVersion, 10)) {
1994 ar.serialize(
"writeAccess", writeAccess);
1996 writeAccess = !cpuVramReqIsRead;
2007 if constexpr (Archive::IS_LOADER) {
#define REGISTER_MSXDEVICE(CLASS, NAME)
void printWarning(std::string_view message)
constexpr void reset(EmuTime::param e)
Reset the clock to start ticking at the given time.
constexpr EmuTime::param getTime() const
Gets the time at which the last clock tick occurred.
const XMLElement * findChild(std::string_view name) const
int getChildDataAsInt(std::string_view name, int defaultValue) const
std::string_view getChildData(std::string_view name) const
constexpr byte getBase() const
Get the base display mode as an integer: M5..M1 combined.
constexpr void setByte(byte mode_)
Used for de-serialization.
constexpr bool isPlanar() const
Is VRAM "planar" in the current display mode? Graphic 6 and 7 spread their bytes over two VRAM ICs,...
constexpr bool isBitmapMode() const
Is the current mode a bitmap mode? Graphic4 and higher are bitmap modes.
static constexpr byte REG25_MASK
Bits of VDP register 25 that encode part of the display mode.
constexpr bool isV9938Mode() const
Was this mode introduced by the V9938?
constexpr void reset()
Bring the display mode to its initial state.
static constexpr byte REG1_MASK
Bits of VDP register 1 that encode part of the display mode.
constexpr bool isTextMode() const
Is the current mode a text mode? Text1 and Text2 are text modes.
constexpr byte getByte() const
Get the display mode as a byte: YAE YJK M5..M1 combined.
constexpr int getSpriteMode(bool isMSX1) const
Get the sprite mode of this display mode.
static constexpr byte REG0_MASK
Bits of VDP register 0 that encode part of the display mode.
void detach(VideoSystemChangeListener &listener)
RenderSettings & getRenderSettings()
void attach(VideoSystemChangeListener &listener)
void set()
Set the interrupt request on the bus.
void reset()
Reset the interrupt request on the bus.
bool getState() const
Get the interrupt state.
EmuTime waitCyclesZ80(EmuTime::param time, unsigned cycles)
An MSXDevice is an emulated hardware component connected to the bus of the emulated MSX.
Reactor & getReactor() const
const XMLElement & getDeviceConfig() const
Get the configuration section for this device.
CommandController & getCommandController() const
EmuTime::param getCurrentTime() const
MSXCliComm & getCliComm() const
A post processor builds the frame that is displayed from the MSX frame, while applying effects such a...
static void restoreOld(Archive &ar, std::vector< Schedulable * > schedulables)
void detach(Observer< T > &observer)
void attach(Observer< T > &observer)
TclObject execute() const
void addDictKeyValues(Args &&... args)
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).
int getLinesPerFrame() const
Gets the number of lines per frame.
int getNumberOfLines() const
Gets the number of display lines per screen.
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.
void changeRegister(byte reg, byte val, EmuTime::param time)
VDP control register has changed, work out the consequences.
byte peekRegister(unsigned address) const
BlinkStateCount calculateLineBlinkState(unsigned line) const
bool isFastBlinkEnabled() const
Get 'fast-blink' status.
std::array< std::array< uint8_t, 3 >, 16 > getMSX1Palette() const
Get the (fixed) palette for this MSX1 VDP.
PostProcessor * getPostProcessor() const
Used by Video9000 to be able to couple the VDP and V9990 output.
bool isVDPwithPALonly() const
Is this a VDP only capable of PAL?
void getExtraDeviceInfo(TclObject &result) const override
bool getCmdBit() const
Are commands possible in non Graphic modes? (V9958 only)
VDPAccessSlots::Calculator getAccessSlotCalculator(EmuTime::param time, EmuTime::param limit) const
Same as getAccessSlot(), but it can be much faster for repeated calls, e.g.
void reset(EmuTime::param time) override
This method is called on reset.
void setPalette(unsigned index, word grb, EmuTime::param time)
Sets a palette entry.
static constexpr int TICKS_PER_LINE
Number of VDP clock ticks per line.
int getTicksPerFrame() const
Gets the number of VDP clock ticks (21MHz) per frame.
bool isInsideFrame(EmuTime::param time) const
Is the given timestamp inside the current frame? Mainly useful for debugging, because relevant timest...
void serialize(Archive &ar, unsigned version)
int getRightBorder() const
Gets the number of VDP clock ticks between start of line and the start of the right border.
DisplayMode getDisplayMode() const
Get the display mode the VDP is in.
bool isMSX1VDP() const
Is this an MSX1 VDP?
bool isVDPwithVRAMremapping() const
Does this VDP have VRAM remapping when switching from 4k to 8/16k mode?
EmuTime::param getFrameStartTime() const
byte peekIO(word port, EmuTime::param time) const override
Read a byte from a given IO port.
EmuTime getAccessSlot(EmuTime::param time, VDPAccessSlots::Delta delta) const
Get the earliest access slot that is at least 'delta' cycles in the future.
bool vdpHasPatColMirroring() const
Is this a VDP that has pattern/color table mirroring?
byte readIO(word port, EmuTime::param time) override
Read a byte from an IO port at a certain time from this device.
std::string_view getVersionString() const
bool isDisplayEnabled() const
Is the display enabled? Both the regular border and forced blanking by clearing the display enable bi...
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.
bool vdpLacksMirroring() const
Is this a VDP that lacks mirroring?
VDP(const DeviceConfig &config)
byte peekStatusReg(byte reg, EmuTime::param time) const
void powerUp(EmuTime::param time) override
This method is called when MSX is powered up.
const XMLElement * findChild(std::string_view childName) const
uint8_t clipIntToByte(int x)
Clip x to range [0,255].
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:
uint8_t byte
8 bit unsigned integer
AmdFlash::SectorInfo Info
uint16_t word
16 bit unsigned integer
constexpr void fill(ForwardRange &&range, const T &value)
constexpr To narrow_cast(From &&from) noexcept
#define OUTER(type, member)
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
constexpr auto xrange(T e)