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