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