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 , frameCountInfo (*this)
82 , cycleInFrameInfo (*this)
83 , lineInFrameInfo (*this)
84 , cycleInLineInfo (*this)
86 , msxX256PosInfo (*this)
87 , msxX512PosInfo (*this)
88 , frameStartTime(getCurrentTime())
89 , irqVertical (getMotherBoard(),
getName() +
".IRQvertical", config)
90 , irqHorizontal(getMotherBoard(),
getName() +
".IRQhorizontal", config)
91 , displayStartSyncTime(getCurrentTime())
92 , vScanSyncTime(getCurrentTime())
93 , hScanSyncTime(getCurrentTime())
95 getCommandController(),
96 getName() +
".too_fast_vram_access_callback",
97 "Tcl proc called when the VRAM is read or written too fast",
101 , fixedVDPIOdelayCycles(getDelayCycles(getMotherBoard().getMachineConfig()->getConfig().getChild(
"devices")))
113 int defaultSaturation = 54;
115 const auto& versionString = config.
getChildData(
"version");
116 if (versionString ==
"TMS99X8A") version = TMS99X8A;
117 else if (versionString ==
"TMS9918A") {
119 defaultSaturation = 100;
120 }
else if (versionString ==
"TMS9928A") version = TMS99X8A;
121 else if (versionString ==
"T6950PAL") version = T6950PAL;
122 else if (versionString ==
"T6950NTSC") version = T6950NTSC;
123 else if (versionString ==
"T7937APAL") version = T7937APAL;
124 else if (versionString ==
"T7937ANTSC") version = T7937ANTSC;
125 else if (versionString ==
"TMS91X8") version = TMS91X8;
126 else if (versionString ==
"TMS9118") {
128 defaultSaturation = 100;
129 }
else if (versionString ==
"TMS9128") version = TMS91X8;
130 else if (versionString ==
"TMS9929A") version = TMS9929A;
131 else if (versionString ==
"TMS9129") version = TMS9129;
132 else if (versionString ==
"V9938") version = V9938;
133 else if (versionString ==
"V9958") version = V9958;
134 else if (versionString ==
"YM2220PAL") version = YM2220PAL;
135 else if (versionString ==
"YM2220NTSC") version = YM2220NTSC;
136 else throw MSXException(
"Unknown VDP version \"", versionString,
'"');
139 if ((versionString.find(
"TMS") != 0) && ((config.
findChild(
"saturationPr") !=
nullptr) || (config.
findChild(
"saturationPb") !=
nullptr) || (config.
findChild(
"saturation") !=
nullptr))) {
140 throw MSXException(
"Specifying saturation parameters only makes sense for TMS VDPs");
143 auto getPercentage = [&](std::string_view name, std::string_view extra,
int defaultValue) {
145 if ((result < 0) || (result > 100)) {
147 "Saturation percentage ", extra,
"is not in range 0..100: ", result);
151 int saturation = getPercentage(
"saturation",
"", defaultSaturation);
152 saturationPr = getPercentage(
"saturationPr",
"for Pr component ", saturation);
153 saturationPb = getPercentage(
"saturationPb",
"for Pb component ", saturation);
156 static constexpr std::array<byte, 32> VALUE_MASKS_MSX1 = {
157 0x03, 0xFB, 0x0F, 0xFF, 0x07, 0x7F, 0x07, 0xFF
159 static constexpr std::array<byte, 32> VALUE_MASKS_MSX2 = {
160 0x7E, 0x7F, 0x7F, 0xFF, 0x3F, 0xFF, 0x3F, 0xFF,
161 0xFB, 0xBF, 0x07, 0x03, 0xFF, 0xFF, 0x07, 0x0F,
162 0x0F, 0xBF, 0xFF, 0xFF, 0x3F, 0x3F, 0x3F, 0xFF,
163 0, 0, 0, 0, 0, 0, 0, 0,
165 controlRegMask =
isMSX1VDP() ? 0x07 : 0x3F;
166 controlValueMasks =
isMSX1VDP() ? VALUE_MASKS_MSX1 : VALUE_MASKS_MSX2;
167 if (version == V9958) {
169 controlValueMasks[25] = 0x7F;
170 controlValueMasks[26] = 0x3F;
171 controlValueMasks[27] = 0x07;
180 if (vramSize !=
one_of(16u, 64u, 128u, 192u)) {
182 "VRAM size of ", vramSize,
"kB is not supported!");
184 vram = std::make_unique<VDPVRAM>(*
this, vramSize * 1024, time);
188 spriteChecker = std::make_unique<SpriteChecker>(*
this, renderSettings, time);
189 vram->setSpriteChecker(spriteChecker.get());
193 vram->setCmdEngine(cmdEngine.get());
203 tooFastAccess.
attach(*
this);
204 update(tooFastAccess);
209 tooFastAccess.
detach(*
this);
214void VDP::preVideoSystemChange() noexcept
219void VDP::postVideoSystemChange() noexcept
224void VDP::createRenderer()
230 vram->setRenderer(renderer.get(), frameStartTime.
getTime());
235 return renderer->getPostProcessor();
245 controlRegs[9] |= 0x02;
249 controlRegs[21] = 0x3B;
250 controlRegs[22] = 0x05;
258 cpuVramReqIsRead =
false;
260 cpuExtendedVram =
false;
261 registerDataStored =
false;
262 paletteDataStored =
false;
265 horizontalAdjust = 7;
268 isDisplayArea =
false;
269 displayEnabled =
false;
270 spriteEnabled =
true;
271 superimposing =
nullptr;
272 externalVideo =
nullptr;
276 statusReg1 = (version == V9958 ? 0x04 : 0x00);
281 irqHorizontal.
reset();
284 const std::array<uint16_t, 16> V9938_PALETTE = {
285 0x000, 0x000, 0x611, 0x733, 0x117, 0x327, 0x151, 0x627,
286 0x171, 0x373, 0x661, 0x664, 0x411, 0x265, 0x555, 0x777
289 palette = V9938_PALETTE;
292void VDP::resetMasks(EmuTime::param time)
294 updateNameBase(time);
295 updateColorBase(time);
296 updatePatternBase(time);
297 updateSpriteAttributeBase(time);
298 updateSpritePatternBase(time);
312 syncVSync .removeSyncPoint();
313 syncDisplayStart .removeSyncPoint();
314 syncVScan .removeSyncPoint();
315 syncHScan .removeSyncPoint();
316 syncHorAdjust .removeSyncPoint();
317 syncSetMode .removeSyncPoint();
318 syncSetBlank .removeSyncPoint();
319 syncSetSprites .removeSyncPoint();
320 syncCpuVramAccess.removeSyncPoint();
321 syncCmdDone .removeSyncPoint();
322 pendingCpuAccess =
false;
325 cmdEngine->sync(time);
327 spriteChecker->reset(time);
328 cmdEngine->reset(time);
337 assert(frameCount == 0);
340void VDP::execVSync(EmuTime::param time)
345 renderer->frameEnd(time);
346 spriteChecker->frameEnd(time);
351 blinkState = next.state;
352 blinkCount = next.count;
356 cmdEngine->sync(time);
362void VDP::execDisplayStart(EmuTime::param time)
366 if (!isDisplayArea) {
367 if (displayEnabled) {
368 vram->updateDisplayEnabled(
true, time);
370 isDisplayArea =
true;
374void VDP::execVScan(EmuTime::param time)
383 vram->updateDisplayEnabled(
false, time);
385 isDisplayArea =
false;
389 if (controlRegs[1] & 0x20) {
397 if (controlRegs[0] & 0x10) {
402void VDP::execHorAdjust(EmuTime::param time)
404 int newHorAdjust = (controlRegs[18] & 0x0F) ^ 0x07;
405 if (controlRegs[25] & 0x08) {
408 renderer->updateHorizontalAdjust(newHorAdjust, time);
409 horizontalAdjust = newHorAdjust;
412void VDP::execSetMode(EmuTime::param time)
415 DisplayMode(controlRegs[0], controlRegs[1], controlRegs[25]),
420void VDP::execSetBlank(EmuTime::param time)
422 bool newDisplayEnabled = (controlRegs[1] & 0x40) != 0;
424 vram->updateDisplayEnabled(newDisplayEnabled, time);
426 displayEnabled = newDisplayEnabled;
429void VDP::execSetSprites(EmuTime::param time)
431 bool newSpriteEnabled = (controlRegs[8] & 0x02) == 0;
432 vram->updateSpritesEnabled(newSpriteEnabled, time);
433 spriteEnabled = newSpriteEnabled;
436void VDP::execCpuVramAccess(EmuTime::param time)
438 assert(!allowTooFastAccess);
439 pendingCpuAccess =
false;
440 executeCpuVramAccess(time);
443void VDP::execSyncCmdDone(EmuTime::param time)
445 cmdEngine->sync(time);
451void VDP::scheduleDisplayStart(EmuTime::param time)
454 if (displayStartSyncTime > time) {
455 syncDisplayStart.removeSyncPoint();
464 (palTiming ? 36 : 9) +
465 (controlRegs[9] & 0x80 ? 0 : 10) +
470 displayStartSyncTime = frameStartTime + displayStart;
474 if (displayStartSyncTime > time) {
475 syncDisplayStart.setSyncPoint(displayStartSyncTime);
484void VDP::scheduleVScan(EmuTime::param time)
496 if (vScanSyncTime > time) {
497 syncVScan.removeSyncPoint();
502 vScanSyncTime = frameStartTime +
507 if (vScanSyncTime > time) {
508 syncVScan.setSyncPoint(vScanSyncTime);
513void VDP::scheduleHScan(EmuTime::param time)
516 if (hScanSyncTime > time) {
517 syncHScan.removeSyncPoint();
518 hScanSyncTime = time;
522 horizontalScanOffset = displayStart - (100 + 102)
532 if (horizontalScanOffset >= ticksPerFrame) {
533 horizontalScanOffset -= ticksPerFrame;
547 int lineCountResetTicks = (8 + getVerticalAdjust()) *
TICKS_PER_LINE;
551 if (horizontalScanOffset >= lineCountResetTicks) {
558 if ((controlRegs[0] & 0x10) && horizontalScanOffset >= 0) {
566 hScanSyncTime = frameStartTime + horizontalScanOffset;
567 if (hScanSyncTime > time) {
568 syncHScan.setSyncPoint(hScanSyncTime);
579void VDP::frameStart(EmuTime::param time)
594 palTiming = (controlRegs[9] & 0x02) != 0;
600 if (blinkCount == 0) {
601 renderer->updateBlinkState(!blinkState, time);
602 blinkState = !blinkState;
603 blinkCount = (blinkState
604 ? controlRegs[13] >> 4 : controlRegs[13] & 0x0F) * 10;
613 const RawFrame* newSuperimposing = (controlRegs[0] & 1) ? externalVideo :
nullptr;
614 if (superimposing != newSuperimposing) {
615 superimposing = newSuperimposing;
616 renderer->updateSuperimposing(superimposing, time);
620 frameStartTime.
reset(time);
623 scheduleDisplayStart(time);
627 renderer->frameStart(time);
628 spriteChecker->frameStart(time);
644 EmuTime time = time_;
650 if (fixedVDPIOdelayCycles > 0) {
655 switch (port & (
isMSX1VDP() ? 0x01 : 0x03)) {
657 vramWrite(value, time);
658 registerDataStored =
false;
661 if (registerDataStored) {
666 value & controlRegMask,
681 vramPointer = (value << 8 | (vramPointer & 0xFF)) & 0x3FFF;
685 vramPointer = (value << 8 | dataLatch) & 0x3FFF;
686 if (!(value & 0x40)) {
688 (void)vramRead(time);
691 registerDataStored =
false;
698 vramPointer = (vramPointer & 0x3F00) | value;
701 registerDataStored =
true;
705 if (paletteDataStored) {
706 unsigned index = controlRegs[16];
707 word grb = ((value << 8) | dataLatch) & 0x777;
708 setPalette(index, grb, time);
709 controlRegs[16] = (index + 1) & 0x0F;
710 paletteDataStored =
false;
713 paletteDataStored =
true;
720 byte regNr = controlRegs[17];
721 changeRegister(regNr & 0x3F, value, time);
722 if ((regNr & 0x80) == 0) {
724 controlRegs[17] = (regNr + 1) & 0x3F;
738 assert(vdpVersionString);
742void VDP::setPalette(
unsigned index,
word grb, EmuTime::param time)
744 if (palette[index] != grb) {
745 renderer->updatePalette(index, grb, time);
746 palette[index] = grb;
750void VDP::vramWrite(
byte value, EmuTime::param time)
752 scheduleCpuVramAccess(
false, value, time);
755byte VDP::vramRead(EmuTime::param time)
760 byte result = cpuVramData;
763 scheduleCpuVramAccess(
true, dummy, time);
767void VDP::scheduleCpuVramAccess(
bool isRead,
byte write, EmuTime::param time)
771 if (!isRead) cpuVramData = write;
772 cpuVramReqIsRead = isRead;
773 if (pendingCpuAccess) [[unlikely]] {
776 assert(!allowTooFastAccess);
779 if (allowTooFastAccess) [[unlikely]] {
792 assert(!pendingCpuAccess);
793 executeCpuVramAccess(time);
821 pendingCpuAccess =
true;
829void VDP::executeCpuVramAccess(EmuTime::param time)
831 int addr = (controlRegs[14] << 14) | vramPointer;
836 addr = ((addr << 16) | (addr >> 1)) & 0x1FFFF;
839 bool doAccess = [&] {
840 if (!cpuExtendedVram) [[likely]] {
842 }
else if (vram->getSize() == 192 * 1024) [[likely]] {
843 addr = 0x20000 | (addr & 0xFFFF);
850 if (cpuVramReqIsRead) {
851 cpuVramData = vram->cpuRead(addr, time);
853 vram->cpuWrite(addr, cpuVramData, time);
856 if (cpuVramReqIsRead) {
863 vramPointer = (vramPointer + 1) & 0x3FFF;
864 if (vramPointer == 0 && displayMode.
isV9938Mode()) {
866 controlRegs[14] = (controlRegs[14] + 1) & 0x07;
877 EmuTime::param time, EmuTime::param limit)
const
883byte VDP::peekStatusReg(
byte reg, EmuTime::param time)
const
887 spriteChecker->sync(time);
890 if (controlRegs[0] & 0x10) {
891 return statusReg1 | (irqHorizontal.
getState() ? 1:0);
898 if (afterMatch < 0) {
903 int matchLength = (displayMode.
isTextMode() ? 87 : 59)
906 (0 <= afterMatch && afterMatch < matchLength);
916 || ticksThisFrame >= displayEnd;
918 | (getHR(ticksThisFrame) ? 0x20 : 0x00)
920 | cmdEngine->getStatus(time);
923 return byte(spriteChecker->getCollisionX(time));
925 return byte(spriteChecker->getCollisionX(time) >> 8) | 0xFE;
927 return byte(spriteChecker->getCollisionY(time));
929 return byte(spriteChecker->getCollisionY(time) >> 8) | 0xFC;
931 return cmdEngine->readColor(time);
933 return byte(cmdEngine->getBorderX(time));
935 return byte(cmdEngine->getBorderX(time) >> 8) | 0xFE;
941byte VDP::readStatusReg(
byte reg, EmuTime::param time)
943 byte ret = peekStatusReg(reg, time);
946 spriteChecker->resetStatus();
951 if (controlRegs[0] & 0x10) {
952 irqHorizontal.
reset();
956 spriteChecker->resetCollision();
959 cmdEngine->resetColor();
969 registerDataStored =
false;
971 switch (port & (
isMSX1VDP() ? 0x01 : 0x03)) {
973 return vramRead(time);
976 return readStatusReg(controlRegs[15], time);
991void VDP::changeRegister(
byte reg,
byte val, EmuTime::param time)
997 cpuExtendedVram = (val & 0x40) != 0;
1001 cmdEngine->setCmdReg(reg - 32, val, time);
1007 val &= controlValueMasks[reg];
1009 byte change = val ^ controlRegs[reg];
1015 if (blinkState == ((val & 0xF0) == 0)) {
1016 renderer->updateBlinkState(!blinkState, time);
1017 blinkState = !blinkState;
1020 if ((val & 0xF0) && (val & 0x0F)) {
1022 blinkCount = (val >> 4) * 10;
1033 if (!change)
return;
1039 syncAtNextLine(syncSetMode, time);
1043 if (change & 0x03) {
1045 spriteChecker->updateSpriteSizeMag(val, time);
1049 syncAtNextLine(syncSetMode, time);
1051 if (change & 0x40) {
1052 syncAtNextLine(syncSetBlank, time);
1056 unsigned base = (val << 10) | ~(~0u << 10);
1069 renderer->updateNameBase(base, time);
1074 if (change & 0xF0) {
1075 renderer->updateForegroundColor(val >> 4, time);
1077 if (change & 0x0F) {
1078 renderer->updateBackgroundColor(val & 0x0F, time);
1081 renderer->updateBackgroundColor(val, time);
1085 if (change & 0x20) {
1086 renderer->updateTransparency((val & 0x20) == 0, time);
1087 spriteChecker->updateTransparency((val & 0x20) == 0, time);
1089 if (change & 0x02) {
1090 syncAtNextLine(syncSetSprites, time);
1092 if (change & 0x08) {
1093 vram->updateVRMode((val & 0x08) != 0, time);
1097 if (change & 0xF0) {
1098 renderer->updateBlinkForegroundColor(val >> 4, time);
1100 if (change & 0x0F) {
1101 renderer->updateBlinkBackgroundColor(val & 0x0F, time);
1106 paletteDataStored =
false;
1109 if (change & 0x0F) {
1110 syncAtNextLine(syncHorAdjust, time);
1114 spriteChecker->updateVerticalScroll(val, time);
1115 renderer->updateVerticalScroll(val, time);
1123 if (change & 0x08) {
1124 syncAtNextLine(syncHorAdjust, time);
1126 if (change & 0x02) {
1127 renderer->updateBorderMask((val & 0x02) != 0, time);
1129 if (change & 0x01) {
1130 renderer->updateMultiPage((val & 0x01) != 0, time);
1134 renderer->updateHorizontalScrollHigh(val, time);
1137 renderer->updateHorizontalScrollLow(val, time);
1142 controlRegs[reg] = val;
1149 if (change & 0x10) {
1151 scheduleHScan(time);
1153 irqHorizontal.
reset();
1158 if (change & 0x20) {
1163 if (statusReg0 & 0x80) {
1167 irqVertical.
reset();
1173 vram->change4k8kMapping((val & 0x80) != 0);
1177 updateNameBase(time);
1181 updateColorBase(time);
1185 updatePatternBase(time);
1189 updateSpriteAttributeBase(time);
1192 updateSpritePatternBase(time);
1195 if ((val & 1) && ! warningPrinted) {
1196 warningPrinted =
true;
1198 "The running MSX software has set bit 0 of VDP register 9 "
1199 "(dot clock direction) to one. In an ordinary MSX, "
1200 "the screen would go black and the CPU would stop running.");
1203 if (change & 0x80) {
1213 if (time < displayStartSyncTime) {
1215 scheduleDisplayStart(time);
1218 scheduleVScan(time);
1224 scheduleHScan(time);
1227 if (change & 0x01) {
1228 updateNameBase(time);
1234void VDP::syncAtNextLine(SyncBase& type, EmuTime::param time)
1238 int offset = 144 + (horizontalAdjust - 7) * 4;
1241 EmuTime nextTime = frameStartTime + ticks;
1242 type.setSyncPoint(nextTime);
1245void VDP::updateNameBase(EmuTime::param time)
1247 unsigned base = (controlRegs[2] << 10) | ~(~0u << 10);
1260 unsigned indexMask =
1263 : ~0u << (displayMode.
isTextMode() ? 12 : 10);
1264 if (controlRegs[25] & 0x01) {
1268 indexMask &= ~0x8000;
1270 vram->nameTable.setMask(base, indexMask, time);
1273void VDP::updateColorBase(EmuTime::param time)
1275 unsigned base = (controlRegs[10] << 14) | (controlRegs[3] << 6) | ~(~0u << 6);
1276 renderer->updateColorBase(base, time);
1277 switch (displayMode.
getBase()) {
1280 vram->colorTable.setMask(base, ~0u << 9, time);
1283 vram->colorTable.setMask(base, ~0u << 6, time);
1286 vram->colorTable.setMask(base | (
vdpLacksMirroring() ? 0x1800 : 0), ~0u << 13, time);
1289 vram->colorTable.setMask(base, ~0u << 13, time);
1293 vram->colorTable.disable(time);
1297void VDP::updatePatternBase(EmuTime::param time)
1299 unsigned base = (controlRegs[4] << 11) | ~(~0u << 11);
1300 renderer->updatePatternBase(base, time);
1301 switch (displayMode.
getBase()) {
1308 vram->patternTable.setMask(base, ~0u << 11, time);
1316 base = (controlRegs[4] << 11)
1317 | ((controlRegs[3] & 0x1f) << 6)
1320 vram->patternTable.setMask(base | (
vdpLacksMirroring() ? 0x1800 : 0), ~0u << 13, time);
1323 vram->patternTable.setMask(base, ~0u << 13, time);
1327 vram->patternTable.disable(time);
1331void VDP::updateSpriteAttributeBase(EmuTime::param time)
1335 vram->spriteAttribTable.disable(time);
1338 unsigned baseMask = (controlRegs[11] << 15) | (controlRegs[5] << 7) | ~(~0u << 7);
1339 unsigned indexMask = mode == 1 ? ~0u << 7 : ~0u << 10;
1341 baseMask = ((baseMask << 16) | (baseMask >> 1)) & 0x1FFFF;
1342 indexMask = ((indexMask << 16) | ~(1 << 16)) & (indexMask >> 1);
1344 vram->spriteAttribTable.setMask(baseMask, indexMask, time);
1347void VDP::updateSpritePatternBase(EmuTime::param time)
1350 vram->spritePatternTable.disable(time);
1353 unsigned baseMask = (controlRegs[6] << 11) | ~(~0u << 11);
1354 unsigned indexMask = ~0u << 11;
1356 baseMask = ((baseMask << 16) | (baseMask >> 1)) & 0x1FFFF;
1357 indexMask = ((indexMask << 16) | ~(1 << 16)) & (indexMask >> 1);
1359 vram->spritePatternTable.setMask(baseMask, indexMask, time);
1362void VDP::updateDisplayMode(DisplayMode newMode,
bool cmdBit, EmuTime::param time)
1365 vram->updateDisplayMode(newMode, cmdBit, time);
1372 newMode.isPlanar() != displayMode.
isPlanar();
1375 bool spriteModeChange =
1376 newMode.getSpriteMode(msx1) != displayMode.
getSpriteMode(msx1);
1379 displayMode = newMode;
1385 updateColorBase(time);
1386 updatePatternBase(time);
1388 if (planarChange || spriteModeChange) {
1389 updateSpritePatternBase(time);
1390 updateSpriteAttributeBase(time);
1392 updateNameBase(time);
1401void VDP::update(
const Setting&
setting)
noexcept
1405 brokenCmdTiming = cmdTiming .getEnum();
1406 allowTooFastAccess = tooFastAccess.getEnum();
1408 if (allowTooFastAccess && pendingCpuAccess) [[unlikely]] {
1410 syncCpuVramAccess.removeSyncPoint();
1411 pendingCpuAccess =
false;
1412 executeCpuVramAccess(getCurrentTime());
1433static constexpr std::array<std::array<uint8_t, 3>, 16> TOSHIBA_PALETTE = {{
1459static constexpr std::array<std::array<uint8_t, 3>, 16> YM2220_PALETTE = {{
1486static constexpr std::array<std::array<uint8_t, 3>, 16> THREE_BIT_RGB_PALETTE = {{
1507static constexpr std::array<std::array<float, 3>, 16> TMS9XXXA_ANALOG_OUTPUT = {
1509 std::array{0.00f, 0.47f, 0.47f},
1510 std::array{0.00f, 0.47f, 0.47f},
1511 std::array{0.53f, 0.07f, 0.20f},
1512 std::array{0.67f, 0.17f, 0.27f},
1513 std::array{0.40f, 0.40f, 1.00f},
1514 std::array{0.53f, 0.43f, 0.93f},
1515 std::array{0.47f, 0.83f, 0.30f},
1516 std::array{0.73f, 0.00f, 0.70f},
1517 std::array{0.53f, 0.93f, 0.27f},
1518 std::array{0.67f, 0.93f, 0.27f},
1519 std::array{0.73f, 0.57f, 0.07f},
1520 std::array{0.80f, 0.57f, 0.17f},
1521 std::array{0.47f, 0.13f, 0.23f},
1522 std::array{0.53f, 0.73f, 0.67f},
1523 std::array{0.80f, 0.47f, 0.47f},
1524 std::array{1.00f, 0.47f, 0.47f},
1531 return THREE_BIT_RGB_PALETTE;
1533 if ((version & VM_TOSHIBA_PALETTE) != 0) {
1534 return TOSHIBA_PALETTE;
1536 if ((version & VM_YM2220_PALETTE) != 0) {
1537 return YM2220_PALETTE;
1539 std::array<std::array<uint8_t, 3>, 16> tmsPalette;
1540 for (
auto color :
xrange(16)) {
1542 float Y = TMS9XXXA_ANALOG_OUTPUT[color][0];
1543 float Pr = TMS9XXXA_ANALOG_OUTPUT[color][1] - 0.5f;
1544 float Pb = TMS9XXXA_ANALOG_OUTPUT[color][2] - 0.5f;
1546 Pr *= (narrow<float>(saturationPr) / 100.0f);
1547 Pb *= (narrow<float>(saturationPb) / 100.0f);
1554 float R = Y + 0 + 1.402f * Pr;
1555 float G = Y - 0.344f * Pb - 0.714f * Pr;
1556 float B = Y + 1.722f * Pb + 0;
1577VDP::RegDebug::RegDebug(
VDP& vdp_)
1579 vdp_.
getName() +
" regs",
"VDP registers.", 0x40)
1583byte VDP::RegDebug::read(
unsigned address)
1585 auto& vdp =
OUTER(
VDP, vdpRegDebug);
1586 if (address < 0x20) {
1587 return vdp.controlRegs[address];
1588 }
else if (address < 0x2F) {
1589 return vdp.cmdEngine->peekCmdReg(narrow<byte>(address - 0x20));
1595void VDP::RegDebug::write(
unsigned address,
byte value, EmuTime::param time)
1597 auto& vdp =
OUTER(
VDP, vdpRegDebug);
1602 if ((address >= 8) && vdp.isMSX1VDP())
return;
1603 vdp.changeRegister(narrow<byte>(address), value, time);
1609VDP::StatusRegDebug::StatusRegDebug(
VDP& vdp_)
1610 : SimpleDebuggable(vdp_.getMotherBoard(),
1611 vdp_.
getName() +
" status regs",
"VDP status registers.", 0x10)
1615byte VDP::StatusRegDebug::read(
unsigned address, EmuTime::param time)
1617 auto& vdp =
OUTER(
VDP, vdpStatusRegDebug);
1618 return vdp.peekStatusReg(narrow<byte>(address), time);
1624VDP::PaletteDebug::PaletteDebug(
VDP& vdp_)
1625 : SimpleDebuggable(vdp_.getMotherBoard(),
1626 vdp_.
getName() +
" palette",
"V99x8 palette (RBG format)", 0x20)
1630byte VDP::PaletteDebug::read(
unsigned address)
1632 auto& vdp =
OUTER(
VDP, vdpPaletteDebug);
1633 word grb = vdp.getPalette(address / 2);
1634 return (address & 1) ? narrow_cast<byte>(grb >> 8)
1638void VDP::PaletteDebug::write(
unsigned address,
byte value, EmuTime::param time)
1640 auto& vdp =
OUTER(
VDP, vdpPaletteDebug);
1644 if (vdp.isMSX1VDP())
return;
1646 unsigned index = address / 2;
1647 word grb = vdp.getPalette(index);
1649 ?
word((grb & 0x0077) | ((value & 0x07) << 8))
1650 :
word((grb & 0x0700) | ((value & 0x77) << 0));
1651 vdp.setPalette(index, grb, time);
1657VDP::VRAMPointerDebug::VRAMPointerDebug(
VDP& vdp_)
1658 : SimpleDebuggable(vdp_.getMotherBoard(), vdp_.
getName() ==
"VDP" ?
1659 "VRAM pointer" : vdp_.
getName() +
" VRAM pointer",
1660 "VDP VRAM pointer (14 lower bits)", 2)
1664byte VDP::VRAMPointerDebug::read(
unsigned address)
1666 auto& vdp =
OUTER(
VDP, vramPointerDebug);
1668 return narrow_cast<byte>(vdp.vramPointer >> 8);
1670 return narrow_cast<byte>(vdp.vramPointer & 0xFF);
1674void VDP::VRAMPointerDebug::write(
unsigned address,
byte value, EmuTime::param )
1676 auto& vdp =
OUTER(
VDP, vramPointerDebug);
1677 int& ptr = vdp.vramPointer;
1679 ptr = (ptr & 0x00FF) | ((value & 0x3F) << 8);
1681 ptr = (ptr & 0xFF00) | value;
1689 : InfoTopic(vdp_.getMotherBoard().getMachineInfoCommand(),
1692 , helpText(
std::move(helpText_))
1696void VDP::Info::execute(std::span<const TclObject> , TclObject& result)
const
1698 result = calc(vdp.getCurrentTime());
1701std::string VDP::Info::help(std::span<const TclObject> )
const
1709VDP::FrameCountInfo::FrameCountInfo(
VDP& vdp_)
1710 :
Info(vdp_,
"frame_count",
1711 "The current frame number, starts counting at 0 "
1712 "when MSX is powered up or reset.")
1716int VDP::FrameCountInfo::calc(
const EmuTime& )
const
1718 return vdp.frameCount;
1724VDP::CycleInFrameInfo::CycleInFrameInfo(
VDP& vdp_)
1725 :
Info(vdp_,
"cycle_in_frame",
1726 "The number of VDP cycles since the beginning of "
1727 "the current frame. The VDP runs at 6 times the Z80 "
1728 "clock frequency, so at approximately 21.5MHz.")
1732int VDP::CycleInFrameInfo::calc(
const EmuTime& time)
const
1734 return vdp.getTicksThisFrame(time);
1740VDP::LineInFrameInfo::LineInFrameInfo(
VDP& vdp_)
1741 :
Info(vdp_,
"line_in_frame",
1742 "The absolute line number since the beginning of "
1743 "the current frame. Goes from 0 till 262 (NTSC) or "
1744 "313 (PAL). Note that this number includes the "
1745 "border lines, use 'msx_y_pos' to get MSX "
1750int VDP::LineInFrameInfo::calc(
const EmuTime& time)
const
1758VDP::CycleInLineInfo::CycleInLineInfo(
VDP& vdp_)
1759 :
Info(vdp_,
"cycle_in_line",
1760 "The number of VDP cycles since the beginning of "
1761 "the current line. See also 'cycle_in_frame'."
1762 "Note that this includes the cycles in the border, "
1763 "use 'msx_x256_pos' or 'msx_x512_pos' to get MSX "
1768int VDP::CycleInLineInfo::calc(
const EmuTime& time)
const
1776VDP::MsxYPosInfo::MsxYPosInfo(
VDP& vdp_)
1777 :
Info(vdp_,
"msx_y_pos",
1778 "Similar to 'line_in_frame', but expressed in MSX "
1779 "coordinates. So lines in the top border have "
1780 "negative coordinates, lines in the bottom border "
1781 "have coordinates bigger or equal to 192 or 212.")
1785int VDP::MsxYPosInfo::calc(
const EmuTime& time)
const
1794VDP::MsxX256PosInfo::MsxX256PosInfo(
VDP& vdp_)
1795 :
Info(vdp_,
"msx_x256_pos",
1796 "Similar to 'cycle_in_frame', but expressed in MSX "
1797 "coordinates. So a position in the left border has "
1798 "a negative coordinate and a position in the right "
1799 "border has a coordinated bigger or equal to 256. "
1800 "See also 'msx_x512_pos'.")
1804int VDP::MsxX256PosInfo::calc(
const EmuTime& time)
const
1807 vdp.getLeftSprites()) / 4;
1813VDP::MsxX512PosInfo::MsxX512PosInfo(
VDP& vdp_)
1814 :
Info(vdp_,
"msx_x512_pos",
1815 "Similar to 'cycle_in_frame', but expressed in "
1816 "'narrow' (screen 7) MSX coordinates. So a position "
1817 "in the left border has a negative coordinate and "
1818 "a position in the right border has a coordinated "
1819 "bigger or equal to 512. See also 'msx_x256_pos'.")
1823int VDP::MsxX512PosInfo::calc(
const EmuTime& time)
const
1826 vdp.getLeftSprites()) / 2;
1839template<
typename Archive>
1842 ar.template serializeBase<MSXDevice>(*
this);
1844 if (ar.versionAtLeast(serVersion, 8)) {
1845 ar.serialize(
"syncVSync", syncVSync,
1846 "syncDisplayStart", syncDisplayStart,
1847 "syncVScan", syncVScan,
1848 "syncHScan", syncHScan,
1849 "syncHorAdjust", syncHorAdjust,
1850 "syncSetMode", syncSetMode,
1851 "syncSetBlank", syncSetBlank,
1852 "syncCpuVramAccess", syncCpuVramAccess);
1856 {&syncVSync, &syncDisplayStart, &syncVScan,
1857 &syncHScan, &syncHorAdjust, &syncSetMode,
1858 &syncSetBlank, &syncCpuVramAccess});
1868 ar.serialize(
"irqVertical", irqVertical,
1869 "irqHorizontal", irqHorizontal,
1870 "frameStartTime", frameStartTime,
1871 "displayStartSyncTime", displayStartSyncTime,
1872 "vScanSyncTime", vScanSyncTime,
1873 "hScanSyncTime", hScanSyncTime,
1874 "displayStart", displayStart,
1875 "horizontalScanOffset", horizontalScanOffset,
1876 "horizontalAdjust", horizontalAdjust,
1877 "registers", controlRegs,
1878 "blinkCount", blinkCount,
1879 "vramPointer", vramPointer,
1881 "isDisplayArea", isDisplayArea,
1882 "palTiming", palTiming,
1883 "interlaced", interlaced,
1884 "statusReg0", statusReg0,
1885 "statusReg1", statusReg1,
1886 "statusReg2", statusReg2,
1887 "blinkState", blinkState,
1888 "dataLatch", dataLatch,
1889 "registerDataStored", registerDataStored,
1890 "paletteDataStored", paletteDataStored);
1891 if (ar.versionAtLeast(serVersion, 5)) {
1892 ar.serialize(
"cpuVramData", cpuVramData,
1893 "cpuVramReqIsRead", cpuVramReqIsRead);
1895 ar.serialize(
"readAhead", cpuVramData);
1897 ar.serialize(
"cpuExtendedVram", cpuExtendedVram,
1898 "displayEnabled", displayEnabled);
1899 byte mode = displayMode.
getByte();
1900 ar.serialize(
"displayMode", mode);
1903 ar.serialize(
"cmdEngine", *cmdEngine,
1904 "spriteChecker", *spriteChecker,
1906 if constexpr (Archive::IS_LOADER) {
1907 pendingCpuAccess = syncCpuVramAccess.pendingSyncPoint();
1908 update(tooFastAccess);
1911 if (ar.versionAtLeast(serVersion, 2)) {
1912 ar.serialize(
"frameCount", frameCount);
1914 assert(Archive::IS_LOADER);
1921 if (ar.versionAtLeast(serVersion, 9)) {
1922 ar.serialize(
"syncSetSprites", syncSetSprites);
1923 ar.serialize(
"spriteEnabled", spriteEnabled);
1925 assert(Archive::IS_LOADER);
1926 spriteEnabled = (controlRegs[8] & 0x02) == 0;
1937 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.
CliComm & getCliComm() const
Reactor & getReactor() const
const XMLElement & getDeviceConfig() const
Get the configuration section for this device.
CommandController & getCommandController() const
EmuTime::param getCurrentTime() 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)