VDPVRAM.cc 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. #include "VDPVRAM.hh"
  2. #include "SpriteChecker.hh"
  3. #include "Renderer.hh"
  4. #include "Math.hh"
  5. #include "outer.hh"
  6. #include "serialize.hh"
  7. #include <algorithm>
  8. #include <cstring>
  9. namespace openmsx {
  10. // class VRAMWindow
  11. VRAMWindow::VRAMWindow(Ram& vram)
  12. : data(&vram[0])
  13. {
  14. observer = &dummyObserver;
  15. baseAddr = -1; // disable window
  16. origBaseMask = 0;
  17. effectiveBaseMask = 0;
  18. indexMask = 0; // these 4 don't matter but it makes valgrind happy
  19. combiMask = 0;
  20. // sizeMask will be initialized shortly by the VDPVRAM class
  21. }
  22. // class LogicalVRAMDebuggable
  23. /** This debuggable is always 128kB in size, even if the actual amount of VRAM
  24. * is different:
  25. * - If there is less actual VRAM (e.g. 64kB), the upper regions of this
  26. * debuggable act in the same way as if the CPU would read/write those
  27. * addresses (mirroring or return fixed value / ignore write).
  28. * - If there is more VRAM (128kB + 64kB extended VRAM), the extended VRAM
  29. * part is not accessible via this debuggable (it is via the 'physical VRAM'
  30. * debuggable). We might change this in the future, but the interaction
  31. * between interleaving and extended VRAM can be tricky, for example the
  32. * size of this debuggable would have to be 256kB to be able to access the
  33. * complete extended VRAM in interleaved mode.
  34. */
  35. VDPVRAM::LogicalVRAMDebuggable::LogicalVRAMDebuggable(VDP& vdp_)
  36. : SimpleDebuggable(vdp_.getMotherBoard(), vdp_.getName() == "VDP" ? "VRAM" :
  37. vdp_.getName() + " VRAM",
  38. "CPU view on video RAM given the current display mode.",
  39. 128 * 1024) // always 128kB
  40. {
  41. }
  42. unsigned VDPVRAM::LogicalVRAMDebuggable::transform(unsigned address)
  43. {
  44. auto& vram = OUTER(VDPVRAM, logicalVRAMDebug);
  45. return vram.vdp.getDisplayMode().isPlanar()
  46. ? ((address << 16) | (address >> 1)) & 0x1FFFF
  47. : address;
  48. }
  49. byte VDPVRAM::LogicalVRAMDebuggable::read(unsigned address, EmuTime::param time)
  50. {
  51. auto& vram = OUTER(VDPVRAM, logicalVRAMDebug);
  52. return vram.cpuRead(transform(address), time);
  53. }
  54. void VDPVRAM::LogicalVRAMDebuggable::write(
  55. unsigned address, byte value, EmuTime::param time)
  56. {
  57. auto& vram = OUTER(VDPVRAM, logicalVRAMDebug);
  58. vram.cpuWrite(transform(address), value, time);
  59. }
  60. // class PhysicalVRAMDebuggable
  61. VDPVRAM::PhysicalVRAMDebuggable::PhysicalVRAMDebuggable(
  62. VDP& vdp_, unsigned actualSize_)
  63. : SimpleDebuggable(vdp_.getMotherBoard(), vdp_.getName() == "VDP" ?
  64. "physical VRAM" : strCat("physical ", vdp_.getName(), " VRAM"),
  65. "VDP-screen-mode-independent view on the video RAM.",
  66. actualSize_)
  67. {
  68. }
  69. byte VDPVRAM::PhysicalVRAMDebuggable::read(unsigned address, EmuTime::param time)
  70. {
  71. auto& vram = OUTER(VDPVRAM, physicalVRAMDebug);
  72. return vram.cpuRead(address, time);
  73. }
  74. void VDPVRAM::PhysicalVRAMDebuggable::write(
  75. unsigned address, byte value, EmuTime::param time)
  76. {
  77. auto& vram = OUTER(VDPVRAM, physicalVRAMDebug);
  78. vram.cpuWrite(address, value, time);
  79. }
  80. // class VDPVRAM
  81. static unsigned bufferSize(unsigned size)
  82. {
  83. // Always allocate at least a buffer of 128kB, this makes the VR0/VR1
  84. // swapping a lot easier. Actually only in case there is also extended
  85. // VRAM, we need to allocate more.
  86. // TODO possible optimization: for TMS99x8 we could allocate 16kB, it
  87. // has no VR modes.
  88. return std::max(0x20000u, size);
  89. }
  90. VDPVRAM::VDPVRAM(VDP& vdp_, unsigned size, EmuTime::param time)
  91. : vdp(vdp_)
  92. , data(*vdp_.getDeviceConfig2().getXML(), bufferSize(size))
  93. , logicalVRAMDebug (vdp)
  94. , physicalVRAMDebug(vdp, size)
  95. #ifdef DEBUG
  96. , vramTime(EmuTime::zero())
  97. #endif
  98. , actualSize(size)
  99. , cmdReadWindow(data)
  100. , cmdWriteWindow(data)
  101. , nameTable(data)
  102. , colorTable(data)
  103. , patternTable(data)
  104. , bitmapVisibleWindow(data)
  105. , bitmapCacheWindow(data)
  106. , spriteAttribTable(data)
  107. , spritePatternTable(data)
  108. {
  109. (void)time;
  110. vrMode = vdp.getVRMode();
  111. setSizeMask(time);
  112. // Whole VRAM is cachable.
  113. // Because this window has no observer, any EmuTime can be passed.
  114. // TODO: Move this to cache registration.
  115. bitmapCacheWindow.setMask(0x1FFFF, ~0u << 17, EmuTime::zero());
  116. }
  117. void VDPVRAM::clear()
  118. {
  119. // Initialise VRAM data array.
  120. data.clear(0); // fill with zeros (unless initialContent is specified)
  121. if (data.getSize() != actualSize) {
  122. assert(data.getSize() > actualSize);
  123. // Read from unconnected VRAM returns random data.
  124. // TODO reading same location multiple times does not always
  125. // give the same value.
  126. memset(&data[actualSize], 0xFF, data.getSize() - actualSize);
  127. }
  128. }
  129. void VDPVRAM::updateDisplayMode(DisplayMode mode, bool cmdBit, EmuTime::param time)
  130. {
  131. assert(vdp.isInsideFrame(time));
  132. cmdEngine->updateDisplayMode(mode, cmdBit, time);
  133. renderer->updateDisplayMode(mode, time);
  134. spriteChecker->updateDisplayMode(mode, time);
  135. }
  136. void VDPVRAM::updateDisplayEnabled(bool enabled, EmuTime::param time)
  137. {
  138. assert(vdp.isInsideFrame(time));
  139. cmdEngine->sync(time);
  140. renderer->updateDisplayEnabled(enabled, time);
  141. spriteChecker->updateDisplayEnabled(enabled, time);
  142. }
  143. void VDPVRAM::updateSpritesEnabled(bool enabled, EmuTime::param time)
  144. {
  145. assert(vdp.isInsideFrame(time));
  146. cmdEngine->sync(time);
  147. renderer->updateSpritesEnabled(enabled, time);
  148. spriteChecker->updateSpritesEnabled(enabled, time);
  149. }
  150. void VDPVRAM::setSizeMask(EmuTime::param time)
  151. {
  152. sizeMask = (
  153. vrMode
  154. // VR = 1: 64K address space, CAS0/1 is determined by A16
  155. ? (Math::ceil2(actualSize) - 1) | (1u << 16)
  156. // VR = 0: 16K address space, CAS0/1 is determined by A14
  157. : (std::min(Math::ceil2(actualSize), 16384u) - 1) | (1u << 14)
  158. ) | (1u << 17); // CASX (expansion RAM) is always relevant
  159. cmdReadWindow.setSizeMask(sizeMask, time);
  160. cmdWriteWindow.setSizeMask(sizeMask, time);
  161. nameTable.setSizeMask(sizeMask, time);
  162. colorTable.setSizeMask(sizeMask, time);
  163. patternTable.setSizeMask(sizeMask, time);
  164. bitmapVisibleWindow.setSizeMask(sizeMask, time);
  165. bitmapCacheWindow.setSizeMask(sizeMask, time);
  166. spriteAttribTable.setSizeMask(sizeMask, time);
  167. spritePatternTable.setSizeMask(sizeMask, time);
  168. }
  169. static inline unsigned swapAddr(unsigned x)
  170. {
  171. // translate VR0 address to corresponding VR1 address
  172. // note: output bit 0 is always 1
  173. // input bit 6 is taken twice
  174. // only 15 bits of the input are used
  175. return 1 | ((x & 0x007F) << 1) | ((x & 0x7FC0) << 2);
  176. }
  177. void VDPVRAM::updateVRMode(bool newVRmode, EmuTime::param time)
  178. {
  179. if (vrMode == newVRmode) {
  180. // The swapping below may only happen when the mode is
  181. // actually changed. So this test is not only an optimization.
  182. return;
  183. }
  184. vrMode = newVRmode;
  185. setSizeMask(time);
  186. if (vrMode) {
  187. // switch from VR=0 to VR=1
  188. for (int i = 0x7FFF; i >=0; --i) {
  189. std::swap(data[i], data[swapAddr(i)]);
  190. }
  191. } else {
  192. // switch from VR=1 to VR=0
  193. for (int i = 0; i < 0x8000; ++i) {
  194. std::swap(data[i], data[swapAddr(i)]);
  195. }
  196. }
  197. }
  198. void VDPVRAM::setRenderer(Renderer* newRenderer, EmuTime::param time)
  199. {
  200. renderer = newRenderer;
  201. bitmapVisibleWindow.resetObserver();
  202. // Set up bitmapVisibleWindow to full VRAM.
  203. // TODO: Have VDP/Renderer set the actual range.
  204. bitmapVisibleWindow.setMask(0x1FFFF, ~0u << 17, time);
  205. // TODO: If it is a good idea to send an initial sync,
  206. // then call setObserver before setMask.
  207. bitmapVisibleWindow.setObserver(renderer);
  208. }
  209. void VDPVRAM::change4k8kMapping(bool mapping8k)
  210. {
  211. /* Sources:
  212. * - http://www.msx.org/forumtopicl8624.html
  213. * - Charles MacDonald's sc3000h.txt (http://cgfm2.emuviews.com)
  214. *
  215. * Bit 7 of register #1 affects how the VDP generates addresses when
  216. * accessing VRAM. Here's a table illustrating the differences:
  217. *
  218. * VDP address VRAM address
  219. * (Column) 4K mode 8/16K mode
  220. * AD0 VA0 VA0
  221. * AD1 VA1 VA1
  222. * AD2 VA2 VA2
  223. * AD3 VA3 VA3
  224. * AD4 VA4 VA4
  225. * AD5 VA5 VA5
  226. * AD6 VA12 VA6
  227. * AD7 Not used Not used
  228. * (Row)
  229. * AD0 VA6 VA7
  230. * AD1 VA7 VA8
  231. * AD2 VA8 VA9
  232. * AD3 VA9 VA10
  233. * AD4 VA10 VA11
  234. * AD5 VA11 VA12
  235. * AD6 VA13 VA13
  236. * AD7 Not used Not used
  237. *
  238. * ADx - TMS9928 8-bit VRAM address/data bus
  239. * VAx - 14-bit VRAM address that the VDP wants to access
  240. *
  241. * How the address is formed has to do with the physical layout of
  242. * memory cells in a DRAM chip. A 4Kx1 chip has 64x64 cells, a 8Kx1 or
  243. * 16Kx1 chip has 128x64 or 128x128 cells. Because the DRAM address bus
  244. * is multiplexed, this means 6 bits are used for 4K DRAMs and 7 bits
  245. * are used for 8K or 16K DRAMs.
  246. *
  247. * In 4K mode the 6 bits of the row and column are output first, with
  248. * the remaining high-order bits mapped to AD6. In 8/16K mode the 7
  249. * bits of the row and column are output normally. This also means that
  250. * even in 4K mode, all 16K of VRAM can be accessed. The only
  251. * difference is in what addresses are used to store data.
  252. */
  253. byte tmp[0x4000];
  254. if (mapping8k) {
  255. // from 8k/16k to 4k mapping
  256. for (unsigned addr8 = 0; addr8 < 0x4000; addr8 += 64) {
  257. unsigned addr4 = (addr8 & 0x203F) |
  258. ((addr8 & 0x1000) >> 6) |
  259. ((addr8 & 0x0FC0) << 1);
  260. const byte* src = &data[addr8];
  261. byte* dst = &tmp[addr4];
  262. memcpy(dst, src, 64);
  263. }
  264. } else {
  265. // from 4k to 8k/16k mapping
  266. for (unsigned addr4 = 0; addr4 < 0x4000; addr4 += 64) {
  267. unsigned addr8 = (addr4 & 0x203F) |
  268. ((addr4 & 0x0040) << 6) |
  269. ((addr4 & 0x1F80) >> 1);
  270. const byte* src = &data[addr4];
  271. byte* dst = &tmp[addr8];
  272. memcpy(dst, src, 64);
  273. }
  274. }
  275. memcpy(&data[0], tmp, sizeof(tmp));
  276. }
  277. template<typename Archive>
  278. void VRAMWindow::serialize(Archive& ar, unsigned /*version*/)
  279. {
  280. ar.serialize("baseAddr", baseAddr,
  281. "baseMask", origBaseMask,
  282. "indexMask", indexMask);
  283. if (ar.isLoader()) {
  284. effectiveBaseMask = origBaseMask & sizeMask;
  285. combiMask = ~effectiveBaseMask | indexMask;
  286. // TODO ? observer->updateWindow(isEnabled(), time);
  287. }
  288. }
  289. template<typename Archive>
  290. void VDPVRAM::serialize(Archive& ar, unsigned /*version*/)
  291. {
  292. if (ar.isLoader()) {
  293. vrMode = vdp.getVRMode();
  294. setSizeMask(static_cast<MSXDevice&>(vdp).getCurrentTime());
  295. }
  296. ar.serialize_blob("data", &data[0], actualSize);
  297. ar.serialize("cmdReadWindow", cmdReadWindow,
  298. "cmdWriteWindow", cmdWriteWindow,
  299. "nameTable", nameTable,
  300. // TODO: Find a way of changing the line below to "colorTable",
  301. // without breaking backwards compatibility
  302. "colourTable", colorTable,
  303. "patternTable", patternTable,
  304. "bitmapVisibleWindow", bitmapVisibleWindow,
  305. "bitmapCacheWindow", bitmapCacheWindow,
  306. "spriteAttribTable", spriteAttribTable,
  307. "spritePatternTable", spritePatternTable);
  308. }
  309. INSTANTIATE_SERIALIZE_METHODS(VDPVRAM);
  310. } // namespace openmsx