46static byte getDelayCycles(
const XMLElement& devices) {
48 if (
const auto* t9769Dev = devices.findChild(
"T9769")) {
49 if (t9769Dev->getChildData(
"subtype") ==
"C") {
54 }
else if (devices.findChild(
"S1990")) {
65 , syncDisplayStart(*this)
68 , syncHorAdjust(*this)
71 , syncSetSprites(*this)
72 , syncCpuVramAccess(*this)
74 , display(getReactor().getDisplay())
75 , cmdTiming (display.getRenderSettings().getCmdTimingSetting())
76 , tooFastAccess(display.getRenderSettings().getTooFastAccessSetting())
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)
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())
99 getCommandController(),
100 getName() +
".too_fast_vram_access_callback",
101 "Tcl proc called when the VRAM is read or written too fast",
105 , fixedVDPIOdelayCycles(getDelayCycles(getMotherBoard().getMachineConfig()->getConfig().getChild(
"devices")))
117 int defaultSaturation = 54;
119 const auto& versionString = config.
getChildData(
"version");
120 if (versionString ==
"TMS99X8A") version = TMS99X8A;
121 else if (versionString ==
"TMS9918A") {
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") {
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,
'"');
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");
147 auto getPercentage = [&](std::string_view name, std::string_view extra,
int defaultValue) {
149 if ((result < 0) || (result > 100)) {
151 "Saturation percentage ", extra,
"is not in range 0..100: ", result);
155 int saturation = getPercentage(
"saturation",
"", defaultSaturation);
156 saturationPr = getPercentage(
"saturationPr",
"for Pr component ", saturation);
157 saturationPb = getPercentage(
"saturationPb",
"for Pb component ", saturation);
160 static constexpr std::array<byte, 32> VALUE_MASKS_MSX1 = {
161 0x03, 0xFB, 0x0F, 0xFF, 0x07, 0x7F, 0x07, 0xFF
163 static constexpr std::array<byte, 32> VALUE_MASKS_MSX2 = {
164 0x7E, 0x7F, 0x7F, 0xFF, 0x3F, 0xFF, 0x3F, 0xFF,
165 0xFB, 0xBF, 0x07, 0x03, 0xFF, 0xFF, 0x07, 0x0F,
166 0x0F, 0xBF, 0xFF, 0xFF, 0x3F, 0x3F, 0x3F, 0xFF,
167 0, 0, 0, 0, 0, 0, 0, 0,
169 controlRegMask =
isMSX1VDP() ? 0x07 : 0x3F;
170 controlValueMasks =
isMSX1VDP() ? VALUE_MASKS_MSX1 : VALUE_MASKS_MSX2;
171 if (version == V9958) {
173 controlValueMasks[25] = 0x7F;
174 controlValueMasks[26] = 0x3F;
175 controlValueMasks[27] = 0x07;
184 if (vramSize !=
one_of(16u, 64u, 128u, 192u)) {
186 "VRAM size of ", vramSize,
"kB is not supported!");
188 vram = std::make_unique<VDPVRAM>(*
this, vramSize * 1024, time);
192 spriteChecker = std::make_unique<SpriteChecker>(*
this, renderSettings, time);
193 vram->setSpriteChecker(spriteChecker.get());
197 vram->setCmdEngine(cmdEngine.get());
207 tooFastAccess.
attach(*
this);
208 update(tooFastAccess);
213 tooFastAccess.
detach(*
this);
218void VDP::preVideoSystemChange() noexcept
223void VDP::postVideoSystemChange() noexcept
228void VDP::createRenderer()
234 vram->setRenderer(renderer.get(), frameStartTime.
getTime());
239 return renderer->getPostProcessor();
249 controlRegs[9] |= 0x02;
253 controlRegs[21] = 0x3B;
254 controlRegs[22] = 0x05;
262 cpuVramReqIsRead =
false;
264 cpuExtendedVram =
false;
265 registerDataStored =
false;
267 paletteDataStored =
false;
270 horizontalAdjust = 7;
273 isDisplayArea =
false;
274 displayEnabled =
false;
275 spriteEnabled =
true;
276 superimposing =
nullptr;
277 externalVideo =
nullptr;
281 statusReg1 = (version == V9958 ? 0x04 : 0x00);
286 irqHorizontal.
reset();
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
294 palette = V9938_PALETTE;
297void VDP::resetMasks(EmuTime::param time)
299 updateNameBase(time);
300 updateColorBase(time);
301 updatePatternBase(time);
302 updateSpriteAttributeBase(time);
303 updateSpritePatternBase(time);
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;
330 cmdEngine->sync(time);
332 spriteChecker->reset(time);
333 cmdEngine->reset(time);
342 assert(frameCount == 0);
345void VDP::execVSync(EmuTime::param time)
350 renderer->frameEnd(time);
351 spriteChecker->frameEnd(time);
356 blinkState = next.state;
357 blinkCount = next.count;
361 cmdEngine->sync(time);
367void VDP::execDisplayStart(EmuTime::param time)
371 if (!isDisplayArea) {
372 if (displayEnabled) {
373 vram->updateDisplayEnabled(
true, time);
375 isDisplayArea =
true;
379void VDP::execVScan(EmuTime::param time)
388 vram->updateDisplayEnabled(
false, time);
390 isDisplayArea =
false;
394 if (controlRegs[1] & 0x20) {
402 if (controlRegs[0] & 0x10) {
407void VDP::execHorAdjust(EmuTime::param time)
409 int newHorAdjust = (controlRegs[18] & 0x0F) ^ 0x07;
410 if (controlRegs[25] & 0x08) {
413 renderer->updateHorizontalAdjust(newHorAdjust, time);
414 horizontalAdjust = newHorAdjust;
417void VDP::execSetMode(EmuTime::param time)
420 DisplayMode(controlRegs[0], controlRegs[1], controlRegs[25]),
425void VDP::execSetBlank(EmuTime::param time)
427 bool newDisplayEnabled = (controlRegs[1] & 0x40) != 0;
429 vram->updateDisplayEnabled(newDisplayEnabled, time);
431 displayEnabled = newDisplayEnabled;
434void VDP::execSetSprites(EmuTime::param time)
436 bool newSpriteEnabled = (controlRegs[8] & 0x02) == 0;
437 vram->updateSpritesEnabled(newSpriteEnabled, time);
438 spriteEnabled = newSpriteEnabled;
441void VDP::execCpuVramAccess(EmuTime::param time)
443 assert(!allowTooFastAccess);
444 pendingCpuAccess =
false;
445 executeCpuVramAccess(time);
448void VDP::execSyncCmdDone(EmuTime::param time)
450 cmdEngine->sync(time);
456void VDP::scheduleDisplayStart(EmuTime::param time)
459 if (displayStartSyncTime > time) {
460 syncDisplayStart.removeSyncPoint();
469 (palTiming ? 36 : 9) +
470 (controlRegs[9] & 0x80 ? 0 : 10) +
475 displayStartSyncTime = frameStartTime + displayStart;
479 if (displayStartSyncTime > time) {
480 syncDisplayStart.setSyncPoint(displayStartSyncTime);
489void VDP::scheduleVScan(EmuTime::param time)
501 if (vScanSyncTime > time) {
502 syncVScan.removeSyncPoint();
507 vScanSyncTime = frameStartTime +
512 if (vScanSyncTime > time) {
513 syncVScan.setSyncPoint(vScanSyncTime);
518void VDP::scheduleHScan(EmuTime::param time)
521 if (hScanSyncTime > time) {
522 syncHScan.removeSyncPoint();
523 hScanSyncTime = time;
527 horizontalScanOffset = displayStart - (100 + 102)
537 if (horizontalScanOffset >= ticksPerFrame) {
538 horizontalScanOffset -= ticksPerFrame;
552 int lineCountResetTicks = (8 + getVerticalAdjust()) *
TICKS_PER_LINE;
556 if (horizontalScanOffset >= lineCountResetTicks) {
563 if ((controlRegs[0] & 0x10) && horizontalScanOffset >= 0) {
571 hScanSyncTime = frameStartTime + horizontalScanOffset;
572 if (hScanSyncTime > time) {
573 syncHScan.setSyncPoint(hScanSyncTime);
584void VDP::frameStart(EmuTime::param time)
599 palTiming = (controlRegs[9] & 0x02) != 0;
605 if (blinkCount == 0) {
606 renderer->updateBlinkState(!blinkState, time);
607 blinkState = !blinkState;
608 blinkCount = (blinkState
609 ? controlRegs[13] >> 4 : controlRegs[13] & 0x0F) * 10;
618 const RawFrame* newSuperimposing = (controlRegs[0] & 1) ? externalVideo :
nullptr;
619 if (superimposing != newSuperimposing) {
620 superimposing = newSuperimposing;
621 renderer->updateSuperimposing(superimposing, time);
625 frameStartTime.
reset(time);
628 scheduleDisplayStart(time);
632 renderer->frameStart(time);
633 spriteChecker->frameStart(time);
649 EmuTime time = time_;
655 if (fixedVDPIOdelayCycles > 0) {
660 switch (port & (
isMSX1VDP() ? 0x01 : 0x03)) {
662 vramWrite(value, time);
663 registerDataStored =
false;
666 if (registerDataStored) {
671 value & controlRegMask,
686 vramPointer = (value << 8 | (vramPointer & 0xFF)) & 0x3FFF;
690 writeAccess = value & 0x40;
691 vramPointer = (value << 8 | dataLatch) & 0x3FFF;
692 if (!(value & 0x40)) {
694 (void)vramRead(time);
697 registerDataStored =
false;
704 vramPointer = (vramPointer & 0x3F00) | value;
707 registerDataStored =
true;
711 if (paletteDataStored) {
712 unsigned index = controlRegs[16];
713 word grb = ((value << 8) | dataLatch) & 0x777;
715 controlRegs[16] = (index + 1) & 0x0F;
716 paletteDataStored =
false;
719 paletteDataStored =
true;
726 byte regNr = controlRegs[17];
728 if ((regNr & 0x80) == 0) {
730 controlRegs[17] = (regNr + 1) & 0x3F;
744 assert(vdpVersionString);
745 return vdpVersionString->getData();
755 if (address < 0x20) {
756 return controlRegs[address];
757 }
else if (address < 0x2F) {
758 return cmdEngine->peekCmdReg(narrow<byte>(address - 0x20));
766 if (palette[index] != grb) {
767 renderer->updatePalette(index, grb, time);
768 palette[index] = grb;
772void VDP::vramWrite(
byte value, EmuTime::param time)
774 scheduleCpuVramAccess(
false, value, time);
777byte VDP::vramRead(EmuTime::param time)
782 byte result = cpuVramData;
785 scheduleCpuVramAccess(
true, dummy, time);
789void VDP::scheduleCpuVramAccess(
bool isRead,
byte write, EmuTime::param time)
793 if (!isRead) cpuVramData = write;
794 cpuVramReqIsRead = isRead;
795 if (pendingCpuAccess) [[unlikely]] {
798 assert(!allowTooFastAccess);
801 if (allowTooFastAccess) [[unlikely]] {
814 assert(!pendingCpuAccess);
815 executeCpuVramAccess(time);
843 pendingCpuAccess =
true;
851void VDP::executeCpuVramAccess(EmuTime::param time)
853 int addr = (controlRegs[14] << 14) | vramPointer;
858 addr = ((addr << 16) | (addr >> 1)) & 0x1FFFF;
861 bool doAccess = [&] {
862 if (!cpuExtendedVram) [[likely]] {
864 }
else if (vram->getSize() == 192 * 1024) [[likely]] {
865 addr = 0x20000 | (addr & 0xFFFF);
872 if (cpuVramReqIsRead) {
873 cpuVramData = vram->cpuRead(addr, time);
875 vram->cpuWrite(addr, cpuVramData, time);
878 if (cpuVramReqIsRead) {
885 vramPointer = (vramPointer + 1) & 0x3FFF;
886 if (vramPointer == 0 && displayMode.
isV9938Mode()) {
888 controlRegs[14] = (controlRegs[14] + 1) & 0x07;
899 EmuTime::param time, EmuTime::param limit)
const
909 spriteChecker->sync(time);
912 if (controlRegs[0] & 0x10) {
913 return statusReg1 | (irqHorizontal.
getState() ? 1:0);
920 if (afterMatch < 0) {
925 int matchLength = (displayMode.
isTextMode() ? 87 : 59)
928 (0 <= afterMatch && afterMatch < matchLength);
938 || ticksThisFrame >= displayEnd;
940 | (getHR(ticksThisFrame) ? 0x20 : 0x00)
942 | cmdEngine->getStatus(time);
945 return byte(spriteChecker->getCollisionX(time));
947 return byte(spriteChecker->getCollisionX(time) >> 8) | 0xFE;
949 return byte(spriteChecker->getCollisionY(time));
951 return byte(spriteChecker->getCollisionY(time) >> 8) | 0xFC;
953 return cmdEngine->readColor(time);
955 return byte(cmdEngine->getBorderX(time));
957 return byte(cmdEngine->getBorderX(time) >> 8) | 0xFE;
963byte VDP::readStatusReg(
byte reg, EmuTime::param time)
968 spriteChecker->resetStatus();
973 if (controlRegs[0] & 0x10) {
974 irqHorizontal.
reset();
978 spriteChecker->resetCollision();
981 cmdEngine->resetColor();
991 registerDataStored =
false;
993 switch (port & (
isMSX1VDP() ? 0x01 : 0x03)) {
995 return vramRead(time);
998 return readStatusReg(controlRegs[15], time);
1019 cpuExtendedVram = (val & 0x40) != 0;
1023 cmdEngine->setCmdReg(reg - 32, val, time);
1029 val &= controlValueMasks[reg];
1031 byte change = val ^ controlRegs[reg];
1037 if (blinkState == ((val & 0xF0) == 0)) {
1038 renderer->updateBlinkState(!blinkState, time);
1039 blinkState = !blinkState;
1042 if ((val & 0xF0) && (val & 0x0F)) {
1044 blinkCount = (val >> 4) * 10;
1055 if (!change)
return;
1061 syncAtNextLine(syncSetMode, time);
1065 if (change & 0x03) {
1067 spriteChecker->updateSpriteSizeMag(val, time);
1071 syncAtNextLine(syncSetMode, time);
1073 if (change & 0x40) {
1074 syncAtNextLine(syncSetBlank, time);
1078 unsigned base = (val << 10) | ~(~0u << 10);
1091 renderer->updateNameBase(base, time);
1096 if (change & 0xF0) {
1097 renderer->updateForegroundColor(val >> 4, time);
1099 if (change & 0x0F) {
1100 renderer->updateBackgroundColor(val & 0x0F, time);
1103 renderer->updateBackgroundColor(val, time);
1107 if (change & 0x20) {
1108 renderer->updateTransparency((val & 0x20) == 0, time);
1109 spriteChecker->updateTransparency((val & 0x20) == 0, time);
1111 if (change & 0x02) {
1112 syncAtNextLine(syncSetSprites, time);
1114 if (change & 0x08) {
1115 vram->updateVRMode((val & 0x08) != 0, time);
1119 if (change & 0xF0) {
1120 renderer->updateBlinkForegroundColor(val >> 4, time);
1122 if (change & 0x0F) {
1123 renderer->updateBlinkBackgroundColor(val & 0x0F, time);
1128 paletteDataStored =
false;
1131 if (change & 0x0F) {
1132 syncAtNextLine(syncHorAdjust, time);
1136 spriteChecker->updateVerticalScroll(val, time);
1137 renderer->updateVerticalScroll(val, time);
1145 if (change & 0x08) {
1146 syncAtNextLine(syncHorAdjust, time);
1148 if (change & 0x02) {
1149 renderer->updateBorderMask((val & 0x02) != 0, time);
1151 if (change & 0x01) {
1152 renderer->updateMultiPage((val & 0x01) != 0, time);
1156 renderer->updateHorizontalScrollHigh(val, time);
1159 renderer->updateHorizontalScrollLow(val, time);
1164 controlRegs[reg] = val;
1171 if (change & 0x10) {
1173 scheduleHScan(time);
1175 irqHorizontal.
reset();
1180 if (change & 0x20) {
1185 if (statusReg0 & 0x80) {
1189 irqVertical.
reset();
1195 vram->change4k8kMapping((val & 0x80) != 0);
1199 updateNameBase(time);
1203 updateColorBase(time);
1207 updatePatternBase(time);
1211 updateSpriteAttributeBase(time);
1214 updateSpritePatternBase(time);
1217 if ((val & 1) && ! warningPrinted) {
1218 warningPrinted =
true;
1220 "The running MSX software has set bit 0 of VDP register 9 "
1221 "(dot clock direction) to one. In an ordinary MSX, "
1222 "the screen would go black and the CPU would stop running.");
1225 if (change & 0x80) {
1235 if (time < displayStartSyncTime) {
1237 scheduleDisplayStart(time);
1240 scheduleVScan(time);
1246 scheduleHScan(time);
1249 if (change & 0x01) {
1250 updateNameBase(time);
1256void VDP::syncAtNextLine(SyncBase& type, EmuTime::param time)
1260 int offset = 144 + (horizontalAdjust - 7) * 4;
1263 EmuTime nextTime = frameStartTime + ticks;
1264 type.setSyncPoint(nextTime);
1267void VDP::updateNameBase(EmuTime::param time)
1269 unsigned base = (controlRegs[2] << 10) | ~(~0u << 10);
1282 unsigned indexMask =
1285 : ~0u << (displayMode.
isTextMode() ? 12 : 10);
1286 if (controlRegs[25] & 0x01) {
1290 indexMask &= ~0x8000;
1292 vram->nameTable.setMask(base, indexMask, time);
1295void VDP::updateColorBase(EmuTime::param time)
1297 unsigned base = (controlRegs[10] << 14) | (controlRegs[3] << 6) | ~(~0u << 6);
1298 renderer->updateColorBase(base, time);
1299 switch (displayMode.
getBase()) {
1302 vram->colorTable.setMask(base, ~0u << 9, time);
1305 vram->colorTable.setMask(base, ~0u << 6, time);
1308 vram->colorTable.setMask(base | (
vdpLacksMirroring() ? 0x1800 : 0), ~0u << 13, time);
1311 vram->colorTable.setMask(base, ~0u << 13, time);
1315 vram->colorTable.disable(time);
1319void VDP::updatePatternBase(EmuTime::param time)
1321 unsigned base = (controlRegs[4] << 11) | ~(~0u << 11);
1322 renderer->updatePatternBase(base, time);
1323 switch (displayMode.
getBase()) {
1330 vram->patternTable.setMask(base, ~0u << 11, time);
1338 base = (controlRegs[4] << 11)
1339 | ((controlRegs[3] & 0x1f) << 6)
1342 vram->patternTable.setMask(base | (
vdpLacksMirroring() ? 0x1800 : 0), ~0u << 13, time);
1345 vram->patternTable.setMask(base, ~0u << 13, time);
1349 vram->patternTable.disable(time);
1353void VDP::updateSpriteAttributeBase(EmuTime::param time)
1357 vram->spriteAttribTable.disable(time);
1360 unsigned baseMask = (controlRegs[11] << 15) | (controlRegs[5] << 7) | ~(~0u << 7);
1361 unsigned indexMask = mode == 1 ? ~0u << 7 : ~0u << 10;
1363 baseMask = ((baseMask << 16) | (baseMask >> 1)) & 0x1FFFF;
1364 indexMask = ((indexMask << 16) | ~(1 << 16)) & (indexMask >> 1);
1366 vram->spriteAttribTable.setMask(baseMask, indexMask, time);
1369void VDP::updateSpritePatternBase(EmuTime::param time)
1372 vram->spritePatternTable.disable(time);
1375 unsigned baseMask = (controlRegs[6] << 11) | ~(~0u << 11);
1376 unsigned indexMask = ~0u << 11;
1378 baseMask = ((baseMask << 16) | (baseMask >> 1)) & 0x1FFFF;
1379 indexMask = ((indexMask << 16) | ~(1 << 16)) & (indexMask >> 1);
1381 vram->spritePatternTable.setMask(baseMask, indexMask, time);
1384void VDP::updateDisplayMode(DisplayMode newMode,
bool cmdBit, EmuTime::param time)
1387 vram->updateDisplayMode(newMode, cmdBit, time);
1394 newMode.isPlanar() != displayMode.
isPlanar();
1397 bool spriteModeChange =
1398 newMode.getSpriteMode(msx1) != displayMode.
getSpriteMode(msx1);
1401 displayMode = newMode;
1407 updateColorBase(time);
1408 updatePatternBase(time);
1410 if (planarChange || spriteModeChange) {
1411 updateSpritePatternBase(time);
1412 updateSpriteAttributeBase(time);
1414 updateNameBase(time);
1423void VDP::update(
const Setting&
setting)
noexcept
1427 brokenCmdTiming = cmdTiming .getEnum();
1428 allowTooFastAccess = tooFastAccess.getEnum();
1430 if (allowTooFastAccess && pendingCpuAccess) [[unlikely]] {
1432 syncCpuVramAccess.removeSyncPoint();
1433 pendingCpuAccess =
false;
1434 executeCpuVramAccess(getCurrentTime());
1455static constexpr std::array<std::array<uint8_t, 3>, 16> TOSHIBA_PALETTE = {{
1481static constexpr std::array<std::array<uint8_t, 3>, 16> YM2220_PALETTE = {{
1508static constexpr std::array<std::array<uint8_t, 3>, 16> THREE_BIT_RGB_PALETTE = {{
1529static constexpr std::array<std::array<float, 3>, 16> TMS9XXXA_ANALOG_OUTPUT = {
1531 std::array{0.00f, 0.47f, 0.47f},
1532 std::array{0.00f, 0.47f, 0.47f},
1533 std::array{0.53f, 0.07f, 0.20f},
1534 std::array{0.67f, 0.17f, 0.27f},
1535 std::array{0.40f, 0.40f, 1.00f},
1536 std::array{0.53f, 0.43f, 0.93f},
1537 std::array{0.47f, 0.83f, 0.30f},
1538 std::array{0.73f, 0.00f, 0.70f},
1539 std::array{0.53f, 0.93f, 0.27f},
1540 std::array{0.67f, 0.93f, 0.27f},
1541 std::array{0.73f, 0.57f, 0.07f},
1542 std::array{0.80f, 0.57f, 0.17f},
1543 std::array{0.47f, 0.13f, 0.23f},
1544 std::array{0.53f, 0.73f, 0.67f},
1545 std::array{0.80f, 0.47f, 0.47f},
1546 std::array{1.00f, 0.47f, 0.47f},
1553 return THREE_BIT_RGB_PALETTE;
1555 if ((version & VM_TOSHIBA_PALETTE) != 0) {
1556 return TOSHIBA_PALETTE;
1558 if ((version & VM_YM2220_PALETTE) != 0) {
1559 return YM2220_PALETTE;
1561 std::array<std::array<uint8_t, 3>, 16> tmsPalette;
1562 for (
auto color :
xrange(16)) {
1564 float Y = TMS9XXXA_ANALOG_OUTPUT[color][0];
1565 float Pr = TMS9XXXA_ANALOG_OUTPUT[color][1] - 0.5f;
1566 float Pb = TMS9XXXA_ANALOG_OUTPUT[color][2] - 0.5f;
1568 Pr *= (narrow<float>(saturationPr) * (1.0f / 100.0f));
1569 Pb *= (narrow<float>(saturationPb) * (1.0f / 100.0f));
1576 float R = Y + 0 + 1.402f * Pr;
1577 float G = Y - 0.344f * Pb - 0.714f * Pr;
1578 float B = Y + 1.722f * Pb + 0;
1599VDP::RegDebug::RegDebug(
VDP& vdp_)
1601 vdp_.getName() +
" regs",
"VDP registers.", 0x40)
1605byte VDP::RegDebug::read(
unsigned address)
1607 auto& vdp =
OUTER(
VDP, vdpRegDebug);
1608 return vdp.peekRegister(address);
1611void VDP::RegDebug::write(
unsigned address,
byte value, EmuTime::param time)
1613 auto& vdp =
OUTER(
VDP, vdpRegDebug);
1618 if ((address >= 8) && vdp.isMSX1VDP())
return;
1619 vdp.changeRegister(narrow<byte>(address), value, time);
1625VDP::StatusRegDebug::StatusRegDebug(
VDP& vdp_)
1626 : SimpleDebuggable(vdp_.getMotherBoard(),
1627 vdp_.getName() +
" status regs",
"VDP status registers.", 0x10)
1631byte VDP::StatusRegDebug::read(
unsigned address, EmuTime::param time)
1633 auto& vdp =
OUTER(
VDP, vdpStatusRegDebug);
1634 return vdp.peekStatusReg(narrow<byte>(address), time);
1640VDP::PaletteDebug::PaletteDebug(
VDP& vdp_)
1641 : SimpleDebuggable(vdp_.getMotherBoard(),
1642 vdp_.getName() +
" palette",
"V99x8 palette (RBG format)", 0x20)
1646byte VDP::PaletteDebug::read(
unsigned address)
1648 auto& vdp =
OUTER(
VDP, vdpPaletteDebug);
1649 word grb = vdp.getPalette(address / 2);
1650 return (address & 1) ? narrow_cast<byte>(grb >> 8)
1654void VDP::PaletteDebug::write(
unsigned address,
byte value, EmuTime::param time)
1656 auto& vdp =
OUTER(
VDP, vdpPaletteDebug);
1660 if (vdp.isMSX1VDP())
return;
1662 unsigned index = address / 2;
1663 word grb = vdp.getPalette(index);
1665 ?
word((grb & 0x0077) | ((value & 0x07) << 8))
1666 :
word((grb & 0x0700) | ((value & 0x77) << 0));
1667 vdp.setPalette(index, grb, time);
1673VDP::VRAMPointerDebug::VRAMPointerDebug(
VDP& vdp_)
1674 : SimpleDebuggable(vdp_.getMotherBoard(), vdp_.getName() ==
"VDP" ?
1675 "VRAM pointer" : vdp_.getName() +
" VRAM pointer",
1676 "VDP VRAM pointer (14 lower bits)", 2)
1680byte VDP::VRAMPointerDebug::read(
unsigned address)
1682 auto& vdp =
OUTER(
VDP, vramPointerDebug);
1684 return narrow_cast<byte>(vdp.vramPointer >> 8);
1686 return narrow_cast<byte>(vdp.vramPointer & 0xFF);
1690void VDP::VRAMPointerDebug::write(
unsigned address,
byte value, EmuTime::param )
1692 auto& vdp =
OUTER(
VDP, vramPointerDebug);
1693 int& ptr = vdp.vramPointer;
1695 ptr = (ptr & 0x00FF) | ((value & 0x3F) << 8);
1697 ptr = (ptr & 0xFF00) | value;
1703VDP::RegisterLatchStatusDebug::RegisterLatchStatusDebug(
VDP &vdp_)
1704 : SimpleDebuggable(vdp_.getMotherBoard(),
1705 vdp_.getName() +
" register latch status",
"V99x8 register latch status (0 = expecting a value, 1 = expecting a register)", 1)
1709byte VDP::RegisterLatchStatusDebug::read(
unsigned )
1711 auto& vdp =
OUTER(
VDP, registerLatchStatusDebug);
1712 return byte(vdp.registerDataStored);
1717VDP::VramAccessStatusDebug::VramAccessStatusDebug(
VDP &vdp_)
1718 : SimpleDebuggable(vdp_.getMotherBoard(), vdp_.getName() ==
"VDP" ?
1719 "VRAM access status" : vdp_.getName() +
" VRAM access status",
1720 "VDP VRAM access status (0 = ready to read, 1 = ready to write)", 1)
1724byte VDP::VramAccessStatusDebug::read(
unsigned )
1726 auto& vdp =
OUTER(
VDP, vramAccessStatusDebug);
1727 return byte(vdp.writeAccess);
1732VDP::PaletteLatchStatusDebug::PaletteLatchStatusDebug(
VDP &vdp_)
1733 : SimpleDebuggable(vdp_.getMotherBoard(),
1734 vdp_.getName() +
" palette latch status",
"V99x8 palette latch status (0 = expecting red & blue, 1 = expecting green)", 1)
1738byte VDP::PaletteLatchStatusDebug::read(
unsigned )
1740 auto& vdp =
OUTER(
VDP, paletteLatchStatusDebug);
1741 return byte(vdp.paletteDataStored);
1746VDP::DataLatchDebug::DataLatchDebug(
VDP &vdp_)
1747 : SimpleDebuggable(vdp_.getMotherBoard(),
1748 vdp_.getName() +
" data latch value",
"V99x8 data latch value (byte)", 1)
1752byte VDP::DataLatchDebug::read(
unsigned )
1754 auto& vdp =
OUTER(
VDP, dataLatchDebug);
1755 return vdp.dataLatch;
1761 : InfoTopic(vdp_.getMotherBoard().getMachineInfoCommand(),
1762 strCat(vdp_.getName(),
'_', name_))
1764 , helpText(
std::move(helpText_))
1768void VDP::Info::execute(std::span<const TclObject> , TclObject& result)
const
1770 result = calc(vdp.getCurrentTime());
1773std::string VDP::Info::help(std::span<const TclObject> )
const
1781VDP::FrameCountInfo::FrameCountInfo(
VDP& vdp_)
1782 :
Info(vdp_,
"frame_count",
1783 "The current frame number, starts counting at 0 "
1784 "when MSX is powered up or reset.")
1788int VDP::FrameCountInfo::calc(
const EmuTime& )
const
1790 return vdp.frameCount;
1796VDP::CycleInFrameInfo::CycleInFrameInfo(
VDP& vdp_)
1797 :
Info(vdp_,
"cycle_in_frame",
1798 "The number of VDP cycles since the beginning of "
1799 "the current frame. The VDP runs at 6 times the Z80 "
1800 "clock frequency, so at approximately 21.5MHz.")
1804int VDP::CycleInFrameInfo::calc(
const EmuTime& time)
const
1806 return vdp.getTicksThisFrame(time);
1812VDP::LineInFrameInfo::LineInFrameInfo(
VDP& vdp_)
1813 :
Info(vdp_,
"line_in_frame",
1814 "The absolute line number since the beginning of "
1815 "the current frame. Goes from 0 till 262 (NTSC) or "
1816 "313 (PAL). Note that this number includes the "
1817 "border lines, use 'msx_y_pos' to get MSX "
1822int VDP::LineInFrameInfo::calc(
const EmuTime& time)
const
1830VDP::CycleInLineInfo::CycleInLineInfo(
VDP& vdp_)
1831 :
Info(vdp_,
"cycle_in_line",
1832 "The number of VDP cycles since the beginning of "
1833 "the current line. See also 'cycle_in_frame'."
1834 "Note that this includes the cycles in the border, "
1835 "use 'msx_x256_pos' or 'msx_x512_pos' to get MSX "
1840int VDP::CycleInLineInfo::calc(
const EmuTime& time)
const
1848VDP::MsxYPosInfo::MsxYPosInfo(
VDP& vdp_)
1849 :
Info(vdp_,
"msx_y_pos",
1850 "Similar to 'line_in_frame', but expressed in MSX "
1851 "coordinates. So lines in the top border have "
1852 "negative coordinates, lines in the bottom border "
1853 "have coordinates bigger or equal to 192 or 212.")
1857int VDP::MsxYPosInfo::calc(
const EmuTime& time)
const
1866VDP::MsxX256PosInfo::MsxX256PosInfo(
VDP& vdp_)
1867 :
Info(vdp_,
"msx_x256_pos",
1868 "Similar to 'cycle_in_frame', but expressed in MSX "
1869 "coordinates. So a position in the left border has "
1870 "a negative coordinate and a position in the right "
1871 "border has a coordinated bigger or equal to 256. "
1872 "See also 'msx_x512_pos'.")
1876int VDP::MsxX256PosInfo::calc(
const EmuTime& time)
const
1879 vdp.getLeftSprites()) / 4;
1885VDP::MsxX512PosInfo::MsxX512PosInfo(
VDP& vdp_)
1886 :
Info(vdp_,
"msx_x512_pos",
1887 "Similar to 'cycle_in_frame', but expressed in "
1888 "'narrow' (screen 7) MSX coordinates. So a position "
1889 "in the left border has a negative coordinate and "
1890 "a position in the right border has a coordinated "
1891 "bigger or equal to 512. See also 'msx_x256_pos'.")
1895int VDP::MsxX512PosInfo::calc(
const EmuTime& time)
const
1898 vdp.getLeftSprites()) / 2;
1912template<
typename Archive>
1915 ar.template serializeBase<MSXDevice>(*
this);
1917 if (ar.versionAtLeast(serVersion, 8)) {
1918 ar.serialize(
"syncVSync", syncVSync,
1919 "syncDisplayStart", syncDisplayStart,
1920 "syncVScan", syncVScan,
1921 "syncHScan", syncHScan,
1922 "syncHorAdjust", syncHorAdjust,
1923 "syncSetMode", syncSetMode,
1924 "syncSetBlank", syncSetBlank,
1925 "syncCpuVramAccess", syncCpuVramAccess);
1929 {&syncVSync, &syncDisplayStart, &syncVScan,
1930 &syncHScan, &syncHorAdjust, &syncSetMode,
1931 &syncSetBlank, &syncCpuVramAccess});
1941 ar.serialize(
"irqVertical", irqVertical,
1942 "irqHorizontal", irqHorizontal,
1943 "frameStartTime", frameStartTime,
1944 "displayStartSyncTime", displayStartSyncTime,
1945 "vScanSyncTime", vScanSyncTime,
1946 "hScanSyncTime", hScanSyncTime,
1947 "displayStart", displayStart,
1948 "horizontalScanOffset", horizontalScanOffset,
1949 "horizontalAdjust", horizontalAdjust,
1950 "registers", controlRegs,
1951 "blinkCount", blinkCount,
1952 "vramPointer", vramPointer,
1954 "isDisplayArea", isDisplayArea,
1955 "palTiming", palTiming,
1956 "interlaced", interlaced,
1957 "statusReg0", statusReg0,
1958 "statusReg1", statusReg1,
1959 "statusReg2", statusReg2,
1960 "blinkState", blinkState,
1961 "dataLatch", dataLatch,
1962 "registerDataStored", registerDataStored,
1963 "paletteDataStored", paletteDataStored);
1964 if (ar.versionAtLeast(serVersion, 5)) {
1965 ar.serialize(
"cpuVramData", cpuVramData,
1966 "cpuVramReqIsRead", cpuVramReqIsRead);
1968 ar.serialize(
"readAhead", cpuVramData);
1970 ar.serialize(
"cpuExtendedVram", cpuExtendedVram,
1971 "displayEnabled", displayEnabled);
1972 byte mode = displayMode.
getByte();
1973 ar.serialize(
"displayMode", mode);
1976 ar.serialize(
"cmdEngine", *cmdEngine,
1977 "spriteChecker", *spriteChecker,
1979 if constexpr (Archive::IS_LOADER) {
1980 pendingCpuAccess = syncCpuVramAccess.pendingSyncPoint();
1981 update(tooFastAccess);
1984 if (ar.versionAtLeast(serVersion, 2)) {
1985 ar.serialize(
"frameCount", frameCount);
1987 assert(Archive::IS_LOADER);
1994 if (ar.versionAtLeast(serVersion, 9)) {
1995 ar.serialize(
"syncSetSprites", syncSetSprites);
1996 ar.serialize(
"spriteEnabled", spriteEnabled);
1998 assert(Archive::IS_LOADER);
1999 spriteEnabled = (controlRegs[8] & 0x02) == 0;
2001 if (ar.versionAtLeast(serVersion, 10)) {
2002 ar.serialize(
"writeAccess", writeAccess);
2004 writeAccess = !cpuVramReqIsRead;
2015 if constexpr (Archive::IS_LOADER) {
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
REGISTER_MSXDEVICE(ChakkariCopy, "ChakkariCopy")
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)
std::string strCat(Ts &&...ts)
constexpr auto xrange(T e)