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 "outer.hh"
6 #include "serialize.hh"
7 #include <algorithm>
8 #include <cstring>
9 
10 namespace openmsx {
11 
12 // class VRAMWindow
13 
14 DummyVRAMOBserver VRAMWindow::dummyObserver;
15 
17  : data(&vram[0])
18 {
19  observer = &dummyObserver;
20  baseAddr = -1; // disable window
21  origBaseMask = 0;
22  effectiveBaseMask = 0;
23  indexMask = 0; // these 4 don't matter but it makes valgrind happy
24  combiMask = 0;
25  // sizeMask will be initialized shortly by the VDPVRAM class
26 }
27 
28 
29 // class LogicalVRAMDebuggable
30 
43 VDPVRAM::LogicalVRAMDebuggable::LogicalVRAMDebuggable(VDP& vdp_)
44  : SimpleDebuggable(vdp_.getMotherBoard(), vdp_.getName() == "VDP" ? "VRAM" :
45  vdp_.getName() + " VRAM",
46  "CPU view on video RAM given the current display mode.",
47  128 * 1024) // always 128kB
48 {
49 }
50 
51 unsigned VDPVRAM::LogicalVRAMDebuggable::transform(unsigned address)
52 {
53  auto& vram = OUTER(VDPVRAM, logicalVRAMDebug);
54  return vram.vdp.getDisplayMode().isPlanar()
55  ? ((address << 16) | (address >> 1)) & 0x1FFFF
56  : address;
57 }
58 
59 byte VDPVRAM::LogicalVRAMDebuggable::read(unsigned address, EmuTime::param time)
60 {
61  auto& vram = OUTER(VDPVRAM, logicalVRAMDebug);
62  return vram.cpuRead(transform(address), time);
63 }
64 
65 void VDPVRAM::LogicalVRAMDebuggable::write(
66  unsigned address, byte value, EmuTime::param time)
67 {
68  auto& vram = OUTER(VDPVRAM, logicalVRAMDebug);
69  vram.cpuWrite(transform(address), value, time);
70 }
71 
72 
73 // class PhysicalVRAMDebuggable
74 
75 VDPVRAM::PhysicalVRAMDebuggable::PhysicalVRAMDebuggable(
76  VDP& vdp_, unsigned actualSize_)
77  : SimpleDebuggable(vdp_.getMotherBoard(), vdp_.getName() == "VDP" ?
78  "physical VRAM" : strCat("physical ", vdp_.getName(), " VRAM"),
79  "VDP-screen-mode-independent view on the video RAM.",
80  actualSize_)
81 {
82 }
83 
84 byte VDPVRAM::PhysicalVRAMDebuggable::read(unsigned address, EmuTime::param time)
85 {
86  auto& vram = OUTER(VDPVRAM, physicalVRAMDebug);
87  return vram.cpuRead(address, time);
88 }
89 
90 void VDPVRAM::PhysicalVRAMDebuggable::write(
91  unsigned address, byte value, EmuTime::param time)
92 {
93  auto& vram = OUTER(VDPVRAM, physicalVRAMDebug);
94  vram.cpuWrite(address, value, time);
95 }
96 
97 
98 // class VDPVRAM
99 
100 static unsigned bufferSize(unsigned size)
101 {
102  // Always allocate at least a buffer of 128kB, this makes the VR0/VR1
103  // swapping a lot easier. Actually only in case there is also extended
104  // VRAM, we need to allocate more.
105  // TODO possible optimization: for TMS99x8 we could allocate 16kB, it
106  // has no VR modes.
107  return std::max(0x20000u, size);
108 }
109 
110 VDPVRAM::VDPVRAM(VDP& vdp_, unsigned size, EmuTime::param time)
111  : vdp(vdp_)
112  , data(*vdp_.getDeviceConfig2().getXML(), bufferSize(size))
113  , logicalVRAMDebug (vdp)
114  , physicalVRAMDebug(vdp, size)
115  #ifdef DEBUG
116  , vramTime(EmuTime::zero)
117  #endif
118  , actualSize(size)
119  , cmdReadWindow(data)
120  , cmdWriteWindow(data)
121  , nameTable(data)
122  , colorTable(data)
123  , patternTable(data)
124  , bitmapVisibleWindow(data)
125  , bitmapCacheWindow(data)
126  , spriteAttribTable(data)
127  , spritePatternTable(data)
128 {
129  (void)time;
130 
131  vrMode = vdp.getVRMode();
132  setSizeMask(time);
133 
134  // Whole VRAM is cachable.
135  // Because this window has no observer, any EmuTime can be passed.
136  // TODO: Move this to cache registration.
137  bitmapCacheWindow.setMask(0x1FFFF, ~0u << 17, EmuTime::zero);
138 }
139 
141 {
142  // Initialise VRAM data array.
143  data.clear(0); // fill with zeros (unless initialContent is specified)
144  if (data.getSize() != actualSize) {
145  assert(data.getSize() > actualSize);
146  // Read from unconnected VRAM returns random data.
147  // TODO reading same location multiple times does not always
148  // give the same value.
149  memset(&data[actualSize], 0xFF, data.getSize() - actualSize);
150  }
151 }
152 
153 void VDPVRAM::updateDisplayMode(DisplayMode mode, bool cmdBit, EmuTime::param time)
154 {
155  assert(vdp.isInsideFrame(time));
156  cmdEngine->updateDisplayMode(mode, cmdBit, time);
157  renderer->updateDisplayMode(mode, time);
158  spriteChecker->updateDisplayMode(mode, time);
159 }
160 
161 void VDPVRAM::updateDisplayEnabled(bool enabled, EmuTime::param time)
162 {
163  assert(vdp.isInsideFrame(time));
164  cmdEngine->sync(time);
165  renderer->updateDisplayEnabled(enabled, time);
166  spriteChecker->updateDisplayEnabled(enabled, time);
167 }
168 
169 void VDPVRAM::updateSpritesEnabled(bool enabled, EmuTime::param time)
170 {
171  assert(vdp.isInsideFrame(time));
172  cmdEngine->sync(time);
173  renderer->updateSpritesEnabled(enabled, time);
174  spriteChecker->updateSpritesEnabled(enabled, time);
175 }
176 
177 void VDPVRAM::setSizeMask(EmuTime::param time)
178 {
179  sizeMask = (
180  vrMode
181  // VR = 1: 64K address space, CAS0/1 is determined by A16
182  ? (Math::ceil2(actualSize) - 1) | (1u << 16)
183  // VR = 0: 16K address space, CAS0/1 is determined by A14
184  : (std::min(Math::ceil2(actualSize), 16384u) - 1) | (1u << 14)
185  ) | (1u << 17); // CASX (expansion RAM) is always relevant
186 
187  cmdReadWindow.setSizeMask(sizeMask, time);
188  cmdWriteWindow.setSizeMask(sizeMask, time);
189  nameTable.setSizeMask(sizeMask, time);
190  colorTable.setSizeMask(sizeMask, time);
191  patternTable.setSizeMask(sizeMask, time);
192  bitmapVisibleWindow.setSizeMask(sizeMask, time);
193  bitmapCacheWindow.setSizeMask(sizeMask, time);
194  spriteAttribTable.setSizeMask(sizeMask, time);
195  spritePatternTable.setSizeMask(sizeMask, time);
196 }
197 static inline unsigned swapAddr(unsigned x)
198 {
199  // translate VR0 address to corresponding VR1 address
200  // note: output bit 0 is always 1
201  // input bit 6 is taken twice
202  // only 15 bits of the input are used
203  return 1 | ((x & 0x007F) << 1) | ((x & 0x7FC0) << 2);
204 }
205 void VDPVRAM::updateVRMode(bool newVRmode, EmuTime::param time)
206 {
207  if (vrMode == newVRmode) {
208  // The swapping below may only happen when the mode is
209  // actually changed. So this test is not only an optimization.
210  return;
211  }
212  vrMode = newVRmode;
213  setSizeMask(time);
214 
215  if (vrMode) {
216  // switch from VR=0 to VR=1
217  for (int i = 0x7FFF; i >=0; --i) {
218  std::swap(data[i], data[swapAddr(i)]);
219  }
220  } else {
221  // switch from VR=1 to VR=0
222  for (int i = 0; i < 0x8000; ++i) {
223  std::swap(data[i], data[swapAddr(i)]);
224  }
225  }
226 }
227 
228 void VDPVRAM::setRenderer(Renderer* newRenderer, EmuTime::param time)
229 {
230  renderer = newRenderer;
231 
233  // Set up bitmapVisibleWindow to full VRAM.
234  // TODO: Have VDP/Renderer set the actual range.
235  bitmapVisibleWindow.setMask(0x1FFFF, ~0u << 17, time);
236  // TODO: If it is a good idea to send an initial sync,
237  // then call setObserver before setMask.
239 }
240 
241 void VDPVRAM::change4k8kMapping(bool mapping8k)
242 {
243  /* Sources:
244  * - http://www.msx.org/forumtopicl8624.html
245  * - Charles MacDonald's sc3000h.txt (http://cgfm2.emuviews.com)
246  *
247  * Bit 7 of register #1 affects how the VDP generates addresses when
248  * accessing VRAM. Here's a table illustrating the differences:
249  *
250  * VDP address VRAM address
251  * (Column) 4K mode 8/16K mode
252  * AD0 VA0 VA0
253  * AD1 VA1 VA1
254  * AD2 VA2 VA2
255  * AD3 VA3 VA3
256  * AD4 VA4 VA4
257  * AD5 VA5 VA5
258  * AD6 VA12 VA6
259  * AD7 Not used Not used
260  * (Row)
261  * AD0 VA6 VA7
262  * AD1 VA7 VA8
263  * AD2 VA8 VA9
264  * AD3 VA9 VA10
265  * AD4 VA10 VA11
266  * AD5 VA11 VA12
267  * AD6 VA13 VA13
268  * AD7 Not used Not used
269  *
270  * ADx - TMS9928 8-bit VRAM address/data bus
271  * VAx - 14-bit VRAM address that the VDP wants to access
272  *
273  * How the address is formed has to do with the physical layout of
274  * memory cells in a DRAM chip. A 4Kx1 chip has 64x64 cells, a 8Kx1 or
275  * 16Kx1 chip has 128x64 or 128x128 cells. Because the DRAM address bus
276  * is multiplexed, this means 6 bits are used for 4K DRAMs and 7 bits
277  * are used for 8K or 16K DRAMs.
278  *
279  * In 4K mode the 6 bits of the row and column are output first, with
280  * the remaining high-order bits mapped to AD6. In 8/16K mode the 7
281  * bits of the row and column are output normally. This also means that
282  * even in 4K mode, all 16K of VRAM can be accessed. The only
283  * difference is in what addresses are used to store data.
284  */
285  byte tmp[0x4000];
286  if (mapping8k) {
287  // from 8k/16k to 4k mapping
288  for (unsigned addr8 = 0; addr8 < 0x4000; addr8 += 64) {
289  unsigned addr4 = (addr8 & 0x203F) |
290  ((addr8 & 0x1000) >> 6) |
291  ((addr8 & 0x0FC0) << 1);
292  const byte* src = &data[addr8];
293  byte* dst = &tmp[addr4];
294  memcpy(dst, src, 64);
295  }
296  } else {
297  // from 4k to 8k/16k mapping
298  for (unsigned addr4 = 0; addr4 < 0x4000; addr4 += 64) {
299  unsigned addr8 = (addr4 & 0x203F) |
300  ((addr4 & 0x0040) << 6) |
301  ((addr4 & 0x1F80) >> 1);
302  const byte* src = &data[addr4];
303  byte* dst = &tmp[addr8];
304  memcpy(dst, src, 64);
305  }
306  }
307  memcpy(&data[0], tmp, sizeof(tmp));
308 }
309 
310 
311 template<typename Archive>
312 void VRAMWindow::serialize(Archive& ar, unsigned /*version*/)
313 {
314  ar.serialize("baseAddr", baseAddr,
315  "baseMask", origBaseMask,
316  "indexMask", indexMask);
317  if (ar.isLoader()) {
318  effectiveBaseMask = origBaseMask & sizeMask;
319  combiMask = ~effectiveBaseMask | indexMask;
320  // TODO ? observer->updateWindow(isEnabled(), time);
321  }
322 }
323 
324 template<typename Archive>
325 void VDPVRAM::serialize(Archive& ar, unsigned /*version*/)
326 {
327  if (ar.isLoader()) {
328  vrMode = vdp.getVRMode();
329  setSizeMask(static_cast<MSXDevice&>(vdp).getCurrentTime());
330  }
331 
332  ar.serialize_blob("data", &data[0], actualSize);
333  ar.serialize("cmdReadWindow", cmdReadWindow,
334  "cmdWriteWindow", cmdWriteWindow,
335  "nameTable", nameTable,
336  // TODO: Find a way of changing the line below to "colorTable",
337  // without breaking backwards compatibility
338  "colourTable", colorTable,
339  "patternTable", patternTable,
340  "bitmapVisibleWindow", bitmapVisibleWindow,
341  "bitmapCacheWindow", bitmapCacheWindow,
342  "spriteAttribTable", spriteAttribTable,
343  "spritePatternTable", spritePatternTable);
344 }
346 
347 } // namespace openmsx
const DeviceConfig & getDeviceConfig2() const
Definition: MSXDevice.hh:221
VDPVRAM(const VDPVRAM &)=delete
void swap(optional< T > &x, optional< T > &y) noexcept(noexcept(x.swap(y)))
Definition: optional.hh:816
void updateSpritesEnabled(bool enabled, EmuTime::param time)
Informs the sprite checker of sprite enable changes.
vecN< N, T > min(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:269
virtual void updateDisplayMode(DisplayMode mode, EmuTime::param time)=0
Informs the renderer of a VDP display mode change.
void updateDisplayMode(DisplayMode mode, EmuTime::param time)
Informs the sprite checker of a VDP display mode change.
void updateDisplayEnabled(bool enabled, EmuTime::param time)
Used by the VDP to signal display enabled changes.
Definition: VDPVRAM.cc:161
uint8_t byte
8 bit unsigned integer
Definition: openmsx.hh:26
void updateDisplayEnabled(bool enabled, EmuTime::param time)
Informs the sprite checker of a VDP display enabled change.
void serialize(Archive &ar, unsigned version)
Definition: VDPVRAM.cc:325
VRAMWindow patternTable
Definition: VDPVRAM.hh:668
virtual void updateSpritesEnabled(bool enabled, EmuTime::param time)=0
Informs the renderer of a VDP sprites enabled change.
VRAMWindow bitmapVisibleWindow
Definition: VDPVRAM.hh:669
virtual std::string getName() const
Returns a human-readable name for this device.
Definition: MSXDevice.cc:374
Represents a VDP display mode.
Definition: DisplayMode.hh:14
void updateDisplayMode(DisplayMode mode, bool cmdBit, EmuTime::param time)
Used by the VDP to signal display mode changes.
Definition: VDPVRAM.cc:153
void setRenderer(Renderer *renderer, EmuTime::param time)
Definition: VDPVRAM.cc:228
VRAMWindow nameTable
Definition: VDPVRAM.hh:666
vecN< N, T > max(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:287
VRAMWindow spriteAttribTable
Definition: VDPVRAM.hh:671
void setObserver(VRAMObserver *newObserver)
Register an observer on this VRAM window.
Definition: VDPVRAM.hh:279
Ram
Definition: Ram.cc:124
void updateSpritesEnabled(bool enabled, EmuTime::param time)
Used by the VDP to signal sprites enabled changes.
Definition: VDPVRAM.cc:169
void clear(byte c=0xff)
Definition: Ram.cc:51
constexpr auto data(C &c) -> decltype(c.data())
Definition: span.hh:69
void setSizeMask(unsigned newSizeMask, EmuTime::param time)
Inform VRAMWindow of changed sizeMask.
Definition: VDPVRAM.hh:315
VRAMWindow colorTable
Definition: VDPVRAM.hh:667
void sync(EmuTime::param time)
Synchronises the command engine with the VDP.
Definition: VDPCmdEngine.hh:36
Abstract base class for Renderers.
Definition: Renderer.hh:22
void serialize(Archive &ar, unsigned version)
Definition: VDPVRAM.cc:312
bool isInsideFrame(EmuTime::param time) const
Is the given timestamp inside the current frame? Mainly useful for debugging, because relevant timest...
Definition: VDP.hh:420
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
MSXMotherBoard & getMotherBoard() const
Get the mother board this device belongs to.
Definition: MSXDevice.cc:77
VRAMWindow spritePatternTable
Definition: VDPVRAM.hh:672
void clear()
Initialize VRAM content to power-up state.
Definition: VDPVRAM.cc:140
virtual void updateDisplayEnabled(bool enabled, EmuTime::param time)=0
Informs the renderer of a VDP display enabled change.
Unified implementation of MSX Video Display Processors (VDPs).
Definition: VDP.hh:61
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:1006
void updateDisplayMode(DisplayMode mode, bool cmdBit, EmuTime::param time)
Informs the command engine of a VDP display mode change.
VRAMWindow cmdWriteWindow
Definition: VDPVRAM.hh:665
VRAMWindow cmdReadWindow
Definition: VDPVRAM.hh:664
Manages VRAM contents and synchronises the various users of the VRAM.
Definition: VDPVRAM.hh:384
VRAMWindow(const VRAMWindow &)=delete
auto transform(InputRange &&range, OutputIter out, UnaryOperation op)
Definition: ranges.hh:161
std::string strCat(Ts &&...ts)
Definition: strCat.hh:577
constexpr T ceil2(T x) noexcept
Returns the smallest number that is both >=a and a power of two.
Definition: Math.hh:84
VRAMWindow bitmapCacheWindow
Definition: VDPVRAM.hh:670
#define OUTER(type, member)
Definition: outer.hh:38
void resetObserver()
Unregister the observer of this VRAM window.
Definition: VDPVRAM.hh:285
void change4k8kMapping(bool mapping8k)
TMS99x8 VRAM can be mapped in two ways.
Definition: VDPVRAM.cc:241
constexpr auto size(const C &c) -> decltype(c.size())
Definition: span.hh:62
void updateVRMode(bool mode, EmuTime::param time)
Change between VR=0 and VR=1 mode.
Definition: VDPVRAM.cc:205
void setMask(int newBaseMask, int newIndexMask, EmuTime::param time)
Sets the mask and enables this window.
Definition: VDPVRAM.hh:163
bool getVRMode() const
Returns current VR mode.
Definition: VDP.hh:492
unsigned getSize() const
Definition: Ram.hh:33