OXYGENHUB
◆ main openmsx / src / video / VDPVRAM.hh
23.3 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#ifndef VDPVRAM_HH
2
#define VDPVRAM_HH
3
4
#include "VDP.hh"
5
#include "VDPCmdEngine.hh"
6
#include "VRAMObserver.hh"
7
8
#include "Ram.hh"
9
#include "SimpleDebuggable.hh"
10
11
#include "Math.hh"
12
13
#include <cassert>
14
#include <cstdint>
15
16
namespace openmsx {
17
18
class DisplayMode;
19
class SpriteChecker;
20
class Renderer;
21
22
/*
23
Note: The way VRAM is accessed depends a lot on who is doing the accessing.
24
25
For example, the ranges:
26
- Table access is done using masks.
27
- Command engine work areas are rectangles.
28
- CPU access always spans full memory.
29
30
Maybe define an interface with multiple subclasses?
31
Or is that too much of a performance hit?
32
If accessed through the interface, a virtual method call is made.
33
But invoking the objects directly should be inlined.
34
35
Timing:
36
37
Each window reflects the state of the VRAM at a specified moment in time.
38
39
Because the CPU has full-range write access, it is incorrect for any window
40
to be ahead in time compared to the CPU. Because multi-cycle operations are
41
implemented as atomic, it is currently possible that a window which starts
42
an operation slightly before CPU time ends up slightly after CPU time.
43
Solutions:
44
- break up operations in 1-cycle suboperations
45
  (very hard to reverse engineer accurately)
46
- do not start an operation until its end time is after CPU time
47
  (requires minor rewrite of command engine)
48
- make the code that uses the timestamps resilient to after-CPU times
49
  (current implementation; investigate if this is correct)
50
51
Window ranges are not at fixed. But they can only be changed by the CPU, so
52
they are fixed until CPU time, which subsystems will never go beyond anyway.
53
54
The only two subsystems with write access are CPU and command engine.
55
The command engine can only start executing a new command if instructed so
56
by the CPU. Therefore it is known which area the command engine can write
57
in until CPU time:
58
- empty, if the command engine is not executing a command
59
- the command's reach, if the command engine is executing a command
60
Currently the command's reach is not computed: full VRAM is used.
61
Taking the Y coordinate into account would speed things up a lot, because
62
usually commands execute on invisible pages, so the number of area overlaps
63
between renderer and command engine would be reduced significantly.
64
Also sprite tables are usually not written by commands.
65
66
Reading through a window is done as follows:
67
A subsystem reads the VRAM because it is updating itself to a certain moment
68
in time T.
69
1. the subsystems syncs the window to T
70
2. VDPVRAM checks overlap of the window with the command write area
71
   no overlap -> go to step 6
72
3. VDPVRAM syncs the command engine to T
73
4. the command engine calls VDPVRAM to write each byte it changes in VRAM,
74
   call the times this happens C1, C2, C3...
75
5. at the n-th write, VDPVRAM updates any subsystem with the written address
76
   in its window to Cn, this can include the original subsystem
77
6. the window has reached T
78
   now the subsystem can update itself to T
79
Using this approach instead of syncing on read makes sure there is no
80
re-entrance on the subsystem update methods.
81
82
Note: command engine reads through write window when doing logic-ops.
83
So "source window" and "destination window" would be better names.
84
85
Interesting observation:
86
Each window is at the same moment in time as the command engine (C):
87
- if a window doesn't overlap with the command destination window, it is
88
  stable from a moment before C until the CPU time T
89
- if a window overlaps with the command destination window, it cannot be
90
  before C (incorrect) or after C (uncertainty)
91
Since there is only one time for the entire VRAM, the VRAM itself can be said
92
to be at C. This is a justification for having the sync method in VDPVRAM
93
instead of in Window.
94
95
Writing through a window is done as follows:
96
- CPU write: sync with all non-CPU windows, including command engine write
97
- command engine write: sync with non-CPU and non-command engine windows
98
Syncing with a window is only necessary if the write falls into that window.
99
100
If all non-CPU windows are disjunct, then all subsystems function
101
independently (at least until CPU time), no need for syncs.
102
So what is interesting, is which windows overlap.
103
Since windows change position infrequently, it may be beneficial to
104
precalculate overlaps.
105
Not necessarily though, because even if two windows overlap, a single write
106
may not be inside the other window. So precalculated overlaps only speeds up
107
in the case there is no overlap.
108
Maybe it's not necessary to know exactly which windows overlap with cmdWrite,
109
only to know whether there are any. If not, sync can be skipped.
110
111
Is it possible to read multiple bytes at the same time?
112
In other words, get a pointer to an array instead of reading single bytes.
113
Yes, but only the first 64 bytes are guaranteed to be correct, because that
114
is the granularity of the color table.
115
But since whatever is reading the VRAM knows what it is operating on, it
116
can decide for itself how many bytes to read.
117
118
*/
119
120
class DummyVRAMObserver final : public VRAMObserver
121
{
122
public:
123
	void updateVRAM(unsigned /*offset*/, EmuTime /*time*/) override {}
124
	void updateWindow(bool /*enabled*/, EmuTime /*time*/) override {}
125
};
126
127
/** Specifies an address range in the VRAM.
128
  * A VDP subsystem can use this to put a claim on a certain area.
129
  * For example, the owner of a read window will be notified before
130
  * writes to the corresponding area are commited.
131
  * The address range is specified by a mask and is not necessarily
132
  * continuous. See "doc/vram-addressing.txt" for details.
133
  * TODO: Rename to "Table"? That's the term the VDP data book uses.
134
  *       Maybe have two classes: "Table" for tables, using a mask,
135
  *       and "Window" for the command engine, using an interval.
136
  */
137
class VRAMWindow
138
{
139
public:
140
	VRAMWindow(const VRAMWindow&) = delete;
141
	VRAMWindow(VRAMWindow&&) = delete;
142
	VRAMWindow& operator=(const VRAMWindow&) = delete;
143
	VRAMWindow& operator=(VRAMWindow&&) = delete;
144
145
	/** Gets the mask for this window.
146
	  * Should only be called if the window is enabled.
147
	  * TODO: Only used by dirty checking. Maybe a new dirty checking
148
	  *       approach can obsolete this method?
149
	  */
150
	[[nodiscard]] unsigned getMask() const {
151
		assert(isEnabled());
152
		return effectiveBaseMask;
153
	}
154
155
	/** Sets the mask and enables this window.
156
	  * @param newBaseMask The table base register,
157
	  *     with the unused bits all ones.
158
	  * @param newIndexMask The table index mask,
159
	  *     with the unused bits all ones.
160
	  * @param newSizeMask
161
	  * @param time The moment in emulated time this change occurs.
162
	  * TODO: In planar mode, the index bits are rotated one to the right.
163
	  *       Solution: have the caller pass index mask instead of the
164
	  *       number of bits.
165
	  *       For many tables the number of index bits depends on the
166
	  *       display mode anyway.
167
	  */
168
	void setMask(unsigned newBaseMask, unsigned newIndexMask,
169
	                    unsigned newSizeMask, EmuTime time) {
170
		origBaseMask = newBaseMask;
171
		newBaseMask &= newSizeMask;
172
		if (isEnabled() &&
173
		    (newBaseMask  == effectiveBaseMask) &&
174
		    (newIndexMask == indexMask)) {
175
			return;
176
		}
177
		observer->updateWindow(true, time);
178
		effectiveBaseMask = newBaseMask;
179
		indexMask         = newIndexMask;
180
		baseAddr  =  effectiveBaseMask & indexMask; // this enables window
181
		combiMask = ~effectiveBaseMask | indexMask;
182
	}
183
184
	/** Same as above, but 'sizeMask' doesn't change.
185
	 * This is a useful shortcut, because 'sizeMask' rarely changes.
186
	 */
187
	void setMask(unsigned newBaseMask, unsigned newIndexMask,
188
	                    EmuTime time) {
189
		setMask(newBaseMask, newIndexMask, sizeMask, time);
190
	}
191
192
	/** Disable this window: no address will be considered inside.
193
	  * @param time The moment in emulated time this change occurs.
194
	  */
195
	void disable(EmuTime time) {
196
		observer->updateWindow(false, time);
197
		baseAddr = unsigned(-1);
198
	}
199
200
	/** Is the given index range continuous in VRAM (iow there's no mirroring)
201
	  * Only if the range is continuous it's allowed to call getReadArea().
202
	  */
203
	[[nodiscard]] bool isContinuous(unsigned index, unsigned size) const {
204
		assert(isEnabled());
205
		unsigned endIndex = index + size - 1;
206
		unsigned areaBits = Math::floodRight(index ^ endIndex);
207
		if ((areaBits & effectiveBaseMask) != areaBits) return false;
208
		if ((areaBits & ~indexMask)        != areaBits) return false;
209
		return true;
210
	}
211
212
	/** Alternative version to check whether a region is continuous in
213
	  * VRAM. This tests whether all addresses in the range
214
	  *    0bCCCCCCCCCCXXXXXXXX (with C constant and X varying)
215
	  * are continuous. The input must be a value 1-less-than-a-power-of-2
216
	  * (so a binary value containing zeros on the left ones on the right)
217
	  * 1-bits in the parameter correspond with 'X' in the pattern above.
218
	  * Or IOW it tests an aligned-power-of-2-sized region.
219
	  */
220
	[[nodiscard]] bool isContinuous(unsigned mask) const {
221
		assert(isEnabled());
222
		assert((mask & ~indexMask)        == mask);
223
		return (mask & effectiveBaseMask) == mask;
224
	}
225
226
	/** Gets a span of a contiguous part of the VRAM. The region is
227
	  * [index, index + size) inside the current window.
228
	  * @param index Index in window
229
	  */
230
	template<size_t size>
231
	[[nodiscard]] std::span<const uint8_t, size> getReadArea(unsigned index) const {
232
		assert(isContinuous(index, size));
233
		return std::span<const uint8_t, size>{
234
				&data[effectiveBaseMask & (indexMask | index)],
235
				size};
236
	}
237
238
	/** Similar to getReadArea(), but now with planar addressing mode.
239
	  * This means the region is split in two: one region for the even bytes
240
	  * (span0) and another for the odd bytes (span1).
241
	  * @param index Index in window
242
	  * @return pair{span0, span1}
243
	  *    span0: The block of even numbered bytes.
244
	  *    span1: The block of odd  numbered bytes.
245
	  */
246
	template<size_t size>
247
	[[nodiscard]] std::pair<std::span<const uint8_t, size / 2>, std::span<const uint8_t, size / 2>>
248
			getReadAreaPlanar(unsigned index) const {
249
		assert((index & 1) == 0);
250
		assert((size & 1) == 0);
251
		unsigned endIndex = index + size - 1;
252
		unsigned areaBits = Math::floodRight(index ^ endIndex);
253
		areaBits = ((areaBits << 16) | (areaBits >> 1)) & 0x1FFFF & sizeMask;
254
		(void)areaBits;
255
		assert((areaBits & effectiveBaseMask) == areaBits);
256
		assert((areaBits & ~indexMask)        == areaBits);
257
		assert(isEnabled());
258
		unsigned addr = effectiveBaseMask & (indexMask | (index >> 1));
259
		const uint8_t* ptr0 = &data[addr | 0x00000];
260
		const uint8_t* ptr1 = &data[addr | 0x10000];
261
		return {std::span<const uint8_t, size / 2>{ptr0, size / 2},
262
		        std::span<const uint8_t, size / 2>{ptr1, size / 2}};
263
	}
264
265
	/** Reads a byte from VRAM in its current state.
266
	  * @param index Index in table, with unused bits set to 1.
267
	  */
268
	[[nodiscard]] uint8_t readNP(unsigned index) const {
269
		assert(isEnabled());
270
		return data[effectiveBaseMask & index];
271
	}
272
273
	/** Similar to readNP, but now with planar addressing.
274
	  * @param index Index in table, with unused bits set to 1.
275
	  */
276
	[[nodiscard]] uint8_t readPlanar(unsigned index) const {
277
		assert(isEnabled());
278
		index = ((index & 1) << 16) | ((index & 0x1FFFE) >> 1);
279
		unsigned addr = effectiveBaseMask & index;
280
		return data[addr];
281
	}
282
283
	/** Is there an observer registered for this window?
284
	  */
285
	[[nodiscard]] bool hasObserver() const {
286
		return observer != &dummyObserver;
287
	}
288
289
	/** Register an observer on this VRAM window.
290
	  * It will be called when changes occur within the window.
291
	  * There can be only one observer per window at any given time.
292
	  * @param newObserver The observer to register.
293
	  */
294
	void setObserver(VRAMObserver* newObserver) {
295
		observer = newObserver;
296
	}
297
298
	/** Unregister the observer of this VRAM window.
299
	  */
300
	void resetObserver() {
301
		observer = &dummyObserver;
302
	}
303
304
	/** Test whether an address is inside this window.
305
	  * "Inside" is defined as: there is at least one index in this window,
306
	  * which is mapped to the given address.
307
	  * TODO: Might be replaced by notify().
308
	  * @param address The address to test.
309
	  * @return true iff the address is inside this window.
310
	  */
311
	[[nodiscard]] bool isInside(unsigned address) const {
312
		return (address & combiMask) == baseAddr;
313
	}
314
315
	/** Notifies the observer of this window of a VRAM change,
316
	  * if the changes address is inside this window.
317
	  * @param address The address to test.
318
	  * @param time The moment in emulated time the change occurs.
319
	  */
320
	void notify(unsigned address, EmuTime time) {
321
		if (isInside(address)) {
322
			observer->updateVRAM(address - baseAddr, time);
323
		}
324
	}
325
326
	/** Inform VRAMWindow of changed sizeMask.
327
	  * For the moment this only happens when switching the VR bit in VDP
328
	  * register 8 (in VR=0 mode only 32kB VRAM is addressable).
329
	  */
330
	void setSizeMask(unsigned newSizeMask, EmuTime time) {
331
		if (isEnabled()) {
332
			setMask(origBaseMask, indexMask, newSizeMask, time);
333
		}
334
		// only apply sizeMask after observers have been notified
335
		sizeMask = newSizeMask;
336
	}
337
338
	template<typename Archive>
339
	void serialize(Archive& ar, unsigned version);
340
341
private:
342
	[[nodiscard]] bool isEnabled() const {
343
		return baseAddr != unsigned(-1);
344
	}
345
346
private:
347
	/** Only VDPVRAM may construct VRAMWindow objects.
348
	  */
349
	friend class VDPVRAM;
350
351
	/** Create a new window.
352
	  * Initially, the window is disabled; use setRange to enable it.
353
	  */
354
	explicit VRAMWindow(Ram& vram);
355
356
	/** Pointer to the entire VRAM data.
357
	  */
358
	uint8_t* data;
359
360
	/** Observer associated with this VRAM window.
361
	  * It will be called when changes occur within the window.
362
	  * If there is no observer, this variable is &dummyObserver.
363
	  */
364
	VRAMObserver* observer = &dummyObserver;
365
366
	/** Base mask as passed to the setMask() method.
367
	 */
368
	unsigned origBaseMask = 0;
369
370
	/** Effective mask of this window.
371
	  * This is always equal to 'origBaseMask & sizeMask'.
372
	  */
373
	unsigned effectiveBaseMask = 0;
374
375
	/** Index mask of this window.
376
	  */
377
	unsigned indexMask = 0;
378
379
	/** Lowest address in this window.
380
	  * Or -1 when this window is disabled.
381
	  */
382
	unsigned baseAddr = unsigned(-1); // disable window
383
384
	/** Combination of effectiveBaseMask and index mask used for "inside" checks.
385
	  */
386
	unsigned combiMask = 0;
387
388
	/** Mask to handle vram mirroring
389
	  * Note: this only handles mirroring for power-of-2 sizes
390
	  *       mirroring of extended VRAM is handled in a different way
391
	  */
392
	unsigned sizeMask;
393
394
	static inline DummyVRAMObserver dummyObserver;
395
};
396
397
/** Manages VRAM contents and synchronizes the various users of the VRAM.
398
  * VDPVRAM does not apply planar remapping to addresses, this is the
399
  * responsibility of the caller.
400
  */
401
class VDPVRAM
402
{
403
public:
404
	VDPVRAM(const VDPVRAM&) = delete;
405
	VDPVRAM(VDPVRAM&&) = delete;
406
	VDPVRAM& operator=(const VDPVRAM&) = delete;
407
	VDPVRAM& operator=(VDPVRAM&&) = delete;
408
409
	VDPVRAM(VDP& vdp, unsigned size, EmuTime time);
410
411
	/** Initialize VRAM content to power-up state.
412
	 */
413
	void clear();
414
415
	/** Update VRAM state to specified moment in time.
416
	  * @param time Moment in emulated time to update VRAM to.
417
	  * TODO: Replace this method by VRAMWindow::sync().
418
	  */
419
	void sync(EmuTime time) {
420
		assert(vdp.isInsideFrame(time));
421
		cmdEngine->sync(time);
422
	}
423
424
	/** Write a byte from the command engine.
425
	  * Synchronisation with reads by the command engine is skipped.
426
	  * TODO: Replace by "cmdSync ; VRAMWindow::write".
427
	  *       Note: "cmdSync", because it checks against read windows, unlike
428
	  *       the other sync which checks against the cmd write window.
429
	  */
430
	void cmdWrite(unsigned address, uint8_t value, EmuTime time) {
431
		#ifdef DEBUG
432
		// Rewriting history is not allowed.
433
		assert(time >= vramTime);
434
		#endif
435
		assert(vdp.isInsideFrame(time));
436
437
		// handle mirroring and non-present ram chips
438
		address &= sizeMask;
439
		if (address >= actualSize) [[unlikely]] {
440
			// 192kb vram is mirroring is handled elsewhere
441
			assert(address < 0x30000);
442
			// only happens in case of 16kb vram while you write
443
			// to range [0x4000,0x8000)
444
			return;
445
		}
446
447
		writeCommon(address, value, time);
448
	}
449
450
	/** Write a byte to VRAM through the CPU interface.
451
	  * @param address The address to write.
452
	  * @param value The value to write.
453
	  * @param time The moment in emulated time this write occurs.
454
	  */
455
	void cpuWrite(unsigned address, uint8_t value, EmuTime time) {
456
		#ifdef DEBUG
457
		// Rewriting history is not allowed.
458
		assert(time >= vramTime);
459
		#endif
460
		assert(vdp.isInsideFrame(time));
461
462
		// handle mirroring and non-present ram chips
463
		address &= sizeMask;
464
		if (address >= actualSize) [[unlikely]] {
465
			// 192kb vram is mirroring is handled elsewhere
466
			assert(address < 0x30000);
467
			// only happens in case of 16kb vram while you write
468
			// to range [0x4000,0x8000)
469
			return;
470
		}
471
472
		// We should still sync with cmdEngine, even if the VRAM already
473
		// contains the value we're about to write (e.g. it's possible
474
		// syncing with cmdEngine changes that value, and this write
475
		// restores it again). This fixes bug:
476
		//   [2844043] Hinotori - Firebird small graphics corruption
477
		if (cmdReadWindow .isInside(address) ||
478
		    cmdWriteWindow.isInside(address)) {
479
			cmdEngine->sync(time);
480
		}
481
		writeCommon(address, value, time);
482
483
		cmdEngine->stealAccessSlot(time);
484
	}
485
486
	/** Read a byte from VRAM though the CPU interface.
487
	  * @param address The address to read.
488
	  * @param time The moment in emulated time this read occurs.
489
	  * @return The VRAM contents at the specified address.
490
	  */
491
	[[nodiscard]] uint8_t cpuRead(unsigned address, EmuTime time) {
492
		#ifdef DEBUG
493
		// VRAM should never get ahead of CPU.
494
		assert(time >= vramTime);
495
		#endif
496
		assert(vdp.isInsideFrame(time));
497
498
		address &= sizeMask;
499
		if (cmdWriteWindow.isInside(address)) {
500
			cmdEngine->sync(time);
501
		}
502
		cmdEngine->stealAccessSlot(time);
503
504
		#ifdef DEBUG
505
		vramTime = time;
506
		#endif
507
		return data[address];
508
	}
509
510
	/** Used by the VDP to signal display mode changes.
511
	  * VDPVRAM will inform the Renderer, command engine and the sprite
512
	  * checker of this change.
513
	  * TODO: Does this belong in VDPVRAM?
514
	  * @param mode The new display mode.
515
	  * @param cmdBit Are VDP commands allowed in non-bitmap mode.
516
	  * @param time The moment in emulated time this change occurs.
517
	  */
518
	void updateDisplayMode(DisplayMode mode, bool cmdBit, EmuTime time);
519
520
	/** Used by the VDP to signal display enabled changes.
521
	  * Both the regular border start/end and forced blanking by clearing
522
	  * the display enable bit are considered display enabled changes.
523
	  * @param enabled The new display enabled state.
524
	  * @param time The moment in emulated time this change occurs.
525
	  */
526
	void updateDisplayEnabled(bool enabled, EmuTime time);
527
528
	/** Used by the VDP to signal sprites enabled changes.
529
	  * @param enabled The new sprites enabled state.
530
	  * @param time The moment in emulated time this change occurs.
531
	  */
532
	void updateSpritesEnabled(bool enabled, EmuTime time);
533
534
	/** Change between VR=0 and VR=1 mode.
535
	  * @param mode false->VR=0 true->VR=1
536
	  * @param time The moment in emulated time this change occurs.
537
	  */
538
	void updateVRMode(bool mode, EmuTime time);
539
540
	void setRenderer(Renderer* renderer, EmuTime time);
541
542
	/** Returns the size of VRAM in bytes
543
	  */
544
	[[nodiscard]] unsigned getSize() const {
545
		return actualSize;
546
	}
547
548
	/** Necessary because of circular dependencies.
549
	  */
550
	void setSpriteChecker(SpriteChecker* newSpriteChecker) {
551
		spriteChecker = newSpriteChecker;
552
	}
553
554
	/** Necessary because of circular dependencies.
555
	  */
556
	void setCmdEngine(VDPCmdEngine* newCmdEngine) {
557
		cmdEngine = newCmdEngine;
558
	}
559
560
	/** TMS99x8 VRAM can be mapped in two ways.
561
	  * See implementation for more details.
562
	  */
563
	void change4k8kMapping(bool mapping8k);
564
565
	/** Only used by debugger
566
	 */
567
	[[nodiscard]] std::span<const uint8_t> getData() const {
568
		return {data.data(), data.size()};
569
	}
570
571
	template<typename Archive>
572
	void serialize(Archive& ar, unsigned version);
573
574
private:
575
	/* Common code of cmdWrite() and cpuWrite()
576
	 */
577
	void writeCommon(unsigned address, uint8_t value, EmuTime time) {
578
		#ifdef DEBUG
579
		assert(time >= vramTime);
580
		vramTime = time;
581
		#endif
582
583
		// Check that VRAM will actually be changed.
584
		// A lot of costly syncs can be saved if the same value is written.
585
		// For example Penguin Adventure always uploads the whole frame,
586
		// even if it is the same as the previous frame.
587
		if (data[address] == value) return;
588
589
		// Subsystem synchronisation should happen before the commit,
590
		// to be able to draw backlog using old state.
591
		bitmapVisibleWindow.notify(address, time);
592
		spriteAttribTable.notify(address, time);
593
		spritePatternTable.notify(address, time);
594
595
		data[address] = value;
596
597
		// Cache dirty marking should happen after the commit,
598
		// otherwise the cache could be re-validated based on old state.
599
600
		// these two seem to be unused
601
		// bitmapCacheWindow.notify(address, time);
602
		// nameTable.notify(address, time);
603
		assert(!bitmapCacheWindow.hasObserver());
604
		assert(!nameTable.hasObserver());
605
606
		// in the past GLRasterizer observed these two, now there are none
607
		assert(!colorTable.hasObserver());
608
		assert(!patternTable.hasObserver());
609
610
		/* TODO:
611
		There seems to be a significant difference between subsystem sync
612
		and cache admin. One example is the code above, the other is
613
		updateWindow, where subsystem sync is interested in windows that
614
		were enabled before (new state doesn't matter), while cache admin
615
		is interested in windows that become enabled (old state doesn't
616
		matter).
617
		Does this mean it makes sense to have separate VRAMWindow like
618
		classes for each category?
619
		Note: In the future, sprites may switch category, or fall in both.
620
		*/
621
	}
622
623
	void setSizeMask(EmuTime time);
624
625
private:
626
	/** VDP this VRAM belongs to.
627
	  */
628
	VDP& vdp;
629
630
	/** VRAM data block.
631
	  */
632
	Ram data;
633
634
	/** Debuggable with mode dependent view on the vram
635
	  *   Screen7/8 are not interleaved in this mode.
636
	  *   This debuggable is also at least 128kB in size (it possibly
637
	  *   contains unmapped regions).
638
	  */
639
	class LogicalVRAMDebuggable final : public SimpleDebuggable {
640
	public:
641
		explicit LogicalVRAMDebuggable(const VDP& vdp);
642
		[[nodiscard]] uint8_t read(unsigned address, EmuTime time) override;
643
		void write(unsigned address, uint8_t value, EmuTime time) override;
644
	private:
645
		unsigned transform(unsigned address);
646
	} logicalVRAMDebug;
647
648
	/** Physical view on the VRAM.
649
	  *   Screen 7/8 are interleaved in this mode. The size of this
650
	  *   debuggable is the same as the actual VRAM size.
651
	  */
652
	struct PhysicalVRAMDebuggable final : SimpleDebuggable {
653
		PhysicalVRAMDebuggable(const VDP& vdp, unsigned actualSize);
654
		[[nodiscard]] uint8_t read(unsigned address, EmuTime time) override;
655
		void write(unsigned address, uint8_t value, EmuTime time) override;
656
	} physicalVRAMDebug;
657
658
	// TODO: Renderer field can be removed, if updateDisplayMode
659
	//       and updateDisplayEnabled are moved back to VDP.
660
	//       Is that a good idea?
661
	Renderer* renderer;
662
663
	VDPCmdEngine* cmdEngine;
664
	SpriteChecker* spriteChecker;
665
666
	/** The last time a CmdEngine write or a CPU read/write occurred.
667
	  * This is only used in a debug build to check if read/writes come
668
	  * in the correct order.
669
	  */
670
	#ifdef DEBUG
671
	EmuTime vramTime = EmuTime::zero();
672
	#endif
673
674
	/** Mask to handle vram mirroring
675
	  * Note: this only handles mirroring at power-of-2 sizes
676
	  *       mirroring of extended VRAM is handled in a different way
677
	  */
678
	unsigned sizeMask;
679
680
	/** Actual size of VRAM. Normally this is in sync with sizeMask, but
681
	  * for 16kb VRAM sizeMask is 32kb-1 while actualSize is only 16kb.
682
	  */
683
	const unsigned actualSize;
684
685
	/** Corresponds to the VR bit (bit 3 in VDP register 8).
686
	  */
687
	bool vrMode;
688
689
public:
690
	VRAMWindow cmdReadWindow;
691
	VRAMWindow cmdWriteWindow;
692
	VRAMWindow nameTable;
693
	VRAMWindow colorTable;
694
	VRAMWindow patternTable;
695
	VRAMWindow bitmapVisibleWindow;
696
	VRAMWindow bitmapCacheWindow;
697
	VRAMWindow spriteAttribTable;
698
	VRAMWindow spritePatternTable;
699
};
700
701
} // namespace openmsx
702
703
#endif