45 static byte getDelayCycles(
const XMLElement& devices) {
47 if (
const auto* t9769Dev = devices.findChild(
"T9769")) {
48 if (t9769Dev->getChildData(
"subtype") ==
"C") {
53 }
else if (devices.findChild(
"S1990")) {
64 , syncDisplayStart(*this)
67 , syncHorAdjust(*this)
70 , syncSetSprites(*this)
71 , syncCpuVramAccess(*this)
73 , display(getReactor().getDisplay())
74 , cmdTiming (display.getRenderSettings().getCmdTimingSetting())
75 , tooFastAccess(display.getRenderSettings().getTooFastAccessSetting())
77 , vdpStatusRegDebug(*this)
78 , vdpPaletteDebug (*this)
79 , vramPointerDebug (*this)
80 , frameCountInfo (*this)
81 , cycleInFrameInfo (*this)
82 , lineInFrameInfo (*this)
83 , cycleInLineInfo (*this)
85 , msxX256PosInfo (*this)
86 , msxX512PosInfo (*this)
87 , frameStartTime(getCurrentTime())
88 , irqVertical (getMotherBoard(),
getName() +
".IRQvertical", config)
89 , irqHorizontal(getMotherBoard(),
getName() +
".IRQhorizontal", config)
90 , displayStartSyncTime(getCurrentTime())
91 , vScanSyncTime(getCurrentTime())
92 , hScanSyncTime(getCurrentTime())
94 getCommandController(),
95 getName() +
".too_fast_vram_access_callback",
96 "Tcl proc called when the VRAM is read or written too fast")
98 , warningPrinted(false)
100 , fixedVDPIOdelayCycles(getDelayCycles(getMotherBoard().getMachineConfig()->getConfig().getChild(
"devices")))
112 int defaultSaturation = 54;
114 const auto& versionString = config.
getChildData(
"version");
115 if (versionString ==
"TMS99X8A") version = TMS99X8A;
116 else if (versionString ==
"TMS9918A") {
118 defaultSaturation = 100;
119 }
else if (versionString ==
"TMS9928A") version = TMS99X8A;
120 else if (versionString ==
"T6950PAL") version = T6950PAL;
121 else if (versionString ==
"T6950NTSC") version = T6950NTSC;
122 else if (versionString ==
"T7937APAL") version = T7937APAL;
123 else if (versionString ==
"T7937ANTSC") version = T7937ANTSC;
124 else if (versionString ==
"TMS91X8") version = TMS91X8;
125 else if (versionString ==
"TMS9118") {
127 defaultSaturation = 100;
128 }
else if (versionString ==
"TMS9128") version = TMS91X8;
129 else if (versionString ==
"TMS9929A") version = TMS9929A;
130 else if (versionString ==
"TMS9129") version = TMS9129;
131 else if (versionString ==
"V9938") version = V9938;
132 else if (versionString ==
"V9958") version = V9958;
133 else if (versionString ==
"YM2220PAL") version = YM2220PAL;
134 else if (versionString ==
"YM2220NTSC") version = YM2220NTSC;
135 else throw MSXException(
"Unknown VDP version \"", versionString,
'"');
138 if ((versionString.find(
"TMS") != 0) && ((config.
findChild(
"saturationPr") !=
nullptr) || (config.
findChild(
"saturationPb") !=
nullptr) || (config.
findChild(
"saturation") !=
nullptr))) {
139 throw MSXException(
"Specifying saturation parameters only makes sense for TMS VDPs");
143 if (!((0 <= saturation) && (saturation <= 100))) {
145 "Saturation percentage is not in range 0..100: ", saturationPr);
148 if (!((0 <= saturationPr) && (saturationPr <= 100))) {
150 "Saturation percentage for Pr component is not in range 0..100: ",
154 if (!((0 <= saturationPb) && (saturationPb <= 100))) {
156 "Saturation percentage for Pb component is not in range 0..100: ",
161 static constexpr
byte VALUE_MASKS_MSX1[32] = {
162 0x03, 0xFB, 0x0F, 0xFF, 0x07, 0x7F, 0x07, 0xFF
164 static constexpr
byte VALUE_MASKS_MSX2[32] = {
165 0x7E, 0x7F, 0x7F, 0xFF, 0x3F, 0xFF, 0x3F, 0xFF,
166 0xFB, 0xBF, 0x07, 0x03, 0xFF, 0xFF, 0x07, 0x0F,
167 0x0F, 0xBF, 0xFF, 0xFF, 0x3F, 0x3F, 0x3F, 0xFF,
168 0, 0, 0, 0, 0, 0, 0, 0,
170 controlRegMask = (
isMSX1VDP() ? 0x07 : 0x3F);
171 memcpy(controlValueMasks,
172 isMSX1VDP() ? VALUE_MASKS_MSX1 : VALUE_MASKS_MSX2,
173 sizeof(controlValueMasks));
174 if (version == V9958) {
176 controlValueMasks[25] = 0x7F;
177 controlValueMasks[26] = 0x3F;
178 controlValueMasks[27] = 0x07;
187 if (vramSize !=
one_of(16u, 64u, 128u, 192u)) {
189 "VRAM size of ", vramSize,
"kB is not supported!");
191 vram = std::make_unique<VDPVRAM>(*
this, vramSize * 1024, time);
195 spriteChecker = std::make_unique<SpriteChecker>(*
this, renderSettings, time);
196 vram->setSpriteChecker(spriteChecker.get());
200 vram->setCmdEngine(cmdEngine.get());
210 tooFastAccess.
attach(*
this);
211 update(tooFastAccess);
216 tooFastAccess.
detach(*
this);
221 void VDP::preVideoSystemChange() noexcept
226 void VDP::postVideoSystemChange() noexcept
231 void VDP::createRenderer()
237 vram->setRenderer(renderer.get(), frameStartTime.
getTime());
242 return renderer->getPostProcessor();
245 void VDP::resetInit()
252 controlRegs[9] |= 0x02;
256 controlRegs[21] = 0x3B;
257 controlRegs[22] = 0x05;
265 cpuVramReqIsRead =
false;
267 cpuExtendedVram =
false;
268 registerDataStored =
false;
269 paletteDataStored =
false;
272 horizontalAdjust = 7;
275 isDisplayArea =
false;
276 displayEnabled =
false;
277 spriteEnabled =
true;
278 superimposing =
nullptr;
279 externalVideo =
nullptr;
283 statusReg1 = (version == V9958 ? 0x04 : 0x00);
288 irqHorizontal.
reset();
291 const word V9938_PALETTE[16] = {
292 0x000, 0x000, 0x611, 0x733, 0x117, 0x327, 0x151, 0x627,
293 0x171, 0x373, 0x661, 0x664, 0x411, 0x265, 0x555, 0x777
296 memcpy(palette, V9938_PALETTE,
sizeof(V9938_PALETTE));
299 void VDP::resetMasks(EmuTime::param time)
301 updateNameBase(time);
302 updateColorBase(time);
303 updatePatternBase(time);
304 updateSpriteAttributeBase(time);
305 updateSpritePatternBase(time);
319 syncVSync .removeSyncPoint();
320 syncDisplayStart .removeSyncPoint();
321 syncVScan .removeSyncPoint();
322 syncHScan .removeSyncPoint();
323 syncHorAdjust .removeSyncPoint();
324 syncSetMode .removeSyncPoint();
325 syncSetBlank .removeSyncPoint();
326 syncSetSprites .removeSyncPoint();
327 syncCpuVramAccess.removeSyncPoint();
328 syncCmdDone .removeSyncPoint();
329 pendingCpuAccess =
false;
332 cmdEngine->sync(time);
334 spriteChecker->reset(time);
335 cmdEngine->reset(time);
344 assert(frameCount == 0);
347 void VDP::execVSync(EmuTime::param time)
352 renderer->frameEnd(time);
353 spriteChecker->frameEnd(time);
358 blinkState = next.state;
359 blinkCount = next.count;
363 cmdEngine->sync(time);
369 void VDP::execDisplayStart(EmuTime::param time)
373 if (!isDisplayArea) {
374 if (displayEnabled) {
375 vram->updateDisplayEnabled(
true, time);
377 isDisplayArea =
true;
381 void VDP::execVScan(EmuTime::param time)
390 vram->updateDisplayEnabled(
false, time);
392 isDisplayArea =
false;
396 if (controlRegs[1] & 0x20) {
401 void VDP::execHScan()
404 if (controlRegs[0] & 0x10) {
409 void VDP::execHorAdjust(EmuTime::param time)
411 int newHorAdjust = (controlRegs[18] & 0x0F) ^ 0x07;
412 if (controlRegs[25] & 0x08) {
415 renderer->updateHorizontalAdjust(newHorAdjust, time);
416 horizontalAdjust = newHorAdjust;
419 void VDP::execSetMode(EmuTime::param time)
422 DisplayMode(controlRegs[0], controlRegs[1], controlRegs[25]),
427 void VDP::execSetBlank(EmuTime::param time)
429 bool newDisplayEnabled = (controlRegs[1] & 0x40) != 0;
431 vram->updateDisplayEnabled(newDisplayEnabled, time);
433 displayEnabled = newDisplayEnabled;
436 void VDP::execSetSprites(EmuTime::param time)
438 bool newSpriteEnabled = (controlRegs[8] & 0x02) == 0;
439 vram->updateSpritesEnabled(newSpriteEnabled, time);
440 spriteEnabled = newSpriteEnabled;
443 void VDP::execCpuVramAccess(EmuTime::param time)
445 assert(!allowTooFastAccess);
446 pendingCpuAccess =
false;
447 executeCpuVramAccess(time);
450 void VDP::execSyncCmdDone(EmuTime::param time)
452 cmdEngine->sync(time);
458 void VDP::scheduleDisplayStart(EmuTime::param time)
461 if (displayStartSyncTime > time) {
462 syncDisplayStart.removeSyncPoint();
471 (palTiming ? 36 : 9) +
472 (controlRegs[9] & 0x80 ? 0 : 10) +
477 displayStartSyncTime = frameStartTime + displayStart;
481 if (displayStartSyncTime > time) {
482 syncDisplayStart.setSyncPoint(displayStartSyncTime);
491 void VDP::scheduleVScan(EmuTime::param time)
503 if (vScanSyncTime > time) {
504 syncVScan.removeSyncPoint();
509 vScanSyncTime = frameStartTime +
514 if (vScanSyncTime > time) {
515 syncVScan.setSyncPoint(vScanSyncTime);
520 void VDP::scheduleHScan(EmuTime::param time)
523 if (hScanSyncTime > time) {
524 syncHScan.removeSyncPoint();
525 hScanSyncTime = time;
529 horizontalScanOffset = displayStart - (100 + 102)
539 if (horizontalScanOffset >= ticksPerFrame) {
540 horizontalScanOffset -= ticksPerFrame;
554 int lineCountResetTicks = (8 + getVerticalAdjust()) *
TICKS_PER_LINE;
558 if (horizontalScanOffset >= lineCountResetTicks) {
565 if ((controlRegs[0] & 0x10) && horizontalScanOffset >= 0) {
573 hScanSyncTime = frameStartTime + horizontalScanOffset;
574 if (hScanSyncTime > time) {
575 syncHScan.setSyncPoint(hScanSyncTime);
586 void VDP::frameStart(EmuTime::param time)
601 palTiming = (controlRegs[9] & 0x02) != 0;
607 if (blinkCount == 0) {
608 renderer->updateBlinkState(!blinkState, time);
609 blinkState = !blinkState;
610 blinkCount = (blinkState
611 ? controlRegs[13] >> 4 : controlRegs[13] & 0x0F) * 10;
620 const RawFrame* newSuperimposing = (controlRegs[0] & 1) ? externalVideo :
nullptr;
621 if (superimposing != newSuperimposing) {
622 superimposing = newSuperimposing;
623 renderer->updateSuperimposing(superimposing, time);
627 frameStartTime.
reset(time);
630 scheduleDisplayStart(time);
634 renderer->frameStart(time);
635 spriteChecker->frameStart(time);
651 EmuTime time = time_;
657 if (fixedVDPIOdelayCycles > 0) {
662 switch (port & (
isMSX1VDP() ? 0x01 : 0x03)) {
664 vramWrite(value, time);
665 registerDataStored =
false;
668 if (registerDataStored) {
673 value & controlRegMask,
688 vramPointer = (value << 8 | (vramPointer & 0xFF)) & 0x3FFF;
692 vramPointer = (value << 8 | dataLatch) & 0x3FFF;
693 if (!(value & 0x40)) {
695 (void)vramRead(time);
698 registerDataStored =
false;
705 vramPointer = (vramPointer & 0x3F00) | value;
708 registerDataStored =
true;
712 if (paletteDataStored) {
713 int index = controlRegs[16];
714 int grb = ((value << 8) | dataLatch) & 0x777;
715 setPalette(index, grb, time);
716 controlRegs[16] = (index + 1) & 0x0F;
717 paletteDataStored =
false;
720 paletteDataStored =
true;
727 byte regNr = controlRegs[17];
728 changeRegister(regNr & 0x3F, value, time);
729 if ((regNr & 0x80) == 0) {
731 controlRegs[17] = (regNr + 1) & 0x3F;
738 void VDP::setPalette(
int index,
word grb, EmuTime::param time)
740 if (palette[index] != grb) {
741 renderer->updatePalette(index, grb, time);
742 palette[index] = grb;
746 void VDP::vramWrite(
byte value, EmuTime::param time)
748 scheduleCpuVramAccess(
false, value, time);
751 byte VDP::vramRead(EmuTime::param time)
756 byte result = cpuVramData;
759 scheduleCpuVramAccess(
true, dummy, time);
763 void VDP::scheduleCpuVramAccess(
bool isRead,
byte write, EmuTime::param time)
767 if (!isRead) cpuVramData = write;
768 cpuVramReqIsRead = isRead;
769 if (pendingCpuAccess) [[unlikely]] {
772 assert(!allowTooFastAccess);
775 if (allowTooFastAccess) [[unlikely]] {
788 assert(!pendingCpuAccess);
789 executeCpuVramAccess(time);
817 pendingCpuAccess =
true;
825 void VDP::executeCpuVramAccess(EmuTime::param time)
827 int addr = (controlRegs[14] << 14) | vramPointer;
832 addr = ((addr << 16) | (addr >> 1)) & 0x1FFFF;
835 bool doAccess = [&] {
836 if (!cpuExtendedVram) [[likely]] {
838 }
else if (vram->getSize() == 192 * 1024) [[likely]] {
839 addr = 0x20000 | (addr & 0xFFFF);
846 if (cpuVramReqIsRead) {
847 cpuVramData = vram->cpuRead(addr, time);
849 vram->cpuWrite(addr, cpuVramData, time);
852 if (cpuVramReqIsRead) {
859 vramPointer = (vramPointer + 1) & 0x3FFF;
860 if (vramPointer == 0 && displayMode.
isV9938Mode()) {
862 controlRegs[14] = (controlRegs[14] + 1) & 0x07;
873 EmuTime::param time, EmuTime::param limit)
const
879 byte VDP::peekStatusReg(
byte reg, EmuTime::param time)
const
883 spriteChecker->sync(time);
886 if (controlRegs[0] & 0x10) {
887 return statusReg1 | (irqHorizontal.
getState() ? 1:0);
894 if (afterMatch < 0) {
899 int matchLength = (displayMode.
isTextMode() ? 87 : 59)
902 (0 <= afterMatch && afterMatch < matchLength);
912 || ticksThisFrame >= displayEnd;
914 | (getHR(ticksThisFrame) ? 0x20 : 0x00)
916 | cmdEngine->getStatus(time);
919 return byte(spriteChecker->getCollisionX(time));
921 return byte(spriteChecker->getCollisionX(time) >> 8) | 0xFE;
923 return byte(spriteChecker->getCollisionY(time));
925 return byte(spriteChecker->getCollisionY(time) >> 8) | 0xFC;
927 return cmdEngine->readColor(time);
929 return byte(cmdEngine->getBorderX(time));
931 return byte(cmdEngine->getBorderX(time) >> 8) | 0xFE;
937 byte VDP::readStatusReg(
byte reg, EmuTime::param time)
939 byte ret = peekStatusReg(reg, time);
942 spriteChecker->resetStatus();
947 if (controlRegs[0] & 0x10) {
948 irqHorizontal.
reset();
952 spriteChecker->resetCollision();
955 cmdEngine->resetColor();
965 registerDataStored =
false;
967 switch (port & (
isMSX1VDP() ? 0x01 : 0x03)) {
969 return vramRead(time);
972 return readStatusReg(controlRegs[15], time);
987 void VDP::changeRegister(
byte reg,
byte val, EmuTime::param time)
993 cpuExtendedVram = (val & 0x40) != 0;
997 cmdEngine->setCmdReg(reg - 32, val, time);
1003 val &= controlValueMasks[reg];
1005 byte change = val ^ controlRegs[reg];
1011 if (blinkState == ((val & 0xF0) == 0)) {
1012 renderer->updateBlinkState(!blinkState, time);
1013 blinkState = !blinkState;
1016 if ((val & 0xF0) && (val & 0x0F)) {
1018 blinkCount = (val >> 4) * 10;
1029 if (!change)
return;
1035 syncAtNextLine(syncSetMode, time);
1039 if (change & 0x03) {
1041 spriteChecker->updateSpriteSizeMag(val, time);
1045 syncAtNextLine(syncSetMode, time);
1047 if (change & 0x40) {
1048 syncAtNextLine(syncSetBlank, time);
1052 int base = (val << 10) | ~(~0u << 10);
1065 renderer->updateNameBase(base, time);
1070 if (change & 0xF0) {
1071 renderer->updateForegroundColor(val >> 4, time);
1073 if (change & 0x0F) {
1074 renderer->updateBackgroundColor(val & 0x0F, time);
1077 renderer->updateBackgroundColor(val, time);
1081 if (change & 0x20) {
1082 renderer->updateTransparency((val & 0x20) == 0, time);
1083 spriteChecker->updateTransparency((val & 0x20) == 0, time);
1085 if (change & 0x02) {
1086 syncAtNextLine(syncSetSprites, time);
1088 if (change & 0x08) {
1089 vram->updateVRMode((val & 0x08) != 0, time);
1093 if (change & 0xF0) {
1094 renderer->updateBlinkForegroundColor(val >> 4, time);
1096 if (change & 0x0F) {
1097 renderer->updateBlinkBackgroundColor(val & 0x0F, time);
1102 paletteDataStored =
false;
1105 if (change & 0x0F) {
1106 syncAtNextLine(syncHorAdjust, time);
1110 spriteChecker->updateVerticalScroll(val, time);
1111 renderer->updateVerticalScroll(val, time);
1119 if (change & 0x08) {
1120 syncAtNextLine(syncHorAdjust, time);
1122 if (change & 0x02) {
1123 renderer->updateBorderMask((val & 0x02) != 0, time);
1125 if (change & 0x01) {
1126 renderer->updateMultiPage((val & 0x01) != 0, time);
1130 renderer->updateHorizontalScrollHigh(val, time);
1133 renderer->updateHorizontalScrollLow(val, time);
1138 controlRegs[reg] = val;
1145 if (change & 0x10) {
1147 scheduleHScan(time);
1149 irqHorizontal.
reset();
1154 if (change & 0x20) {
1159 if (statusReg0 & 0x80) {
1163 irqVertical.
reset();
1169 vram->change4k8kMapping((val & 0x80) != 0);
1173 updateNameBase(time);
1177 updateColorBase(time);
1181 updatePatternBase(time);
1185 updateSpriteAttributeBase(time);
1188 updateSpritePatternBase(time);
1191 if ((val & 1) && ! warningPrinted) {
1192 warningPrinted =
true;
1194 "The running MSX software has set bit 0 of VDP register 9 "
1195 "(dot clock direction) to one. In an ordinary MSX, "
1196 "the screen would go black and the CPU would stop running.");
1199 if (change & 0x80) {
1209 if (time < displayStartSyncTime) {
1211 scheduleDisplayStart(time);
1214 scheduleVScan(time);
1220 scheduleHScan(time);
1223 if (change & 0x01) {
1224 updateNameBase(time);
1230 void VDP::syncAtNextLine(SyncBase& type, EmuTime::param time)
1234 int offset = 144 + (horizontalAdjust - 7) * 4;
1237 EmuTime nextTime = frameStartTime + ticks;
1238 type.setSyncPoint(nextTime);
1241 void VDP::updateNameBase(EmuTime::param time)
1243 int base = (controlRegs[2] << 10) | ~(~0u << 10);
1259 : ~0u << (displayMode.
isTextMode() ? 12 : 10);
1260 if (controlRegs[25] & 0x01) {
1264 indexMask &= ~0x8000;
1266 vram->nameTable.setMask(base, indexMask, time);
1269 void VDP::updateColorBase(EmuTime::param time)
1271 int base = (controlRegs[10] << 14) | (controlRegs[3] << 6) | ~(~0u << 6);
1272 renderer->updateColorBase(base, time);
1273 switch (displayMode.
getBase()) {
1276 vram->colorTable.setMask(base, ~0u << 9, time);
1279 vram->colorTable.setMask(base, ~0u << 6, time);
1282 vram->colorTable.setMask(base | (
vdpLacksMirroring() ? 0x1800 : 0), ~0u << 13, time);
1285 vram->colorTable.setMask(base, ~0u << 13, time);
1289 vram->colorTable.disable(time);
1293 void VDP::updatePatternBase(EmuTime::param time)
1295 int base = (controlRegs[4] << 11) | ~(~0u << 11);
1296 renderer->updatePatternBase(base, time);
1297 switch (displayMode.
getBase()) {
1304 vram->patternTable.setMask(base, ~0u << 11, time);
1312 base = (controlRegs[4] << 11)
1313 | ((controlRegs[3] & 0x1f) << 6)
1316 vram->patternTable.setMask(base | (
vdpLacksMirroring() ? 0x1800 : 0), ~0u << 13, time);
1319 vram->patternTable.setMask(base, ~0u << 13, time);
1323 vram->patternTable.disable(time);
1327 void VDP::updateSpriteAttributeBase(EmuTime::param time)
1331 vram->spriteAttribTable.disable(time);
1334 int baseMask = (controlRegs[11] << 15) | (controlRegs[5] << 7) | ~(~0u << 7);
1335 int indexMask = mode == 1 ? ~0u << 7 : ~0u << 10;
1337 baseMask = ((baseMask << 16) | (baseMask >> 1)) & 0x1FFFF;
1338 indexMask = ((unsigned(indexMask) << 16) | ~(1 << 16)) & (indexMask >> 1);
1340 vram->spriteAttribTable.setMask(baseMask, indexMask, time);
1343 void VDP::updateSpritePatternBase(EmuTime::param time)
1346 vram->spritePatternTable.disable(time);
1349 int baseMask = (controlRegs[6] << 11) | ~(~0u << 11);
1350 int indexMask = ~0u << 11;
1352 baseMask = ((baseMask << 16) | (baseMask >> 1)) & 0x1FFFF;
1353 indexMask = ((unsigned(indexMask) << 16) | ~(1 << 16)) & (indexMask >> 1);
1355 vram->spritePatternTable.setMask(baseMask, indexMask, time);
1358 void VDP::updateDisplayMode(DisplayMode newMode,
bool cmdBit, EmuTime::param time)
1361 vram->updateDisplayMode(newMode, cmdBit, time);
1368 newMode.isPlanar() != displayMode.
isPlanar();
1371 bool spriteModeChange =
1372 newMode.getSpriteMode(msx1) != displayMode.
getSpriteMode(msx1);
1375 displayMode = newMode;
1381 updateColorBase(time);
1382 updatePatternBase(time);
1384 if (planarChange || spriteModeChange) {
1385 updateSpritePatternBase(time);
1386 updateSpriteAttributeBase(time);
1388 updateNameBase(time);
1397 void VDP::update(
const Setting&
setting) noexcept
1401 brokenCmdTiming = cmdTiming .getEnum();
1402 allowTooFastAccess = tooFastAccess.getEnum();
1404 if (allowTooFastAccess && pendingCpuAccess) [[unlikely]] {
1406 syncCpuVramAccess.removeSyncPoint();
1407 pendingCpuAccess =
false;
1408 executeCpuVramAccess(getCurrentTime());
1505 { 0.00f, 0.47f, 0.47f },
1506 { 0.00f, 0.47f, 0.47f },
1507 { 0.53f, 0.07f, 0.20f },
1508 { 0.67f, 0.17f, 0.27f },
1509 { 0.40f, 0.40f, 1.00f },
1510 { 0.53f, 0.43f, 0.93f },
1511 { 0.47f, 0.83f, 0.30f },
1512 { 0.73f, 0.00f, 0.70f },
1513 { 0.53f, 0.93f, 0.27f },
1514 { 0.67f, 0.93f, 0.27f },
1515 { 0.73f, 0.57f, 0.07f },
1516 { 0.80f, 0.57f, 0.17f },
1517 { 0.47f, 0.13f, 0.23f },
1518 { 0.53f, 0.73f, 0.67f },
1519 { 0.80f, 0.47f, 0.47f },
1520 { 1.00f, 0.47f, 0.47f },
1529 if ((version & VM_TOSHIBA_PALETTE) != 0) {
1532 if ((version & VM_YM2220_PALETTE) != 0) {
1535 std::array<std::array<uint8_t, 3>, 16> tmsPalette;
1536 for (
auto color :
xrange(16)) {
1542 Pr *= (saturationPr / 100.0f);
1543 Pb *= (saturationPb / 100.0f);
1550 float R = Y + 0 + 1.402f * Pr;
1551 float G = Y - 0.344f * Pb - 0.714f * Pr;
1552 float B = Y + 1.722f * Pb + 0;
1573 VDP::RegDebug::RegDebug(
VDP& vdp_)
1575 vdp_.
getName() +
" regs",
"VDP registers.", 0x40)
1579 byte VDP::RegDebug::read(
unsigned address)
1581 auto& vdp =
OUTER(
VDP, vdpRegDebug);
1582 if (address < 0x20) {
1583 return vdp.controlRegs[address];
1584 }
else if (address < 0x2F) {
1585 return vdp.cmdEngine->peekCmdReg(address - 0x20);
1591 void VDP::RegDebug::write(
unsigned address,
byte value, EmuTime::param time)
1593 auto& vdp =
OUTER(
VDP, vdpRegDebug);
1598 if ((address >= 8) && vdp.isMSX1VDP())
return;
1599 vdp.changeRegister(address, value, time);
1605 VDP::StatusRegDebug::StatusRegDebug(
VDP& vdp_)
1606 : SimpleDebuggable(vdp_.getMotherBoard(),
1607 vdp_.
getName() +
" status regs",
"VDP status registers.", 0x10)
1611 byte VDP::StatusRegDebug::read(
unsigned address, EmuTime::param time)
1613 auto& vdp =
OUTER(
VDP, vdpStatusRegDebug);
1614 return vdp.peekStatusReg(address, time);
1620 VDP::PaletteDebug::PaletteDebug(
VDP& vdp_)
1621 : SimpleDebuggable(vdp_.getMotherBoard(),
1622 vdp_.
getName() +
" palette",
"V99x8 palette (RBG format)", 0x20)
1626 byte VDP::PaletteDebug::read(
unsigned address)
1628 auto& vdp =
OUTER(
VDP, vdpPaletteDebug);
1629 word grb = vdp.getPalette(address / 2);
1630 return (address & 1) ? (grb >> 8) : (grb & 0xff);
1633 void VDP::PaletteDebug::write(
unsigned address,
byte value, EmuTime::param time)
1635 auto& vdp =
OUTER(
VDP, vdpPaletteDebug);
1639 if (vdp.isMSX1VDP())
return;
1641 int index = address / 2;
1642 word grb = vdp.getPalette(index);
1644 ? (grb & 0x0077) | ((value & 0x07) << 8)
1645 : (grb & 0x0700) | (value & 0x77);
1646 vdp.setPalette(index, grb, time);
1652 VDP::VRAMPointerDebug::VRAMPointerDebug(
VDP& vdp_)
1653 : SimpleDebuggable(vdp_.getMotherBoard(), vdp_.
getName() ==
"VDP" ?
1654 "VRAM pointer" : vdp_.
getName() +
" VRAM pointer",
1655 "VDP VRAM pointer (14 lower bits)", 2)
1659 byte VDP::VRAMPointerDebug::read(
unsigned address)
1661 auto& vdp =
OUTER(
VDP, vramPointerDebug);
1663 return vdp.vramPointer >> 8;
1665 return vdp.vramPointer & 0xFF;
1669 void VDP::VRAMPointerDebug::write(
unsigned address,
byte value, EmuTime::param )
1671 auto& vdp =
OUTER(
VDP, vramPointerDebug);
1672 int& ptr = vdp.vramPointer;
1674 ptr = (ptr & 0x00FF) | ((value & 0x3F) << 8);
1676 ptr = (ptr & 0xFF00) | value;
1684 : InfoTopic(vdp_.getMotherBoard().getMachineInfoCommand(),
1687 , helpText(std::move(helpText_))
1691 void VDP::Info::execute(std::span<const TclObject> , TclObject& result)
const
1693 result = calc(vdp.getCurrentTime());
1696 std::string VDP::Info::help(std::span<const TclObject> )
const
1704 VDP::FrameCountInfo::FrameCountInfo(
VDP& vdp_)
1705 :
Info(vdp_,
"frame_count",
1706 "The current frame number, starts counting at 0 "
1707 "when MSX is powered up or reset.")
1711 int VDP::FrameCountInfo::calc(
const EmuTime& )
const
1713 return vdp.frameCount;
1719 VDP::CycleInFrameInfo::CycleInFrameInfo(
VDP& vdp_)
1720 :
Info(vdp_,
"cycle_in_frame",
1721 "The number of VDP cycles since the beginning of "
1722 "the current frame. The VDP runs at 6 times the Z80 "
1723 "clock frequency, so at approximately 21.5MHz.")
1727 int VDP::CycleInFrameInfo::calc(
const EmuTime& time)
const
1729 return vdp.getTicksThisFrame(time);
1735 VDP::LineInFrameInfo::LineInFrameInfo(
VDP& vdp_)
1736 :
Info(vdp_,
"line_in_frame",
1737 "The absolute line number since the beginning of "
1738 "the current frame. Goes from 0 till 262 (NTSC) or "
1739 "313 (PAL). Note that this number includes the "
1740 "border lines, use 'msx_y_pos' to get MSX "
1745 int VDP::LineInFrameInfo::calc(
const EmuTime& time)
const
1753 VDP::CycleInLineInfo::CycleInLineInfo(
VDP& vdp_)
1754 :
Info(vdp_,
"cycle_in_line",
1755 "The number of VDP cycles since the beginning of "
1756 "the current line. See also 'cycle_in_frame'."
1757 "Note that this includes the cycles in the border, "
1758 "use 'msx_x256_pos' or 'msx_x512_pos' to get MSX "
1763 int VDP::CycleInLineInfo::calc(
const EmuTime& time)
const
1771 VDP::MsxYPosInfo::MsxYPosInfo(
VDP& vdp_)
1772 :
Info(vdp_,
"msx_y_pos",
1773 "Similar to 'line_in_frame', but expressed in MSX "
1774 "coordinates. So lines in the top border have "
1775 "negative coordinates, lines in the bottom border "
1776 "have coordinates bigger or equal to 192 or 212.")
1780 int VDP::MsxYPosInfo::calc(
const EmuTime& time)
const
1789 VDP::MsxX256PosInfo::MsxX256PosInfo(
VDP& vdp_)
1790 :
Info(vdp_,
"msx_x256_pos",
1791 "Similar to 'cycle_in_frame', but expressed in MSX "
1792 "coordinates. So a position in the left border has "
1793 "a negative coordinate and a position in the right "
1794 "border has a coordinated bigger or equal to 256. "
1795 "See also 'msx_x512_pos'.")
1799 int VDP::MsxX256PosInfo::calc(
const EmuTime& time)
const
1802 vdp.getLeftSprites()) / 4;
1808 VDP::MsxX512PosInfo::MsxX512PosInfo(
VDP& vdp_)
1809 :
Info(vdp_,
"msx_x512_pos",
1810 "Similar to 'cycle_in_frame', but expressed in "
1811 "'narrow' (screen 7) MSX coordinates. So a position "
1812 "in the left border has a negative coordinate and "
1813 "a position in the right border has a coordinated "
1814 "bigger or equal to 512. See also 'msx_x256_pos'.")
1818 int VDP::MsxX512PosInfo::calc(
const EmuTime& time)
const
1821 vdp.getLeftSprites()) / 2;
1834 template<
typename Archive>
1837 ar.template serializeBase<MSXDevice>(*
this);
1839 if (ar.versionAtLeast(serVersion, 8)) {
1840 ar.serialize(
"syncVSync", syncVSync,
1841 "syncDisplayStart", syncDisplayStart,
1842 "syncVScan", syncVScan,
1843 "syncHScan", syncHScan,
1844 "syncHorAdjust", syncHorAdjust,
1845 "syncSetMode", syncSetMode,
1846 "syncSetBlank", syncSetBlank,
1847 "syncCpuVramAccess", syncCpuVramAccess);
1851 {&syncVSync, &syncDisplayStart, &syncVScan,
1852 &syncHScan, &syncHorAdjust, &syncSetMode,
1853 &syncSetBlank, &syncCpuVramAccess});
1863 ar.serialize(
"irqVertical", irqVertical,
1864 "irqHorizontal", irqHorizontal,
1865 "frameStartTime", frameStartTime,
1866 "displayStartSyncTime", displayStartSyncTime,
1867 "vScanSyncTime", vScanSyncTime,
1868 "hScanSyncTime", hScanSyncTime,
1869 "displayStart", displayStart,
1870 "horizontalScanOffset", horizontalScanOffset,
1871 "horizontalAdjust", horizontalAdjust,
1872 "registers", controlRegs,
1873 "blinkCount", blinkCount,
1874 "vramPointer", vramPointer,
1876 "isDisplayArea", isDisplayArea,
1877 "palTiming", palTiming,
1878 "interlaced", interlaced,
1879 "statusReg0", statusReg0,
1880 "statusReg1", statusReg1,
1881 "statusReg2", statusReg2,
1882 "blinkState", blinkState,
1883 "dataLatch", dataLatch,
1884 "registerDataStored", registerDataStored,
1885 "paletteDataStored", paletteDataStored);
1886 if (ar.versionAtLeast(serVersion, 5)) {
1887 ar.serialize(
"cpuVramData", cpuVramData,
1888 "cpuVramReqIsRead", cpuVramReqIsRead);
1890 ar.serialize(
"readAhead", cpuVramData);
1892 ar.serialize(
"cpuExtendedVram", cpuExtendedVram,
1893 "displayEnabled", displayEnabled);
1894 byte mode = displayMode.
getByte();
1895 ar.serialize(
"displayMode", mode);
1898 ar.serialize(
"cmdEngine", *cmdEngine,
1899 "spriteChecker", *spriteChecker,
1901 if constexpr (Archive::IS_LOADER) {
1902 pendingCpuAccess = syncCpuVramAccess.pendingSyncPoint();
1903 update(tooFastAccess);
1906 if (ar.versionAtLeast(serVersion, 2)) {
1907 ar.serialize(
"frameCount", frameCount);
1909 assert(Archive::IS_LOADER);
1916 if (ar.versionAtLeast(serVersion, 9)) {
1917 ar.serialize(
"syncSetSprites", syncSetSprites);
1918 ar.serialize(
"spriteEnabled", spriteEnabled);
1920 assert(Archive::IS_LOADER);
1921 spriteEnabled = (controlRegs[8] & 0x02) == 0;
1932 if constexpr (Archive::IS_LOADER) {
void printWarning(std::string_view message)
constexpr void reset(EmuTime::param e)
Reset the clock to start ticking at the given time.
constexpr EmuTime::param getTime() const
Gets the time at which the last clock tick occurred.
const XMLElement * findChild(std::string_view name) const
int getChildDataAsInt(std::string_view name, int defaultValue) const
std::string_view getChildData(std::string_view name) const
constexpr byte getBase() const
Get the base dispay mode as an integer: M5..M1 combined.
constexpr void setByte(byte mode_)
Used for de-serialization.
constexpr bool isPlanar() const
Is VRAM "planar" in the current display mode? Graphic 6 and 7 spread their bytes over two VRAM ICs,...
constexpr bool isBitmapMode() const
Is the current mode a bitmap mode? Graphic4 and higher are bitmap modes.
static constexpr byte REG25_MASK
Bits of VDP register 25 that encode part of the display mode.
constexpr bool isV9938Mode() const
Was this mode introduced by the V9938?
constexpr void reset()
Bring the display mode to its initial state.
static constexpr byte REG1_MASK
Bits of VDP register 1 that encode part of the display mode.
constexpr bool isTextMode() const
Is the current mode a text mode? Text1 and Text2 are text modes.
constexpr byte getByte() const
Get the display mode as a byte: YAE YJK M5..M1 combined.
constexpr int getSpriteMode(bool isMSX1) const
Get the sprite mode of this display mode.
static constexpr byte REG0_MASK
Bits of VDP register 0 that encode part of the display mode.
void detach(VideoSystemChangeListener &listener)
void attach(VideoSystemChangeListener &listener)
RenderSettings & getRenderSettings()
void set()
Set the interrupt request on the bus.
void reset()
Reset the interrupt request on the bus.
bool getState() const
Get the interrupt state.
EmuTime waitCyclesZ80(EmuTime::param time, unsigned cycles)
An MSXDevice is an emulated hardware component connected to the bus of the emulated MSX.
CliComm & getCliComm() const
Reactor & getReactor() const
const XMLElement & getDeviceConfig() const
Get the configuration section for this device.
CommandController & getCommandController() const
EmuTime::param getCurrentTime() const
Abstract base class for post processors.
static void restoreOld(Archive &ar, std::vector< Schedulable * > schedulables)
void detach(Observer< T > &observer)
void attach(Observer< T > &observer)
VDP-VRAM access slot calculator, meant to be used in the inner loops of the VDPCmdEngine commands.
Unified implementation of MSX Video Display Processors (VDPs).
int getLinesPerFrame() const
Gets the number of lines per frame.
void writeIO(word port, byte value, EmuTime::param time) override
Write a byte to a given IO port at a certain time to this device.
BlinkStateCount calculateLineBlinkState(unsigned line) const
bool isFastBlinkEnabled() const
Get 'fast-blink' status.
std::array< std::array< uint8_t, 3 >, 16 > getMSX1Palette() const
Get the (fixed) palette for this MSX1 VDP.
PostProcessor * getPostProcessor() const
Used by Video9000 to be able to couple the VDP and V9990 output.
bool isVDPwithPALonly() const
Is this a VDP only capable of PAL?
bool getCmdBit() const
Are commands possible in non Graphic modes? (V9958 only)
VDPAccessSlots::Calculator getAccessSlotCalculator(EmuTime::param time, EmuTime::param limit) const
Same as getAccessSlot(), but it can be much faster for repeated calls, e.g.
void reset(EmuTime::param time) override
This method is called on reset.
static constexpr int TICKS_PER_LINE
Number of VDP clock ticks per line.
int getTicksPerFrame() const
Gets the number of VDP clockticks (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 clockticks 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/colortable mirroring?
byte readIO(word port, EmuTime::param time) override
Read a byte from an IO port at a certain time from this device.
bool isDisplayEnabled() const
Is the display enabled? Both the regular border and forced blanking by clearing the display enable bi...
int getTicksThisFrame(EmuTime::param time) const
Gets the number of VDP clock ticks (21MHz) elapsed between a given time and the start of this frame.
bool vdpLacksMirroring() const
Is this a VDP that lacks mirroring?
VDP(const DeviceConfig &config)
void powerUp(EmuTime::param time) override
This method is called when MSX is powered up.
uint8_t clipIntToByte(int x)
Clip x to range [0,255].
std::string getName(KeyCode keyCode)
Translate key code to key name.
std::unique_ptr< Renderer > createRenderer(VDP &vdp, Display &display)
Create the Renderer selected by the current renderer setting.
EmuTime getAccessSlot(EmuTime::param frame_, EmuTime::param time, Delta delta, const VDP &vdp)
Return the time of the next available access slot that is at least 'delta' cycles later than 'time'.
Calculator getCalculator(EmuTime::param frame, EmuTime::param time, EmuTime::param limit, const VDP &vdp)
When many calls to getAccessSlot() are needed, it's more efficient to instead use this function.
This file implemented 3 utility functions:
uint8_t byte
8 bit unsigned integer
constexpr std::array< std::array< uint8_t, 3 >, 16 > TOSHIBA_PALETTE
constexpr std::array< std::array< uint8_t, 3 >, 16 > THREE_BIT_RGB_PALETTE
REGISTER_MSXDEVICE(ChakkariCopy, "ChakkariCopy")
AmdFlash::SectorInfo Info
uint16_t word
16 bit unsigned integer
constexpr std::array< std::array< uint8_t, 3 >, 16 > YM2220_PALETTE
constexpr float TMS9XXXA_ANALOG_OUTPUT[16][3]
constexpr void fill(ForwardRange &&range, const T &value)
#define OUTER(type, member)
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
std::string strCat(Ts &&...ts)
constexpr auto xrange(T e)