openMSX
VDPVRAM.cc
Go to the documentation of this file.
1 #include "VDPVRAM.hh"
2 #include "SpriteChecker.hh"
3 #include "Renderer.hh"
4 #include "outer.hh"
5 #include "serialize.hh"
6 #include <algorithm>
7 #include <bit>
8 #include <cstring>
9 
10 namespace openmsx {
11 
12 // class VRAMWindow
13 
15  : data(&vram[0])
16  , observer(&dummyObserver)
17  , origBaseMask(0)
18  , effectiveBaseMask(0)
19  , indexMask(0) // these 4 don't matter but it makes valgrind happy
20  , baseAddr(-1) // disable window
21  , combiMask(0)
22  // sizeMask will be initialized shortly by the VDPVRAM class
23 {
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 constexpr 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  , vrMode(vdp.getVRMode())
118  , cmdReadWindow(data)
119  , cmdWriteWindow(data)
120  , nameTable(data)
121  , colorTable(data)
122  , patternTable(data)
123  , bitmapVisibleWindow(data)
124  , bitmapCacheWindow(data)
125  , spriteAttribTable(data)
126  , spritePatternTable(data)
127 {
128  setSizeMask(time);
129 
130  // Whole VRAM is cacheable.
131  // Because this window has no observer, any EmuTime can be passed.
132  // TODO: Move this to cache registration.
133  bitmapCacheWindow.setMask(0x1FFFF, ~0u << 17, EmuTime::zero());
134 }
135 
137 {
138  // Initialise VRAM data array.
139  data.clear(0); // fill with zeros (unless initialContent is specified)
140  if (data.getSize() != actualSize) {
141  assert(data.getSize() > actualSize);
142  // Read from unconnected VRAM returns random data.
143  // TODO reading same location multiple times does not always
144  // give the same value.
145  memset(&data[actualSize], 0xFF, data.getSize() - actualSize);
146  }
147 }
148 
149 void VDPVRAM::updateDisplayMode(DisplayMode mode, bool cmdBit, EmuTime::param time)
150 {
151  assert(vdp.isInsideFrame(time));
152  cmdEngine->updateDisplayMode(mode, cmdBit, time);
153  renderer->updateDisplayMode(mode, time);
154  spriteChecker->updateDisplayMode(mode, time);
155 }
156 
157 void VDPVRAM::updateDisplayEnabled(bool enabled, EmuTime::param time)
158 {
159  assert(vdp.isInsideFrame(time));
160  cmdEngine->sync(time);
161  renderer->updateDisplayEnabled(enabled, time);
162  spriteChecker->updateDisplayEnabled(enabled, time);
163 }
164 
165 void VDPVRAM::updateSpritesEnabled(bool enabled, EmuTime::param time)
166 {
167  assert(vdp.isInsideFrame(time));
168  cmdEngine->sync(time);
169  renderer->updateSpritesEnabled(enabled, time);
170  spriteChecker->updateSpritesEnabled(enabled, time);
171 }
172 
173 void VDPVRAM::setSizeMask(EmuTime::param time)
174 {
175  sizeMask = (
176  vrMode
177  // VR = 1: 64K address space, CAS0/1 is determined by A16
178  ? (std::bit_ceil(actualSize) - 1) | (1u << 16)
179  // VR = 0: 16K address space, CAS0/1 is determined by A14
180  : (std::min(std::bit_ceil(actualSize), 0x4000u) - 1) | (1u << 14)
181  ) | (1u << 17); // CASX (expansion RAM) is always relevant
182 
183  cmdReadWindow.setSizeMask(sizeMask, time);
184  cmdWriteWindow.setSizeMask(sizeMask, time);
185  nameTable.setSizeMask(sizeMask, time);
186  colorTable.setSizeMask(sizeMask, time);
187  patternTable.setSizeMask(sizeMask, time);
188  bitmapVisibleWindow.setSizeMask(sizeMask, time);
189  bitmapCacheWindow.setSizeMask(sizeMask, time);
190  spriteAttribTable.setSizeMask(sizeMask, time);
191  spritePatternTable.setSizeMask(sizeMask, time);
192 }
193 static constexpr unsigned swapAddr(unsigned x)
194 {
195  // translate VR0 address to corresponding VR1 address
196  // note: output bit 0 is always 1
197  // input bit 6 is taken twice
198  // only 15 bits of the input are used
199  return 1 | ((x & 0x007F) << 1) | ((x & 0x7FC0) << 2);
200 }
201 void VDPVRAM::updateVRMode(bool newVRmode, EmuTime::param time)
202 {
203  if (vrMode == newVRmode) {
204  // The swapping below may only happen when the mode is
205  // actually changed. So this test is not only an optimization.
206  return;
207  }
208  vrMode = newVRmode;
209  setSizeMask(time);
210 
211  if (vrMode) {
212  // switch from VR=0 to VR=1
213  for (int i = 0x7FFF; i >=0; --i) {
214  std::swap(data[i], data[swapAddr(i)]);
215  }
216  } else {
217  // switch from VR=1 to VR=0
218  for (auto i : xrange(0x8000)) {
219  std::swap(data[i], data[swapAddr(i)]);
220  }
221  }
222 }
223 
224 void VDPVRAM::setRenderer(Renderer* newRenderer, EmuTime::param time)
225 {
226  renderer = newRenderer;
227 
229  // Set up bitmapVisibleWindow to full VRAM.
230  // TODO: Have VDP/Renderer set the actual range.
231  bitmapVisibleWindow.setMask(0x1FFFF, ~0u << 17, time);
232  // TODO: If it is a good idea to send an initial sync,
233  // then call setObserver before setMask.
235 }
236 
237 void VDPVRAM::change4k8kMapping(bool mapping8k)
238 {
239  /* Sources:
240  * - http://www.msx.org/forumtopicl8624.html
241  * - Charles MacDonald's sc3000h.txt (http://cgfm2.emuviews.com)
242  *
243  * Bit 7 of register #1 affects how the VDP generates addresses when
244  * accessing VRAM. Here's a table illustrating the differences:
245  *
246  * VDP address VRAM address
247  * (Column) 4K mode 8/16K mode
248  * AD0 VA0 VA0
249  * AD1 VA1 VA1
250  * AD2 VA2 VA2
251  * AD3 VA3 VA3
252  * AD4 VA4 VA4
253  * AD5 VA5 VA5
254  * AD6 VA12 VA6
255  * AD7 Not used Not used
256  * (Row)
257  * AD0 VA6 VA7
258  * AD1 VA7 VA8
259  * AD2 VA8 VA9
260  * AD3 VA9 VA10
261  * AD4 VA10 VA11
262  * AD5 VA11 VA12
263  * AD6 VA13 VA13
264  * AD7 Not used Not used
265  *
266  * ADx - TMS9928 8-bit VRAM address/data bus
267  * VAx - 14-bit VRAM address that the VDP wants to access
268  *
269  * How the address is formed has to do with the physical layout of
270  * memory cells in a DRAM chip. A 4Kx1 chip has 64x64 cells, a 8Kx1 or
271  * 16Kx1 chip has 128x64 or 128x128 cells. Because the DRAM address bus
272  * is multiplexed, this means 6 bits are used for 4K DRAMs and 7 bits
273  * are used for 8K or 16K DRAMs.
274  *
275  * In 4K mode the 6 bits of the row and column are output first, with
276  * the remaining high-order bits mapped to AD6. In 8/16K mode the 7
277  * bits of the row and column are output normally. This also means that
278  * even in 4K mode, all 16K of VRAM can be accessed. The only
279  * difference is in what addresses are used to store data.
280  */
281  byte tmp[0x4000];
282  if (mapping8k) {
283  // from 8k/16k to 4k mapping
284  for (unsigned addr8 = 0; addr8 < 0x4000; addr8 += 64) {
285  unsigned addr4 = (addr8 & 0x203F) |
286  ((addr8 & 0x1000) >> 6) |
287  ((addr8 & 0x0FC0) << 1);
288  const byte* src = &data[addr8];
289  byte* dst = &tmp[addr4];
290  memcpy(dst, src, 64);
291  }
292  } else {
293  // from 4k to 8k/16k mapping
294  for (unsigned addr4 = 0; addr4 < 0x4000; addr4 += 64) {
295  unsigned addr8 = (addr4 & 0x203F) |
296  ((addr4 & 0x0040) << 6) |
297  ((addr4 & 0x1F80) >> 1);
298  const byte* src = &data[addr4];
299  byte* dst = &tmp[addr8];
300  memcpy(dst, src, 64);
301  }
302  }
303  memcpy(&data[0], tmp, sizeof(tmp));
304 }
305 
306 
307 template<typename Archive>
308 void VRAMWindow::serialize(Archive& ar, unsigned /*version*/)
309 {
310  ar.serialize("baseAddr", baseAddr,
311  "baseMask", origBaseMask,
312  "indexMask", indexMask);
313  if constexpr (Archive::IS_LOADER) {
314  effectiveBaseMask = origBaseMask & sizeMask;
315  combiMask = ~effectiveBaseMask | indexMask;
316  // TODO ? observer->updateWindow(isEnabled(), time);
317  }
318 }
319 
320 template<typename Archive>
321 void VDPVRAM::serialize(Archive& ar, unsigned /*version*/)
322 {
323  if constexpr (Archive::IS_LOADER) {
324  vrMode = vdp.getVRMode();
325  setSizeMask(static_cast<MSXDevice&>(vdp).getCurrentTime());
326  }
327 
328  ar.serialize_blob("data", &data[0], actualSize);
329  ar.serialize("cmdReadWindow", cmdReadWindow,
330  "cmdWriteWindow", cmdWriteWindow,
331  "nameTable", nameTable,
332  // TODO: Find a way of changing the line below to "colorTable",
333  // without breaking backwards compatibility
334  "colourTable", colorTable,
335  "patternTable", patternTable,
336  "bitmapVisibleWindow", bitmapVisibleWindow,
337  "bitmapCacheWindow", bitmapCacheWindow,
338  "spriteAttribTable", spriteAttribTable,
339  "spritePatternTable", spritePatternTable);
340 }
342 
343 } // namespace openmsx
Represents a VDP display mode.
Definition: DisplayMode.hh:16
An MSXDevice is an emulated hardware component connected to the bus of the emulated MSX.
Definition: MSXDevice.hh:33
unsigned getSize() const
Definition: Ram.hh:44
void clear(byte c=0xff)
Definition: Ram.cc:35
Abstract base class for Renderers.
Definition: Renderer.hh:23
virtual void updateDisplayMode(DisplayMode mode, EmuTime::param time)=0
Informs the renderer of a VDP display mode change.
virtual void updateSpritesEnabled(bool enabled, EmuTime::param time)=0
Informs the renderer of a VDP sprites enabled change.
virtual void updateDisplayEnabled(bool enabled, EmuTime::param time)=0
Informs the renderer of a VDP display enabled 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)
Informs the sprite checker of a VDP display enabled change.
void updateSpritesEnabled(bool enabled, EmuTime::param time)
Informs the sprite checker of sprite enable changes.
void updateDisplayMode(DisplayMode mode, bool cmdBit, EmuTime::param time)
Informs the command engine of a VDP display mode change.
void sync(EmuTime::param time)
Synchronizes the command engine with the VDP.
Definition: VDPCmdEngine.hh:37
Manages VRAM contents and synchronizes the various users of the VRAM.
Definition: VDPVRAM.hh:386
void updateSpritesEnabled(bool enabled, EmuTime::param time)
Used by the VDP to signal sprites enabled changes.
Definition: VDPVRAM.cc:165
VRAMWindow spriteAttribTable
Definition: VDPVRAM.hh:673
void clear()
Initialize VRAM content to power-up state.
Definition: VDPVRAM.cc:136
void setRenderer(Renderer *renderer, EmuTime::param time)
Definition: VDPVRAM.cc:224
VRAMWindow colorTable
Definition: VDPVRAM.hh:669
void updateVRMode(bool mode, EmuTime::param time)
Change between VR=0 and VR=1 mode.
Definition: VDPVRAM.cc:201
VRAMWindow cmdReadWindow
Definition: VDPVRAM.hh:666
VRAMWindow bitmapCacheWindow
Definition: VDPVRAM.hh:672
void updateDisplayEnabled(bool enabled, EmuTime::param time)
Used by the VDP to signal display enabled changes.
Definition: VDPVRAM.cc:157
void updateDisplayMode(DisplayMode mode, bool cmdBit, EmuTime::param time)
Used by the VDP to signal display mode changes.
Definition: VDPVRAM.cc:149
VRAMWindow bitmapVisibleWindow
Definition: VDPVRAM.hh:671
void serialize(Archive &ar, unsigned version)
Definition: VDPVRAM.cc:321
VRAMWindow spritePatternTable
Definition: VDPVRAM.hh:674
VRAMWindow patternTable
Definition: VDPVRAM.hh:670
VRAMWindow cmdWriteWindow
Definition: VDPVRAM.hh:667
void change4k8kMapping(bool mapping8k)
TMS99x8 VRAM can be mapped in two ways.
Definition: VDPVRAM.cc:237
VRAMWindow nameTable
Definition: VDPVRAM.hh:668
Unified implementation of MSX Video Display Processors (VDPs).
Definition: VDP.hh:63
bool isInsideFrame(EmuTime::param time) const
Is the given timestamp inside the current frame? Mainly useful for debugging, because relevant timest...
Definition: VDP.hh:514
bool getVRMode() const
Returns current VR mode.
Definition: VDP.hh:586
void serialize(Archive &ar, unsigned version)
Definition: VDPVRAM.cc:308
void setSizeMask(unsigned newSizeMask, EmuTime::param time)
Inform VRAMWindow of changed sizeMask.
Definition: VDPVRAM.hh:315
VRAMWindow(const VRAMWindow &)=delete
void setMask(int newBaseMask, int newIndexMask, EmuTime::param time)
Sets the mask and enables this window.
Definition: VDPVRAM.hh:162
void setObserver(VRAMObserver *newObserver)
Register an observer on this VRAM window.
Definition: VDPVRAM.hh:279
void resetObserver()
Unregister the observer of this VRAM window.
Definition: VDPVRAM.hh:285
constexpr vecN< N, T > min(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:265
constexpr vecN< N, T > max(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:283
std::string getName(KeyCode keyCode)
Translate key code to key name.
Definition: Keys.cc:727
This file implemented 3 utility functions:
Definition: Autofire.cc:9
Ram
Definition: Ram.cc:108
constexpr KeyMatrixPosition x
Keyboard bindings.
Definition: Keyboard.cc:127
auto transform(InputRange &&range, OutputIter out, UnaryOperation op)
Definition: ranges.hh:220
size_t size(std::string_view utf8)
#define OUTER(type, member)
Definition: outer.hh:41
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:1009
std::string strCat(Ts &&...ts)
Definition: strCat.hh:549
constexpr auto xrange(T e)
Definition: xrange.hh:133