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.starts_with(
"TMS") &&
154 throw MSXException(
"Specifying saturation parameters only makes sense for TMS VDPs");
157 auto getPercentage = [&](std::string_view name, std::string_view extra,
int defaultValue) {
159 if ((result < 0) || (result > 100)) {
161 "Saturation percentage ", extra,
"is not in range 0..100: ", result);
165 int saturation = getPercentage(
"saturation",
"", defaultSaturation);
166 saturationPr = getPercentage(
"saturationPr",
"for Pr component ", saturation);
167 saturationPb = getPercentage(
"saturationPb",
"for Pb component ", saturation);
170 static constexpr std::array<byte, 32> VALUE_MASKS_MSX1 = {
171 0x03, 0xFB, 0x0F, 0xFF, 0x07, 0x7F, 0x07, 0xFF
173 static constexpr std::array<byte, 32> VALUE_MASKS_MSX2 = {
174 0x7E, 0x7F, 0x7F, 0xFF, 0x3F, 0xFF, 0x3F, 0xFF,
175 0xFB, 0xBF, 0x07, 0x03, 0xFF, 0xFF, 0x07, 0x0F,
176 0x0F, 0xBF, 0xFF, 0xFF, 0x3F, 0x3F, 0x3F, 0xFF,
177 0, 0, 0, 0, 0, 0, 0, 0,
179 controlRegMask =
isMSX1VDP() ? 0x07 : 0x3F;
180 controlValueMasks =
isMSX1VDP() ? VALUE_MASKS_MSX1 : VALUE_MASKS_MSX2;
181 if (version == V9958) {
183 controlValueMasks[25] = 0x7F;
184 controlValueMasks[26] = 0x3F;
185 controlValueMasks[27] = 0x07;
194 if (vramSize !=
one_of(16u, 64u, 128u, 192u)) {
196 "VRAM size of ", vramSize,
"kB is not supported!");
198 vram = std::make_unique<VDPVRAM>(*
this, vramSize * 1024, time);
202 spriteChecker = std::make_unique<SpriteChecker>(*
this, renderSettings, time);
203 vram->setSpriteChecker(spriteChecker.get());
207 vram->setCmdEngine(cmdEngine.get());
217 tooFastAccess.
attach(*
this);
218 update(tooFastAccess);
223 tooFastAccess.
detach(*
this);
228void VDP::preVideoSystemChange() noexcept
233void VDP::postVideoSystemChange() noexcept
238void VDP::createRenderer()
244 vram->setRenderer(renderer.get(), frameStartTime.
getTime());
249 return renderer->getPostProcessor();
259 controlRegs[9] |= 0x02;
263 controlRegs[21] = 0x3B;
264 controlRegs[22] = 0x05;
272 cpuVramReqIsRead =
false;
274 cpuExtendedVram =
false;
275 registerDataStored =
false;
277 paletteDataStored =
false;
280 horizontalAdjust = 7;
283 isDisplayArea =
false;
284 displayEnabled =
false;
285 spriteEnabled =
true;
286 superimposing =
nullptr;
287 externalVideo =
nullptr;
291 statusReg1 = (version == V9958 ? 0x04 : 0x00);
296 irqHorizontal.
reset();
299 const std::array<uint16_t, 16> V9938_PALETTE = {
300 0x000, 0x000, 0x611, 0x733, 0x117, 0x327, 0x151, 0x627,
301 0x171, 0x373, 0x661, 0x664, 0x411, 0x265, 0x555, 0x777
304 palette = V9938_PALETTE;
307void VDP::resetMasks(EmuTime::param time)
309 updateNameBase(time);
310 updateColorBase(time);
311 updatePatternBase(time);
312 updateSpriteAttributeBase(time);
313 updateSpritePatternBase(time);
327 syncVSync .removeSyncPoint();
328 syncDisplayStart .removeSyncPoint();
329 syncVScan .removeSyncPoint();
330 syncHScan .removeSyncPoint();
331 syncHorAdjust .removeSyncPoint();
332 syncSetMode .removeSyncPoint();
333 syncSetBlank .removeSyncPoint();
334 syncSetSprites .removeSyncPoint();
335 syncCpuVramAccess.removeSyncPoint();
336 syncCmdDone .removeSyncPoint();
337 pendingCpuAccess =
false;
340 cmdEngine->sync(time);
342 spriteChecker->reset(time);
343 cmdEngine->reset(time);
352 assert(frameCount == 0);
355void VDP::execVSync(EmuTime::param time)
360 renderer->frameEnd(time);
361 spriteChecker->frameEnd(time);
366 blinkState = next.state;
367 blinkCount = next.count;
371 cmdEngine->sync(time);
377void VDP::execDisplayStart(EmuTime::param time)
381 if (!isDisplayArea) {
382 if (displayEnabled) {
383 vram->updateDisplayEnabled(
true, time);
385 isDisplayArea =
true;
389void VDP::execVScan(EmuTime::param time)
398 vram->updateDisplayEnabled(
false, time);
400 isDisplayArea =
false;
404 if (controlRegs[1] & 0x20) {
412 if (controlRegs[0] & 0x10) {
417void VDP::execHorAdjust(EmuTime::param time)
419 int newHorAdjust = (controlRegs[18] & 0x0F) ^ 0x07;
420 if (controlRegs[25] & 0x08) {
423 renderer->updateHorizontalAdjust(newHorAdjust, time);
424 horizontalAdjust = newHorAdjust;
427void VDP::execSetMode(EmuTime::param time)
430 DisplayMode(controlRegs[0], controlRegs[1], controlRegs[25]),
435void VDP::execSetBlank(EmuTime::param time)
437 bool newDisplayEnabled = (controlRegs[1] & 0x40) != 0;
439 vram->updateDisplayEnabled(newDisplayEnabled, time);
441 displayEnabled = newDisplayEnabled;
444void VDP::execSetSprites(EmuTime::param time)
446 bool newSpriteEnabled = (controlRegs[8] & 0x02) == 0;
447 vram->updateSpritesEnabled(newSpriteEnabled, time);
448 spriteEnabled = newSpriteEnabled;
451void VDP::execCpuVramAccess(EmuTime::param time)
453 assert(!allowTooFastAccess);
454 pendingCpuAccess =
false;
455 executeCpuVramAccess(time);
458void VDP::execSyncCmdDone(EmuTime::param time)
460 cmdEngine->sync(time);
466void VDP::scheduleDisplayStart(EmuTime::param time)
469 if (displayStartSyncTime > time) {
470 syncDisplayStart.removeSyncPoint();
479 (palTiming ? 36 : 9) +
480 (controlRegs[9] & 0x80 ? 0 : 10) +
485 displayStartSyncTime = frameStartTime + displayStart;
489 if (displayStartSyncTime > time) {
490 syncDisplayStart.setSyncPoint(displayStartSyncTime);
499void VDP::scheduleVScan(EmuTime::param time)
502 if (vScanSyncTime > time) {
503 syncVScan.removeSyncPoint();
508 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 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)
597 palTiming = (controlRegs[9] & 0x02) != 0;
603 if (blinkCount == 0) {
604 renderer->updateBlinkState(!blinkState, time);
605 blinkState = !blinkState;
606 blinkCount = (blinkState
607 ? controlRegs[13] >> 4 : controlRegs[13] & 0x0F) * 10;
616 if (
const RawFrame* newSuperimposing = (controlRegs[0] & 1) ? externalVideo :
nullptr;
617 superimposing != newSuperimposing) {
618 superimposing = newSuperimposing;
619 renderer->updateSuperimposing(superimposing, time);
623 frameStartTime.
reset(time);
626 scheduleDisplayStart(time);
630 renderer->frameStart(time);
631 spriteChecker->frameStart(time);
647 EmuTime time = time_;
653 if (fixedVDPIOdelayCycles > 0) {
658 switch (port & (
isMSX1VDP() ? 0x01 : 0x03)) {
660 vramWrite(value, time);
661 registerDataStored =
false;
664 if (registerDataStored) {
669 value & controlRegMask,
684 vramPointer = (value << 8 | (vramPointer & 0xFF)) & 0x3FFF;
688 writeAccess = value & 0x40;
689 vramPointer = (value << 8 | dataLatch) & 0x3FFF;
690 if (!(value & 0x40)) {
692 (void)vramRead(time);
695 registerDataStored =
false;
702 vramPointer = (vramPointer & 0x3F00) | value;
705 registerDataStored =
true;
709 if (paletteDataStored) {
710 unsigned index = controlRegs[16];
711 word grb = ((value << 8) | dataLatch) & 0x777;
713 controlRegs[16] = (index + 1) & 0x0F;
714 paletteDataStored =
false;
717 paletteDataStored =
true;
724 byte regNr = controlRegs[17];
726 if ((regNr & 0x80) == 0) {
728 controlRegs[17] = (regNr + 1) & 0x3F;
742 assert(vdpVersionString);
743 return vdpVersionString->getData();
753 if (address < 0x20) {
754 return controlRegs[address];
755 }
else if (address < 0x2F) {
756 return cmdEngine->peekCmdReg(narrow<byte>(address - 0x20));
764 if (palette[index] != grb) {
765 renderer->updatePalette(index, grb, time);
766 palette[index] = grb;
770void VDP::vramWrite(
byte value, EmuTime::param time)
772 scheduleCpuVramAccess(
false, value, time);
775byte VDP::vramRead(EmuTime::param time)
780 byte result = cpuVramData;
783 scheduleCpuVramAccess(
true, dummy, time);
787void VDP::scheduleCpuVramAccess(
bool isRead,
byte write, EmuTime::param time)
791 if (!isRead) cpuVramData = write;
792 cpuVramReqIsRead = isRead;
793 if (pendingCpuAccess) [[unlikely]] {
796 assert(!allowTooFastAccess);
799 if (allowTooFastAccess) [[unlikely]] {
812 assert(!pendingCpuAccess);
813 executeCpuVramAccess(time);
841 pendingCpuAccess =
true;
849void VDP::executeCpuVramAccess(EmuTime::param time)
851 int addr = (controlRegs[14] << 14) | vramPointer;
856 addr = ((addr << 16) | (addr >> 1)) & 0x1FFFF;
859 bool doAccess = [&] {
860 if (!cpuExtendedVram) [[likely]] {
862 }
else if (vram->getSize() == 192 * 1024) [[likely]] {
863 addr = 0x20000 | (addr & 0xFFFF);
870 if (cpuVramReqIsRead) {
871 cpuVramData = vram->cpuRead(addr, time);
873 vram->cpuWrite(addr, cpuVramData, time);
876 if (cpuVramReqIsRead) {
883 vramPointer = (vramPointer + 1) & 0x3FFF;
884 if (vramPointer == 0 && displayMode.
isV9938Mode()) {
886 controlRegs[14] = (controlRegs[14] + 1) & 0x07;
897 EmuTime::param time, EmuTime::param limit)
const
907 spriteChecker->sync(time);
910 if (controlRegs[0] & 0x10) {
911 return statusReg1 | (irqHorizontal.
getState() ? 1:0);
918 if (afterMatch < 0) {
923 int matchLength = (displayMode.
isTextMode() ? 87 : 59)
926 (0 <= afterMatch && afterMatch < matchLength);
936 || ticksThisFrame >= displayEnd;
938 | (getHR(ticksThisFrame) ? 0x20 : 0x00)
940 | cmdEngine->getStatus(time);
943 return byte(spriteChecker->getCollisionX(time));
945 return byte(spriteChecker->getCollisionX(time) >> 8) | 0xFE;
947 return byte(spriteChecker->getCollisionY(time));
949 return byte(spriteChecker->getCollisionY(time) >> 8) | 0xFC;
951 return cmdEngine->readColor(time);
953 return byte(cmdEngine->getBorderX(time));
955 return byte(cmdEngine->getBorderX(time) >> 8) | 0xFE;
961byte VDP::readStatusReg(
byte reg, EmuTime::param time)
966 spriteChecker->resetStatus();
971 if (controlRegs[0] & 0x10) {
972 irqHorizontal.
reset();
976 spriteChecker->resetCollision();
979 cmdEngine->resetColor();
988 EmuTime time = time_;
989 if (fixedVDPIOdelayCycles > 0) {
995 registerDataStored =
false;
997 switch (port & (
isMSX1VDP() ? 0x01 : 0x03)) {
999 return vramRead(time);
1002 return readStatusReg(controlRegs[15], time);
1023 cpuExtendedVram = (val & 0x40) != 0;
1027 cmdEngine->setCmdReg(reg - 32, val, time);
1033 val &= controlValueMasks[reg];
1035 byte change = val ^ controlRegs[reg];
1041 if (blinkState == ((val & 0xF0) == 0)) {
1042 renderer->updateBlinkState(!blinkState, time);
1043 blinkState = !blinkState;
1046 if ((val & 0xF0) && (val & 0x0F)) {
1048 blinkCount = (val >> 4) * 10;
1059 if (!change)
return;
1065 syncAtNextLine(syncSetMode, time);
1069 if (change & 0x03) {
1071 spriteChecker->updateSpriteSizeMag(val, time);
1075 syncAtNextLine(syncSetMode, time);
1077 if (change & 0x40) {
1078 syncAtNextLine(syncSetBlank, time);
1082 unsigned base = (val << 10) | ~(~0u << 10);
1095 renderer->updateNameBase(base, time);
1100 if (change & 0xF0) {
1101 renderer->updateForegroundColor(val >> 4, time);
1103 if (change & 0x0F) {
1104 renderer->updateBackgroundColor(val & 0x0F, time);
1107 renderer->updateBackgroundColor(val, time);
1111 if (change & 0x20) {
1112 renderer->updateTransparency((val & 0x20) == 0, time);
1113 spriteChecker->updateTransparency((val & 0x20) == 0, time);
1115 if (change & 0x02) {
1116 syncAtNextLine(syncSetSprites, time);
1118 if (change & 0x08) {
1119 vram->updateVRMode((val & 0x08) != 0, time);
1123 if (change & 0xF0) {
1124 renderer->updateBlinkForegroundColor(val >> 4, time);
1126 if (change & 0x0F) {
1127 renderer->updateBlinkBackgroundColor(val & 0x0F, time);
1132 paletteDataStored =
false;
1135 if (change & 0x0F) {
1136 syncAtNextLine(syncHorAdjust, time);
1140 spriteChecker->updateVerticalScroll(val, time);
1141 renderer->updateVerticalScroll(val, time);
1149 if (change & 0x08) {
1150 syncAtNextLine(syncHorAdjust, time);
1152 if (change & 0x02) {
1153 renderer->updateBorderMask((val & 0x02) != 0, time);
1155 if (change & 0x01) {
1156 renderer->updateMultiPage((val & 0x01) != 0, time);
1160 renderer->updateHorizontalScrollHigh(val, time);
1163 renderer->updateHorizontalScrollLow(val, time);
1168 controlRegs[reg] = val;
1175 if (change & 0x10) {
1177 scheduleHScan(time);
1179 irqHorizontal.
reset();
1184 if (change & 0x20) {
1189 if (statusReg0 & 0x80) {
1193 irqVertical.
reset();
1199 vram->change4k8kMapping((val & 0x80) != 0);
1203 updateNameBase(time);
1207 updateColorBase(time);
1211 updatePatternBase(time);
1215 updateSpriteAttributeBase(time);
1218 updateSpritePatternBase(time);
1221 if ((val & 1) && ! warningPrinted) {
1222 warningPrinted =
true;
1223 dotClockDirectionCallback.
execute();
1226 if (change & 0x80) {
1236 if (time < displayStartSyncTime) {
1238 scheduleDisplayStart(time);
1241 scheduleVScan(time);
1247 scheduleHScan(time);
1250 if (change & 0x01) {
1251 updateNameBase(time);
1257void VDP::syncAtNextLine(SyncBase& type, EmuTime::param time)
const
1261 int offset = 144 + (horizontalAdjust - 7) * 4;
1264 EmuTime nextTime = frameStartTime + ticks;
1265 type.setSyncPoint(nextTime);
1268void VDP::updateNameBase(EmuTime::param time)
1270 unsigned base = (controlRegs[2] << 10) | ~(~0u << 10);
1283 unsigned indexMask =
1286 : ~0u << (displayMode.
isTextMode() ? 12 : 10);
1287 if (controlRegs[25] & 0x01) {
1291 indexMask &= ~0x8000;
1293 vram->nameTable.setMask(base, indexMask, time);
1296void VDP::updateColorBase(EmuTime::param time)
1298 unsigned base = (controlRegs[10] << 14) | (controlRegs[3] << 6) | ~(~0u << 6);
1299 renderer->updateColorBase(base, time);
1300 switch (displayMode.
getBase()) {
1303 vram->colorTable.setMask(base, ~0u << 9, time);
1306 vram->colorTable.setMask(base, ~0u << 6, time);
1309 vram->colorTable.setMask(base | (
vdpLacksMirroring() ? 0x1800 : 0), ~0u << 13, time);
1312 vram->colorTable.setMask(base, ~0u << 13, time);
1316 vram->colorTable.disable(time);
1320void VDP::updatePatternBase(EmuTime::param time)
1322 unsigned base = (controlRegs[4] << 11) | ~(~0u << 11);
1323 renderer->updatePatternBase(base, time);
1324 switch (displayMode.
getBase()) {
1331 vram->patternTable.setMask(base, ~0u << 11, time);
1339 base = (controlRegs[4] << 11)
1340 | ((controlRegs[3] & 0x1f) << 6)
1343 vram->patternTable.setMask(base | (
vdpLacksMirroring() ? 0x1800 : 0), ~0u << 13, time);
1346 vram->patternTable.setMask(base, ~0u << 13, time);
1350 vram->patternTable.disable(time);
1354void VDP::updateSpriteAttributeBase(EmuTime::param time)
1358 vram->spriteAttribTable.disable(time);
1361 unsigned baseMask = (controlRegs[11] << 15) | (controlRegs[5] << 7) | ~(~0u << 7);
1362 unsigned indexMask = mode == 1 ? ~0u << 7 : ~0u << 10;
1364 baseMask = ((baseMask << 16) | (baseMask >> 1)) & 0x1FFFF;
1365 indexMask = ((indexMask << 16) | ~(1 << 16)) & (indexMask >> 1);
1367 vram->spriteAttribTable.setMask(baseMask, indexMask, time);
1370void VDP::updateSpritePatternBase(EmuTime::param time)
1373 vram->spritePatternTable.disable(time);
1376 unsigned baseMask = (controlRegs[6] << 11) | ~(~0u << 11);
1377 unsigned indexMask = ~0u << 11;
1379 baseMask = ((baseMask << 16) | (baseMask >> 1)) & 0x1FFFF;
1380 indexMask = ((indexMask << 16) | ~(1 << 16)) & (indexMask >> 1);
1382 vram->spritePatternTable.setMask(baseMask, indexMask, time);
1385void VDP::updateDisplayMode(DisplayMode newMode,
bool cmdBit, EmuTime::param time)
1388 vram->updateDisplayMode(newMode, cmdBit, time);
1395 newMode.isPlanar() != displayMode.
isPlanar();
1398 bool spriteModeChange =
1399 newMode.getSpriteMode(msx1) != displayMode.
getSpriteMode(msx1);
1402 displayMode = newMode;
1408 updateColorBase(time);
1409 updatePatternBase(time);
1411 if (planarChange || spriteModeChange) {
1412 updateSpritePatternBase(time);
1413 updateSpriteAttributeBase(time);
1415 updateNameBase(time);
1424void VDP::update(
const Setting&
setting)
noexcept
1428 brokenCmdTiming = cmdTiming .getEnum();
1429 allowTooFastAccess = tooFastAccess.getEnum();
1431 if (allowTooFastAccess && pendingCpuAccess) [[unlikely]] {
1433 syncCpuVramAccess.removeSyncPoint();
1434 pendingCpuAccess =
false;
1435 executeCpuVramAccess(getCurrentTime());
1456static constexpr std::array<std::array<uint8_t, 3>, 16> TOSHIBA_PALETTE = {{
1482static constexpr std::array<std::array<uint8_t, 3>, 16> YM2220_PALETTE = {{
1509static constexpr std::array<std::array<uint8_t, 3>, 16> THREE_BIT_RGB_PALETTE = {{
1530static constexpr std::array<std::array<float, 3>, 16> TMS9XXXA_ANALOG_OUTPUT = {
1532 std::array{0.00f, 0.47f, 0.47f},
1533 std::array{0.00f, 0.47f, 0.47f},
1534 std::array{0.53f, 0.07f, 0.20f},
1535 std::array{0.67f, 0.17f, 0.27f},
1536 std::array{0.40f, 0.40f, 1.00f},
1537 std::array{0.53f, 0.43f, 0.93f},
1538 std::array{0.47f, 0.83f, 0.30f},
1539 std::array{0.73f, 0.00f, 0.70f},
1540 std::array{0.53f, 0.93f, 0.27f},
1541 std::array{0.67f, 0.93f, 0.27f},
1542 std::array{0.73f, 0.57f, 0.07f},
1543 std::array{0.80f, 0.57f, 0.17f},
1544 std::array{0.47f, 0.13f, 0.23f},
1545 std::array{0.53f, 0.73f, 0.67f},
1546 std::array{0.80f, 0.47f, 0.47f},
1547 std::array{1.00f, 0.47f, 0.47f},
1554 return THREE_BIT_RGB_PALETTE;
1556 if ((version & VM_TOSHIBA_PALETTE) != 0) {
1557 return TOSHIBA_PALETTE;
1559 if ((version & VM_YM2220_PALETTE) != 0) {
1560 return YM2220_PALETTE;
1562 std::array<std::array<uint8_t, 3>, 16> tmsPalette;
1563 for (
auto color :
xrange(16)) {
1565 float Y = TMS9XXXA_ANALOG_OUTPUT[color][0];
1566 float Pr = TMS9XXXA_ANALOG_OUTPUT[color][1] - 0.5f;
1567 float Pb = TMS9XXXA_ANALOG_OUTPUT[color][2] - 0.5f;
1569 Pr *= (narrow<float>(saturationPr) * (1.0f / 100.0f));
1570 Pb *= (narrow<float>(saturationPb) * (1.0f / 100.0f));
1577 float R = Y + 0 + 1.402f * Pr;
1578 float G = Y - 0.344f * Pb - 0.714f * Pr;
1579 float B = Y + 1.722f * Pb + 0;
1600VDP::RegDebug::RegDebug(
const VDP& vdp_)
1602 vdp_.getName() +
" regs",
"VDP registers.", 0x40)
1606byte VDP::RegDebug::read(
unsigned address)
1608 const auto& vdp =
OUTER(VDP, vdpRegDebug);
1609 return vdp.peekRegister(address);
1612void VDP::RegDebug::write(
unsigned address,
byte value, EmuTime::param time)
1614 auto& vdp =
OUTER(VDP, vdpRegDebug);
1619 if ((address >= 8) && vdp.isMSX1VDP())
return;
1620 vdp.changeRegister(narrow<byte>(address), value, time);
1626VDP::StatusRegDebug::StatusRegDebug(
const VDP& vdp_)
1627 : SimpleDebuggable(vdp_.getMotherBoard(),
1628 vdp_.getName() +
" status regs",
"VDP status registers.", 0x10)
1632byte VDP::StatusRegDebug::read(
unsigned address, EmuTime::param time)
1634 const auto& vdp =
OUTER(VDP, vdpStatusRegDebug);
1635 return vdp.peekStatusReg(narrow<byte>(address), time);
1641VDP::PaletteDebug::PaletteDebug(
const VDP& vdp_)
1642 : SimpleDebuggable(vdp_.getMotherBoard(),
1643 vdp_.getName() +
" palette",
"V99x8 palette (RBG format)", 0x20)
1647byte VDP::PaletteDebug::read(
unsigned address)
1649 const auto& vdp =
OUTER(VDP, vdpPaletteDebug);
1650 word grb = vdp.getPalette(address / 2);
1651 return (address & 1) ? narrow_cast<byte>(grb >> 8)
1655void VDP::PaletteDebug::write(
unsigned address,
byte value, EmuTime::param time)
1657 auto& vdp =
OUTER(VDP, vdpPaletteDebug);
1661 if (vdp.isMSX1VDP())
return;
1663 unsigned index = address / 2;
1664 word grb = vdp.getPalette(index);
1666 ?
word((grb & 0x0077) | ((value & 0x07) << 8))
1667 :
word((grb & 0x0700) | ((value & 0x77) << 0));
1668 vdp.setPalette(index, grb, time);
1674VDP::VRAMPointerDebug::VRAMPointerDebug(
const VDP& vdp_)
1675 : SimpleDebuggable(vdp_.getMotherBoard(), vdp_.getName() ==
"VDP" ?
1676 "VRAM pointer" : vdp_.getName() +
" VRAM pointer",
1677 "VDP VRAM pointer (14 lower bits)", 2)
1681byte VDP::VRAMPointerDebug::read(
unsigned address)
1683 const auto& vdp =
OUTER(VDP, vramPointerDebug);
1685 return narrow_cast<byte>(vdp.vramPointer >> 8);
1687 return narrow_cast<byte>(vdp.vramPointer & 0xFF);
1691void VDP::VRAMPointerDebug::write(
unsigned address,
byte value, EmuTime::param )
1693 auto& vdp =
OUTER(VDP, vramPointerDebug);
1694 int& ptr = vdp.vramPointer;
1696 ptr = (ptr & 0x00FF) | ((value & 0x3F) << 8);
1698 ptr = (ptr & 0xFF00) | value;
1704VDP::RegisterLatchStatusDebug::RegisterLatchStatusDebug(
const VDP &vdp_)
1705 : SimpleDebuggable(vdp_.getMotherBoard(),
1706 vdp_.getName() +
" register latch status",
"V99x8 register latch status (0 = expecting a value, 1 = expecting a register)", 1)
1710byte VDP::RegisterLatchStatusDebug::read(
unsigned )
1712 const auto& vdp =
OUTER(VDP, registerLatchStatusDebug);
1713 return byte(vdp.registerDataStored);
1718VDP::VramAccessStatusDebug::VramAccessStatusDebug(
const VDP &vdp_)
1719 : SimpleDebuggable(vdp_.getMotherBoard(), vdp_.getName() ==
"VDP" ?
1720 "VRAM access status" : vdp_.getName() +
" VRAM access status",
1721 "VDP VRAM access status (0 = ready to read, 1 = ready to write)", 1)
1725byte VDP::VramAccessStatusDebug::read(
unsigned )
1727 const auto& vdp =
OUTER(VDP, vramAccessStatusDebug);
1728 return byte(vdp.writeAccess);
1733VDP::PaletteLatchStatusDebug::PaletteLatchStatusDebug(
const VDP &vdp_)
1734 : SimpleDebuggable(vdp_.getMotherBoard(),
1735 vdp_.getName() +
" palette latch status",
"V99x8 palette latch status (0 = expecting red & blue, 1 = expecting green)", 1)
1739byte VDP::PaletteLatchStatusDebug::read(
unsigned )
1741 const auto& vdp =
OUTER(VDP, paletteLatchStatusDebug);
1742 return byte(vdp.paletteDataStored);
1747VDP::DataLatchDebug::DataLatchDebug(
const VDP &vdp_)
1748 : SimpleDebuggable(vdp_.getMotherBoard(),
1749 vdp_.getName() +
" data latch value",
"V99x8 data latch value (byte)", 1)
1753byte VDP::DataLatchDebug::read(
unsigned )
1755 const auto& vdp =
OUTER(VDP, dataLatchDebug);
1756 return vdp.dataLatch;
1761VDP::Info::Info(VDP& vdp_,
const std::string& name_, std::string helpText_)
1762 : InfoTopic(vdp_.getMotherBoard().getMachineInfoCommand(),
1763 strCat(vdp_.getName(),
'_', name_))
1765 , helpText(
std::move(helpText_))
1769void VDP::Info::execute(std::span<const TclObject> , TclObject& result)
const
1771 result = calc(vdp.getCurrentTime());
1774std::string VDP::Info::help(std::span<const TclObject> )
const
1782VDP::FrameCountInfo::FrameCountInfo(VDP& vdp_)
1783 : Info(vdp_,
"frame_count",
1784 "The current frame number, starts counting at 0 "
1785 "when MSX is powered up or reset.")
1789int VDP::FrameCountInfo::calc(
const EmuTime& )
const
1791 return vdp.frameCount;
1797VDP::CycleInFrameInfo::CycleInFrameInfo(VDP& vdp_)
1798 : Info(vdp_,
"cycle_in_frame",
1799 "The number of VDP cycles since the beginning of "
1800 "the current frame. The VDP runs at 6 times the Z80 "
1801 "clock frequency, so at approximately 21.5MHz.")
1805int VDP::CycleInFrameInfo::calc(
const EmuTime& time)
const
1807 return vdp.getTicksThisFrame(time);
1813VDP::LineInFrameInfo::LineInFrameInfo(VDP& vdp_)
1814 : Info(vdp_,
"line_in_frame",
1815 "The absolute line number since the beginning of "
1816 "the current frame. Goes from 0 till 262 (NTSC) or "
1817 "313 (PAL). Note that this number includes the "
1818 "border lines, use 'msx_y_pos' to get MSX "
1823int VDP::LineInFrameInfo::calc(
const EmuTime& time)
const
1831VDP::CycleInLineInfo::CycleInLineInfo(VDP& vdp_)
1832 : Info(vdp_,
"cycle_in_line",
1833 "The number of VDP cycles since the beginning of "
1834 "the current line. See also 'cycle_in_frame'."
1835 "Note that this includes the cycles in the border, "
1836 "use 'msx_x256_pos' or 'msx_x512_pos' to get MSX "
1841int VDP::CycleInLineInfo::calc(
const EmuTime& time)
const
1849VDP::MsxYPosInfo::MsxYPosInfo(VDP& vdp_)
1850 : Info(vdp_,
"msx_y_pos",
1851 "Similar to 'line_in_frame', but expressed in MSX "
1852 "coordinates. So lines in the top border have "
1853 "negative coordinates, lines in the bottom border "
1854 "have coordinates bigger or equal to 192 or 212.")
1858int VDP::MsxYPosInfo::calc(
const EmuTime& time)
const
1860 return vdp.getMSXPos(time).y;
1866VDP::MsxX256PosInfo::MsxX256PosInfo(VDP& vdp_)
1867 : Info(vdp_,
"msx_x256_pos",
1868 "Similar to 'cycle_in_frame', but expressed in MSX "
1869 "coordinates. So a position in the left border has "
1870 "a negative coordinate and a position in the right "
1871 "border has a coordinate bigger or equal to 256. "
1872 "See also 'msx_x512_pos'.")
1876int VDP::MsxX256PosInfo::calc(
const EmuTime& time)
const
1878 return vdp.getMSXPos(time).x / 2;
1884VDP::MsxX512PosInfo::MsxX512PosInfo(VDP& vdp_)
1885 : Info(vdp_,
"msx_x512_pos",
1886 "Similar to 'cycle_in_frame', but expressed in "
1887 "'narrow' (screen 7) MSX coordinates. So a position "
1888 "in the left border has a negative coordinate and "
1889 "a position in the right border has a coordinate "
1890 "bigger or equal to 512. See also 'msx_x256_pos'.")
1894int VDP::MsxX512PosInfo::calc(
const EmuTime& time)
const
1896 return vdp.getMSXPos(time).x;
1910template<
typename Archive>
1913 ar.template serializeBase<MSXDevice>(*
this);
1915 if (ar.versionAtLeast(serVersion, 8)) {
1916 ar.serialize(
"syncVSync", syncVSync,
1917 "syncDisplayStart", syncDisplayStart,
1918 "syncVScan", syncVScan,
1919 "syncHScan", syncHScan,
1920 "syncHorAdjust", syncHorAdjust,
1921 "syncSetMode", syncSetMode,
1922 "syncSetBlank", syncSetBlank,
1923 "syncCpuVramAccess", syncCpuVramAccess);
1927 {&syncVSync, &syncDisplayStart, &syncVScan,
1928 &syncHScan, &syncHorAdjust, &syncSetMode,
1929 &syncSetBlank, &syncCpuVramAccess});
1939 ar.serialize(
"irqVertical", irqVertical,
1940 "irqHorizontal", irqHorizontal,
1941 "frameStartTime", frameStartTime,
1942 "displayStartSyncTime", displayStartSyncTime,
1943 "vScanSyncTime", vScanSyncTime,
1944 "hScanSyncTime", hScanSyncTime,
1945 "displayStart", displayStart,
1946 "horizontalScanOffset", horizontalScanOffset,
1947 "horizontalAdjust", horizontalAdjust,
1948 "registers", controlRegs,
1949 "blinkCount", blinkCount,
1950 "vramPointer", vramPointer,
1952 "isDisplayArea", isDisplayArea,
1953 "palTiming", palTiming,
1954 "interlaced", interlaced,
1955 "statusReg0", statusReg0,
1956 "statusReg1", statusReg1,
1957 "statusReg2", statusReg2,
1958 "blinkState", blinkState,
1959 "dataLatch", dataLatch,
1960 "registerDataStored", registerDataStored,
1961 "paletteDataStored", paletteDataStored);
1962 if (ar.versionAtLeast(serVersion, 5)) {
1963 ar.serialize(
"cpuVramData", cpuVramData,
1964 "cpuVramReqIsRead", cpuVramReqIsRead);
1966 ar.serialize(
"readAhead", cpuVramData);
1968 ar.serialize(
"cpuExtendedVram", cpuExtendedVram,
1969 "displayEnabled", displayEnabled);
1970 byte mode = displayMode.
getByte();
1971 ar.serialize(
"displayMode", mode);
1974 ar.serialize(
"cmdEngine", *cmdEngine,
1975 "spriteChecker", *spriteChecker,
1977 if constexpr (Archive::IS_LOADER) {
1978 pendingCpuAccess = syncCpuVramAccess.pendingSyncPoint();
1979 update(tooFastAccess);
1982 if (ar.versionAtLeast(serVersion, 2)) {
1983 ar.serialize(
"frameCount", frameCount);
1985 assert(Archive::IS_LOADER);
1992 if (ar.versionAtLeast(serVersion, 9)) {
1993 ar.serialize(
"syncSetSprites", syncSetSprites);
1994 ar.serialize(
"spriteEnabled", spriteEnabled);
1996 assert(Archive::IS_LOADER);
1997 spriteEnabled = (controlRegs[8] & 0x02) == 0;
1999 if (ar.versionAtLeast(serVersion, 10)) {
2000 ar.serialize(
"writeAccess", writeAccess);
2002 writeAccess = !cpuVramReqIsRead;
2013 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)