49static byte getDelayCycles(
const XMLElement& devices) {
51 if (
const auto* t9769Dev = devices.findChild(
"T9769")) {
52 if (t9769Dev->getChildData(
"subtype") ==
"C") {
57 }
else if (devices.findChild(
"S1990")) {
68 , syncDisplayStart(*this)
71 , syncHorAdjust(*this)
74 , syncSetSprites(*this)
75 , syncCpuVramAccess(*this)
77 , display(getReactor().getDisplay())
78 , cmdTiming (display.getRenderSettings().getCmdTimingSetting())
79 , tooFastAccess(display.getRenderSettings().getTooFastAccessSetting())
81 , vdpStatusRegDebug(*this)
82 , vdpPaletteDebug (*this)
83 , vramPointerDebug (*this)
84 , registerLatchStatusDebug(*this)
85 , vramAccessStatusDebug(*this)
86 , paletteLatchStatusDebug(*this)
87 , dataLatchDebug (*this)
88 , frameCountInfo (*this)
89 , cycleInFrameInfo (*this)
90 , lineInFrameInfo (*this)
91 , cycleInLineInfo (*this)
93 , msxX256PosInfo (*this)
94 , msxX512PosInfo (*this)
95 , frameStartTime(getCurrentTime())
96 , irqVertical (getMotherBoard(), getName() +
".IRQvertical", config)
97 , irqHorizontal(getMotherBoard(), getName() +
".IRQhorizontal", config)
98 , displayStartSyncTime(getCurrentTime())
99 , vScanSyncTime(getCurrentTime())
100 , hScanSyncTime(getCurrentTime())
102 getCommandController(),
103 getName() +
".too_fast_vram_access_callback",
104 "Tcl proc called when the VRAM is read or written too fast",
107 , dotClockDirectionCallback(
108 getCommandController(),
109 getName() +
".dot_clock_direction_callback",
110 "Tcl proc called when DLCLK is set as input",
111 "default_dot_clock_direction_callback",
114 , fixedVDPIOdelayCycles(getDelayCycles(getMotherBoard().getMachineConfig()->getConfig().getChild(
"devices")))
126 int defaultSaturation = 54;
128 const auto& versionString = config.
getChildData(
"version");
129 if (versionString ==
"TMS99X8A") version = TMS99X8A;
130 else if (versionString ==
"TMS9918A") {
132 defaultSaturation = 100;
133 }
else if (versionString ==
"TMS9928A") version = TMS99X8A;
134 else if (versionString ==
"T6950PAL") version = T6950PAL;
135 else if (versionString ==
"T6950NTSC") version = T6950NTSC;
136 else if (versionString ==
"T7937APAL") version = T7937APAL;
137 else if (versionString ==
"T7937ANTSC") version = T7937ANTSC;
138 else if (versionString ==
"TMS91X8") version = TMS91X8;
139 else if (versionString ==
"TMS9118") {
141 defaultSaturation = 100;
142 }
else if (versionString ==
"TMS9128") version = TMS91X8;
143 else if (versionString ==
"TMS9929A") version = TMS9929A;
144 else if (versionString ==
"TMS9129") version = TMS9129;
145 else if (versionString ==
"V9938") version = V9938;
146 else if (versionString ==
"V9958") version = V9958;
147 else if (versionString ==
"YM2220PAL") version = YM2220PAL;
148 else if (versionString ==
"YM2220NTSC") version = YM2220NTSC;
149 else throw MSXException(
"Unknown VDP version \"", versionString,
'"');
152 if ((versionString.find(
"TMS") != 0) && ((config.
findChild(
"saturationPr") !=
nullptr) || (config.
findChild(
"saturationPb") !=
nullptr) || (config.
findChild(
"saturation") !=
nullptr))) {
153 throw MSXException(
"Specifying saturation parameters only makes sense for TMS VDPs");
156 auto getPercentage = [&](std::string_view name, std::string_view extra,
int defaultValue) {
158 if ((result < 0) || (result > 100)) {
160 "Saturation percentage ", extra,
"is not in range 0..100: ", result);
164 int saturation = getPercentage(
"saturation",
"", defaultSaturation);
165 saturationPr = getPercentage(
"saturationPr",
"for Pr component ", saturation);
166 saturationPb = getPercentage(
"saturationPb",
"for Pb component ", saturation);
169 static constexpr std::array<byte, 32> VALUE_MASKS_MSX1 = {
170 0x03, 0xFB, 0x0F, 0xFF, 0x07, 0x7F, 0x07, 0xFF
172 static constexpr std::array<byte, 32> VALUE_MASKS_MSX2 = {
173 0x7E, 0x7F, 0x7F, 0xFF, 0x3F, 0xFF, 0x3F, 0xFF,
174 0xFB, 0xBF, 0x07, 0x03, 0xFF, 0xFF, 0x07, 0x0F,
175 0x0F, 0xBF, 0xFF, 0xFF, 0x3F, 0x3F, 0x3F, 0xFF,
176 0, 0, 0, 0, 0, 0, 0, 0,
178 controlRegMask =
isMSX1VDP() ? 0x07 : 0x3F;
179 controlValueMasks =
isMSX1VDP() ? VALUE_MASKS_MSX1 : VALUE_MASKS_MSX2;
180 if (version == V9958) {
182 controlValueMasks[25] = 0x7F;
183 controlValueMasks[26] = 0x3F;
184 controlValueMasks[27] = 0x07;
193 if (vramSize !=
one_of(16u, 64u, 128u, 192u)) {
195 "VRAM size of ", vramSize,
"kB is not supported!");
197 vram = std::make_unique<VDPVRAM>(*
this, vramSize * 1024, time);
201 spriteChecker = std::make_unique<SpriteChecker>(*
this, renderSettings, time);
202 vram->setSpriteChecker(spriteChecker.get());
206 vram->setCmdEngine(cmdEngine.get());
216 tooFastAccess.
attach(*
this);
217 update(tooFastAccess);
222 tooFastAccess.
detach(*
this);
227void VDP::preVideoSystemChange() noexcept
232void VDP::postVideoSystemChange() noexcept
237void VDP::createRenderer()
243 vram->setRenderer(renderer.get(), frameStartTime.
getTime());
248 return renderer->getPostProcessor();
258 controlRegs[9] |= 0x02;
262 controlRegs[21] = 0x3B;
263 controlRegs[22] = 0x05;
271 cpuVramReqIsRead =
false;
273 cpuExtendedVram =
false;
274 registerDataStored =
false;
276 paletteDataStored =
false;
279 horizontalAdjust = 7;
282 isDisplayArea =
false;
283 displayEnabled =
false;
284 spriteEnabled =
true;
285 superimposing =
nullptr;
286 externalVideo =
nullptr;
290 statusReg1 = (version == V9958 ? 0x04 : 0x00);
295 irqHorizontal.
reset();
298 const std::array<uint16_t, 16> V9938_PALETTE = {
299 0x000, 0x000, 0x611, 0x733, 0x117, 0x327, 0x151, 0x627,
300 0x171, 0x373, 0x661, 0x664, 0x411, 0x265, 0x555, 0x777
303 palette = V9938_PALETTE;
306void VDP::resetMasks(EmuTime::param time)
308 updateNameBase(time);
309 updateColorBase(time);
310 updatePatternBase(time);
311 updateSpriteAttributeBase(time);
312 updateSpritePatternBase(time);
326 syncVSync .removeSyncPoint();
327 syncDisplayStart .removeSyncPoint();
328 syncVScan .removeSyncPoint();
329 syncHScan .removeSyncPoint();
330 syncHorAdjust .removeSyncPoint();
331 syncSetMode .removeSyncPoint();
332 syncSetBlank .removeSyncPoint();
333 syncSetSprites .removeSyncPoint();
334 syncCpuVramAccess.removeSyncPoint();
335 syncCmdDone .removeSyncPoint();
336 pendingCpuAccess =
false;
339 cmdEngine->sync(time);
341 spriteChecker->reset(time);
342 cmdEngine->reset(time);
351 assert(frameCount == 0);
354void VDP::execVSync(EmuTime::param time)
359 renderer->frameEnd(time);
360 spriteChecker->frameEnd(time);
365 blinkState = next.state;
366 blinkCount = next.count;
370 cmdEngine->sync(time);
376void VDP::execDisplayStart(EmuTime::param time)
380 if (!isDisplayArea) {
381 if (displayEnabled) {
382 vram->updateDisplayEnabled(
true, time);
384 isDisplayArea =
true;
388void VDP::execVScan(EmuTime::param time)
397 vram->updateDisplayEnabled(
false, time);
399 isDisplayArea =
false;
403 if (controlRegs[1] & 0x20) {
411 if (controlRegs[0] & 0x10) {
416void VDP::execHorAdjust(EmuTime::param time)
418 int newHorAdjust = (controlRegs[18] & 0x0F) ^ 0x07;
419 if (controlRegs[25] & 0x08) {
422 renderer->updateHorizontalAdjust(newHorAdjust, time);
423 horizontalAdjust = newHorAdjust;
426void VDP::execSetMode(EmuTime::param time)
429 DisplayMode(controlRegs[0], controlRegs[1], controlRegs[25]),
434void VDP::execSetBlank(EmuTime::param time)
436 bool newDisplayEnabled = (controlRegs[1] & 0x40) != 0;
438 vram->updateDisplayEnabled(newDisplayEnabled, time);
440 displayEnabled = newDisplayEnabled;
443void VDP::execSetSprites(EmuTime::param time)
445 bool newSpriteEnabled = (controlRegs[8] & 0x02) == 0;
446 vram->updateSpritesEnabled(newSpriteEnabled, time);
447 spriteEnabled = newSpriteEnabled;
450void VDP::execCpuVramAccess(EmuTime::param time)
452 assert(!allowTooFastAccess);
453 pendingCpuAccess =
false;
454 executeCpuVramAccess(time);
457void VDP::execSyncCmdDone(EmuTime::param time)
459 cmdEngine->sync(time);
465void VDP::scheduleDisplayStart(EmuTime::param time)
468 if (displayStartSyncTime > time) {
469 syncDisplayStart.removeSyncPoint();
478 (palTiming ? 36 : 9) +
479 (controlRegs[9] & 0x80 ? 0 : 10) +
484 displayStartSyncTime = frameStartTime + displayStart;
488 if (displayStartSyncTime > time) {
489 syncDisplayStart.setSyncPoint(displayStartSyncTime);
498void VDP::scheduleVScan(EmuTime::param time)
501 if (vScanSyncTime > time) {
502 syncVScan.removeSyncPoint();
507 vScanSyncTime = frameStartTime +
511 if (vScanSyncTime > time) {
512 syncVScan.setSyncPoint(vScanSyncTime);
517void VDP::scheduleHScan(EmuTime::param time)
520 if (hScanSyncTime > time) {
521 syncHScan.removeSyncPoint();
522 hScanSyncTime = time;
526 horizontalScanOffset = displayStart - (100 + 102)
536 horizontalScanOffset >= ticksPerFrame) {
537 horizontalScanOffset -= ticksPerFrame;
551 int lineCountResetTicks = (8 + getVerticalAdjust()) *
TICKS_PER_LINE;
555 if (horizontalScanOffset >= lineCountResetTicks) {
562 if ((controlRegs[0] & 0x10) && horizontalScanOffset >= 0) {
570 hScanSyncTime = frameStartTime + horizontalScanOffset;
571 if (hScanSyncTime > time) {
572 syncHScan.setSyncPoint(hScanSyncTime);
583void VDP::frameStart(EmuTime::param time)
596 palTiming = (controlRegs[9] & 0x02) != 0;
602 if (blinkCount == 0) {
603 renderer->updateBlinkState(!blinkState, time);
604 blinkState = !blinkState;
605 blinkCount = (blinkState
606 ? controlRegs[13] >> 4 : controlRegs[13] & 0x0F) * 10;
615 if (
const RawFrame* newSuperimposing = (controlRegs[0] & 1) ? externalVideo :
nullptr;
616 superimposing != newSuperimposing) {
617 superimposing = newSuperimposing;
618 renderer->updateSuperimposing(superimposing, time);
622 frameStartTime.
reset(time);
625 scheduleDisplayStart(time);
629 renderer->frameStart(time);
630 spriteChecker->frameStart(time);
646 EmuTime time = time_;
652 if (fixedVDPIOdelayCycles > 0) {
657 switch (port & (
isMSX1VDP() ? 0x01 : 0x03)) {
659 vramWrite(value, time);
660 registerDataStored =
false;
663 if (registerDataStored) {
668 value & controlRegMask,
683 vramPointer = (value << 8 | (vramPointer & 0xFF)) & 0x3FFF;
687 writeAccess = value & 0x40;
688 vramPointer = (value << 8 | dataLatch) & 0x3FFF;
689 if (!(value & 0x40)) {
691 (void)vramRead(time);
694 registerDataStored =
false;
701 vramPointer = (vramPointer & 0x3F00) | value;
704 registerDataStored =
true;
708 if (paletteDataStored) {
709 unsigned index = controlRegs[16];
710 word grb = ((value << 8) | dataLatch) & 0x777;
712 controlRegs[16] = (index + 1) & 0x0F;
713 paletteDataStored =
false;
716 paletteDataStored =
true;
723 byte regNr = controlRegs[17];
725 if ((regNr & 0x80) == 0) {
727 controlRegs[17] = (regNr + 1) & 0x3F;
741 assert(vdpVersionString);
742 return vdpVersionString->getData();
752 if (address < 0x20) {
753 return controlRegs[address];
754 }
else if (address < 0x2F) {
755 return cmdEngine->peekCmdReg(narrow<byte>(address - 0x20));
763 if (palette[index] != grb) {
764 renderer->updatePalette(index, grb, time);
765 palette[index] = grb;
769void VDP::vramWrite(
byte value, EmuTime::param time)
771 scheduleCpuVramAccess(
false, value, time);
774byte VDP::vramRead(EmuTime::param time)
779 byte result = cpuVramData;
782 scheduleCpuVramAccess(
true, dummy, time);
786void VDP::scheduleCpuVramAccess(
bool isRead,
byte write, EmuTime::param time)
790 if (!isRead) cpuVramData = write;
791 cpuVramReqIsRead = isRead;
792 if (pendingCpuAccess) [[unlikely]] {
795 assert(!allowTooFastAccess);
798 if (allowTooFastAccess) [[unlikely]] {
811 assert(!pendingCpuAccess);
812 executeCpuVramAccess(time);
840 pendingCpuAccess =
true;
848void VDP::executeCpuVramAccess(EmuTime::param time)
850 int addr = (controlRegs[14] << 14) | vramPointer;
855 addr = ((addr << 16) | (addr >> 1)) & 0x1FFFF;
858 bool doAccess = [&] {
859 if (!cpuExtendedVram) [[likely]] {
861 }
else if (vram->getSize() == 192 * 1024) [[likely]] {
862 addr = 0x20000 | (addr & 0xFFFF);
869 if (cpuVramReqIsRead) {
870 cpuVramData = vram->cpuRead(addr, time);
872 vram->cpuWrite(addr, cpuVramData, time);
875 if (cpuVramReqIsRead) {
882 vramPointer = (vramPointer + 1) & 0x3FFF;
883 if (vramPointer == 0 && displayMode.
isV9938Mode()) {
885 controlRegs[14] = (controlRegs[14] + 1) & 0x07;
896 EmuTime::param time, EmuTime::param limit)
const
906 spriteChecker->sync(time);
909 if (controlRegs[0] & 0x10) {
910 return statusReg1 | (irqHorizontal.
getState() ? 1:0);
917 if (afterMatch < 0) {
922 int matchLength = (displayMode.
isTextMode() ? 87 : 59)
925 (0 <= afterMatch && afterMatch < matchLength);
935 || ticksThisFrame >= displayEnd;
937 | (getHR(ticksThisFrame) ? 0x20 : 0x00)
939 | cmdEngine->getStatus(time);
942 return byte(spriteChecker->getCollisionX(time));
944 return byte(spriteChecker->getCollisionX(time) >> 8) | 0xFE;
946 return byte(spriteChecker->getCollisionY(time));
948 return byte(spriteChecker->getCollisionY(time) >> 8) | 0xFC;
950 return cmdEngine->readColor(time);
952 return byte(cmdEngine->getBorderX(time));
954 return byte(cmdEngine->getBorderX(time) >> 8) | 0xFE;
960byte VDP::readStatusReg(
byte reg, EmuTime::param time)
965 spriteChecker->resetStatus();
970 if (controlRegs[0] & 0x10) {
971 irqHorizontal.
reset();
975 spriteChecker->resetCollision();
978 cmdEngine->resetColor();
988 registerDataStored =
false;
990 switch (port & (
isMSX1VDP() ? 0x01 : 0x03)) {
992 return vramRead(time);
995 return readStatusReg(controlRegs[15], time);
1016 cpuExtendedVram = (val & 0x40) != 0;
1020 cmdEngine->setCmdReg(reg - 32, val, time);
1026 val &= controlValueMasks[reg];
1028 byte change = val ^ controlRegs[reg];
1034 if (blinkState == ((val & 0xF0) == 0)) {
1035 renderer->updateBlinkState(!blinkState, time);
1036 blinkState = !blinkState;
1039 if ((val & 0xF0) && (val & 0x0F)) {
1041 blinkCount = (val >> 4) * 10;
1052 if (!change)
return;
1058 syncAtNextLine(syncSetMode, time);
1062 if (change & 0x03) {
1064 spriteChecker->updateSpriteSizeMag(val, time);
1068 syncAtNextLine(syncSetMode, time);
1070 if (change & 0x40) {
1071 syncAtNextLine(syncSetBlank, time);
1075 unsigned base = (val << 10) | ~(~0u << 10);
1088 renderer->updateNameBase(base, time);
1093 if (change & 0xF0) {
1094 renderer->updateForegroundColor(val >> 4, time);
1096 if (change & 0x0F) {
1097 renderer->updateBackgroundColor(val & 0x0F, time);
1100 renderer->updateBackgroundColor(val, time);
1104 if (change & 0x20) {
1105 renderer->updateTransparency((val & 0x20) == 0, time);
1106 spriteChecker->updateTransparency((val & 0x20) == 0, time);
1108 if (change & 0x02) {
1109 syncAtNextLine(syncSetSprites, time);
1111 if (change & 0x08) {
1112 vram->updateVRMode((val & 0x08) != 0, time);
1116 if (change & 0xF0) {
1117 renderer->updateBlinkForegroundColor(val >> 4, time);
1119 if (change & 0x0F) {
1120 renderer->updateBlinkBackgroundColor(val & 0x0F, time);
1125 paletteDataStored =
false;
1128 if (change & 0x0F) {
1129 syncAtNextLine(syncHorAdjust, time);
1133 spriteChecker->updateVerticalScroll(val, time);
1134 renderer->updateVerticalScroll(val, time);
1142 if (change & 0x08) {
1143 syncAtNextLine(syncHorAdjust, time);
1145 if (change & 0x02) {
1146 renderer->updateBorderMask((val & 0x02) != 0, time);
1148 if (change & 0x01) {
1149 renderer->updateMultiPage((val & 0x01) != 0, time);
1153 renderer->updateHorizontalScrollHigh(val, time);
1156 renderer->updateHorizontalScrollLow(val, time);
1161 controlRegs[reg] = val;
1168 if (change & 0x10) {
1170 scheduleHScan(time);
1172 irqHorizontal.
reset();
1177 if (change & 0x20) {
1182 if (statusReg0 & 0x80) {
1186 irqVertical.
reset();
1192 vram->change4k8kMapping((val & 0x80) != 0);
1196 updateNameBase(time);
1200 updateColorBase(time);
1204 updatePatternBase(time);
1208 updateSpriteAttributeBase(time);
1211 updateSpritePatternBase(time);
1214 if ((val & 1) && ! warningPrinted) {
1215 warningPrinted =
true;
1216 dotClockDirectionCallback.
execute();
1219 if (change & 0x80) {
1229 if (time < displayStartSyncTime) {
1231 scheduleDisplayStart(time);
1234 scheduleVScan(time);
1240 scheduleHScan(time);
1243 if (change & 0x01) {
1244 updateNameBase(time);
1250void VDP::syncAtNextLine(SyncBase& type, EmuTime::param time)
const
1254 int offset = 144 + (horizontalAdjust - 7) * 4;
1257 EmuTime nextTime = frameStartTime + ticks;
1258 type.setSyncPoint(nextTime);
1261void VDP::updateNameBase(EmuTime::param time)
1263 unsigned base = (controlRegs[2] << 10) | ~(~0u << 10);
1276 unsigned indexMask =
1279 : ~0u << (displayMode.
isTextMode() ? 12 : 10);
1280 if (controlRegs[25] & 0x01) {
1284 indexMask &= ~0x8000;
1286 vram->nameTable.setMask(base, indexMask, time);
1289void VDP::updateColorBase(EmuTime::param time)
1291 unsigned base = (controlRegs[10] << 14) | (controlRegs[3] << 6) | ~(~0u << 6);
1292 renderer->updateColorBase(base, time);
1293 switch (displayMode.
getBase()) {
1296 vram->colorTable.setMask(base, ~0u << 9, time);
1299 vram->colorTable.setMask(base, ~0u << 6, time);
1302 vram->colorTable.setMask(base | (
vdpLacksMirroring() ? 0x1800 : 0), ~0u << 13, time);
1305 vram->colorTable.setMask(base, ~0u << 13, time);
1309 vram->colorTable.disable(time);
1313void VDP::updatePatternBase(EmuTime::param time)
1315 unsigned base = (controlRegs[4] << 11) | ~(~0u << 11);
1316 renderer->updatePatternBase(base, time);
1317 switch (displayMode.
getBase()) {
1324 vram->patternTable.setMask(base, ~0u << 11, time);
1332 base = (controlRegs[4] << 11)
1333 | ((controlRegs[3] & 0x1f) << 6)
1336 vram->patternTable.setMask(base | (
vdpLacksMirroring() ? 0x1800 : 0), ~0u << 13, time);
1339 vram->patternTable.setMask(base, ~0u << 13, time);
1343 vram->patternTable.disable(time);
1347void VDP::updateSpriteAttributeBase(EmuTime::param time)
1351 vram->spriteAttribTable.disable(time);
1354 unsigned baseMask = (controlRegs[11] << 15) | (controlRegs[5] << 7) | ~(~0u << 7);
1355 unsigned indexMask = mode == 1 ? ~0u << 7 : ~0u << 10;
1357 baseMask = ((baseMask << 16) | (baseMask >> 1)) & 0x1FFFF;
1358 indexMask = ((indexMask << 16) | ~(1 << 16)) & (indexMask >> 1);
1360 vram->spriteAttribTable.setMask(baseMask, indexMask, time);
1363void VDP::updateSpritePatternBase(EmuTime::param time)
1366 vram->spritePatternTable.disable(time);
1369 unsigned baseMask = (controlRegs[6] << 11) | ~(~0u << 11);
1370 unsigned indexMask = ~0u << 11;
1372 baseMask = ((baseMask << 16) | (baseMask >> 1)) & 0x1FFFF;
1373 indexMask = ((indexMask << 16) | ~(1 << 16)) & (indexMask >> 1);
1375 vram->spritePatternTable.setMask(baseMask, indexMask, time);
1378void VDP::updateDisplayMode(DisplayMode newMode,
bool cmdBit, EmuTime::param time)
1381 vram->updateDisplayMode(newMode, cmdBit, time);
1388 newMode.isPlanar() != displayMode.
isPlanar();
1391 bool spriteModeChange =
1392 newMode.getSpriteMode(msx1) != displayMode.
getSpriteMode(msx1);
1395 displayMode = newMode;
1401 updateColorBase(time);
1402 updatePatternBase(time);
1404 if (planarChange || spriteModeChange) {
1405 updateSpritePatternBase(time);
1406 updateSpriteAttributeBase(time);
1408 updateNameBase(time);
1417void VDP::update(
const Setting&
setting)
noexcept
1421 brokenCmdTiming = cmdTiming .getEnum();
1422 allowTooFastAccess = tooFastAccess.getEnum();
1424 if (allowTooFastAccess && pendingCpuAccess) [[unlikely]] {
1426 syncCpuVramAccess.removeSyncPoint();
1427 pendingCpuAccess =
false;
1428 executeCpuVramAccess(getCurrentTime());
1449static constexpr std::array<std::array<uint8_t, 3>, 16> TOSHIBA_PALETTE = {{
1475static constexpr std::array<std::array<uint8_t, 3>, 16> YM2220_PALETTE = {{
1502static constexpr std::array<std::array<uint8_t, 3>, 16> THREE_BIT_RGB_PALETTE = {{
1523static constexpr std::array<std::array<float, 3>, 16> TMS9XXXA_ANALOG_OUTPUT = {
1525 std::array{0.00f, 0.47f, 0.47f},
1526 std::array{0.00f, 0.47f, 0.47f},
1527 std::array{0.53f, 0.07f, 0.20f},
1528 std::array{0.67f, 0.17f, 0.27f},
1529 std::array{0.40f, 0.40f, 1.00f},
1530 std::array{0.53f, 0.43f, 0.93f},
1531 std::array{0.47f, 0.83f, 0.30f},
1532 std::array{0.73f, 0.00f, 0.70f},
1533 std::array{0.53f, 0.93f, 0.27f},
1534 std::array{0.67f, 0.93f, 0.27f},
1535 std::array{0.73f, 0.57f, 0.07f},
1536 std::array{0.80f, 0.57f, 0.17f},
1537 std::array{0.47f, 0.13f, 0.23f},
1538 std::array{0.53f, 0.73f, 0.67f},
1539 std::array{0.80f, 0.47f, 0.47f},
1540 std::array{1.00f, 0.47f, 0.47f},
1547 return THREE_BIT_RGB_PALETTE;
1549 if ((version & VM_TOSHIBA_PALETTE) != 0) {
1550 return TOSHIBA_PALETTE;
1552 if ((version & VM_YM2220_PALETTE) != 0) {
1553 return YM2220_PALETTE;
1555 std::array<std::array<uint8_t, 3>, 16> tmsPalette;
1556 for (
auto color :
xrange(16)) {
1558 float Y = TMS9XXXA_ANALOG_OUTPUT[color][0];
1559 float Pr = TMS9XXXA_ANALOG_OUTPUT[color][1] - 0.5f;
1560 float Pb = TMS9XXXA_ANALOG_OUTPUT[color][2] - 0.5f;
1562 Pr *= (narrow<float>(saturationPr) * (1.0f / 100.0f));
1563 Pb *= (narrow<float>(saturationPb) * (1.0f / 100.0f));
1570 float R = Y + 0 + 1.402f * Pr;
1571 float G = Y - 0.344f * Pb - 0.714f * Pr;
1572 float B = Y + 1.722f * Pb + 0;
1593VDP::RegDebug::RegDebug(
const VDP& vdp_)
1595 vdp_.getName() +
" regs",
"VDP registers.", 0x40)
1599byte VDP::RegDebug::read(
unsigned address)
1601 const auto& vdp =
OUTER(VDP, vdpRegDebug);
1602 return vdp.peekRegister(address);
1605void VDP::RegDebug::write(
unsigned address,
byte value, EmuTime::param time)
1607 auto& vdp =
OUTER(VDP, vdpRegDebug);
1612 if ((address >= 8) && vdp.isMSX1VDP())
return;
1613 vdp.changeRegister(narrow<byte>(address), value, time);
1619VDP::StatusRegDebug::StatusRegDebug(
const VDP& vdp_)
1620 : SimpleDebuggable(vdp_.getMotherBoard(),
1621 vdp_.getName() +
" status regs",
"VDP status registers.", 0x10)
1625byte VDP::StatusRegDebug::read(
unsigned address, EmuTime::param time)
1627 const auto& vdp =
OUTER(VDP, vdpStatusRegDebug);
1628 return vdp.peekStatusReg(narrow<byte>(address), time);
1634VDP::PaletteDebug::PaletteDebug(
const VDP& vdp_)
1635 : SimpleDebuggable(vdp_.getMotherBoard(),
1636 vdp_.getName() +
" palette",
"V99x8 palette (RBG format)", 0x20)
1640byte VDP::PaletteDebug::read(
unsigned address)
1642 const auto& vdp =
OUTER(VDP, vdpPaletteDebug);
1643 word grb = vdp.getPalette(address / 2);
1644 return (address & 1) ? narrow_cast<byte>(grb >> 8)
1648void VDP::PaletteDebug::write(
unsigned address,
byte value, EmuTime::param time)
1650 auto& vdp =
OUTER(VDP, vdpPaletteDebug);
1654 if (vdp.isMSX1VDP())
return;
1656 unsigned index = address / 2;
1657 word grb = vdp.getPalette(index);
1659 ?
word((grb & 0x0077) | ((value & 0x07) << 8))
1660 :
word((grb & 0x0700) | ((value & 0x77) << 0));
1661 vdp.setPalette(index, grb, time);
1667VDP::VRAMPointerDebug::VRAMPointerDebug(
const VDP& vdp_)
1668 : SimpleDebuggable(vdp_.getMotherBoard(), vdp_.getName() ==
"VDP" ?
1669 "VRAM pointer" : vdp_.getName() +
" VRAM pointer",
1670 "VDP VRAM pointer (14 lower bits)", 2)
1674byte VDP::VRAMPointerDebug::read(
unsigned address)
1676 const auto& vdp =
OUTER(VDP, vramPointerDebug);
1678 return narrow_cast<byte>(vdp.vramPointer >> 8);
1680 return narrow_cast<byte>(vdp.vramPointer & 0xFF);
1684void VDP::VRAMPointerDebug::write(
unsigned address,
byte value, EmuTime::param )
1686 auto& vdp =
OUTER(VDP, vramPointerDebug);
1687 int& ptr = vdp.vramPointer;
1689 ptr = (ptr & 0x00FF) | ((value & 0x3F) << 8);
1691 ptr = (ptr & 0xFF00) | value;
1697VDP::RegisterLatchStatusDebug::RegisterLatchStatusDebug(
const VDP &vdp_)
1698 : SimpleDebuggable(vdp_.getMotherBoard(),
1699 vdp_.getName() +
" register latch status",
"V99x8 register latch status (0 = expecting a value, 1 = expecting a register)", 1)
1703byte VDP::RegisterLatchStatusDebug::read(
unsigned )
1705 const auto& vdp =
OUTER(VDP, registerLatchStatusDebug);
1706 return byte(vdp.registerDataStored);
1711VDP::VramAccessStatusDebug::VramAccessStatusDebug(
const VDP &vdp_)
1712 : SimpleDebuggable(vdp_.getMotherBoard(), vdp_.getName() ==
"VDP" ?
1713 "VRAM access status" : vdp_.getName() +
" VRAM access status",
1714 "VDP VRAM access status (0 = ready to read, 1 = ready to write)", 1)
1718byte VDP::VramAccessStatusDebug::read(
unsigned )
1720 const auto& vdp =
OUTER(VDP, vramAccessStatusDebug);
1721 return byte(vdp.writeAccess);
1726VDP::PaletteLatchStatusDebug::PaletteLatchStatusDebug(
const VDP &vdp_)
1727 : SimpleDebuggable(vdp_.getMotherBoard(),
1728 vdp_.getName() +
" palette latch status",
"V99x8 palette latch status (0 = expecting red & blue, 1 = expecting green)", 1)
1732byte VDP::PaletteLatchStatusDebug::read(
unsigned )
1734 const auto& vdp =
OUTER(VDP, paletteLatchStatusDebug);
1735 return byte(vdp.paletteDataStored);
1740VDP::DataLatchDebug::DataLatchDebug(
const VDP &vdp_)
1741 : SimpleDebuggable(vdp_.getMotherBoard(),
1742 vdp_.getName() +
" data latch value",
"V99x8 data latch value (byte)", 1)
1746byte VDP::DataLatchDebug::read(
unsigned )
1748 const auto& vdp =
OUTER(VDP, dataLatchDebug);
1749 return vdp.dataLatch;
1754VDP::Info::Info(VDP& vdp_,
const std::string& name_, std::string helpText_)
1755 : InfoTopic(vdp_.getMotherBoard().getMachineInfoCommand(),
1756 strCat(vdp_.getName(),
'_', name_))
1758 , helpText(
std::move(helpText_))
1762void VDP::Info::execute(std::span<const TclObject> , TclObject& result)
const
1764 result = calc(vdp.getCurrentTime());
1767std::string VDP::Info::help(std::span<const TclObject> )
const
1775VDP::FrameCountInfo::FrameCountInfo(VDP& vdp_)
1776 : Info(vdp_,
"frame_count",
1777 "The current frame number, starts counting at 0 "
1778 "when MSX is powered up or reset.")
1782int VDP::FrameCountInfo::calc(
const EmuTime& )
const
1784 return vdp.frameCount;
1790VDP::CycleInFrameInfo::CycleInFrameInfo(VDP& vdp_)
1791 : Info(vdp_,
"cycle_in_frame",
1792 "The number of VDP cycles since the beginning of "
1793 "the current frame. The VDP runs at 6 times the Z80 "
1794 "clock frequency, so at approximately 21.5MHz.")
1798int VDP::CycleInFrameInfo::calc(
const EmuTime& time)
const
1800 return vdp.getTicksThisFrame(time);
1806VDP::LineInFrameInfo::LineInFrameInfo(VDP& vdp_)
1807 : Info(vdp_,
"line_in_frame",
1808 "The absolute line number since the beginning of "
1809 "the current frame. Goes from 0 till 262 (NTSC) or "
1810 "313 (PAL). Note that this number includes the "
1811 "border lines, use 'msx_y_pos' to get MSX "
1816int VDP::LineInFrameInfo::calc(
const EmuTime& time)
const
1824VDP::CycleInLineInfo::CycleInLineInfo(VDP& vdp_)
1825 : Info(vdp_,
"cycle_in_line",
1826 "The number of VDP cycles since the beginning of "
1827 "the current line. See also 'cycle_in_frame'."
1828 "Note that this includes the cycles in the border, "
1829 "use 'msx_x256_pos' or 'msx_x512_pos' to get MSX "
1834int VDP::CycleInLineInfo::calc(
const EmuTime& time)
const
1842VDP::MsxYPosInfo::MsxYPosInfo(VDP& vdp_)
1843 : Info(vdp_,
"msx_y_pos",
1844 "Similar to 'line_in_frame', but expressed in MSX "
1845 "coordinates. So lines in the top border have "
1846 "negative coordinates, lines in the bottom border "
1847 "have coordinates bigger or equal to 192 or 212.")
1851int VDP::MsxYPosInfo::calc(
const EmuTime& time)
const
1853 return vdp.getMSXPos(time).y;
1859VDP::MsxX256PosInfo::MsxX256PosInfo(VDP& vdp_)
1860 : Info(vdp_,
"msx_x256_pos",
1861 "Similar to 'cycle_in_frame', but expressed in MSX "
1862 "coordinates. So a position in the left border has "
1863 "a negative coordinate and a position in the right "
1864 "border has a coordinate bigger or equal to 256. "
1865 "See also 'msx_x512_pos'.")
1869int VDP::MsxX256PosInfo::calc(
const EmuTime& time)
const
1871 return vdp.getMSXPos(time).x / 2;
1877VDP::MsxX512PosInfo::MsxX512PosInfo(VDP& vdp_)
1878 : Info(vdp_,
"msx_x512_pos",
1879 "Similar to 'cycle_in_frame', but expressed in "
1880 "'narrow' (screen 7) MSX coordinates. So a position "
1881 "in the left border has a negative coordinate and "
1882 "a position in the right border has a coordinate "
1883 "bigger or equal to 512. See also 'msx_x256_pos'.")
1887int VDP::MsxX512PosInfo::calc(
const EmuTime& time)
const
1889 return vdp.getMSXPos(time).x;
1903template<
typename Archive>
1906 ar.template serializeBase<MSXDevice>(*
this);
1908 if (ar.versionAtLeast(serVersion, 8)) {
1909 ar.serialize(
"syncVSync", syncVSync,
1910 "syncDisplayStart", syncDisplayStart,
1911 "syncVScan", syncVScan,
1912 "syncHScan", syncHScan,
1913 "syncHorAdjust", syncHorAdjust,
1914 "syncSetMode", syncSetMode,
1915 "syncSetBlank", syncSetBlank,
1916 "syncCpuVramAccess", syncCpuVramAccess);
1920 {&syncVSync, &syncDisplayStart, &syncVScan,
1921 &syncHScan, &syncHorAdjust, &syncSetMode,
1922 &syncSetBlank, &syncCpuVramAccess});
1932 ar.serialize(
"irqVertical", irqVertical,
1933 "irqHorizontal", irqHorizontal,
1934 "frameStartTime", frameStartTime,
1935 "displayStartSyncTime", displayStartSyncTime,
1936 "vScanSyncTime", vScanSyncTime,
1937 "hScanSyncTime", hScanSyncTime,
1938 "displayStart", displayStart,
1939 "horizontalScanOffset", horizontalScanOffset,
1940 "horizontalAdjust", horizontalAdjust,
1941 "registers", controlRegs,
1942 "blinkCount", blinkCount,
1943 "vramPointer", vramPointer,
1945 "isDisplayArea", isDisplayArea,
1946 "palTiming", palTiming,
1947 "interlaced", interlaced,
1948 "statusReg0", statusReg0,
1949 "statusReg1", statusReg1,
1950 "statusReg2", statusReg2,
1951 "blinkState", blinkState,
1952 "dataLatch", dataLatch,
1953 "registerDataStored", registerDataStored,
1954 "paletteDataStored", paletteDataStored);
1955 if (ar.versionAtLeast(serVersion, 5)) {
1956 ar.serialize(
"cpuVramData", cpuVramData,
1957 "cpuVramReqIsRead", cpuVramReqIsRead);
1959 ar.serialize(
"readAhead", cpuVramData);
1961 ar.serialize(
"cpuExtendedVram", cpuExtendedVram,
1962 "displayEnabled", displayEnabled);
1963 byte mode = displayMode.
getByte();
1964 ar.serialize(
"displayMode", mode);
1967 ar.serialize(
"cmdEngine", *cmdEngine,
1968 "spriteChecker", *spriteChecker,
1970 if constexpr (Archive::IS_LOADER) {
1971 pendingCpuAccess = syncCpuVramAccess.pendingSyncPoint();
1972 update(tooFastAccess);
1975 if (ar.versionAtLeast(serVersion, 2)) {
1976 ar.serialize(
"frameCount", frameCount);
1978 assert(Archive::IS_LOADER);
1985 if (ar.versionAtLeast(serVersion, 9)) {
1986 ar.serialize(
"syncSetSprites", syncSetSprites);
1987 ar.serialize(
"spriteEnabled", spriteEnabled);
1989 assert(Archive::IS_LOADER);
1990 spriteEnabled = (controlRegs[8] & 0x02) == 0;
1992 if (ar.versionAtLeast(serVersion, 10)) {
1993 ar.serialize(
"writeAccess", writeAccess);
1995 writeAccess = !cpuVramReqIsRead;
2006 if constexpr (Archive::IS_LOADER) {
#define REGISTER_MSXDEVICE(CLASS, NAME)
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,...
static constexpr uint8_t GRAPHIC7
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
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
uint16_t word
16 bit unsigned integer
constexpr void fill(ForwardRange &&range, const T &value)
constexpr To narrow_cast(From &&from) noexcept
#define OUTER(type, member)
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
constexpr auto xrange(T e)