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 sizeMask = (
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(sizeMask, time);
179 cmdWriteWindow.setSizeMask(sizeMask, time);
180 nameTable.setSizeMask(sizeMask, time);
181 colorTable.setSizeMask(sizeMask, time);
182 patternTable.setSizeMask(sizeMask, time);
183 bitmapVisibleWindow.setSizeMask(sizeMask, time);
184 bitmapCacheWindow.setSizeMask(sizeMask, time);
185 spriteAttribTable.setSizeMask(sizeMask, time);
186 spritePatternTable.setSizeMask(sizeMask, time);
187}
188static constexpr unsigned swapAddr(unsigned x)
189{
190 // translate VR0 address to corresponding VR1 address
191 // note: output bit 0 is always 1
192 // input bit 6 is taken twice
193 // only 15 bits of the input are used
194 return 1 | ((x & 0x007F) << 1) | ((x & 0x7FC0) << 2);
195}
196void VDPVRAM::updateVRMode(bool newVRmode, EmuTime::param time)
197{
198 if (vrMode == newVRmode) {
199 // The swapping below may only happen when the mode is
200 // actually changed. So this test is not only an optimization.
201 return;
202 }
203 vrMode = newVRmode;
204 setSizeMask(time);
205
206 if (vrMode) {
207 // switch from VR=0 to VR=1
208 for (int i = 0x7FFF; i >=0; --i) {
209 std::swap(data[i], data[swapAddr(i)]);
210 }
211 } else {
212 // switch from VR=1 to VR=0
213 for (auto i : xrange(0x8000)) {
214 std::swap(data[i], data[swapAddr(i)]);
215 }
216 }
217}
218
219void VDPVRAM::setRenderer(Renderer* newRenderer, EmuTime::param time)
220{
221 renderer = newRenderer;
222
224 // Set up bitmapVisibleWindow to full VRAM.
225 // TODO: Have VDP/Renderer set the actual range.
226 bitmapVisibleWindow.setMask(0x1FFFF, ~0u << 17, time);
227 // TODO: If it is a good idea to send an initial sync,
228 // then call setObserver before setMask.
230}
231
232void VDPVRAM::change4k8kMapping(bool mapping8k)
233{
234 /* Sources:
235 * - http://www.msx.org/forumtopicl8624.html
236 * - Charles MacDonald's sc3000h.txt (http://cgfm2.emuviews.com)
237 *
238 * Bit 7 of register #1 affects how the VDP generates addresses when
239 * accessing VRAM. Here's a table illustrating the differences:
240 *
241 * VDP address VRAM address
242 * (Column) 4K mode 8/16K mode
243 * AD0 VA0 VA0
244 * AD1 VA1 VA1
245 * AD2 VA2 VA2
246 * AD3 VA3 VA3
247 * AD4 VA4 VA4
248 * AD5 VA5 VA5
249 * AD6 VA12 VA6
250 * AD7 Not used Not used
251 * (Row)
252 * AD0 VA6 VA7
253 * AD1 VA7 VA8
254 * AD2 VA8 VA9
255 * AD3 VA9 VA10
256 * AD4 VA10 VA11
257 * AD5 VA11 VA12
258 * AD6 VA13 VA13
259 * AD7 Not used Not used
260 *
261 * ADx - TMS9928 8-bit VRAM address/data bus
262 * VAx - 14-bit VRAM address that the VDP wants to access
263 *
264 * How the address is formed has to do with the physical layout of
265 * memory cells in a DRAM chip. A 4Kx1 chip has 64x64 cells, a 8Kx1 or
266 * 16Kx1 chip has 128x64 or 128x128 cells. Because the DRAM address bus
267 * is multiplexed, this means 6 bits are used for 4K DRAMs and 7 bits
268 * are used for 8K or 16K DRAMs.
269 *
270 * In 4K mode the 6 bits of the row and column are output first, with
271 * the remaining high-order bits mapped to AD6. In 8/16K mode the 7
272 * bits of the row and column are output normally. This also means that
273 * even in 4K mode, all 16K of VRAM can be accessed. The only
274 * difference is in what addresses are used to store data.
275 */
276 std::array<byte, 0x4000> tmp;
277 if (mapping8k) {
278 // from 8k/16k to 4k mapping
279 for (unsigned addr8 = 0; addr8 < 0x4000; addr8 += 64) {
280 unsigned addr4 = (addr8 & 0x203F) |
281 ((addr8 & 0x1000) >> 6) |
282 ((addr8 & 0x0FC0) << 1);
283 ranges::copy(subspan<64>(data, addr8),
284 subspan<64>(tmp, addr4));
285 }
286 } else {
287 // from 4k to 8k/16k mapping
288 for (unsigned addr4 = 0; addr4 < 0x4000; addr4 += 64) {
289 unsigned addr8 = (addr4 & 0x203F) |
290 ((addr4 & 0x0040) << 6) |
291 ((addr4 & 0x1F80) >> 1);
292 ranges::copy(subspan<64>(data, addr4),
293 subspan<64>(tmp, addr8));
294 }
295 }
296 //ranges::copy(tmp, std::span{data}); // TODO error with clang-15/libc++
297 ranges::copy(tmp, std::span{data.begin(), data.end()});
298}
299
300
301template<typename Archive>
302void VRAMWindow::serialize(Archive& ar, unsigned /*version*/)
303{
304 ar.serialize("baseAddr", baseAddr,
305 "baseMask", origBaseMask,
306 "indexMask", indexMask);
307 if constexpr (Archive::IS_LOADER) {
308 effectiveBaseMask = origBaseMask & sizeMask;
309 combiMask = ~effectiveBaseMask | indexMask;
310 // TODO ? observer->updateWindow(isEnabled(), time);
311 }
312}
313
314template<typename Archive>
315void VDPVRAM::serialize(Archive& ar, unsigned /*version*/)
316{
317 if constexpr (Archive::IS_LOADER) {
318 vrMode = vdp.getVRMode();
319 setSizeMask(static_cast<MSXDevice&>(vdp).getCurrentTime());
320 }
321
322 ar.serialize_blob("data", std::span{data.data(), actualSize});
323 ar.serialize("cmdReadWindow", cmdReadWindow,
324 "cmdWriteWindow", cmdWriteWindow,
325 "nameTable", nameTable,
326 // TODO: Find a way of changing the line below to "colorTable",
327 // without breaking backwards compatibility
328 "colourTable", colorTable,
329 "patternTable", patternTable,
330 "bitmapVisibleWindow", bitmapVisibleWindow,
331 "bitmapCacheWindow", bitmapCacheWindow,
332 "spriteAttribTable", spriteAttribTable,
333 "spritePatternTable", spritePatternTable);
334}
336
337} // 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:34
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.
Definition: VDPCmdEngine.hh:37
Manages VRAM contents and synchronizes the various users of the VRAM.
Definition: VDPVRAM.hh:387
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:674
void clear()
Initialize VRAM content to power-up state.
Definition: VDPVRAM.cc:131
void setRenderer(Renderer *renderer, EmuTime::param time)
Definition: VDPVRAM.cc:219
VRAMWindow colorTable
Definition: VDPVRAM.hh:670
void updateVRMode(bool mode, EmuTime::param time)
Change between VR=0 and VR=1 mode.
Definition: VDPVRAM.cc:196
VRAMWindow cmdReadWindow
Definition: VDPVRAM.hh:667
VRAMWindow bitmapCacheWindow
Definition: VDPVRAM.hh:673
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:672
void serialize(Archive &ar, unsigned version)
Definition: VDPVRAM.cc:315
VRAMWindow spritePatternTable
Definition: VDPVRAM.hh:675
VRAMWindow patternTable
Definition: VDPVRAM.hh:671
VRAMWindow cmdWriteWindow
Definition: VDPVRAM.hh:668
void change4k8kMapping(bool mapping8k)
TMS99x8 VRAM can be mapped in two ways.
Definition: VDPVRAM.cc:232
VRAMWindow nameTable
Definition: VDPVRAM.hh:669
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:517
bool getVRMode() const
Returns current VR mode.
Definition: VDP.hh:589
void serialize(Archive &ar, unsigned version)
Definition: VDPVRAM.cc:302
void setMask(unsigned newBaseMask, unsigned newIndexMask, EmuTime::param time)
Sets the mask and enables this window.
Definition: VDPVRAM.hh:162
void setSizeMask(unsigned newSizeMask, EmuTime::param time)
Inform VRAMWindow of changed sizeMask.
Definition: VDPVRAM.hh:316
VRAMWindow(const VRAMWindow &)=delete
void setObserver(VRAMObserver *newObserver)
Register an observer on this VRAM window.
Definition: VDPVRAM.hh:280
void resetObserver()
Unregister the observer of this VRAM window.
Definition: VDPVRAM.hh:286
constexpr vecN< N, T > min(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:266
constexpr vecN< N, T > max(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:284
std::string getName(KeyCode keyCode)
Translate key code to key name.
Definition: Keys.cc:730
This file implemented 3 utility functions:
Definition: Autofire.cc:9
Ram
Definition: Ram.cc:108
constexpr void fill(ForwardRange &&range, const T &value)
Definition: ranges.hh:287
auto copy(InputRange &&range, OutputIter out)
Definition: ranges.hh:232
auto transform(InputRange &&range, OutputIter out, UnaryOperation op)
Definition: ranges.hh:251
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
constexpr auto subspan(Range &&range, size_t offset, size_t count=std::dynamic_extent)
Definition: ranges.hh:446
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:1021
std::string strCat(Ts &&...ts)
Definition: strCat.hh:542
constexpr auto xrange(T e)
Definition: xrange.hh:133