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;
714 setPalette(index, grb, time);
715 controlRegs[16] = (index + 1) & 0x0F;
716 paletteDataStored =
false;
719 paletteDataStored =
true;
726 byte regNr = controlRegs[17];
727 changeRegister(regNr & 0x3F, value, time);
728 if ((regNr & 0x80) == 0) {
730 controlRegs[17] = (regNr + 1) & 0x3F;
744 assert(vdpVersionString);
748void VDP::setPalette(
unsigned index,
word grb, EmuTime::param time)
750 if (palette[index] != grb) {
751 renderer->updatePalette(index, grb, time);
752 palette[index] = grb;
756void VDP::vramWrite(
byte value, EmuTime::param time)
758 scheduleCpuVramAccess(
false, value, time);
761byte VDP::vramRead(EmuTime::param time)
766 byte result = cpuVramData;
769 scheduleCpuVramAccess(
true, dummy, time);
773void VDP::scheduleCpuVramAccess(
bool isRead,
byte write, EmuTime::param time)
777 if (!isRead) cpuVramData = write;
778 cpuVramReqIsRead = isRead;
779 if (pendingCpuAccess) [[unlikely]] {
782 assert(!allowTooFastAccess);
785 if (allowTooFastAccess) [[unlikely]] {
798 assert(!pendingCpuAccess);
799 executeCpuVramAccess(time);
827 pendingCpuAccess =
true;
835void VDP::executeCpuVramAccess(EmuTime::param time)
837 int addr = (controlRegs[14] << 14) | vramPointer;
842 addr = ((addr << 16) | (addr >> 1)) & 0x1FFFF;
845 bool doAccess = [&] {
846 if (!cpuExtendedVram) [[likely]] {
848 }
else if (vram->getSize() == 192 * 1024) [[likely]] {
849 addr = 0x20000 | (addr & 0xFFFF);
856 if (cpuVramReqIsRead) {
857 cpuVramData = vram->cpuRead(addr, time);
859 vram->cpuWrite(addr, cpuVramData, time);
862 if (cpuVramReqIsRead) {
869 vramPointer = (vramPointer + 1) & 0x3FFF;
870 if (vramPointer == 0 && displayMode.
isV9938Mode()) {
872 controlRegs[14] = (controlRegs[14] + 1) & 0x07;
883 EmuTime::param time, EmuTime::param limit)
const
889byte VDP::peekStatusReg(
byte reg, EmuTime::param time)
const
893 spriteChecker->sync(time);
896 if (controlRegs[0] & 0x10) {
897 return statusReg1 | (irqHorizontal.
getState() ? 1:0);
904 if (afterMatch < 0) {
909 int matchLength = (displayMode.
isTextMode() ? 87 : 59)
912 (0 <= afterMatch && afterMatch < matchLength);
922 || ticksThisFrame >= displayEnd;
924 | (getHR(ticksThisFrame) ? 0x20 : 0x00)
926 | cmdEngine->getStatus(time);
929 return byte(spriteChecker->getCollisionX(time));
931 return byte(spriteChecker->getCollisionX(time) >> 8) | 0xFE;
933 return byte(spriteChecker->getCollisionY(time));
935 return byte(spriteChecker->getCollisionY(time) >> 8) | 0xFC;
937 return cmdEngine->readColor(time);
939 return byte(cmdEngine->getBorderX(time));
941 return byte(cmdEngine->getBorderX(time) >> 8) | 0xFE;
947byte VDP::readStatusReg(
byte reg, EmuTime::param time)
949 byte ret = peekStatusReg(reg, time);
952 spriteChecker->resetStatus();
957 if (controlRegs[0] & 0x10) {
958 irqHorizontal.
reset();
962 spriteChecker->resetCollision();
965 cmdEngine->resetColor();
975 registerDataStored =
false;
977 switch (port & (
isMSX1VDP() ? 0x01 : 0x03)) {
979 return vramRead(time);
982 return readStatusReg(controlRegs[15], time);
997void VDP::changeRegister(
byte reg,
byte val, EmuTime::param time)
1003 cpuExtendedVram = (val & 0x40) != 0;
1007 cmdEngine->setCmdReg(reg - 32, val, time);
1013 val &= controlValueMasks[reg];
1015 byte change = val ^ controlRegs[reg];
1021 if (blinkState == ((val & 0xF0) == 0)) {
1022 renderer->updateBlinkState(!blinkState, time);
1023 blinkState = !blinkState;
1026 if ((val & 0xF0) && (val & 0x0F)) {
1028 blinkCount = (val >> 4) * 10;
1039 if (!change)
return;
1045 syncAtNextLine(syncSetMode, time);
1049 if (change & 0x03) {
1051 spriteChecker->updateSpriteSizeMag(val, time);
1055 syncAtNextLine(syncSetMode, time);
1057 if (change & 0x40) {
1058 syncAtNextLine(syncSetBlank, time);
1062 unsigned base = (val << 10) | ~(~0u << 10);
1075 renderer->updateNameBase(base, time);
1080 if (change & 0xF0) {
1081 renderer->updateForegroundColor(val >> 4, time);
1083 if (change & 0x0F) {
1084 renderer->updateBackgroundColor(val & 0x0F, time);
1087 renderer->updateBackgroundColor(val, time);
1091 if (change & 0x20) {
1092 renderer->updateTransparency((val & 0x20) == 0, time);
1093 spriteChecker->updateTransparency((val & 0x20) == 0, time);
1095 if (change & 0x02) {
1096 syncAtNextLine(syncSetSprites, time);
1098 if (change & 0x08) {
1099 vram->updateVRMode((val & 0x08) != 0, time);
1103 if (change & 0xF0) {
1104 renderer->updateBlinkForegroundColor(val >> 4, time);
1106 if (change & 0x0F) {
1107 renderer->updateBlinkBackgroundColor(val & 0x0F, time);
1112 paletteDataStored =
false;
1115 if (change & 0x0F) {
1116 syncAtNextLine(syncHorAdjust, time);
1120 spriteChecker->updateVerticalScroll(val, time);
1121 renderer->updateVerticalScroll(val, time);
1129 if (change & 0x08) {
1130 syncAtNextLine(syncHorAdjust, time);
1132 if (change & 0x02) {
1133 renderer->updateBorderMask((val & 0x02) != 0, time);
1135 if (change & 0x01) {
1136 renderer->updateMultiPage((val & 0x01) != 0, time);
1140 renderer->updateHorizontalScrollHigh(val, time);
1143 renderer->updateHorizontalScrollLow(val, time);
1148 controlRegs[reg] = val;
1155 if (change & 0x10) {
1157 scheduleHScan(time);
1159 irqHorizontal.
reset();
1164 if (change & 0x20) {
1169 if (statusReg0 & 0x80) {
1173 irqVertical.
reset();
1179 vram->change4k8kMapping((val & 0x80) != 0);
1183 updateNameBase(time);
1187 updateColorBase(time);
1191 updatePatternBase(time);
1195 updateSpriteAttributeBase(time);
1198 updateSpritePatternBase(time);
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.");
1209 if (change & 0x80) {
1219 if (time < displayStartSyncTime) {
1221 scheduleDisplayStart(time);
1224 scheduleVScan(time);
1230 scheduleHScan(time);
1233 if (change & 0x01) {
1234 updateNameBase(time);
1240void VDP::syncAtNextLine(SyncBase& type, EmuTime::param time)
1244 int offset = 144 + (horizontalAdjust - 7) * 4;
1247 EmuTime nextTime = frameStartTime + ticks;
1248 type.setSyncPoint(nextTime);
1251void VDP::updateNameBase(EmuTime::param time)
1253 unsigned base = (controlRegs[2] << 10) | ~(~0u << 10);
1266 unsigned indexMask =
1269 : ~0u << (displayMode.
isTextMode() ? 12 : 10);
1270 if (controlRegs[25] & 0x01) {
1274 indexMask &= ~0x8000;
1276 vram->nameTable.setMask(base, indexMask, time);
1279void VDP::updateColorBase(EmuTime::param time)
1281 unsigned base = (controlRegs[10] << 14) | (controlRegs[3] << 6) | ~(~0u << 6);
1282 renderer->updateColorBase(base, time);
1283 switch (displayMode.
getBase()) {
1286 vram->colorTable.setMask(base, ~0u << 9, time);
1289 vram->colorTable.setMask(base, ~0u << 6, time);
1292 vram->colorTable.setMask(base | (
vdpLacksMirroring() ? 0x1800 : 0), ~0u << 13, time);
1295 vram->colorTable.setMask(base, ~0u << 13, time);
1299 vram->colorTable.disable(time);
1303void VDP::updatePatternBase(EmuTime::param time)
1305 unsigned base = (controlRegs[4] << 11) | ~(~0u << 11);
1306 renderer->updatePatternBase(base, time);
1307 switch (displayMode.
getBase()) {
1314 vram->patternTable.setMask(base, ~0u << 11, time);
1322 base = (controlRegs[4] << 11)
1323 | ((controlRegs[3] & 0x1f) << 6)
1326 vram->patternTable.setMask(base | (
vdpLacksMirroring() ? 0x1800 : 0), ~0u << 13, time);
1329 vram->patternTable.setMask(base, ~0u << 13, time);
1333 vram->patternTable.disable(time);
1337void VDP::updateSpriteAttributeBase(EmuTime::param time)
1341 vram->spriteAttribTable.disable(time);
1344 unsigned baseMask = (controlRegs[11] << 15) | (controlRegs[5] << 7) | ~(~0u << 7);
1345 unsigned indexMask = mode == 1 ? ~0u << 7 : ~0u << 10;
1347 baseMask = ((baseMask << 16) | (baseMask >> 1)) & 0x1FFFF;
1348 indexMask = ((indexMask << 16) | ~(1 << 16)) & (indexMask >> 1);
1350 vram->spriteAttribTable.setMask(baseMask, indexMask, time);
1353void VDP::updateSpritePatternBase(EmuTime::param time)
1356 vram->spritePatternTable.disable(time);
1359 unsigned baseMask = (controlRegs[6] << 11) | ~(~0u << 11);
1360 unsigned indexMask = ~0u << 11;
1362 baseMask = ((baseMask << 16) | (baseMask >> 1)) & 0x1FFFF;
1363 indexMask = ((indexMask << 16) | ~(1 << 16)) & (indexMask >> 1);
1365 vram->spritePatternTable.setMask(baseMask, indexMask, time);
1368void VDP::updateDisplayMode(DisplayMode newMode,
bool cmdBit, EmuTime::param time)
1371 vram->updateDisplayMode(newMode, cmdBit, time);
1378 newMode.isPlanar() != displayMode.
isPlanar();
1381 bool spriteModeChange =
1382 newMode.getSpriteMode(msx1) != displayMode.
getSpriteMode(msx1);
1385 displayMode = newMode;
1391 updateColorBase(time);
1392 updatePatternBase(time);
1394 if (planarChange || spriteModeChange) {
1395 updateSpritePatternBase(time);
1396 updateSpriteAttributeBase(time);
1398 updateNameBase(time);
1407void VDP::update(
const Setting&
setting)
noexcept
1411 brokenCmdTiming = cmdTiming .getEnum();
1412 allowTooFastAccess = tooFastAccess.getEnum();
1414 if (allowTooFastAccess && pendingCpuAccess) [[unlikely]] {
1416 syncCpuVramAccess.removeSyncPoint();
1417 pendingCpuAccess =
false;
1418 executeCpuVramAccess(getCurrentTime());
1439static constexpr std::array<std::array<uint8_t, 3>, 16> TOSHIBA_PALETTE = {{
1465static constexpr std::array<std::array<uint8_t, 3>, 16> YM2220_PALETTE = {{
1492static constexpr std::array<std::array<uint8_t, 3>, 16> THREE_BIT_RGB_PALETTE = {{
1513static constexpr std::array<std::array<float, 3>, 16> TMS9XXXA_ANALOG_OUTPUT = {
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},
1537 return THREE_BIT_RGB_PALETTE;
1539 if ((version & VM_TOSHIBA_PALETTE) != 0) {
1540 return TOSHIBA_PALETTE;
1542 if ((version & VM_YM2220_PALETTE) != 0) {
1543 return YM2220_PALETTE;
1545 std::array<std::array<uint8_t, 3>, 16> tmsPalette;
1546 for (
auto color :
xrange(16)) {
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;
1552 Pr *= (narrow<float>(saturationPr) / 100.0f);
1553 Pb *= (narrow<float>(saturationPb) / 100.0f);
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;
1583VDP::RegDebug::RegDebug(
VDP& vdp_)
1585 vdp_.
getName() +
" regs",
"VDP registers.", 0x40)
1589byte VDP::RegDebug::read(
unsigned address)
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));
1601void VDP::RegDebug::write(
unsigned address,
byte value, EmuTime::param time)
1603 auto& vdp =
OUTER(
VDP, vdpRegDebug);
1608 if ((address >= 8) && vdp.isMSX1VDP())
return;
1609 vdp.changeRegister(narrow<byte>(address), value, time);
1615VDP::StatusRegDebug::StatusRegDebug(
VDP& vdp_)
1616 : SimpleDebuggable(vdp_.getMotherBoard(),
1617 vdp_.
getName() +
" status regs",
"VDP status registers.", 0x10)
1621byte VDP::StatusRegDebug::read(
unsigned address, EmuTime::param time)
1623 auto& vdp =
OUTER(
VDP, vdpStatusRegDebug);
1624 return vdp.peekStatusReg(narrow<byte>(address), time);
1630VDP::PaletteDebug::PaletteDebug(
VDP& vdp_)
1631 : SimpleDebuggable(vdp_.getMotherBoard(),
1632 vdp_.
getName() +
" palette",
"V99x8 palette (RBG format)", 0x20)
1636byte VDP::PaletteDebug::read(
unsigned address)
1638 auto& vdp =
OUTER(
VDP, vdpPaletteDebug);
1639 word grb = vdp.getPalette(address / 2);
1640 return (address & 1) ? narrow_cast<byte>(grb >> 8)
1644void VDP::PaletteDebug::write(
unsigned address,
byte value, EmuTime::param time)
1646 auto& vdp =
OUTER(
VDP, vdpPaletteDebug);
1650 if (vdp.isMSX1VDP())
return;
1652 unsigned index = address / 2;
1653 word grb = vdp.getPalette(index);
1655 ?
word((grb & 0x0077) | ((value & 0x07) << 8))
1656 :
word((grb & 0x0700) | ((value & 0x77) << 0));
1657 vdp.setPalette(index, grb, time);
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)
1670byte VDP::VRAMPointerDebug::read(
unsigned address)
1672 auto& vdp =
OUTER(
VDP, vramPointerDebug);
1674 return narrow_cast<byte>(vdp.vramPointer >> 8);
1676 return narrow_cast<byte>(vdp.vramPointer & 0xFF);
1680void VDP::VRAMPointerDebug::write(
unsigned address,
byte value, EmuTime::param )
1682 auto& vdp =
OUTER(
VDP, vramPointerDebug);
1683 int& ptr = vdp.vramPointer;
1685 ptr = (ptr & 0x00FF) | ((value & 0x3F) << 8);
1687 ptr = (ptr & 0xFF00) | value;
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)
1699byte VDP::RegisterLatchStatusDebug::read(
unsigned )
1701 auto& vdp =
OUTER(
VDP, registerLatchStatusDebug);
1702 return byte(vdp.registerDataStored);
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)
1714byte VDP::VramAccessStatusDebug::read(
unsigned )
1716 auto& vdp =
OUTER(
VDP, vramAccessStatusDebug);
1717 return byte(vdp.writeAccess);
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)
1728byte VDP::PaletteLatchStatusDebug::read(
unsigned )
1730 auto& vdp =
OUTER(
VDP, paletteLatchStatusDebug);
1731 return byte(vdp.paletteDataStored);
1736VDP::DataLatchDebug::DataLatchDebug(
VDP &vdp_)
1737 : SimpleDebuggable(vdp_.getMotherBoard(),
1738 vdp_.
getName() +
" data latch value",
"V99x8 data latch value (byte)", 1)
1742byte VDP::DataLatchDebug::read(
unsigned )
1744 auto& vdp =
OUTER(
VDP, dataLatchDebug);
1745 return vdp.dataLatch;
1751 : InfoTopic(vdp_.getMotherBoard().getMachineInfoCommand(),
1754 , helpText(
std::move(helpText_))
1758void VDP::Info::execute(std::span<const TclObject> , TclObject& result)
const
1760 result = calc(vdp.getCurrentTime());
1763std::string VDP::Info::help(std::span<const TclObject> )
const
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.")
1778int VDP::FrameCountInfo::calc(
const EmuTime& )
const
1780 return vdp.frameCount;
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.")
1794int VDP::CycleInFrameInfo::calc(
const EmuTime& time)
const
1796 return vdp.getTicksThisFrame(time);
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 "
1812int VDP::LineInFrameInfo::calc(
const EmuTime& time)
const
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 "
1830int VDP::CycleInLineInfo::calc(
const EmuTime& time)
const
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.")
1847int VDP::MsxYPosInfo::calc(
const EmuTime& time)
const
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'.")
1866int VDP::MsxX256PosInfo::calc(
const EmuTime& time)
const
1869 vdp.getLeftSprites()) / 4;
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'.")
1885int VDP::MsxX512PosInfo::calc(
const EmuTime& time)
const
1888 vdp.getLeftSprites()) / 2;
1902template<
typename Archive>
1905 ar.template serializeBase<MSXDevice>(*
this);
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);
1919 {&syncVSync, &syncDisplayStart, &syncVScan,
1920 &syncHScan, &syncHorAdjust, &syncSetMode,
1921 &syncSetBlank, &syncCpuVramAccess});
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,
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);
1958 ar.serialize(
"readAhead", cpuVramData);
1960 ar.serialize(
"cpuExtendedVram", cpuExtendedVram,
1961 "displayEnabled", displayEnabled);
1962 byte mode = displayMode.
getByte();
1963 ar.serialize(
"displayMode", mode);
1966 ar.serialize(
"cmdEngine", *cmdEngine,
1967 "spriteChecker", *spriteChecker,
1969 if constexpr (Archive::IS_LOADER) {
1970 pendingCpuAccess = syncCpuVramAccess.pendingSyncPoint();
1971 update(tooFastAccess);
1974 if (ar.versionAtLeast(serVersion, 2)) {
1975 ar.serialize(
"frameCount", frameCount);
1977 assert(Archive::IS_LOADER);
1984 if (ar.versionAtLeast(serVersion, 9)) {
1985 ar.serialize(
"syncSetSprites", syncSetSprites);
1986 ar.serialize(
"spriteEnabled", spriteEnabled);
1988 assert(Archive::IS_LOADER);
1989 spriteEnabled = (controlRegs[8] & 0x02) == 0;
1991 if (ar.versionAtLeast(serVersion, 10)) {
1992 ar.serialize(
"writeAccess", writeAccess);
1994 writeAccess = !cpuVramReqIsRead;
2005 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
Abstract base class for post processors.
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.
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.
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.
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.
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)
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::string getName(KeyCode keyCode)
Translate key code to key name.
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)