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