VDP.cc 57 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921
  1. /*
  2. TODO:
  3. - Run more measurements on real MSX to find out how horizontal
  4. scanning interrupt really works.
  5. Finish model and implement it.
  6. Especially test this scenario:
  7. * IE1 enabled, interrupt occurs
  8. * wait until matching line is passed
  9. * disable IE1
  10. * read FH
  11. * read FH
  12. Current implementation would return FH=0 both times.
  13. - Check how Z80 should treat interrupts occurring during DI.
  14. - Bottom erase suspends display even on overscan.
  15. However, it shows black, not border color.
  16. How to handle this? Currently it is treated as "overscan" which
  17. falls outside of the rendered screen area.
  18. */
  19. #include "VDP.hh"
  20. #include "VDPVRAM.hh"
  21. #include "VDPCmdEngine.hh"
  22. #include "SpriteChecker.hh"
  23. #include "Display.hh"
  24. #include "HardwareConfig.hh"
  25. #include "RendererFactory.hh"
  26. #include "Renderer.hh"
  27. #include "RenderSettings.hh"
  28. #include "EnumSetting.hh"
  29. #include "TclObject.hh"
  30. #include "MSXCPU.hh"
  31. #include "MSXMotherBoard.hh"
  32. #include "Reactor.hh"
  33. #include "MSXException.hh"
  34. #include "CliComm.hh"
  35. #include "one_of.hh"
  36. #include "ranges.hh"
  37. #include "unreachable.hh"
  38. #include <cstring>
  39. #include <cassert>
  40. #include <memory>
  41. using std::string;
  42. using std::vector;
  43. namespace openmsx {
  44. static byte getDelayCycles(const XMLElement& devices) {
  45. byte cycles = 0;
  46. const XMLElement* t9769Dev = devices.findChild("T9769");
  47. if (t9769Dev) {
  48. if (t9769Dev->getChildData("subtype") == "C") {
  49. cycles = 1;
  50. } else {
  51. cycles = 2;
  52. }
  53. } else if (devices.findChild("S1990")) {
  54. // this case is purely there for backwards compatibility for
  55. // turboR configs which do not have the T9769 tag yet.
  56. cycles = 1;
  57. }
  58. return cycles;
  59. }
  60. VDP::VDP(const DeviceConfig& config)
  61. : MSXDevice(config)
  62. , syncVSync(*this)
  63. , syncDisplayStart(*this)
  64. , syncVScan(*this)
  65. , syncHScan(*this)
  66. , syncHorAdjust(*this)
  67. , syncSetMode(*this)
  68. , syncSetBlank(*this)
  69. , syncCpuVramAccess(*this)
  70. , syncCmdDone(*this)
  71. , display(getReactor().getDisplay())
  72. , cmdTiming (display.getRenderSettings().getCmdTimingSetting())
  73. , tooFastAccess(display.getRenderSettings().getTooFastAccessSetting())
  74. , vdpRegDebug (*this)
  75. , vdpStatusRegDebug(*this)
  76. , vdpPaletteDebug (*this)
  77. , vramPointerDebug (*this)
  78. , frameCountInfo (*this)
  79. , cycleInFrameInfo (*this)
  80. , lineInFrameInfo (*this)
  81. , cycleInLineInfo (*this)
  82. , msxYPosInfo (*this)
  83. , msxX256PosInfo (*this)
  84. , msxX512PosInfo (*this)
  85. , frameStartTime(getCurrentTime())
  86. , irqVertical (getMotherBoard(), getName() + ".IRQvertical", config)
  87. , irqHorizontal(getMotherBoard(), getName() + ".IRQhorizontal", config)
  88. , displayStartSyncTime(getCurrentTime())
  89. , vScanSyncTime(getCurrentTime())
  90. , hScanSyncTime(getCurrentTime())
  91. , tooFastCallback(
  92. getCommandController(),
  93. getName() + ".too_fast_vram_access_callback",
  94. "Tcl proc called when the VRAM is read or written too fast")
  95. , warningPrinted(false)
  96. , cpu(getCPU()) // used frequently, so cache it
  97. , fixedVDPIOdelayCycles(getDelayCycles(getMotherBoard().getMachineConfig()->getConfig().getChild("devices")))
  98. {
  99. interlaced = false;
  100. // Current general defaults for saturation:
  101. // - Any MSX with a TMS9x18 VDP: SatPr=SatPb=100%
  102. // - Other machines with a TMS9x2x VDP and RGB output:
  103. // SatPr=SatPr=100%, until someone reports a better value
  104. // - Other machines with a TMS9x2x VDP and CVBS only output:
  105. // SatPr=SatPb=54%, until someone reports a better value
  106. // At this point we don't know anything about the connector here, so
  107. // only the first point can be implemented. The default default is
  108. // still 54, which is very similar to the old palette implementation
  109. int defaultSaturation = 54;
  110. std::string versionString = config.getChildData("version");
  111. if (versionString == "TMS99X8A") version = TMS99X8A;
  112. else if (versionString == "TMS9918A") {
  113. version = TMS99X8A;
  114. defaultSaturation = 100;
  115. } else if (versionString == "TMS9928A") version = TMS99X8A;
  116. else if (versionString == "T6950PAL") version = T6950PAL;
  117. else if (versionString == "T6950NTSC") version = T6950NTSC;
  118. else if (versionString == "T7937APAL") version = T7937APAL;
  119. else if (versionString == "T7937ANTSC") version = T7937ANTSC;
  120. else if (versionString == "TMS91X8") version = TMS91X8;
  121. else if (versionString == "TMS9118") {
  122. version = TMS91X8;
  123. defaultSaturation = 100;
  124. } else if (versionString == "TMS9128") version = TMS91X8;
  125. else if (versionString == "TMS9929A") version = TMS9929A;
  126. else if (versionString == "TMS9129") version = TMS9129;
  127. else if (versionString == "V9938") version = V9938;
  128. else if (versionString == "V9958") version = V9958;
  129. else if (versionString == "YM2220PAL") version = YM2220PAL;
  130. else if (versionString == "YM2220NTSC") version = YM2220NTSC;
  131. else throw MSXException("Unknown VDP version \"", versionString, '"');
  132. // saturation parameters only make sense when using TMS VDP's
  133. if ((versionString.find("TMS") != 0) && ((config.findChild("saturationPr") != nullptr) || (config.findChild("saturationPb") != nullptr) || (config.findChild("saturation") != nullptr))) {
  134. throw MSXException("Specifying saturation parameters only makes sense for TMS VDP's");
  135. }
  136. int saturation = config.getChildDataAsInt("saturation", defaultSaturation);
  137. if (!((0 <= saturation) && (saturation <= 100))) {
  138. throw MSXException(
  139. "Saturation percentage is not in range 0..100: ", saturationPr);
  140. }
  141. saturationPr = config.getChildDataAsInt("saturationPr", saturation);
  142. if (!((0 <= saturationPr) && (saturationPr <= 100))) {
  143. throw MSXException(
  144. "Saturation percentage for Pr component is not in range 0..100: ",
  145. saturationPr);
  146. }
  147. saturationPb = config.getChildDataAsInt("saturationPb", saturation);
  148. if (!((0 <= saturationPb) && (saturationPb <= 100))) {
  149. throw MSXException(
  150. "Saturation percentage for Pb component is not in range 0..100: ",
  151. saturationPr);
  152. }
  153. // Set up control register availability.
  154. static constexpr byte VALUE_MASKS_MSX1[32] = {
  155. 0x03, 0xFB, 0x0F, 0xFF, 0x07, 0x7F, 0x07, 0xFF // 00..07
  156. };
  157. static constexpr byte VALUE_MASKS_MSX2[32] = {
  158. 0x7E, 0x7F, 0x7F, 0xFF, 0x3F, 0xFF, 0x3F, 0xFF, // 00..07
  159. 0xFB, 0xBF, 0x07, 0x03, 0xFF, 0xFF, 0x07, 0x0F, // 08..15
  160. 0x0F, 0xBF, 0xFF, 0xFF, 0x3F, 0x3F, 0x3F, 0xFF, // 16..23
  161. 0, 0, 0, 0, 0, 0, 0, 0, // 24..31
  162. };
  163. controlRegMask = (isMSX1VDP() ? 0x07 : 0x3F);
  164. memcpy(controlValueMasks,
  165. isMSX1VDP() ? VALUE_MASKS_MSX1 : VALUE_MASKS_MSX2,
  166. sizeof(controlValueMasks));
  167. if (version == V9958) {
  168. // Enable V9958-specific control registers.
  169. controlValueMasks[25] = 0x7F;
  170. controlValueMasks[26] = 0x3F;
  171. controlValueMasks[27] = 0x07;
  172. }
  173. resetInit(); // must be done early to avoid UMRs
  174. // Video RAM.
  175. EmuTime::param time = getCurrentTime();
  176. unsigned vramSize =
  177. (isMSX1VDP() ? 16 : config.getChildDataAsInt("vram"));
  178. if (vramSize != one_of(16u, 64u, 128u, 192u)) {
  179. throw MSXException(
  180. "VRAM size of ", vramSize, "kB is not supported!");
  181. }
  182. vram = std::make_unique<VDPVRAM>(*this, vramSize * 1024, time);
  183. // Create sprite checker.
  184. auto& renderSettings = display.getRenderSettings();
  185. spriteChecker = std::make_unique<SpriteChecker>(*this, renderSettings, time);
  186. vram->setSpriteChecker(spriteChecker.get());
  187. // Create command engine.
  188. cmdEngine = std::make_unique<VDPCmdEngine>(*this, getCommandController());
  189. vram->setCmdEngine(cmdEngine.get());
  190. // Initialise renderer.
  191. createRenderer();
  192. // Reset state.
  193. powerUp(time);
  194. display .attach(*this);
  195. cmdTiming .attach(*this);
  196. tooFastAccess.attach(*this);
  197. update(tooFastAccess); // handles both cmdTiming and tooFastAccess
  198. }
  199. VDP::~VDP()
  200. {
  201. tooFastAccess.detach(*this);
  202. cmdTiming .detach(*this);
  203. display .detach(*this);
  204. }
  205. void VDP::preVideoSystemChange()
  206. {
  207. renderer.reset();
  208. }
  209. void VDP::postVideoSystemChange()
  210. {
  211. createRenderer();
  212. }
  213. void VDP::createRenderer()
  214. {
  215. renderer = RendererFactory::createRenderer(*this, display);
  216. // TODO: Is it safe to use frameStartTime,
  217. // which is most likely in the past?
  218. //renderer->reset(frameStartTime.getTime());
  219. vram->setRenderer(renderer.get(), frameStartTime.getTime());
  220. }
  221. PostProcessor* VDP::getPostProcessor() const
  222. {
  223. return renderer->getPostProcessor();
  224. }
  225. void VDP::resetInit()
  226. {
  227. // note: vram, spriteChecker, cmdEngine, renderer may not yet be
  228. // created at this point
  229. ranges::fill(controlRegs, 0);
  230. if (isVDPwithPALonly()) {
  231. // Boots (and remains) in PAL mode, all other VDPs boot in NTSC.
  232. controlRegs[9] |= 0x02;
  233. }
  234. // According to page 6 of the V9938 data book the color burst registers
  235. // are loaded with these values at power on.
  236. controlRegs[21] = 0x3B;
  237. controlRegs[22] = 0x05;
  238. // Note: frameStart is the actual place palTiming is written, but it
  239. // can be read before frameStart is called.
  240. // TODO: Clean up initialisation sequence.
  241. palTiming = true; // controlRegs[9] & 0x02;
  242. displayMode.reset();
  243. vramPointer = 0;
  244. cpuVramData = 0;
  245. cpuVramReqIsRead = false; // avoid UMR
  246. dataLatch = 0;
  247. cpuExtendedVram = false;
  248. registerDataStored = false;
  249. paletteDataStored = false;
  250. blinkState = false;
  251. blinkCount = 0;
  252. horizontalAdjust = 7;
  253. // TODO: Real VDP probably resets timing as well.
  254. isDisplayArea = false;
  255. displayEnabled = false;
  256. superimposing = nullptr;
  257. externalVideo = nullptr;
  258. // Init status registers.
  259. statusReg0 = 0x00;
  260. statusReg1 = (version == V9958 ? 0x04 : 0x00);
  261. statusReg2 = 0x0C;
  262. // Update IRQ to reflect new register values.
  263. irqVertical.reset();
  264. irqHorizontal.reset();
  265. // From appendix 8 of the V9938 data book (page 148).
  266. const word V9938_PALETTE[16] = {
  267. 0x000, 0x000, 0x611, 0x733, 0x117, 0x327, 0x151, 0x627,
  268. 0x171, 0x373, 0x661, 0x664, 0x411, 0x265, 0x555, 0x777
  269. };
  270. // Init the palette.
  271. memcpy(palette, V9938_PALETTE, sizeof(V9938_PALETTE));
  272. }
  273. void VDP::resetMasks(EmuTime::param time)
  274. {
  275. updateNameBase(time);
  276. updateColorBase(time);
  277. updatePatternBase(time);
  278. updateSpriteAttributeBase(time);
  279. updateSpritePatternBase(time);
  280. // TODO: It is not clear to me yet how bitmapWindow should be used.
  281. // Currently it always spans 128K of VRAM.
  282. //vram->bitmapWindow.setMask(~(~0u << 17), ~0u << 17, time);
  283. }
  284. void VDP::powerUp(EmuTime::param time)
  285. {
  286. vram->clear();
  287. reset(time);
  288. }
  289. void VDP::reset(EmuTime::param time)
  290. {
  291. syncVSync .removeSyncPoint();
  292. syncDisplayStart .removeSyncPoint();
  293. syncVScan .removeSyncPoint();
  294. syncHScan .removeSyncPoint();
  295. syncHorAdjust .removeSyncPoint();
  296. syncSetMode .removeSyncPoint();
  297. syncSetBlank .removeSyncPoint();
  298. syncCpuVramAccess.removeSyncPoint();
  299. syncCmdDone .removeSyncPoint();
  300. pendingCpuAccess = false;
  301. // Reset subsystems.
  302. cmdEngine->sync(time);
  303. resetInit();
  304. spriteChecker->reset(time);
  305. cmdEngine->reset(time);
  306. renderer->reInit();
  307. // Tell the subsystems of the new mask values.
  308. resetMasks(time);
  309. // Init scheduling.
  310. frameCount = -1;
  311. frameStart(time);
  312. assert(frameCount == 0);
  313. }
  314. void VDP::execVSync(EmuTime::param time)
  315. {
  316. // This frame is finished.
  317. // Inform VDP subcomponents.
  318. // TODO: Do this via VDPVRAM?
  319. renderer->frameEnd(time);
  320. spriteChecker->frameEnd(time);
  321. if (isFastBlinkEnabled()) {
  322. // adjust blinkState and blinkCount for next frame
  323. std::tie(blinkState, blinkCount) =
  324. calculateLineBlinkState(getLinesPerFrame());
  325. }
  326. // Finish the previous frame, because access-slot calculations work within a frame.
  327. cmdEngine->sync(time);
  328. // Start next frame.
  329. frameStart(time);
  330. }
  331. void VDP::execDisplayStart(EmuTime::param time)
  332. {
  333. // Display area starts here, unless we're doing overscan and it
  334. // was already active.
  335. if (!isDisplayArea) {
  336. if (displayEnabled) {
  337. vram->updateDisplayEnabled(true, time);
  338. }
  339. isDisplayArea = true;
  340. }
  341. }
  342. void VDP::execVScan(EmuTime::param time)
  343. {
  344. // VSCAN is the end of display.
  345. // This will generate a VBLANK IRQ. Typically MSX software will
  346. // poll the keyboard/joystick on this IRQ. So now is a good
  347. // time to also poll for host events.
  348. getReactor().enterMainLoop();
  349. if (isDisplayEnabled()) {
  350. vram->updateDisplayEnabled(false, time);
  351. }
  352. isDisplayArea = false;
  353. // Vertical scanning occurs.
  354. statusReg0 |= 0x80;
  355. if (controlRegs[1] & 0x20) {
  356. irqVertical.set();
  357. }
  358. }
  359. void VDP::execHScan()
  360. {
  361. // Horizontal scanning occurs.
  362. if (controlRegs[0] & 0x10) {
  363. irqHorizontal.set();
  364. }
  365. }
  366. void VDP::execHorAdjust(EmuTime::param time)
  367. {
  368. int newHorAdjust = (controlRegs[18] & 0x0F) ^ 0x07;
  369. if (controlRegs[25] & 0x08) {
  370. newHorAdjust += 4;
  371. }
  372. renderer->updateHorizontalAdjust(newHorAdjust, time);
  373. horizontalAdjust = newHorAdjust;
  374. }
  375. void VDP::execSetMode(EmuTime::param time)
  376. {
  377. updateDisplayMode(
  378. DisplayMode(controlRegs[0], controlRegs[1], controlRegs[25]),
  379. getCmdBit(),
  380. time);
  381. }
  382. void VDP::execSetBlank(EmuTime::param time)
  383. {
  384. bool newDisplayEnabled = (controlRegs[1] & 0x40) != 0;
  385. if (isDisplayArea) {
  386. vram->updateDisplayEnabled(newDisplayEnabled, time);
  387. }
  388. displayEnabled = newDisplayEnabled;
  389. }
  390. void VDP::execCpuVramAccess(EmuTime::param time)
  391. {
  392. assert(!allowTooFastAccess);
  393. pendingCpuAccess = false;
  394. executeCpuVramAccess(time);
  395. }
  396. void VDP::execSyncCmdDone(EmuTime::param time)
  397. {
  398. cmdEngine->sync(time);
  399. }
  400. // TODO: This approach assumes that an overscan-like approach can be used
  401. // skip display start, so that the border is rendered instead.
  402. // This makes sense, but it has not been tested on real MSX yet.
  403. void VDP::scheduleDisplayStart(EmuTime::param time)
  404. {
  405. // Remove pending DISPLAY_START sync point, if any.
  406. if (displayStartSyncTime > time) {
  407. syncDisplayStart.removeSyncPoint();
  408. //cerr << "removing predicted DISPLAY_START sync point\n";
  409. }
  410. // Calculate when (lines and time) display starts.
  411. int lineZero =
  412. // sync + top erase:
  413. 3 + 13 +
  414. // top border:
  415. (palTiming ? 36 : 9) +
  416. (controlRegs[9] & 0x80 ? 0 : 10) +
  417. getVerticalAdjust(); // 0..15
  418. displayStart =
  419. lineZero * TICKS_PER_LINE
  420. + 100 + 102; // VR flips at start of left border
  421. displayStartSyncTime = frameStartTime + displayStart;
  422. //cerr << "new DISPLAY_START is " << (displayStart / TICKS_PER_LINE) << "\n";
  423. // Register new DISPLAY_START sync point.
  424. if (displayStartSyncTime > time) {
  425. syncDisplayStart.setSyncPoint(displayStartSyncTime);
  426. //cerr << "inserting new DISPLAY_START sync point\n";
  427. }
  428. // HSCAN and VSCAN are relative to display start.
  429. scheduleHScan(time);
  430. scheduleVScan(time);
  431. }
  432. void VDP::scheduleVScan(EmuTime::param time)
  433. {
  434. /*
  435. cerr << "scheduleVScan @ " << (getTicksThisFrame(time) / TICKS_PER_LINE) << "\n";
  436. if (vScanSyncTime < frameStartTime) {
  437. cerr << "old VSCAN was previous frame\n";
  438. } else {
  439. cerr << "old VSCAN was " << (frameStartTime.getTicksTill(vScanSyncTime) / TICKS_PER_LINE) << "\n";
  440. }
  441. */
  442. // Remove pending VSCAN sync point, if any.
  443. if (vScanSyncTime > time) {
  444. syncVScan.removeSyncPoint();
  445. //cerr << "removing predicted VSCAN sync point\n";
  446. }
  447. // Calculate moment in time display end occurs.
  448. vScanSyncTime = frameStartTime +
  449. (displayStart + getNumberOfLines() * TICKS_PER_LINE);
  450. //cerr << "new VSCAN is " << (frameStartTime.getTicksTill(vScanSyncTime) / TICKS_PER_LINE) << "\n";
  451. // Register new VSCAN sync point.
  452. if (vScanSyncTime > time) {
  453. syncVScan.setSyncPoint(vScanSyncTime);
  454. //cerr << "inserting new VSCAN sync point\n";
  455. }
  456. }
  457. void VDP::scheduleHScan(EmuTime::param time)
  458. {
  459. // Remove pending HSCAN sync point, if any.
  460. if (hScanSyncTime > time) {
  461. syncHScan.removeSyncPoint();
  462. hScanSyncTime = time;
  463. }
  464. // Calculate moment in time line match occurs.
  465. horizontalScanOffset = displayStart - (100 + 102)
  466. + ((controlRegs[19] - controlRegs[23]) & 0xFF) * TICKS_PER_LINE
  467. + getRightBorder();
  468. // Display line counter continues into the next frame.
  469. // Note that this implementation is not 100% accurate, since the
  470. // number of ticks of the *previous* frame should be subtracted.
  471. // By switching from NTSC to PAL it may even be possible to get two
  472. // HSCANs in a single frame without modifying any other setting.
  473. // Fortunately, no known program relies on this.
  474. int ticksPerFrame = getTicksPerFrame();
  475. if (horizontalScanOffset >= ticksPerFrame) {
  476. horizontalScanOffset -= ticksPerFrame;
  477. // Time at which the internal VDP display line counter is reset,
  478. // expressed in ticks after vsync.
  479. // I would expect the counter to reset at line 16 (for neutral
  480. // set-adjust), but measurements on NMS8250 show it is one line
  481. // earlier. I'm not sure whether the actual counter reset
  482. // happens on line 15 or whether the VDP timing may be one line
  483. // off for some reason.
  484. // TODO: This is just an assumption, more measurements on real MSX
  485. // are necessary to verify there is really such a thing and
  486. // if so, that the value is accurate.
  487. // Note: see this bug report for some measurements on a real machine:
  488. // https://github.com/openMSX/openMSX/issues/1106
  489. int lineCountResetTicks = (8 + getVerticalAdjust()) * TICKS_PER_LINE;
  490. // Display line counter is reset at the start of the top border.
  491. // Any HSCAN that has a higher line number never occurs.
  492. if (horizontalScanOffset >= lineCountResetTicks) {
  493. // This is one way to say "never".
  494. horizontalScanOffset = -1000 * TICKS_PER_LINE;
  495. }
  496. }
  497. // Register new HSCAN sync point if interrupt is enabled.
  498. if ((controlRegs[0] & 0x10) && horizontalScanOffset >= 0) {
  499. // No line interrupt will occur after bottom erase.
  500. // NOT TRUE: "after next top border start" is correct.
  501. // Note that line interrupt can occur in the next frame.
  502. /*
  503. EmuTime bottomEraseTime =
  504. frameStartTime + getTicksPerFrame() - 3 * TICKS_PER_LINE;
  505. */
  506. hScanSyncTime = frameStartTime + horizontalScanOffset;
  507. if (hScanSyncTime > time) {
  508. syncHScan.setSyncPoint(hScanSyncTime);
  509. }
  510. }
  511. }
  512. // TODO: inline?
  513. // TODO: Is it possible to get rid of this routine and its sync point?
  514. // VSYNC, HSYNC and DISPLAY_START could be scheduled for the next
  515. // frame when their callback occurs.
  516. // But I'm not sure how to handle the PAL/NTSC setting (which also
  517. // influences the frequency at which E/O toggles).
  518. void VDP::frameStart(EmuTime::param time)
  519. {
  520. ++frameCount;
  521. //cerr << "VDP::frameStart @ " << time << "\n";
  522. // Toggle E/O.
  523. // Actually this should occur half a line earlier,
  524. // but for now this is accurate enough.
  525. statusReg2 ^= 0x02;
  526. // Settings which are fixed at start of frame.
  527. // Not sure this is how real MSX does it, but close enough for now.
  528. // TODO: Interlace is effectuated in border height, according to
  529. // the data book. Exactly when is the fixation point?
  530. palTiming = (controlRegs[9] & 0x02) != 0;
  531. interlaced = !isFastBlinkEnabled() && ((controlRegs[9] & 0x08) != 0);
  532. // Blinking.
  533. if ((blinkCount != 0) && !isFastBlinkEnabled()) { // counter active?
  534. blinkCount--;
  535. if (blinkCount == 0) {
  536. renderer->updateBlinkState(!blinkState, time);
  537. blinkState = !blinkState;
  538. blinkCount = ( blinkState
  539. ? controlRegs[13] >> 4 : controlRegs[13] & 0x0F ) * 10;
  540. }
  541. }
  542. // TODO: Presumably this is done here
  543. // Note that if superimposing is enabled but no external video
  544. // signal is provided then the VDP stops producing a signal
  545. // (at least on an MSX1, VDP(0)=1 produces "signal lost" on my
  546. // monitor)
  547. const RawFrame* newSuperimposing = (controlRegs[0] & 1) ? externalVideo : nullptr;
  548. if (superimposing != newSuperimposing) {
  549. superimposing = newSuperimposing;
  550. renderer->updateSuperimposing(superimposing, time);
  551. }
  552. // Schedule next VSYNC.
  553. frameStartTime.reset(time);
  554. syncVSync.setSyncPoint(frameStartTime + getTicksPerFrame());
  555. // Schedule DISPLAY_START, VSCAN and HSCAN.
  556. scheduleDisplayStart(time);
  557. // Inform VDP subcomponents.
  558. // TODO: Do this via VDPVRAM?
  559. renderer->frameStart(time);
  560. spriteChecker->frameStart(time);
  561. /*
  562. cout << "--> frameStart = " << frameStartTime
  563. << ", frameEnd = " << (frameStartTime + getTicksPerFrame())
  564. << ", hscan = " << hScanSyncTime
  565. << ", displayStart = " << displayStart
  566. << ", timing: " << (palTiming ? "PAL" : "NTSC")
  567. << "\n";
  568. */
  569. }
  570. // The I/O functions.
  571. void VDP::writeIO(word port, byte value, EmuTime::param time_)
  572. {
  573. EmuTime time = time_;
  574. // This is the (fixed) delay from
  575. // https://github.com/openMSX/openMSX/issues/563 and
  576. // https://github.com/openMSX/openMSX/issues/989
  577. // It seems to originate from the T9769x and for x=C the delay is 1
  578. // cycle and for other x it seems the delay is 2 cycles
  579. if (fixedVDPIOdelayCycles > 0) {
  580. time = cpu.waitCyclesZ80(time, fixedVDPIOdelayCycles);
  581. }
  582. assert(isInsideFrame(time));
  583. switch (port & (isMSX1VDP() ? 0x01 : 0x03)) {
  584. case 0: // VRAM data write
  585. vramWrite(value, time);
  586. registerDataStored = false;
  587. break;
  588. case 1: // Register or address write
  589. if (registerDataStored) {
  590. if (value & 0x80) {
  591. if (!(value & 0x40) || isMSX1VDP()) {
  592. // Register write.
  593. changeRegister(
  594. value & controlRegMask,
  595. dataLatch,
  596. time);
  597. } else {
  598. // TODO what happens in this case?
  599. // it's not a register write because
  600. // that breaks "SNOW26" demo
  601. }
  602. if (isMSX1VDP()) {
  603. // For these VDP's the VRAM pointer is modified when
  604. // writing to VDP registers. Without this some demos won't
  605. // run as on real MSX1, e.g. Planet of the Epas, Utopia and
  606. // Waves 1.2. Thanks to dvik for finding this out.
  607. // See also below about not using the latch on MSX1.
  608. // Set read/write address.
  609. vramPointer = (value << 8 | (vramPointer & 0xFF)) & 0x3FFF;
  610. }
  611. } else {
  612. // Set read/write address.
  613. vramPointer = (value << 8 | dataLatch) & 0x3FFF;
  614. if (!(value & 0x40)) {
  615. // Read ahead.
  616. vramRead(time);
  617. }
  618. }
  619. registerDataStored = false;
  620. } else {
  621. // Note: on MSX1 there seems to be no
  622. // latch used, but VDP address writes
  623. // are done directly.
  624. // Thanks to hap for finding this out. :)
  625. if (isMSX1VDP()) {
  626. vramPointer = (vramPointer & 0x3F00) | value;
  627. }
  628. dataLatch = value;
  629. registerDataStored = true;
  630. }
  631. break;
  632. case 2: // Palette data write
  633. if (paletteDataStored) {
  634. int index = controlRegs[16];
  635. int grb = ((value << 8) | dataLatch) & 0x777;
  636. setPalette(index, grb, time);
  637. controlRegs[16] = (index + 1) & 0x0F;
  638. paletteDataStored = false;
  639. } else {
  640. dataLatch = value;
  641. paletteDataStored = true;
  642. }
  643. break;
  644. case 3: { // Indirect register write
  645. dataLatch = value;
  646. // TODO: What happens if reg 17 is written indirectly?
  647. //fprintf(stderr, "VDP indirect register write: %02X\n", value);
  648. byte regNr = controlRegs[17];
  649. changeRegister(regNr & 0x3F, value, time);
  650. if ((regNr & 0x80) == 0) {
  651. // Auto-increment.
  652. controlRegs[17] = (regNr + 1) & 0x3F;
  653. }
  654. break;
  655. }
  656. }
  657. }
  658. void VDP::setPalette(int index, word grb, EmuTime::param time)
  659. {
  660. if (palette[index] != grb) {
  661. renderer->updatePalette(index, grb, time);
  662. palette[index] = grb;
  663. }
  664. }
  665. void VDP::vramWrite(byte value, EmuTime::param time)
  666. {
  667. scheduleCpuVramAccess(false, value, time);
  668. }
  669. byte VDP::vramRead(EmuTime::param time)
  670. {
  671. // Return the result from a previous read. In case
  672. // allowTooFastAccess==true, the call to scheduleCpuVramAccess()
  673. // already overwrites that variable, so make a local copy first.
  674. byte result = cpuVramData;
  675. byte dummy = 0;
  676. scheduleCpuVramAccess(true, dummy, time); // schedule next read
  677. return result;
  678. }
  679. void VDP::scheduleCpuVramAccess(bool isRead, byte write, EmuTime::param time)
  680. {
  681. // Tested on real V9938: 'cpuVramData' is shared between read and write.
  682. // E.g. OUT (#98),A followed by IN A,(#98) returns the just written value.
  683. if (!isRead) cpuVramData = write;
  684. cpuVramReqIsRead = isRead;
  685. if (unlikely(pendingCpuAccess)) {
  686. // Already scheduled. Do nothing.
  687. // The old request has been overwritten by the new request!
  688. assert(!allowTooFastAccess);
  689. tooFastCallback.execute();
  690. } else {
  691. if (unlikely(allowTooFastAccess)) {
  692. // Immediately execute request.
  693. // In the past, in allowTooFastAccess-mode, we would
  694. // still schedule the actual access, but process
  695. // pending requests early when a new one arrives before
  696. // the old one was handled. Though this would still go
  697. // wrong because of the delayed update of
  698. // 'vramPointer'. We could _only_ _partly_ work around
  699. // that by calculating the actual vram address early
  700. // (likely not what the real VDP does). But because
  701. // allowTooFastAccess is anyway an artifical situation
  702. // we now solve this in a simpler way: simply not
  703. // schedule CPU-VRAM accesses.
  704. assert(!pendingCpuAccess);
  705. executeCpuVramAccess(time);
  706. } else {
  707. // For V99x8 there are 16 extra cycles, for details see:
  708. // doc/internal/vdp-vram-timing/vdp-timing.html
  709. // For TMS99x8 the situation is less clear, see
  710. // doc/internal/vdp-vram-timing/vdp-timing-2.html
  711. // Additional measurements(*) show that picking either 8 or 9
  712. // TMS cycles (equivalent to 32 or 36 V99x8 cycles) gives the
  713. // same result as on a real MSX. This corresponds to
  714. // respectively 1.49us or 1.68us, the TMS documentation
  715. // specifies 2us for this value.
  716. // (*) In this test we did a lot of OUT operations (writes to
  717. // VRAM) that are exactly N cycles apart. After the writes we
  718. // detect whether all were successful by reading VRAM
  719. // (slowly). We vary N and found that you get corruption for
  720. // N<=26 cycles, but no corruption occurs for N>=27. This test
  721. // was done in screen 2 with 4 sprites visible on one line
  722. // (though the sprites did not seem to make a difference).
  723. // So this test could not decide between 8 or 9 TMS cycles.
  724. // To be on the safe side we picked 8.
  725. //
  726. // Update: 8 cycles (DELTA_32) causes corruption in
  727. // 'Chase HQ', see
  728. // http://www.msx.org/forum/msx-talk/openmsx/openmsx-about-release-testing-help-wanted
  729. // lowering it to 7 cycles seems fine. TODO needs more
  730. // investigation. (Just guessing) possibly there are
  731. // other variables that influence the exact timing (7
  732. // vs 8 cycles).
  733. pendingCpuAccess = true;
  734. auto delta = isMSX1VDP() ? VDPAccessSlots::DELTA_28
  735. : VDPAccessSlots::DELTA_16;
  736. syncCpuVramAccess.setSyncPoint(getAccessSlot(time, delta));
  737. }
  738. }
  739. }
  740. void VDP::executeCpuVramAccess(EmuTime::param time)
  741. {
  742. int addr = (controlRegs[14] << 14) | vramPointer;
  743. if (displayMode.isPlanar()) {
  744. // note: also extended VRAM is interleaved,
  745. // because there is only 64kB it's interleaved
  746. // with itself (every byte repeated twice)
  747. addr = ((addr << 16) | (addr >> 1)) & 0x1FFFF;
  748. }
  749. bool doAccess;
  750. if (likely(!cpuExtendedVram)) {
  751. doAccess = true;
  752. } else if (likely(vram->getSize() == 192 * 1024)) {
  753. addr = 0x20000 | (addr & 0xFFFF);
  754. doAccess = true;
  755. } else {
  756. doAccess = false;
  757. }
  758. if (doAccess) {
  759. if (cpuVramReqIsRead) {
  760. cpuVramData = vram->cpuRead(addr, time);
  761. } else {
  762. vram->cpuWrite(addr, cpuVramData, time);
  763. }
  764. } else {
  765. if (cpuVramReqIsRead) {
  766. cpuVramData = 0xFF;
  767. } else {
  768. // nothing
  769. }
  770. }
  771. vramPointer = (vramPointer + 1) & 0x3FFF;
  772. if (vramPointer == 0 && displayMode.isV9938Mode()) {
  773. // In MSX2 video modes, pointer range is 128K.
  774. controlRegs[14] = (controlRegs[14] + 1) & 0x07;
  775. }
  776. }
  777. EmuTime VDP::getAccessSlot(EmuTime::param time, VDPAccessSlots::Delta delta) const
  778. {
  779. return VDPAccessSlots::getAccessSlot(
  780. getFrameStartTime(), time, delta, *this);
  781. }
  782. VDPAccessSlots::Calculator VDP::getAccessSlotCalculator(
  783. EmuTime::param time, EmuTime::param limit) const
  784. {
  785. return VDPAccessSlots::getCalculator(
  786. getFrameStartTime(), time, limit, *this);
  787. }
  788. byte VDP::peekStatusReg(byte reg, EmuTime::param time) const
  789. {
  790. switch (reg) {
  791. case 0:
  792. spriteChecker->sync(time);
  793. return statusReg0;
  794. case 1:
  795. if (controlRegs[0] & 0x10) { // line int enabled
  796. return statusReg1 | (irqHorizontal.getState() ? 1:0);
  797. } else { // line int disabled
  798. // FH goes up at the start of the right border of IL and
  799. // goes down at the start of the next left border.
  800. // TODO: Precalc matchLength?
  801. int afterMatch =
  802. getTicksThisFrame(time) - horizontalScanOffset;
  803. if (afterMatch < 0) {
  804. afterMatch += getTicksPerFrame();
  805. // afterMatch can still be negative at this
  806. // point, see scheduleHScan()
  807. }
  808. int matchLength = (displayMode.isTextMode() ? 87 : 59)
  809. + 27 + 100 + 102;
  810. return statusReg1 |
  811. (0 <= afterMatch && afterMatch < matchLength);
  812. }
  813. case 2: {
  814. // TODO: Once VDP keeps display/blanking state, keeping
  815. // VR is probably part of that, so use it.
  816. // --> Is isDisplayArea actually !VR?
  817. int ticksThisFrame = getTicksThisFrame(time);
  818. int displayEnd =
  819. displayStart + getNumberOfLines() * TICKS_PER_LINE;
  820. bool vr = ticksThisFrame < displayStart - TICKS_PER_LINE
  821. || ticksThisFrame >= displayEnd;
  822. return statusReg2
  823. | (getHR(ticksThisFrame) ? 0x20 : 0x00)
  824. | (vr ? 0x40 : 0x00)
  825. | cmdEngine->getStatus(time);
  826. }
  827. case 3:
  828. return byte(spriteChecker->getCollisionX(time));
  829. case 4:
  830. return byte(spriteChecker->getCollisionX(time) >> 8) | 0xFE;
  831. case 5:
  832. return byte(spriteChecker->getCollisionY(time));
  833. case 6:
  834. return byte(spriteChecker->getCollisionY(time) >> 8) | 0xFC;
  835. case 7:
  836. return cmdEngine->readColor(time);
  837. case 8:
  838. return byte(cmdEngine->getBorderX(time));
  839. case 9:
  840. return byte(cmdEngine->getBorderX(time) >> 8) | 0xFE;
  841. default: // non-existent status register
  842. return 0xFF;
  843. }
  844. }
  845. byte VDP::readStatusReg(byte reg, EmuTime::param time)
  846. {
  847. byte ret = peekStatusReg(reg, time);
  848. switch (reg) {
  849. case 0:
  850. spriteChecker->resetStatus();
  851. statusReg0 &= ~0x80;
  852. irqVertical.reset();
  853. break;
  854. case 1:
  855. if (controlRegs[0] & 0x10) { // line int enabled
  856. irqHorizontal.reset();
  857. }
  858. break;
  859. case 5:
  860. spriteChecker->resetCollision();
  861. break;
  862. case 7:
  863. cmdEngine->resetColor();
  864. break;
  865. }
  866. return ret;
  867. }
  868. byte VDP::readIO(word port, EmuTime::param time)
  869. {
  870. assert(isInsideFrame(time));
  871. registerDataStored = false; // Abort any port #1 writes in progress.
  872. switch (port & (isMSX1VDP() ? 0x01 : 0x03)) {
  873. case 0: // VRAM data read
  874. return vramRead(time);
  875. case 1: // Status register read
  876. // Calculate status register contents.
  877. return readStatusReg(controlRegs[15], time);
  878. case 2:
  879. case 3:
  880. return 0xFF;
  881. default:
  882. UNREACHABLE; return 0xFF;
  883. }
  884. }
  885. byte VDP::peekIO(word /*port*/, EmuTime::param /*time*/) const
  886. {
  887. // TODO not implemented
  888. return 0xFF;
  889. }
  890. void VDP::changeRegister(byte reg, byte val, EmuTime::param time)
  891. {
  892. if (reg >= 32) {
  893. // MXC belongs to CPU interface;
  894. // other bits in this register belong to command engine.
  895. if (reg == 45) {
  896. cpuExtendedVram = (val & 0x40) != 0;
  897. }
  898. // Pass command register writes to command engine.
  899. if (reg < 47) {
  900. cmdEngine->setCmdReg(reg - 32, val, time);
  901. }
  902. return;
  903. }
  904. // Make sure only bits that actually exist are written.
  905. val &= controlValueMasks[reg];
  906. // Determine the difference between new and old value.
  907. byte change = val ^ controlRegs[reg];
  908. // Register 13 is special because writing it resets blinking state,
  909. // even if the value in the register doesn't change.
  910. if (reg == 13) {
  911. // Switch to ON state unless ON period is zero.
  912. if (blinkState == ((val & 0xF0) == 0)) {
  913. renderer->updateBlinkState(!blinkState, time);
  914. blinkState = !blinkState;
  915. }
  916. if ((val & 0xF0) && (val & 0x0F)) {
  917. // Alternating colors, start with ON.
  918. blinkCount = (val >> 4) * 10;
  919. } else {
  920. // Stable color.
  921. blinkCount = 0;
  922. }
  923. // TODO when 'isFastBlinkEnabled()==true' the variables
  924. // 'blinkState' and 'blinkCount' represent the values at line 0.
  925. // This implementation is not correct for the partial remaining
  926. // frame after register 13 got changed.
  927. }
  928. if (!change) return;
  929. // Perform additional tasks before new value becomes active.
  930. switch (reg) {
  931. case 0:
  932. if (change & DisplayMode::REG0_MASK) {
  933. syncAtNextLine(syncSetMode, time);
  934. }
  935. break;
  936. case 1:
  937. if (change & 0x03) {
  938. // Update sprites on size and mag changes.
  939. spriteChecker->updateSpriteSizeMag(val, time);
  940. }
  941. // TODO: Reset vertical IRQ if IE0 is reset?
  942. if (change & DisplayMode::REG1_MASK) {
  943. syncAtNextLine(syncSetMode, time);
  944. }
  945. if (change & 0x40) {
  946. syncAtNextLine(syncSetBlank, time);
  947. }
  948. break;
  949. case 2: {
  950. int base = (val << 10) | ~(~0u << 10);
  951. // TODO:
  952. // I reverted this fix.
  953. // Although the code is correct, there is also a counterpart in the
  954. // renderer that must be updated. I'm too tired now to find it.
  955. // Since name table checking is currently disabled anyway, keeping the
  956. // old code does not hurt.
  957. // Eventually this line should be re-enabled.
  958. /*
  959. if (displayMode.isPlanar()) {
  960. base = ((base << 16) | (base >> 1)) & 0x1FFFF;
  961. }
  962. */
  963. renderer->updateNameBase(base, time);
  964. break;
  965. }
  966. case 7:
  967. if (getDisplayMode().getByte() != DisplayMode::GRAPHIC7) {
  968. if (change & 0xF0) {
  969. renderer->updateForegroundColor(val >> 4, time);
  970. }
  971. if (change & 0x0F) {
  972. renderer->updateBackgroundColor(val & 0x0F, time);
  973. }
  974. } else {
  975. renderer->updateBackgroundColor(val, time);
  976. }
  977. break;
  978. case 8:
  979. if (change & 0x20) {
  980. renderer->updateTransparency((val & 0x20) == 0, time);
  981. spriteChecker->updateTransparency((val & 0x20) == 0, time);
  982. }
  983. if (change & 0x02) {
  984. vram->updateSpritesEnabled((val & 0x02) == 0, time);
  985. }
  986. if (change & 0x08) {
  987. vram->updateVRMode((val & 0x08) != 0, time);
  988. }
  989. break;
  990. case 12:
  991. if (change & 0xF0) {
  992. renderer->updateBlinkForegroundColor(val >> 4, time);
  993. }
  994. if (change & 0x0F) {
  995. renderer->updateBlinkBackgroundColor(val & 0x0F, time);
  996. }
  997. break;
  998. case 16:
  999. // Any half-finished palette loads are aborted.
  1000. paletteDataStored = false;
  1001. break;
  1002. case 18:
  1003. if (change & 0x0F) {
  1004. syncAtNextLine(syncHorAdjust, time);
  1005. }
  1006. break;
  1007. case 23:
  1008. spriteChecker->updateVerticalScroll(val, time);
  1009. renderer->updateVerticalScroll(val, time);
  1010. break;
  1011. case 25:
  1012. if (change & (DisplayMode::REG25_MASK | 0x40)) {
  1013. updateDisplayMode(getDisplayMode().updateReg25(val),
  1014. val & 0x40,
  1015. time);
  1016. }
  1017. if (change & 0x08) {
  1018. syncAtNextLine(syncHorAdjust, time);
  1019. }
  1020. if (change & 0x02) {
  1021. renderer->updateBorderMask((val & 0x02) != 0, time);
  1022. }
  1023. if (change & 0x01) {
  1024. renderer->updateMultiPage((val & 0x01) != 0, time);
  1025. }
  1026. break;
  1027. case 26:
  1028. renderer->updateHorizontalScrollHigh(val, time);
  1029. break;
  1030. case 27:
  1031. renderer->updateHorizontalScrollLow(val, time);
  1032. break;
  1033. }
  1034. // Commit the change.
  1035. controlRegs[reg] = val;
  1036. // Perform additional tasks after new value became active.
  1037. // Because base masks cannot be read from the VDP, updating them after
  1038. // the commit is equivalent to updating before.
  1039. switch (reg) {
  1040. case 0:
  1041. if (change & 0x10) { // IE1
  1042. if (val & 0x10) {
  1043. scheduleHScan(time);
  1044. } else {
  1045. irqHorizontal.reset();
  1046. }
  1047. }
  1048. break;
  1049. case 1:
  1050. if (change & 0x20) { // IE0
  1051. if (val & 0x20) {
  1052. // This behaviour is important. Without it,
  1053. // the intro music in 'Andonis' is way too slow
  1054. // and the intro logo of 'Zanac' is corrupted.
  1055. if (statusReg0 & 0x80) {
  1056. irqVertical.set();
  1057. }
  1058. } else {
  1059. irqVertical.reset();
  1060. }
  1061. }
  1062. if ((change & 0x80) && isVDPwithVRAMremapping()) {
  1063. // confirmed: VRAM remapping only happens on TMS99xx
  1064. // see VDPVRAM for details on the remapping itself
  1065. vram->change4k8kMapping((val & 0x80) != 0);
  1066. }
  1067. break;
  1068. case 2:
  1069. updateNameBase(time);
  1070. break;
  1071. case 3:
  1072. case 10:
  1073. updateColorBase(time);
  1074. if (vdpHasPatColMirroring()) updatePatternBase(time);
  1075. break;
  1076. case 4:
  1077. updatePatternBase(time);
  1078. break;
  1079. case 5:
  1080. case 11:
  1081. updateSpriteAttributeBase(time);
  1082. break;
  1083. case 6:
  1084. updateSpritePatternBase(time);
  1085. break;
  1086. case 9:
  1087. if ((val & 1) && ! warningPrinted) {
  1088. warningPrinted = true;
  1089. getCliComm().printWarning(
  1090. "The running MSX software has set bit 0 of VDP register 9 "
  1091. "(dot clock direction) to one. In an ordinary MSX, "
  1092. "the screen would go black and the CPU would stop running.");
  1093. // TODO: Emulate such behaviour.
  1094. }
  1095. if (change & 0x80) {
  1096. /*
  1097. cerr << "changed to " << (val & 0x80 ? 212 : 192) << " lines"
  1098. << " at line " << (getTicksThisFrame(time) / TICKS_PER_LINE) << "\n";
  1099. */
  1100. // Display lines (192/212) determines display start and end.
  1101. // TODO: Find out exactly when display start is fixed.
  1102. // If it is fixed at VSYNC that would simplify things,
  1103. // but I think it's more likely the current
  1104. // implementation is accurate.
  1105. if (time < displayStartSyncTime) {
  1106. // Display start is not fixed yet.
  1107. scheduleDisplayStart(time);
  1108. } else {
  1109. // Display start is fixed, but display end is not.
  1110. scheduleVScan(time);
  1111. }
  1112. }
  1113. break;
  1114. case 19:
  1115. case 23:
  1116. scheduleHScan(time);
  1117. break;
  1118. case 25:
  1119. if (change & 0x01) {
  1120. updateNameBase(time);
  1121. }
  1122. break;
  1123. }
  1124. }
  1125. void VDP::syncAtNextLine(SyncBase& type, EmuTime::param time)
  1126. {
  1127. int line = getTicksThisFrame(time) / TICKS_PER_LINE;
  1128. int ticks = (line + 1) * TICKS_PER_LINE;
  1129. EmuTime nextTime = frameStartTime + ticks;
  1130. type.setSyncPoint(nextTime);
  1131. }
  1132. void VDP::updateNameBase(EmuTime::param time)
  1133. {
  1134. int base = (controlRegs[2] << 10) | ~(~0u << 10);
  1135. // TODO:
  1136. // I reverted this fix.
  1137. // Although the code is correct, there is also a counterpart in the
  1138. // renderer that must be updated. I'm too tired now to find it.
  1139. // Since name table checking is currently disabled anyway, keeping the
  1140. // old code does not hurt.
  1141. // Eventually this line should be re-enabled.
  1142. /*
  1143. if (displayMode.isPlanar()) {
  1144. base = ((base << 16) | (base >> 1)) & 0x1FFFF;
  1145. }
  1146. */
  1147. int indexMask =
  1148. displayMode.isBitmapMode()
  1149. ? ~0u << 17 // TODO: Calculate actual value; how to handle planar?
  1150. : ~0u << (displayMode.isTextMode() ? 12 : 10);
  1151. if (controlRegs[25] & 0x01) {
  1152. // Multi page scrolling. The same bit is used in character and
  1153. // (non)planar-bitmap modes.
  1154. // TODO test text modes
  1155. indexMask &= ~0x8000;
  1156. }
  1157. vram->nameTable.setMask(base, indexMask, time);
  1158. }
  1159. void VDP::updateColorBase(EmuTime::param time)
  1160. {
  1161. int base = (controlRegs[10] << 14) | (controlRegs[3] << 6) | ~(~0u << 6);
  1162. renderer->updateColorBase(base, time);
  1163. switch (displayMode.getBase()) {
  1164. case 0x09: // Text 2.
  1165. // TODO: Enable this only if dual color is actually active.
  1166. vram->colorTable.setMask(base, ~0u << 9, time);
  1167. break;
  1168. case 0x00: // Graphic 1.
  1169. vram->colorTable.setMask(base, ~0u << 6, time);
  1170. break;
  1171. case 0x04: // Graphic 2.
  1172. vram->colorTable.setMask(base | (vdpLacksMirroring() ? 0x1800 : 0), ~0u << 13, time);
  1173. break;
  1174. case 0x08: // Graphic 3.
  1175. vram->colorTable.setMask(base, ~0u << 13, time);
  1176. break;
  1177. default:
  1178. // Other display modes do not use a color table.
  1179. vram->colorTable.disable(time);
  1180. }
  1181. }
  1182. void VDP::updatePatternBase(EmuTime::param time)
  1183. {
  1184. int base = (controlRegs[4] << 11) | ~(~0u << 11);
  1185. renderer->updatePatternBase(base, time);
  1186. switch (displayMode.getBase()) {
  1187. case 0x01: // Text 1.
  1188. case 0x05: // Text 1 Q.
  1189. case 0x09: // Text 2.
  1190. case 0x00: // Graphic 1.
  1191. case 0x02: // Multicolor.
  1192. case 0x06: // Multicolor Q.
  1193. vram->patternTable.setMask(base, ~0u << 11, time);
  1194. break;
  1195. case 0x04: // Graphic 2.
  1196. if (vdpHasPatColMirroring()) {
  1197. // TMS99XX has weird pattern table behavior: some
  1198. // bits of the color-base register leak into the
  1199. // pattern-base. See also:
  1200. // http://www.youtube.com/watch?v=XJljSJqzDR0
  1201. base = (controlRegs[4] << 11)
  1202. | ((controlRegs[3] & 0x1f) << 6)
  1203. | ~(~0u << 6);
  1204. }
  1205. vram->patternTable.setMask(base | (vdpLacksMirroring() ? 0x1800 : 0), ~0u << 13, time);
  1206. break;
  1207. case 0x08: // Graphic 3.
  1208. vram->patternTable.setMask(base, ~0u << 13, time);
  1209. break;
  1210. default:
  1211. // Other display modes do not use a pattern table.
  1212. vram->patternTable.disable(time);
  1213. }
  1214. }
  1215. void VDP::updateSpriteAttributeBase(EmuTime::param time)
  1216. {
  1217. int mode = displayMode.getSpriteMode(isMSX1VDP());
  1218. if (mode == 0) {
  1219. vram->spriteAttribTable.disable(time);
  1220. return;
  1221. }
  1222. int baseMask = (controlRegs[11] << 15) | (controlRegs[5] << 7) | ~(~0u << 7);
  1223. int indexMask = mode == 1 ? ~0u << 7 : ~0u << 10;
  1224. if (displayMode.isPlanar()) {
  1225. baseMask = ((baseMask << 16) | (baseMask >> 1)) & 0x1FFFF;
  1226. indexMask = ((indexMask << 16) | ~(1 << 16)) & (indexMask >> 1);
  1227. }
  1228. vram->spriteAttribTable.setMask(baseMask, indexMask, time);
  1229. }
  1230. void VDP::updateSpritePatternBase(EmuTime::param time)
  1231. {
  1232. if (displayMode.getSpriteMode(isMSX1VDP()) == 0) {
  1233. vram->spritePatternTable.disable(time);
  1234. return;
  1235. }
  1236. int baseMask = (controlRegs[6] << 11) | ~(~0u << 11);
  1237. int indexMask = ~0u << 11;
  1238. if (displayMode.isPlanar()) {
  1239. baseMask = ((baseMask << 16) | (baseMask >> 1)) & 0x1FFFF;
  1240. indexMask = ((indexMask << 16) | ~(1 << 16)) & (indexMask >> 1);
  1241. }
  1242. vram->spritePatternTable.setMask(baseMask, indexMask, time);
  1243. }
  1244. void VDP::updateDisplayMode(DisplayMode newMode, bool cmdBit, EmuTime::param time)
  1245. {
  1246. // Synchronise subsystems.
  1247. vram->updateDisplayMode(newMode, cmdBit, time);
  1248. // TODO: Is this a useful optimisation, or doesn't it help
  1249. // in practice?
  1250. // What aspects have changed:
  1251. // Switched from planar to nonplanar or vice versa.
  1252. bool planarChange =
  1253. newMode.isPlanar() != displayMode.isPlanar();
  1254. // Sprite mode changed.
  1255. bool msx1 = isMSX1VDP();
  1256. bool spriteModeChange =
  1257. newMode.getSpriteMode(msx1) != displayMode.getSpriteMode(msx1);
  1258. // Commit the new display mode.
  1259. displayMode = newMode;
  1260. // Speed up performance of bitmap/character mode splits:
  1261. // leave last used character mode active.
  1262. // TODO: Disable it if not used for some time.
  1263. if (!displayMode.isBitmapMode()) {
  1264. updateColorBase(time);
  1265. updatePatternBase(time);
  1266. }
  1267. if (planarChange || spriteModeChange) {
  1268. updateSpritePatternBase(time);
  1269. updateSpriteAttributeBase(time);
  1270. }
  1271. updateNameBase(time);
  1272. // To be extremely accurate, reschedule hscan when changing
  1273. // from/to text mode. Text mode has different border width,
  1274. // which affects the moment hscan occurs.
  1275. // TODO: Why didn't I implement this yet?
  1276. // It's one line of code and overhead is not huge either.
  1277. }
  1278. void VDP::update(const Setting& setting)
  1279. {
  1280. assert(&setting == one_of(&cmdTiming, &tooFastAccess));
  1281. (void)setting;
  1282. brokenCmdTiming = cmdTiming .getEnum();
  1283. allowTooFastAccess = tooFastAccess.getEnum();
  1284. if (unlikely(allowTooFastAccess && pendingCpuAccess)) {
  1285. // in allowTooFastAccess-mode, don't schedule CPU-VRAM access
  1286. syncCpuVramAccess.removeSyncPoint();
  1287. pendingCpuAccess = false;
  1288. executeCpuVramAccess(getCurrentTime());
  1289. }
  1290. }
  1291. /*
  1292. * Roughly measured RGB values in volts.
  1293. * Voltages were in range of 1.12-5.04, and had 2 digits accuracy (it seems
  1294. * minimum difference was 0.04 V).
  1295. * Blue component of color 5 and red component of color 9 were higher than
  1296. * the components for white... There are several methods to handle this...
  1297. * 1) clip to values of white
  1298. * 2) scale all colors by min/max of that component (means white is not 3x 255)
  1299. * 3) scale per color if components for that color are beyond those of white
  1300. * 4) assume the analog values are output by a DA converter, derive the digital
  1301. * values and scale that to the range 0-255 (thanks to FRS for this idea).
  1302. * This also results in white not being 3x 255, of course.
  1303. *
  1304. * Method 4 results in the table below and seems the most accurate (so far).
  1305. *
  1306. * Thanks to Tiago Valença and Carlos Mansur for measuring on a T7937A.
  1307. */
  1308. constexpr std::array<std::array<uint8_t, 3>, 16> TOSHIBA_PALETTE = {{
  1309. { 0, 0, 0 },
  1310. { 0, 0, 0 },
  1311. { 102, 204, 102 },
  1312. { 136, 238, 136 },
  1313. { 68, 68, 221 },
  1314. { 119, 119, 255 },
  1315. { 187, 85, 85 },
  1316. { 119, 221, 221 },
  1317. { 221, 102, 102 },
  1318. { 255, 119, 119 },
  1319. { 204, 204, 85 },
  1320. { 238, 238, 136 },
  1321. { 85, 170, 85 },
  1322. { 187, 85, 187 },
  1323. { 204, 204, 204 },
  1324. { 238, 238, 238 },
  1325. }};
  1326. /*
  1327. * Palette for the YM2220 is a crude approximantion based on the fact that the
  1328. * pictures of a Yamaha AX-150 (YM2220) and a Philips NMS-8250 (V9938) have a
  1329. * quite similar appearance. See first post here:
  1330. *
  1331. * https://www.msx.org/forum/msx-talk/hardware/unknown-vdp-yamaha-ym2220?page=3
  1332. */
  1333. constexpr std::array<std::array<uint8_t, 3>, 16> YM2220_PALETTE = {{
  1334. { 0, 0, 0 },
  1335. { 0, 0, 0 },
  1336. { 36, 218, 36 },
  1337. { 200, 255, 109 },
  1338. { 36, 36, 255 },
  1339. { 72, 109, 255 },
  1340. { 182, 36, 36 },
  1341. { 72, 218, 255 },
  1342. { 255, 36, 36 },
  1343. { 255, 175, 175 },
  1344. { 230, 230, 0 },
  1345. { 230, 230, 200 },
  1346. { 36, 195, 36 },
  1347. { 218, 72, 182 },
  1348. { 182, 182, 182 },
  1349. { 255, 255, 255 },
  1350. }};
  1351. /*
  1352. How come the FM-X has a distinct palette while it clearly has a TMS9928 VDP?
  1353. Because it has an additional circuit that rework the palette for the same one
  1354. used in the Fujitsu FM-7. It's encoded in 3-bit RGB.
  1355. This seems to be the 24-bit RGB equivalent to the palette output by the FM-X on
  1356. its RGB conector:
  1357. */
  1358. constexpr std::array<std::array<uint8_t, 3>, 16> THREE_BIT_RGB_PALETTE = {{
  1359. { 0, 0, 0 },
  1360. { 0, 0, 0 },
  1361. { 0, 255, 0 },
  1362. { 0, 255, 0 },
  1363. { 0, 0, 255 },
  1364. { 0, 0, 255 },
  1365. { 255, 0, 0 },
  1366. { 0, 255, 255 },
  1367. { 255, 0, 0 },
  1368. { 255, 0, 0 },
  1369. { 255, 255, 0 },
  1370. { 255, 255, 0 },
  1371. { 0, 255, 0 },
  1372. { 255, 0, 255 },
  1373. { 255, 255, 255 },
  1374. { 255, 255, 255 },
  1375. }};
  1376. // Source: TMS9918/28/29 Data Book, page 2-17.
  1377. constexpr float TMS9XXXA_ANALOG_OUTPUT[16][3] = {
  1378. // Y R-Y B-Y voltages
  1379. { 0.00f, 0.47f, 0.47f },
  1380. { 0.00f, 0.47f, 0.47f },
  1381. { 0.53f, 0.07f, 0.20f },
  1382. { 0.67f, 0.17f, 0.27f },
  1383. { 0.40f, 0.40f, 1.00f },
  1384. { 0.53f, 0.43f, 0.93f },
  1385. { 0.47f, 0.83f, 0.30f },
  1386. { 0.73f, 0.00f, 0.70f },
  1387. { 0.53f, 0.93f, 0.27f },
  1388. { 0.67f, 0.93f, 0.27f },
  1389. { 0.73f, 0.57f, 0.07f },
  1390. { 0.80f, 0.57f, 0.17f },
  1391. { 0.47f, 0.13f, 0.23f },
  1392. { 0.53f, 0.73f, 0.67f },
  1393. { 0.80f, 0.47f, 0.47f },
  1394. { 1.00f, 0.47f, 0.47f },
  1395. };
  1396. std::array<std::array<uint8_t, 3>, 16> VDP::getMSX1Palette() const
  1397. {
  1398. assert(isMSX1VDP());
  1399. if (MSXDevice::getDeviceConfig().findChild("3bitrgboutput") != nullptr) {
  1400. return THREE_BIT_RGB_PALETTE;
  1401. }
  1402. if ((version & VM_TOSHIBA_PALETTE) != 0) {
  1403. return TOSHIBA_PALETTE;
  1404. }
  1405. if ((version & VM_YM2220_PALETTE) != 0) {
  1406. return YM2220_PALETTE;
  1407. }
  1408. std::array<std::array<uint8_t, 3>, 16> tmsPalette;
  1409. for (int color = 0; color < 16; color++) {
  1410. // convert from analog output to YPbPr
  1411. float Y = TMS9XXXA_ANALOG_OUTPUT[color][0];
  1412. float Pr = TMS9XXXA_ANALOG_OUTPUT[color][1] - 0.5f;
  1413. float Pb = TMS9XXXA_ANALOG_OUTPUT[color][2] - 0.5f;
  1414. // apply the saturation
  1415. Pr *= (saturationPr / 100.0f);
  1416. Pb *= (saturationPb / 100.0f);
  1417. // convert to RGB as follows:
  1418. /*
  1419. |R| | 1 0 1.402 | |Y |
  1420. |G| = | 1 -0.344 -0.714 | x |Pb|
  1421. |B| | 1 1.722 0 | |Pr|
  1422. */
  1423. float R = Y + 0 + 1.402f * Pr;
  1424. float G = Y - 0.344f * Pb - 0.714f * Pr;
  1425. float B = Y + 1.722f * Pb + 0;
  1426. // blow up with factor of 255
  1427. R *= 255;
  1428. G *= 255;
  1429. B *= 255;
  1430. // the final result is that these values
  1431. // are clipped in the [0:255] range.
  1432. // Note: Using roundf instead of std::round because libstdc++ when
  1433. // built on top of uClibc lacks std::round; uClibc does provide
  1434. // roundf, but lacks other C99 math functions and that makes
  1435. // libstdc++ disable all wrappers for C99 math functions.
  1436. tmsPalette[color][0] = Math::clipIntToByte(roundf(R));
  1437. tmsPalette[color][1] = Math::clipIntToByte(roundf(G));
  1438. tmsPalette[color][2] = Math::clipIntToByte(roundf(B));
  1439. // std::cerr << color << " " << int(tmsPalette[color][0]) << " " << int(tmsPalette[color][1]) <<" " << int(tmsPalette[color][2]) << '\n';
  1440. }
  1441. return tmsPalette;
  1442. }
  1443. // RegDebug
  1444. VDP::RegDebug::RegDebug(VDP& vdp_)
  1445. : SimpleDebuggable(vdp_.getMotherBoard(),
  1446. vdp_.getName() + " regs", "VDP registers.", 0x40)
  1447. {
  1448. }
  1449. byte VDP::RegDebug::read(unsigned address)
  1450. {
  1451. auto& vdp = OUTER(VDP, vdpRegDebug);
  1452. if (address < 0x20) {
  1453. return vdp.controlRegs[address];
  1454. } else if (address < 0x2F) {
  1455. return vdp.cmdEngine->peekCmdReg(address - 0x20);
  1456. } else {
  1457. return 0xFF;
  1458. }
  1459. }
  1460. void VDP::RegDebug::write(unsigned address, byte value, EmuTime::param time)
  1461. {
  1462. auto& vdp = OUTER(VDP, vdpRegDebug);
  1463. // Ignore writes to registers >= 8 on MSX1. An alternative is to only
  1464. // expose 8 registers. But changing that now breaks backwards
  1465. // compatibilty with some existing scripts. E.g. script that queries
  1466. // PAL vs NTSC in a VDP agnostic way.
  1467. if ((address >= 8) && vdp.isMSX1VDP()) return;
  1468. vdp.changeRegister(address, value, time);
  1469. }
  1470. // StatusRegDebug
  1471. VDP::StatusRegDebug::StatusRegDebug(VDP& vdp_)
  1472. : SimpleDebuggable(vdp_.getMotherBoard(),
  1473. vdp_.getName() + " status regs", "VDP status registers.", 0x10)
  1474. {
  1475. }
  1476. byte VDP::StatusRegDebug::read(unsigned address, EmuTime::param time)
  1477. {
  1478. auto& vdp = OUTER(VDP, vdpStatusRegDebug);
  1479. return vdp.peekStatusReg(address, time);
  1480. }
  1481. // PaletteDebug
  1482. VDP::PaletteDebug::PaletteDebug(VDP& vdp_)
  1483. : SimpleDebuggable(vdp_.getMotherBoard(),
  1484. vdp_.getName() + " palette", "V99x8 palette (RBG format)", 0x20)
  1485. {
  1486. }
  1487. byte VDP::PaletteDebug::read(unsigned address)
  1488. {
  1489. auto& vdp = OUTER(VDP, vdpPaletteDebug);
  1490. word grb = vdp.getPalette(address / 2);
  1491. return (address & 1) ? (grb >> 8) : (grb & 0xff);
  1492. }
  1493. void VDP::PaletteDebug::write(unsigned address, byte value, EmuTime::param time)
  1494. {
  1495. auto& vdp = OUTER(VDP, vdpPaletteDebug);
  1496. // Ignore writes on MSX1. An alternative could be to not expose the
  1497. // palette at all, but allowing read-only access could be useful for
  1498. // some scripts.
  1499. if (vdp.isMSX1VDP()) return;
  1500. int index = address / 2;
  1501. word grb = vdp.getPalette(index);
  1502. grb = (address & 1)
  1503. ? (grb & 0x0077) | ((value & 0x07) << 8)
  1504. : (grb & 0x0700) | (value & 0x77);
  1505. vdp.setPalette(index, grb, time);
  1506. }
  1507. // class VRAMPointerDebug
  1508. VDP::VRAMPointerDebug::VRAMPointerDebug(VDP& vdp_)
  1509. : SimpleDebuggable(vdp_.getMotherBoard(), vdp_.getName() == "VDP" ?
  1510. "VRAM pointer" : vdp_.getName() + " VRAM pointer",
  1511. "VDP VRAM pointer (14 lower bits)", 2)
  1512. {
  1513. }
  1514. byte VDP::VRAMPointerDebug::read(unsigned address)
  1515. {
  1516. auto& vdp = OUTER(VDP, vramPointerDebug);
  1517. if (address & 1) {
  1518. return vdp.vramPointer >> 8; // TODO add read/write mode?
  1519. } else {
  1520. return vdp.vramPointer & 0xFF;
  1521. }
  1522. }
  1523. void VDP::VRAMPointerDebug::write(unsigned address, byte value, EmuTime::param /*time*/)
  1524. {
  1525. auto& vdp = OUTER(VDP, vramPointerDebug);
  1526. int& ptr = vdp.vramPointer;
  1527. if (address & 1) {
  1528. ptr = (ptr & 0x00FF) | ((value & 0x3F) << 8);
  1529. } else {
  1530. ptr = (ptr & 0xFF00) | value;
  1531. }
  1532. }
  1533. // class Info
  1534. VDP::Info::Info(VDP& vdp_, const string& name_, string helpText_)
  1535. : InfoTopic(vdp_.getMotherBoard().getMachineInfoCommand(),
  1536. strCat(vdp_.getName(), '_', name_))
  1537. , vdp(vdp_)
  1538. , helpText(std::move(helpText_))
  1539. {
  1540. }
  1541. void VDP::Info::execute(span<const TclObject> /*tokens*/, TclObject& result) const
  1542. {
  1543. result = calc(vdp.getCurrentTime());
  1544. }
  1545. string VDP::Info::help(const vector<string>& /*tokens*/) const
  1546. {
  1547. return helpText;
  1548. }
  1549. // class FrameCountInfo
  1550. VDP::FrameCountInfo::FrameCountInfo(VDP& vdp_)
  1551. : Info(vdp_, "frame_count",
  1552. "The current frame number, starts counting at 0 "
  1553. "when MSX is powered up or reset.")
  1554. {
  1555. }
  1556. int VDP::FrameCountInfo::calc(const EmuTime& /*time*/) const
  1557. {
  1558. return vdp.frameCount;
  1559. }
  1560. // class CycleInFrameInfo
  1561. VDP::CycleInFrameInfo::CycleInFrameInfo(VDP& vdp_)
  1562. : Info(vdp_, "cycle_in_frame",
  1563. "The number of VDP cycles since the beginning of "
  1564. "the current frame. The VDP runs at 6 times the Z80 "
  1565. "clock frequency, so at approximately 21.5MHz.")
  1566. {
  1567. }
  1568. int VDP::CycleInFrameInfo::calc(const EmuTime& time) const
  1569. {
  1570. return vdp.getTicksThisFrame(time);
  1571. }
  1572. // class LineInFrameInfo
  1573. VDP::LineInFrameInfo::LineInFrameInfo(VDP& vdp_)
  1574. : Info(vdp_, "line_in_frame",
  1575. "The absolute line number since the beginning of "
  1576. "the current frame. Goes from 0 till 262 (NTSC) or "
  1577. "313 (PAL). Note that this number includes the "
  1578. "border lines, use 'msx_y_pos' to get MSX "
  1579. "coordinates.")
  1580. {
  1581. }
  1582. int VDP::LineInFrameInfo::calc(const EmuTime& time) const
  1583. {
  1584. return vdp.getTicksThisFrame(time) / VDP::TICKS_PER_LINE;
  1585. }
  1586. // class CycleInLineInfo
  1587. VDP::CycleInLineInfo::CycleInLineInfo(VDP& vdp_)
  1588. : Info(vdp_, "cycle_in_line",
  1589. "The number of VDP cycles since the beginning of "
  1590. "the current line. See also 'cycle_in_frame'."
  1591. "Note that this includes the cycles in the border, "
  1592. "use 'msx_x256_pos' or 'msx_x512_pos' to get MSX "
  1593. "coordinates.")
  1594. {
  1595. }
  1596. int VDP::CycleInLineInfo::calc(const EmuTime& time) const
  1597. {
  1598. return vdp.getTicksThisFrame(time) % VDP::TICKS_PER_LINE;
  1599. }
  1600. // class MsxYPosInfo
  1601. VDP::MsxYPosInfo::MsxYPosInfo(VDP& vdp_)
  1602. : Info(vdp_, "msx_y_pos",
  1603. "Similar to 'line_in_frame', but expressed in MSX "
  1604. "coordinates. So lines in the top border have "
  1605. "negative coordinates, lines in the bottom border "
  1606. "have coordinates bigger or equal to 192 or 212.")
  1607. {
  1608. }
  1609. int VDP::MsxYPosInfo::calc(const EmuTime& time) const
  1610. {
  1611. return (vdp.getTicksThisFrame(time) / VDP::TICKS_PER_LINE) -
  1612. vdp.getLineZero();
  1613. }
  1614. // class MsxX256PosInfo
  1615. VDP::MsxX256PosInfo::MsxX256PosInfo(VDP& vdp_)
  1616. : Info(vdp_, "msx_x256_pos",
  1617. "Similar to 'cycle_in_frame', but expressed in MSX "
  1618. "coordinates. So a position in the left border has "
  1619. "a negative coordinate and a position in the right "
  1620. "border has a coordinated bigger or equal to 256. "
  1621. "See also 'msx_x512_pos'.")
  1622. {
  1623. }
  1624. int VDP::MsxX256PosInfo::calc(const EmuTime& time) const
  1625. {
  1626. return ((vdp.getTicksThisFrame(time) % VDP::TICKS_PER_LINE) -
  1627. vdp.getLeftSprites()) / 4;
  1628. }
  1629. // class MsxX512PosInfo
  1630. VDP::MsxX512PosInfo::MsxX512PosInfo(VDP& vdp_)
  1631. : Info(vdp_, "msx_x512_pos",
  1632. "Similar to 'cycle_in_frame', but expressed in "
  1633. "'narrow' (screen 7) MSX coordinates. So a position "
  1634. "in the left border has a negative coordinate and "
  1635. "a position in the right border has a coordinated "
  1636. "bigger or equal to 512. See also 'msx_x256_pos'.")
  1637. {
  1638. }
  1639. int VDP::MsxX512PosInfo::calc(const EmuTime& time) const
  1640. {
  1641. return ((vdp.getTicksThisFrame(time) % VDP::TICKS_PER_LINE) -
  1642. vdp.getLeftSprites()) / 2;
  1643. }
  1644. // version 1: initial version
  1645. // version 2: added frameCount
  1646. // version 3: removed verticalAdjust
  1647. // version 4: removed lineZero
  1648. // version 5: replace readAhead->cpuVramData, added cpuVramReqIsRead
  1649. // version 6: added cpuVramReqAddr to solve too_fast_vram_access issue
  1650. // version 7: removed cpuVramReqAddr again, fixed issue in a different way
  1651. // version 8: removed 'userData' from Schedulable
  1652. template<typename Archive>
  1653. void VDP::serialize(Archive& ar, unsigned serVersion)
  1654. {
  1655. ar.template serializeBase<MSXDevice>(*this);
  1656. if (ar.versionAtLeast(serVersion, 8)) {
  1657. ar.serialize("syncVSync", syncVSync,
  1658. "syncDisplayStart", syncDisplayStart,
  1659. "syncVScan", syncVScan,
  1660. "syncHScan", syncHScan,
  1661. "syncHorAdjust", syncHorAdjust,
  1662. "syncSetMode", syncSetMode,
  1663. "syncSetBlank", syncSetBlank,
  1664. "syncCpuVramAccess", syncCpuVramAccess);
  1665. // no need for syncCmdDone (only used for probe)
  1666. } else {
  1667. Schedulable::restoreOld(ar,
  1668. {&syncVSync, &syncDisplayStart, &syncVScan,
  1669. &syncHScan, &syncHorAdjust, &syncSetMode,
  1670. &syncSetBlank, &syncCpuVramAccess});
  1671. }
  1672. // not serialized
  1673. // std::unique_ptr<Renderer> renderer;
  1674. // VdpVersion version;
  1675. // int controlRegMask;
  1676. // byte controlValueMasks[32];
  1677. // bool warningPrinted;
  1678. ar.serialize("irqVertical", irqVertical,
  1679. "irqHorizontal", irqHorizontal,
  1680. "frameStartTime", frameStartTime,
  1681. "displayStartSyncTime", displayStartSyncTime,
  1682. "vScanSyncTime", vScanSyncTime,
  1683. "hScanSyncTime", hScanSyncTime,
  1684. "displayStart", displayStart,
  1685. "horizontalScanOffset", horizontalScanOffset,
  1686. "horizontalAdjust", horizontalAdjust,
  1687. "registers", controlRegs,
  1688. "blinkCount", blinkCount,
  1689. "vramPointer", vramPointer,
  1690. "palette", palette,
  1691. "isDisplayArea", isDisplayArea,
  1692. "palTiming", palTiming,
  1693. "interlaced", interlaced,
  1694. "statusReg0", statusReg0,
  1695. "statusReg1", statusReg1,
  1696. "statusReg2", statusReg2,
  1697. "blinkState", blinkState,
  1698. "dataLatch", dataLatch,
  1699. "registerDataStored", registerDataStored,
  1700. "paletteDataStored", paletteDataStored);
  1701. if (ar.versionAtLeast(serVersion, 5)) {
  1702. ar.serialize("cpuVramData", cpuVramData,
  1703. "cpuVramReqIsRead", cpuVramReqIsRead);
  1704. } else {
  1705. ar.serialize("readAhead", cpuVramData);
  1706. }
  1707. ar.serialize("cpuExtendedVram", cpuExtendedVram,
  1708. "displayEnabled", displayEnabled);
  1709. byte mode = displayMode.getByte();
  1710. ar.serialize("displayMode", mode);
  1711. displayMode.setByte(mode);
  1712. ar.serialize("cmdEngine", *cmdEngine,
  1713. "spriteChecker", *spriteChecker, // must come after displayMode
  1714. "vram", *vram); // must come after controlRegs and after spriteChecker
  1715. if (ar.isLoader()) {
  1716. pendingCpuAccess = syncCpuVramAccess.pendingSyncPoint();
  1717. update(tooFastAccess);
  1718. }
  1719. if (ar.versionAtLeast(serVersion, 2)) {
  1720. ar.serialize("frameCount", frameCount);
  1721. } else {
  1722. assert(ar.isLoader());
  1723. // We could estimate the frameCount (assume framerate was
  1724. // constant the whole time). But I think it's better to have
  1725. // an obviously wrong value than an almost correct value.
  1726. frameCount = 0;
  1727. }
  1728. // externalVideo does not need serializing. It is set on load by the
  1729. // external video source (e.g. PioneerLDControl).
  1730. //
  1731. // TODO should superimposing be serialized? It cannot be recalculated
  1732. // from register values (it depends on the register values at the start
  1733. // of this frame). But it will be correct at the start of the next
  1734. // frame. Probably good enough.
  1735. if (ar.isLoader()) {
  1736. renderer->reInit();
  1737. }
  1738. }
  1739. INSTANTIATE_SERIALIZE_METHODS(VDP);
  1740. REGISTER_MSXDEVICE(VDP, "VDP");
  1741. } // namespace openmsx