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();
989 registerDataStored =
false;
991 switch (port & (
isMSX1VDP() ? 0x01 : 0x03)) {
993 return vramRead(time);
996 return readStatusReg(controlRegs[15], time);
1017 cpuExtendedVram = (val & 0x40) != 0;
1021 cmdEngine->setCmdReg(reg - 32, val, time);
1027 val &= controlValueMasks[reg];
1029 byte change = val ^ controlRegs[reg];
1035 if (blinkState == ((val & 0xF0) == 0)) {
1036 renderer->updateBlinkState(!blinkState, time);
1037 blinkState = !blinkState;
1040 if ((val & 0xF0) && (val & 0x0F)) {
1042 blinkCount = (val >> 4) * 10;
1053 if (!change)
return;
1059 syncAtNextLine(syncSetMode, time);
1063 if (change & 0x03) {
1065 spriteChecker->updateSpriteSizeMag(val, time);
1069 syncAtNextLine(syncSetMode, time);
1071 if (change & 0x40) {
1072 syncAtNextLine(syncSetBlank, time);
1076 unsigned base = (val << 10) | ~(~0u << 10);
1089 renderer->updateNameBase(base, time);
1094 if (change & 0xF0) {
1095 renderer->updateForegroundColor(val >> 4, time);
1097 if (change & 0x0F) {
1098 renderer->updateBackgroundColor(val & 0x0F, time);
1101 renderer->updateBackgroundColor(val, time);
1105 if (change & 0x20) {
1106 renderer->updateTransparency((val & 0x20) == 0, time);
1107 spriteChecker->updateTransparency((val & 0x20) == 0, time);
1109 if (change & 0x02) {
1110 syncAtNextLine(syncSetSprites, time);
1112 if (change & 0x08) {
1113 vram->updateVRMode((val & 0x08) != 0, time);
1117 if (change & 0xF0) {
1118 renderer->updateBlinkForegroundColor(val >> 4, time);
1120 if (change & 0x0F) {
1121 renderer->updateBlinkBackgroundColor(val & 0x0F, time);
1126 paletteDataStored =
false;
1129 if (change & 0x0F) {
1130 syncAtNextLine(syncHorAdjust, time);
1134 spriteChecker->updateVerticalScroll(val, time);
1135 renderer->updateVerticalScroll(val, time);
1143 if (change & 0x08) {
1144 syncAtNextLine(syncHorAdjust, time);
1146 if (change & 0x02) {
1147 renderer->updateBorderMask((val & 0x02) != 0, time);
1149 if (change & 0x01) {
1150 renderer->updateMultiPage((val & 0x01) != 0, time);
1154 renderer->updateHorizontalScrollHigh(val, time);
1157 renderer->updateHorizontalScrollLow(val, time);
1162 controlRegs[reg] = val;
1169 if (change & 0x10) {
1171 scheduleHScan(time);
1173 irqHorizontal.
reset();
1178 if (change & 0x20) {
1183 if (statusReg0 & 0x80) {
1187 irqVertical.
reset();
1193 vram->change4k8kMapping((val & 0x80) != 0);
1197 updateNameBase(time);
1201 updateColorBase(time);
1205 updatePatternBase(time);
1209 updateSpriteAttributeBase(time);
1212 updateSpritePatternBase(time);
1215 if ((val & 1) && ! warningPrinted) {
1216 warningPrinted =
true;
1217 dotClockDirectionCallback.
execute();
1220 if (change & 0x80) {
1230 if (time < displayStartSyncTime) {
1232 scheduleDisplayStart(time);
1235 scheduleVScan(time);
1241 scheduleHScan(time);
1244 if (change & 0x01) {
1245 updateNameBase(time);
1251void VDP::syncAtNextLine(SyncBase& type, EmuTime::param time)
const
1255 int offset = 144 + (horizontalAdjust - 7) * 4;
1258 EmuTime nextTime = frameStartTime + ticks;
1259 type.setSyncPoint(nextTime);
1262void VDP::updateNameBase(EmuTime::param time)
1264 unsigned base = (controlRegs[2] << 10) | ~(~0u << 10);
1277 unsigned indexMask =
1280 : ~0u << (displayMode.
isTextMode() ? 12 : 10);
1281 if (controlRegs[25] & 0x01) {
1285 indexMask &= ~0x8000;
1287 vram->nameTable.setMask(base, indexMask, time);
1290void VDP::updateColorBase(EmuTime::param time)
1292 unsigned base = (controlRegs[10] << 14) | (controlRegs[3] << 6) | ~(~0u << 6);
1293 renderer->updateColorBase(base, time);
1294 switch (displayMode.
getBase()) {
1297 vram->colorTable.setMask(base, ~0u << 9, time);
1300 vram->colorTable.setMask(base, ~0u << 6, time);
1303 vram->colorTable.setMask(base | (
vdpLacksMirroring() ? 0x1800 : 0), ~0u << 13, time);
1306 vram->colorTable.setMask(base, ~0u << 13, time);
1310 vram->colorTable.disable(time);
1314void VDP::updatePatternBase(EmuTime::param time)
1316 unsigned base = (controlRegs[4] << 11) | ~(~0u << 11);
1317 renderer->updatePatternBase(base, time);
1318 switch (displayMode.
getBase()) {
1325 vram->patternTable.setMask(base, ~0u << 11, time);
1333 base = (controlRegs[4] << 11)
1334 | ((controlRegs[3] & 0x1f) << 6)
1337 vram->patternTable.setMask(base | (
vdpLacksMirroring() ? 0x1800 : 0), ~0u << 13, time);
1340 vram->patternTable.setMask(base, ~0u << 13, time);
1344 vram->patternTable.disable(time);
1348void VDP::updateSpriteAttributeBase(EmuTime::param time)
1352 vram->spriteAttribTable.disable(time);
1355 unsigned baseMask = (controlRegs[11] << 15) | (controlRegs[5] << 7) | ~(~0u << 7);
1356 unsigned indexMask = mode == 1 ? ~0u << 7 : ~0u << 10;
1358 baseMask = ((baseMask << 16) | (baseMask >> 1)) & 0x1FFFF;
1359 indexMask = ((indexMask << 16) | ~(1 << 16)) & (indexMask >> 1);
1361 vram->spriteAttribTable.setMask(baseMask, indexMask, time);
1364void VDP::updateSpritePatternBase(EmuTime::param time)
1367 vram->spritePatternTable.disable(time);
1370 unsigned baseMask = (controlRegs[6] << 11) | ~(~0u << 11);
1371 unsigned indexMask = ~0u << 11;
1373 baseMask = ((baseMask << 16) | (baseMask >> 1)) & 0x1FFFF;
1374 indexMask = ((indexMask << 16) | ~(1 << 16)) & (indexMask >> 1);
1376 vram->spritePatternTable.setMask(baseMask, indexMask, time);
1379void VDP::updateDisplayMode(DisplayMode newMode,
bool cmdBit, EmuTime::param time)
1382 vram->updateDisplayMode(newMode, cmdBit, time);
1389 newMode.isPlanar() != displayMode.
isPlanar();
1392 bool spriteModeChange =
1393 newMode.getSpriteMode(msx1) != displayMode.
getSpriteMode(msx1);
1396 displayMode = newMode;
1402 updateColorBase(time);
1403 updatePatternBase(time);
1405 if (planarChange || spriteModeChange) {
1406 updateSpritePatternBase(time);
1407 updateSpriteAttributeBase(time);
1409 updateNameBase(time);
1418void VDP::update(
const Setting&
setting)
noexcept
1422 brokenCmdTiming = cmdTiming .getEnum();
1423 allowTooFastAccess = tooFastAccess.getEnum();
1425 if (allowTooFastAccess && pendingCpuAccess) [[unlikely]] {
1427 syncCpuVramAccess.removeSyncPoint();
1428 pendingCpuAccess =
false;
1429 executeCpuVramAccess(getCurrentTime());
1450static constexpr std::array<std::array<uint8_t, 3>, 16> TOSHIBA_PALETTE = {{
1476static constexpr std::array<std::array<uint8_t, 3>, 16> YM2220_PALETTE = {{
1503static constexpr std::array<std::array<uint8_t, 3>, 16> THREE_BIT_RGB_PALETTE = {{
1524static constexpr std::array<std::array<float, 3>, 16> TMS9XXXA_ANALOG_OUTPUT = {
1526 std::array{0.00f, 0.47f, 0.47f},
1527 std::array{0.00f, 0.47f, 0.47f},
1528 std::array{0.53f, 0.07f, 0.20f},
1529 std::array{0.67f, 0.17f, 0.27f},
1530 std::array{0.40f, 0.40f, 1.00f},
1531 std::array{0.53f, 0.43f, 0.93f},
1532 std::array{0.47f, 0.83f, 0.30f},
1533 std::array{0.73f, 0.00f, 0.70f},
1534 std::array{0.53f, 0.93f, 0.27f},
1535 std::array{0.67f, 0.93f, 0.27f},
1536 std::array{0.73f, 0.57f, 0.07f},
1537 std::array{0.80f, 0.57f, 0.17f},
1538 std::array{0.47f, 0.13f, 0.23f},
1539 std::array{0.53f, 0.73f, 0.67f},
1540 std::array{0.80f, 0.47f, 0.47f},
1541 std::array{1.00f, 0.47f, 0.47f},
1548 return THREE_BIT_RGB_PALETTE;
1550 if ((version & VM_TOSHIBA_PALETTE) != 0) {
1551 return TOSHIBA_PALETTE;
1553 if ((version & VM_YM2220_PALETTE) != 0) {
1554 return YM2220_PALETTE;
1556 std::array<std::array<uint8_t, 3>, 16> tmsPalette;
1557 for (
auto color :
xrange(16)) {
1559 float Y = TMS9XXXA_ANALOG_OUTPUT[color][0];
1560 float Pr = TMS9XXXA_ANALOG_OUTPUT[color][1] - 0.5f;
1561 float Pb = TMS9XXXA_ANALOG_OUTPUT[color][2] - 0.5f;
1563 Pr *= (narrow<float>(saturationPr) * (1.0f / 100.0f));
1564 Pb *= (narrow<float>(saturationPb) * (1.0f / 100.0f));
1571 float R = Y + 0 + 1.402f * Pr;
1572 float G = Y - 0.344f * Pb - 0.714f * Pr;
1573 float B = Y + 1.722f * Pb + 0;
1594VDP::RegDebug::RegDebug(
const VDP& vdp_)
1596 vdp_.getName() +
" regs",
"VDP registers.", 0x40)
1600byte VDP::RegDebug::read(
unsigned address)
1602 const auto& vdp =
OUTER(VDP, vdpRegDebug);
1603 return vdp.peekRegister(address);
1606void VDP::RegDebug::write(
unsigned address,
byte value, EmuTime::param time)
1608 auto& vdp =
OUTER(VDP, vdpRegDebug);
1613 if ((address >= 8) && vdp.isMSX1VDP())
return;
1614 vdp.changeRegister(narrow<byte>(address), value, time);
1620VDP::StatusRegDebug::StatusRegDebug(
const VDP& vdp_)
1621 : SimpleDebuggable(vdp_.getMotherBoard(),
1622 vdp_.getName() +
" status regs",
"VDP status registers.", 0x10)
1626byte VDP::StatusRegDebug::read(
unsigned address, EmuTime::param time)
1628 const auto& vdp =
OUTER(VDP, vdpStatusRegDebug);
1629 return vdp.peekStatusReg(narrow<byte>(address), time);
1635VDP::PaletteDebug::PaletteDebug(
const VDP& vdp_)
1636 : SimpleDebuggable(vdp_.getMotherBoard(),
1637 vdp_.getName() +
" palette",
"V99x8 palette (RBG format)", 0x20)
1641byte VDP::PaletteDebug::read(
unsigned address)
1643 const auto& vdp =
OUTER(VDP, vdpPaletteDebug);
1644 word grb = vdp.getPalette(address / 2);
1645 return (address & 1) ? narrow_cast<byte>(grb >> 8)
1649void VDP::PaletteDebug::write(
unsigned address,
byte value, EmuTime::param time)
1651 auto& vdp =
OUTER(VDP, vdpPaletteDebug);
1655 if (vdp.isMSX1VDP())
return;
1657 unsigned index = address / 2;
1658 word grb = vdp.getPalette(index);
1660 ?
word((grb & 0x0077) | ((value & 0x07) << 8))
1661 :
word((grb & 0x0700) | ((value & 0x77) << 0));
1662 vdp.setPalette(index, grb, time);
1668VDP::VRAMPointerDebug::VRAMPointerDebug(
const VDP& vdp_)
1669 : SimpleDebuggable(vdp_.getMotherBoard(), vdp_.getName() ==
"VDP" ?
1670 "VRAM pointer" : vdp_.getName() +
" VRAM pointer",
1671 "VDP VRAM pointer (14 lower bits)", 2)
1675byte VDP::VRAMPointerDebug::read(
unsigned address)
1677 const auto& vdp =
OUTER(VDP, vramPointerDebug);
1679 return narrow_cast<byte>(vdp.vramPointer >> 8);
1681 return narrow_cast<byte>(vdp.vramPointer & 0xFF);
1685void VDP::VRAMPointerDebug::write(
unsigned address,
byte value, EmuTime::param )
1687 auto& vdp =
OUTER(VDP, vramPointerDebug);
1688 int& ptr = vdp.vramPointer;
1690 ptr = (ptr & 0x00FF) | ((value & 0x3F) << 8);
1692 ptr = (ptr & 0xFF00) | value;
1698VDP::RegisterLatchStatusDebug::RegisterLatchStatusDebug(
const VDP &vdp_)
1699 : SimpleDebuggable(vdp_.getMotherBoard(),
1700 vdp_.getName() +
" register latch status",
"V99x8 register latch status (0 = expecting a value, 1 = expecting a register)", 1)
1704byte VDP::RegisterLatchStatusDebug::read(
unsigned )
1706 const auto& vdp =
OUTER(VDP, registerLatchStatusDebug);
1707 return byte(vdp.registerDataStored);
1712VDP::VramAccessStatusDebug::VramAccessStatusDebug(
const VDP &vdp_)
1713 : SimpleDebuggable(vdp_.getMotherBoard(), vdp_.getName() ==
"VDP" ?
1714 "VRAM access status" : vdp_.getName() +
" VRAM access status",
1715 "VDP VRAM access status (0 = ready to read, 1 = ready to write)", 1)
1719byte VDP::VramAccessStatusDebug::read(
unsigned )
1721 const auto& vdp =
OUTER(VDP, vramAccessStatusDebug);
1722 return byte(vdp.writeAccess);
1727VDP::PaletteLatchStatusDebug::PaletteLatchStatusDebug(
const VDP &vdp_)
1728 : SimpleDebuggable(vdp_.getMotherBoard(),
1729 vdp_.getName() +
" palette latch status",
"V99x8 palette latch status (0 = expecting red & blue, 1 = expecting green)", 1)
1733byte VDP::PaletteLatchStatusDebug::read(
unsigned )
1735 const auto& vdp =
OUTER(VDP, paletteLatchStatusDebug);
1736 return byte(vdp.paletteDataStored);
1741VDP::DataLatchDebug::DataLatchDebug(
const VDP &vdp_)
1742 : SimpleDebuggable(vdp_.getMotherBoard(),
1743 vdp_.getName() +
" data latch value",
"V99x8 data latch value (byte)", 1)
1747byte VDP::DataLatchDebug::read(
unsigned )
1749 const auto& vdp =
OUTER(VDP, dataLatchDebug);
1750 return vdp.dataLatch;
1755VDP::Info::Info(VDP& vdp_,
const std::string& name_, std::string helpText_)
1756 : InfoTopic(vdp_.getMotherBoard().getMachineInfoCommand(),
1757 strCat(vdp_.getName(),
'_', name_))
1759 , helpText(
std::move(helpText_))
1763void VDP::Info::execute(std::span<const TclObject> , TclObject& result)
const
1765 result = calc(vdp.getCurrentTime());
1768std::string VDP::Info::help(std::span<const TclObject> )
const
1776VDP::FrameCountInfo::FrameCountInfo(VDP& vdp_)
1777 : Info(vdp_,
"frame_count",
1778 "The current frame number, starts counting at 0 "
1779 "when MSX is powered up or reset.")
1783int VDP::FrameCountInfo::calc(
const EmuTime& )
const
1785 return vdp.frameCount;
1791VDP::CycleInFrameInfo::CycleInFrameInfo(VDP& vdp_)
1792 : Info(vdp_,
"cycle_in_frame",
1793 "The number of VDP cycles since the beginning of "
1794 "the current frame. The VDP runs at 6 times the Z80 "
1795 "clock frequency, so at approximately 21.5MHz.")
1799int VDP::CycleInFrameInfo::calc(
const EmuTime& time)
const
1801 return vdp.getTicksThisFrame(time);
1807VDP::LineInFrameInfo::LineInFrameInfo(VDP& vdp_)
1808 : Info(vdp_,
"line_in_frame",
1809 "The absolute line number since the beginning of "
1810 "the current frame. Goes from 0 till 262 (NTSC) or "
1811 "313 (PAL). Note that this number includes the "
1812 "border lines, use 'msx_y_pos' to get MSX "
1817int VDP::LineInFrameInfo::calc(
const EmuTime& time)
const
1825VDP::CycleInLineInfo::CycleInLineInfo(VDP& vdp_)
1826 : Info(vdp_,
"cycle_in_line",
1827 "The number of VDP cycles since the beginning of "
1828 "the current line. See also 'cycle_in_frame'."
1829 "Note that this includes the cycles in the border, "
1830 "use 'msx_x256_pos' or 'msx_x512_pos' to get MSX "
1835int VDP::CycleInLineInfo::calc(
const EmuTime& time)
const
1843VDP::MsxYPosInfo::MsxYPosInfo(VDP& vdp_)
1844 : Info(vdp_,
"msx_y_pos",
1845 "Similar to 'line_in_frame', but expressed in MSX "
1846 "coordinates. So lines in the top border have "
1847 "negative coordinates, lines in the bottom border "
1848 "have coordinates bigger or equal to 192 or 212.")
1852int VDP::MsxYPosInfo::calc(
const EmuTime& time)
const
1854 return vdp.getMSXPos(time).y;
1860VDP::MsxX256PosInfo::MsxX256PosInfo(VDP& vdp_)
1861 : Info(vdp_,
"msx_x256_pos",
1862 "Similar to 'cycle_in_frame', but expressed in MSX "
1863 "coordinates. So a position in the left border has "
1864 "a negative coordinate and a position in the right "
1865 "border has a coordinate bigger or equal to 256. "
1866 "See also 'msx_x512_pos'.")
1870int VDP::MsxX256PosInfo::calc(
const EmuTime& time)
const
1872 return vdp.getMSXPos(time).x / 2;
1878VDP::MsxX512PosInfo::MsxX512PosInfo(VDP& vdp_)
1879 : Info(vdp_,
"msx_x512_pos",
1880 "Similar to 'cycle_in_frame', but expressed in "
1881 "'narrow' (screen 7) MSX coordinates. So a position "
1882 "in the left border has a negative coordinate and "
1883 "a position in the right border has a coordinate "
1884 "bigger or equal to 512. See also 'msx_x256_pos'.")
1888int VDP::MsxX512PosInfo::calc(
const EmuTime& time)
const
1890 return vdp.getMSXPos(time).x;
1904template<
typename Archive>
1907 ar.template serializeBase<MSXDevice>(*
this);
1909 if (ar.versionAtLeast(serVersion, 8)) {
1910 ar.serialize(
"syncVSync", syncVSync,
1911 "syncDisplayStart", syncDisplayStart,
1912 "syncVScan", syncVScan,
1913 "syncHScan", syncHScan,
1914 "syncHorAdjust", syncHorAdjust,
1915 "syncSetMode", syncSetMode,
1916 "syncSetBlank", syncSetBlank,
1917 "syncCpuVramAccess", syncCpuVramAccess);
1921 {&syncVSync, &syncDisplayStart, &syncVScan,
1922 &syncHScan, &syncHorAdjust, &syncSetMode,
1923 &syncSetBlank, &syncCpuVramAccess});
1933 ar.serialize(
"irqVertical", irqVertical,
1934 "irqHorizontal", irqHorizontal,
1935 "frameStartTime", frameStartTime,
1936 "displayStartSyncTime", displayStartSyncTime,
1937 "vScanSyncTime", vScanSyncTime,
1938 "hScanSyncTime", hScanSyncTime,
1939 "displayStart", displayStart,
1940 "horizontalScanOffset", horizontalScanOffset,
1941 "horizontalAdjust", horizontalAdjust,
1942 "registers", controlRegs,
1943 "blinkCount", blinkCount,
1944 "vramPointer", vramPointer,
1946 "isDisplayArea", isDisplayArea,
1947 "palTiming", palTiming,
1948 "interlaced", interlaced,
1949 "statusReg0", statusReg0,
1950 "statusReg1", statusReg1,
1951 "statusReg2", statusReg2,
1952 "blinkState", blinkState,
1953 "dataLatch", dataLatch,
1954 "registerDataStored", registerDataStored,
1955 "paletteDataStored", paletteDataStored);
1956 if (ar.versionAtLeast(serVersion, 5)) {
1957 ar.serialize(
"cpuVramData", cpuVramData,
1958 "cpuVramReqIsRead", cpuVramReqIsRead);
1960 ar.serialize(
"readAhead", cpuVramData);
1962 ar.serialize(
"cpuExtendedVram", cpuExtendedVram,
1963 "displayEnabled", displayEnabled);
1964 byte mode = displayMode.
getByte();
1965 ar.serialize(
"displayMode", mode);
1968 ar.serialize(
"cmdEngine", *cmdEngine,
1969 "spriteChecker", *spriteChecker,
1971 if constexpr (Archive::IS_LOADER) {
1972 pendingCpuAccess = syncCpuVramAccess.pendingSyncPoint();
1973 update(tooFastAccess);
1976 if (ar.versionAtLeast(serVersion, 2)) {
1977 ar.serialize(
"frameCount", frameCount);
1979 assert(Archive::IS_LOADER);
1986 if (ar.versionAtLeast(serVersion, 9)) {
1987 ar.serialize(
"syncSetSprites", syncSetSprites);
1988 ar.serialize(
"spriteEnabled", spriteEnabled);
1990 assert(Archive::IS_LOADER);
1991 spriteEnabled = (controlRegs[8] & 0x02) == 0;
1993 if (ar.versionAtLeast(serVersion, 10)) {
1994 ar.serialize(
"writeAccess", writeAccess);
1996 writeAccess = !cpuVramReqIsRead;
2007 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)