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
10namespace 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
41VDPVRAM::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
49unsigned 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
57byte VDPVRAM::LogicalVRAMDebuggable::read(unsigned address, EmuTime::param time)
58{
59 auto& vram = OUTER(VDPVRAM, logicalVRAMDebug);
60 return vram.cpuRead(transform(address), time);
61}
62
63void 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
73VDPVRAM::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
82byte VDPVRAM::PhysicalVRAMDebuggable::read(unsigned address, EmuTime::param time)
83{
84 auto& vram = OUTER(VDPVRAM, physicalVRAMDebug);
85 return vram.cpuRead(address, time);
86}
87
88void 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
98static 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
108VDPVRAM::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
149void 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
157void 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
165void 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
173void 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}
193static 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}
201void 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
224void 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
237void 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
307template<typename Archive>
308void 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
320template<typename Archive>
321void 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
STL namespace.
void swap(openmsx::MemBuffer< T > &l, openmsx::MemBuffer< T > &r) noexcept
Definition: MemBuffer.hh:202
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