OXYGENHUB
◆ main openmsx / src / video / VDPVRAM.cc
10.6 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
e8555c89 Andy McCall 1
#include "VDPVRAM.hh"
2
3
#include "Renderer.hh"
4
#include "SpriteChecker.hh"
5
6
#include "outer.hh"
7
#include "ranges.hh"
8
#include "serialize.hh"
9
10
#include <algorithm>
11
#include <array>
12
#include <bit>
13
14
namespace openmsx {
15
16
// class VRAMWindow
17
18
VRAMWindow::VRAMWindow(Ram& vram)
19
	: data(vram.data())
20
	// sizeMask will be initialized shortly by the VDPVRAM class
21
{
22
}
23
24
25
// class LogicalVRAMDebuggable
26
27
/** This debuggable is always 128kB in size, even if the actual amount of VRAM
28
 *  is different:
29
 * - If there is less actual VRAM (e.g. 64kB), the upper regions of this
30
 *   debuggable act in the same way as if the CPU would read/write those
31
 *   addresses (mirroring or return fixed value / ignore write).
32
 * - If there is more VRAM (128kB + 64kB extended VRAM), the extended VRAM
33
 *   part is not accessible via this debuggable (it is via the 'physical VRAM'
34
 *   debuggable). We might change this in the future, but the interaction
35
 *   between interleaving and extended VRAM can be tricky, for example the
36
 *   size of this debuggable would have to be 256kB to be able to access the
37
 *   complete extended VRAM in interleaved mode.
38
 */
39
VDPVRAM::LogicalVRAMDebuggable::LogicalVRAMDebuggable(const VDP& vdp_)
40
	: SimpleDebuggable(vdp_.getMotherBoard(), vdp_.getName() == "VDP" ? "VRAM" :
41
			vdp_.getName() + " VRAM",
42
			"CPU view on video RAM given the current display mode.",
43
			 128 * 1024) // always 128kB
44
{
45
}
46
47
unsigned VDPVRAM::LogicalVRAMDebuggable::transform(unsigned address)
48
{
49
	const auto& vram = OUTER(VDPVRAM, logicalVRAMDebug);
50
	return vram.vdp.getDisplayMode().isPlanar()
51
	     ? ((address << 16) | (address >> 1)) & 0x1FFFF
52
	     : address;
53
}
54
55
uint8_t VDPVRAM::LogicalVRAMDebuggable::read(unsigned address, EmuTime time)
56
{
57
	auto& vram = OUTER(VDPVRAM, logicalVRAMDebug);
58
	return vram.cpuRead(transform(address), time);
59
}
60
61
void VDPVRAM::LogicalVRAMDebuggable::write(
62
	unsigned address, uint8_t value, EmuTime time)
63
{
64
	auto& vram = OUTER(VDPVRAM, logicalVRAMDebug);
65
	vram.cpuWrite(transform(address), value, time);
66
}
67
68
69
// class PhysicalVRAMDebuggable
70
71
VDPVRAM::PhysicalVRAMDebuggable::PhysicalVRAMDebuggable(
72
		const VDP& vdp_, unsigned actualSize_)
73
	: SimpleDebuggable(vdp_.getMotherBoard(), vdp_.getName() == "VDP" ?
74
	                   "physical VRAM" : strCat("physical ", vdp_.getName(), " VRAM"),
75
	                   "VDP-screen-mode-independent view on the video RAM.",
76
	                   actualSize_)
77
{
78
}
79
80
uint8_t VDPVRAM::PhysicalVRAMDebuggable::read(unsigned address, EmuTime time)
81
{
82
	auto& vram = OUTER(VDPVRAM, physicalVRAMDebug);
83
	return vram.cpuRead(address, time);
84
}
85
86
void VDPVRAM::PhysicalVRAMDebuggable::write(
87
	unsigned address, uint8_t value, EmuTime time)
88
{
89
	auto& vram = OUTER(VDPVRAM, physicalVRAMDebug);
90
	vram.cpuWrite(address, value, time);
91
}
92
93
94
// class VDPVRAM
95
96
static constexpr unsigned bufferSize(unsigned size)
97
{
98
	// Always allocate at least a buffer of 128kB, this makes the VR0/VR1
99
	// swapping a lot easier. Actually only in case there is also extended
100
	// VRAM, we need to allocate more.
101
	// TODO possible optimization: for TMS99x8 we could allocate 16kB, it
102
	//      has no VR modes.
103
	return std::max(0x20000u, size);
104
}
105
106
VDPVRAM::VDPVRAM(VDP& vdp_, unsigned size, EmuTime time)
107
	: vdp(vdp_)
108
	, data(*vdp_.getDeviceConfig2().getXML(), bufferSize(size))
109
	, logicalVRAMDebug (vdp)
110
	, physicalVRAMDebug(vdp, size)
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
131
void VDPVRAM::clear()
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
		std::ranges::fill(subspan(data, actualSize), 0xFF);
141
	}
142
}
143
144
void VDPVRAM::updateDisplayMode(DisplayMode mode, bool cmdBit, EmuTime 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
152
void VDPVRAM::updateDisplayEnabled(bool enabled, EmuTime time)
153
{
154
	assert(vdp.isInsideFrame(time));
155
	cmdEngine->sync(time);
156
	renderer->updateDisplayEnabled(enabled, time);
157
	spriteChecker->updateDisplayEnabled(enabled, time);
158
}
159
160
void VDPVRAM::updateSpritesEnabled(bool enabled, EmuTime time)
161
{
162
	assert(vdp.isInsideFrame(time));
163
	cmdEngine->sync(time);
164
	renderer->updateSpritesEnabled(enabled, time);
165
	spriteChecker->updateSpritesEnabled(enabled, time);
166
}
167
168
void VDPVRAM::setSizeMask(EmuTime 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
}
190
static 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
}
198
void VDPVRAM::updateVRMode(bool newVRmode, EmuTime 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
221
void VDPVRAM::setRenderer(Renderer* newRenderer, EmuTime time)
222
{
223
	renderer = newRenderer;
224
225
	bitmapVisibleWindow.resetObserver();
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.
231
	bitmapVisibleWindow.setObserver(renderer);
232
}
233
234
void 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<uint8_t, 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
			copy_to_range(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
			copy_to_range(subspan<64>(data, addr4),
295
			              subspan<64>(tmp, addr8));
296
		}
297
	}
298
	copy_to_range(tmp, std::span{data});
299
}
300
301
302
template<typename Archive>
303
void VRAMWindow::serialize(Archive& ar, unsigned /*version*/)
304
{
305
	ar.serialize("baseAddr",  baseAddr,
306
	             "baseMask",  origBaseMask,
307
	             "indexMask", indexMask);
308
	if constexpr (Archive::IS_LOADER) {
309
		effectiveBaseMask = origBaseMask & sizeMask;
310
		combiMask = ~effectiveBaseMask | indexMask;
311
		// TODO ?  observer->updateWindow(isEnabled(), time);
312
	}
313
}
314
315
template<typename Archive>
316
void VDPVRAM::serialize(Archive& ar, unsigned /*version*/)
317
{
318
	if constexpr (Archive::IS_LOADER) {
319
		vrMode = vdp.getVRMode();
320
		setSizeMask(static_cast<MSXDevice&>(vdp).getCurrentTime());
321
	}
322
323
	ar.serialize_blob("data", std::span{data.data(), actualSize});
324
	ar.serialize("cmdReadWindow",       cmdReadWindow,
325
	             "cmdWriteWindow",      cmdWriteWindow,
326
	             "nameTable",           nameTable,
327
	// TODO: Find a way of changing the line below to "colorTable",
328
	// without breaking backwards compatibility
329
	             "colourTable",         colorTable,
330
	             "patternTable",        patternTable,
331
	             "bitmapVisibleWindow", bitmapVisibleWindow,
332
	             "bitmapCacheWindow",   bitmapCacheWindow,
333
	             "spriteAttribTable",   spriteAttribTable,
334
	             "spritePatternTable",  spritePatternTable);
335
}
336
INSTANTIATE_SERIALIZE_METHODS(VDPVRAM);
337
338
} // namespace openmsx