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