OXYGENHUB
◆ main openmsx / src / video / VDP.cc
75.0 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
e8555c89 Andy McCall 1
/*
2
TODO:
3
- Run more measurements on real MSX to find out how horizontal
4
  scanning interrupt really works.
5
  Finish model and implement it.
6
  Especially test this scenario:
7
  * IE1 enabled, interrupt occurs
8
  * wait until matching line is passed
9
  * disable IE1
10
  * read FH
11
  * read FH
12
  Current implementation would return FH=0 both times.
13
- Check how Z80 should treat interrupts occurring during DI.
14
- Bottom erase suspends display even on overscan.
15
  However, it shows black, not border color.
16
  How to handle this? Currently it is treated as "overscan" which
17
  falls outside of the rendered screen area.
18
*/
19
20
#include "VDP.hh"
21
22
#include "Display.hh"
23
#include "RenderSettings.hh"
24
#include "Renderer.hh"
25
#include "RendererFactory.hh"
26
#include "SpriteChecker.hh"
27
#include "VDPCmdEngine.hh"
28
#include "VDPVRAM.hh"
29
30
#include "EnumSetting.hh"
31
#include "HardwareConfig.hh"
32
#include "MSXCPU.hh"
33
#include "MSXException.hh"
34
#include "MSXMotherBoard.hh"
35
#include "Reactor.hh"
36
#include "TclObject.hh"
37
#include "serialize_core.hh"
38
39
#include "narrow.hh"
40
#include "one_of.hh"
41
#include "unreachable.hh"
42
43
#include <algorithm>
44
#include <cassert>
45
#include <memory>
46
47
namespace openmsx {
48
49
static uint8_t getDelayCycles(const XMLElement& devices)
50
{
51
	if (devices.findChild("T9769")) {
52
		// See https://github.com/openMSX/openMSX/issues/989:
53
		// All tested T9769 variants seem to introduce only 1 extra
54
		// cycle. It is currently unknown what the observable
55
		// differences are between the subtypes.
56
		return 1;
57
	} else if (devices.findChild("S1990")) {
58
		// this case is purely there for backwards compatibility for
59
		// turboR configs which do not have the T9769 tag yet.
60
		return 1;
61
	}
62
	return 0;
63
}
64
65
/** The delay between openMSX's port-#98 access timestamp -- the start of T2 of
66
  * the Z80's I/O machine cycle -- and the moment the V9938 registers the CPU's
67
  * VRAM request. Measured to 28.9 +- 0.2 cycles, so it could also be 28. */
68
static constexpr int CPU_REQUEST_DELAY = 29;
69
70
/** How far after openMSX's port-#98 read timestamp a pending VRAM read may
71
  * still have its access slot and be seen by that IN. The Z80 samples the data
72
  * bus 2.5 T-states after the start of T2 (T2, the I/O wait state, and half of
73
  * T3): 15 cycles. The V9938 has the byte out of the DRAM about 6 cycles into
74
  * the access (RAS, CAS, then two cycles to data). The difference is an
75
  * estimate, not a measurement; the case that matters -- the BIOS's INIR scroll
76
  * in TEXT2, 138 cycles per byte, where the slot table has gaps of up to 100
77
  * cycles -- needs 6. */
78
static constexpr int CPU_READ_SAMPLE_DELAY = 9;
79
80
/** A CPU VRAM request that would have been granted a 'tight' slot costs the
81
  * command engine that slot anyway, to a dummy read, if it arrives this many
82
  * cycles before it. */
83
/** The access grid switches to the display tables 162 cycles into the line
84
  * before the display area, which is 1368 + (202 - 162) cycles before
85
  * 'displayStartSyncTime', and back 162 cycles into the line after it, which is
86
  * (202 - 162) cycles before 'vScanSyncTime'. (Both those sync points are at
87
  * cycle 202 of their line, where VR flips.) */
88
static constexpr int SLOT_TABLE_START_LEAD = VDP::TICKS_PER_LINE + 202 - 162;
89
static constexpr int SLOT_TABLE_END_LEAD = 202 - 162;
90
91
static constexpr int DUMMY_WINDOW_LO = 18;
92
static constexpr int DUMMY_WINDOW_HI = 22;
93
94
VDP::VDP(const DeviceConfig& config)
95
	: MSXDevice(config)
96
	, syncVSync(*this)
97
	, syncLineCountReset(*this)
98
	, syncDisplayStart(*this)
99
	, syncSlotTableStart(*this)
100
	, syncSlotTableEnd(*this)
101
	, syncVScan(*this)
102
	, syncHScan(*this)
103
	, syncHorAdjust(*this)
104
	, syncSetMode(*this)
105
	, syncSetBlank(*this)
106
	, syncSetSprites(*this)
107
	, syncCpuVramAccess(*this)
108
	, syncCpuVramDummy(*this)
109
	, syncCmdDone(*this)
110
	, display(getReactor().getDisplay())
111
	, cmdTiming    (display.getRenderSettings().getCmdTimingSetting())
112
	, tooFastAccess(display.getRenderSettings().getTooFastAccessSetting())
113
	, vdpRegDebug      (*this)
114
	, vdpStatusRegDebug(*this)
115
	, vdpPaletteDebug  (*this)
116
	, vramPointerDebug (*this)
117
	, registerLatchStatusDebug(*this)
118
	, vramAccessStatusDebug(*this)
119
	, paletteLatchStatusDebug(*this)
120
	, dataLatchDebug   (*this)
121
	, frameCountInfo   (*this)
122
	, cycleInFrameInfo (*this)
123
	, lineInFrameInfo  (*this)
124
	, cycleInLineInfo  (*this)
125
	, msxYPosInfo      (*this)
126
	, msxX256PosInfo   (*this)
127
	, msxX512PosInfo   (*this)
128
	, frameStartTime(getCurrentTime())
129
	, irqVertical  (getMotherBoard(), getName() + ".IRQvertical",   config)
130
	, irqHorizontal(getMotherBoard(), getName() + ".IRQhorizontal", config)
131
	, lineCountResetSyncTime(getCurrentTime())
132
	, displayStartSyncTime(getCurrentTime())
133
	, slotTableStartSyncTime(getCurrentTime())
134
	, slotTableEndSyncTime(getCurrentTime())
135
	, vScanSyncTime(getCurrentTime())
136
	, hScanSyncTime(getCurrentTime())
137
	, tooFastCallback(
138
		getCommandController(),
139
		getName() + ".too_fast_vram_access_callback",
140
		"Tcl proc called when the VRAM is read or written too fast",
141
		"",
142
		Setting::Save::YES)
143
	, dotClockDirectionCallback(
144
		getCommandController(),
145
		getName() + ".dot_clock_direction_callback",
146
		"Tcl proc called when DLCLK is set as input",
147
		"default_dot_clock_direction_callback",
148
		Setting::Save::YES)
149
	, cpu(getCPU()) // used frequently, so cache it
150
	, fixedVDPIOdelayCycles(getDelayCycles(getMotherBoard().getMachineConfig()->getConfig().getChild("devices")))
151
{
152
	// Current general defaults for saturation:
153
	// - Any MSX with a TMS9x18 VDP: SatPr=SatPb=100%
154
	// - Other machines with a TMS9x2x VDP and RGB output:
155
	//   SatPr=SatPr=100%, until someone reports a better value
156
	// - Other machines with a TMS9x2x VDP and CVBS only output:
157
	//   SatPr=SatPb=54%, until someone reports a better value
158
	// At this point we don't know anything about the connector here, so
159
	// only the first point can be implemented. The default default is
160
	// still 54, which is very similar to the old palette implementation
161
162
	int defaultSaturation = 54;
163
164
	const auto& versionString = config.getChildData("version");
165
	if (versionString == "TMS99X8A") version = TMS99X8A;
166
	else if (versionString == "TMS9918A") {
167
		version = TMS99X8A;
168
		defaultSaturation = 100;
169
	} else if (versionString == "TMS9928A") version = TMS99X8A;
170
	else if (versionString == "T6950PAL") version = T6950PAL;
171
	else if (versionString == "T6950NTSC") version = T6950NTSC;
172
	else if (versionString == "T7937APAL") version = T7937APAL;
173
	else if (versionString == "T7937ANTSC") version = T7937ANTSC;
174
	else if (versionString == "TMS91X8") version = TMS91X8;
175
	else if (versionString == "TMS9118") {
176
		version = TMS91X8;
177
		defaultSaturation = 100;
178
	} else if (versionString == "TMS9128") version = TMS91X8;
179
	else if (versionString == "TMS9929A") version = TMS9929A;
180
	else if (versionString == "TMS9129") version = TMS9129;
181
	else if (versionString == "V9938") version = V9938;
182
	else if (versionString == "V9958") version = V9958;
183
	else if (versionString == "YM2220PAL") version = YM2220PAL;
184
	else if (versionString == "YM2220NTSC") version = YM2220NTSC;
185
	else throw MSXException("Unknown VDP version \"", versionString, '"');
186
187
	// saturation parameters only make sense when using TMS VDPs
188
	if (!versionString.starts_with("TMS") &&
189
	    (config.findChild("saturationPr") || config.findChild("saturationPb") || config.findChild("saturation"))) {
190
		throw MSXException("Specifying saturation parameters only makes sense for TMS VDPs");
191
	}
192
193
	auto getPercentage = [&](std::string_view name, std::string_view extra, int defaultValue) {
194
		int result = config.getChildDataAsInt(name, defaultValue);
195
		if ((result < 0) || (result > 100)) {
196
			throw MSXException(
197
				"Saturation percentage ", extra, "is not in range 0..100: ", result);
198
		}
199
		return result;
200
	};
201
	int saturation = getPercentage("saturation", "", defaultSaturation);
202
	saturationPr   = getPercentage("saturationPr", "for Pr component ", saturation);
203
	saturationPb   = getPercentage("saturationPb", "for Pb component ", saturation);
204
205
	// Set up control register availability.
206
	static constexpr std::array<uint8_t, 32> VALUE_MASKS_MSX1 = {
207
		0x03, 0xFB, 0x0F, 0xFF, 0x07, 0x7F, 0x07, 0xFF  // 00..07
208
	};
209
	static constexpr std::array<uint8_t, 32> VALUE_MASKS_MSX2 = {
210
		0x7E, 0x7F, 0x7F, 0xFF, 0x3F, 0xFF, 0x3F, 0xFF, // 00..07
211
		0xFB, 0xBF, 0x07, 0x03, 0xFF, 0xFF, 0x07, 0x0F, // 08..15
212
		0x0F, 0xBF, 0xFF, 0xFF, 0x3F, 0x3F, 0x3F, 0xFF, // 16..23
213
		0,    0,    0,    0,    0,    0,    0,    0,    // 24..31
214
	};
215
	controlRegMask = isMSX1VDP() ? 0x07 : 0x3F;
216
	controlValueMasks = isMSX1VDP() ? VALUE_MASKS_MSX1 : VALUE_MASKS_MSX2;
217
	if (version == V9958) {
218
		// Enable V9958-specific control registers.
219
		controlValueMasks[25] = 0x7F;
220
		controlValueMasks[26] = 0x3F;
221
		controlValueMasks[27] = 0x07;
222
	}
223
224
	resetInit(); // must be done early to avoid UMRs
225
226
	// Video RAM.
227
	EmuTime time = getCurrentTime();
228
	unsigned vramSize =
229
		(isMSX1VDP() ? 16 : config.getChildDataAsInt("vram", 0));
230
	if (vramSize != one_of(16u, 64u, 128u, 192u)) {
231
		throw MSXException(
232
			"VRAM size of ", vramSize, "kB is not supported!");
233
	}
234
	vram = std::make_unique<VDPVRAM>(*this, vramSize * 1024, time);
235
236
	// Create sprite checker.
237
	auto& renderSettings = display.getRenderSettings();
238
	spriteChecker = std::make_unique<SpriteChecker>(*this, renderSettings, time);
239
	vram->setSpriteChecker(spriteChecker.get());
240
241
	// Create command engine.
242
	cmdEngine = std::make_unique<VDPCmdEngine>(*this, getCommandController());
243
	vram->setCmdEngine(cmdEngine.get());
244
245
	// Initialise renderer.
246
	createRenderer();
247
248
	// Reset state.
249
	powerUp(time);
250
251
	display      .attach(*this);
252
	cmdTiming    .attach(*this);
253
	tooFastAccess.attach(*this);
254
	update(tooFastAccess); // handles both cmdTiming and tooFastAccess
255
}
256
257
VDP::~VDP()
258
{
259
	tooFastAccess.detach(*this);
260
	cmdTiming    .detach(*this);
261
	display      .detach(*this);
262
}
263
264
void VDP::preVideoSystemChange() noexcept
265
{
266
	renderer.reset();
267
}
268
269
void VDP::postVideoSystemChange() noexcept
270
{
271
	createRenderer();
272
}
273
274
void VDP::createRenderer()
275
{
276
	renderer = RendererFactory::createRenderer(*this, display);
277
	// TODO: Is it safe to use frameStartTime,
278
	//       which is most likely in the past?
279
	//renderer->reset(frameStartTime.getTime());
280
	vram->setRenderer(renderer.get(), frameStartTime.getTime());
281
}
282
283
PostProcessor* VDP::getPostProcessor() const
284
{
285
	return renderer->getPostProcessor();
286
}
287
288
void VDP::resetInit()
289
{
290
	// note: vram, spriteChecker, cmdEngine, renderer may not yet be
291
	//       created at this point
292
	std::ranges::fill(controlRegs, 0);
293
	if (isVDPwithPALonly()) {
294
		// Boots (and remains) in PAL mode, all other VDPs boot in NTSC.
295
		controlRegs[9] |= 0x02;
296
	}
297
	// According to page 6 of the V9938 data book the color burst registers
298
	// are loaded with these values at power on.
299
	controlRegs[21] = 0x3B;
300
	controlRegs[22] = 0x05;
301
	// Note: frameStart is the actual place palTiming is written, but it
302
	//       can be read before frameStart is called.
303
	//       TODO: Clean up initialisation sequence.
304
	palTiming = true; // controlRegs[9] & 0x02;
305
	displayMode.reset();
306
	vramPointer = 0;
307
	cpuVramData = 0;
308
	cpuVramReqIsRead = false; // avoid UMR
309
	cpuVramReqData = 0;
310
	dataLatch = 0;
311
	cpuExtendedVram = false;
312
	registerDataStored = false;
313
	writeAccess = false;
314
	paletteDataStored = false;
315
	blinkState = false;
316
	blinkCount = 0;
317
	horizontalAdjust = 7;
318
	// Placeholders until the first display line counter reset latches the
319
	// real values, see execLineCountReset().
320
	displayStart = (8 + getVerticalAdjust()
321
	                + ((controlRegs[9] & 0x02) ? 54 : 27)) * TICKS_PER_LINE
322
	             + 100 + 102;
323
	horizontalScanOffset = -1000 * TICKS_PER_LINE; // "never"
324
325
	// TODO: Real VDP probably resets timing as well.
326
	isDisplayArea = false;
327
	isSlotTableDisplayArea = false;
328
	displayEnabled = false;
329
	spriteEnabled = true;
330
	superimposing = nullptr;
331
	externalVideo = nullptr;
332
333
	// Init status registers.
334
	statusReg0 = 0x00;
335
	statusReg1 = (version == V9958 ? 0x04 : 0x00);
336
	statusReg2 = 0x0C;
337
338
	// Update IRQ to reflect new register values.
339
	irqVertical.reset();
340
	irqHorizontal.reset();
341
342
	// From appendix 8 of the V9938 data book (page 148).
343
	const std::array<uint16_t, 16> V9938_PALETTE = {
344
		0x000, 0x000, 0x611, 0x733, 0x117, 0x327, 0x151, 0x627,
345
		0x171, 0x373, 0x661, 0x664, 0x411, 0x265, 0x555, 0x777
346
	};
347
	// Init the palette.
348
	palette = V9938_PALETTE;
349
}
350
351
void VDP::resetMasks(EmuTime time)
352
{
353
	updateNameBase(time);
354
	updateColorBase(time);
355
	updatePatternBase(time);
356
	updateSpriteAttributeBase(time);
357
	updateSpritePatternBase(time);
358
	// TODO: It is not clear to me yet how bitmapWindow should be used.
359
	//       Currently it always spans 128K of VRAM.
360
	//vram->bitmapWindow.setMask(~(~0u << 17), ~0u << 17, time);
361
}
362
363
void VDP::powerUp(EmuTime time)
364
{
365
	vram->clear();
366
	reset(time);
367
}
368
369
void VDP::reset(EmuTime time)
370
{
371
	syncVSync        .removeSyncPoint();
372
	syncLineCountReset.removeSyncPoint();
373
	syncDisplayStart .removeSyncPoint();
374
	syncSlotTableStart.removeSyncPoint();
375
	syncSlotTableEnd .removeSyncPoint();
376
	syncVScan        .removeSyncPoint();
377
	syncHScan        .removeSyncPoint();
378
	syncHorAdjust    .removeSyncPoint();
379
	syncSetMode      .removeSyncPoint();
380
	syncSetBlank     .removeSyncPoint();
381
	syncSetSprites   .removeSyncPoint();
382
	syncCpuVramAccess.removeSyncPoint();
383
	syncCpuVramDummy .removeSyncPoint();
384
	syncCmdDone      .removeSyncPoint();
385
	pendingCpuAccess = false;
386
	previousCpuSlot = EmuTime::zero();
387
	previousCpuSlotIsLate = false;
388
	secondCpuSlot = EmuTime::infinity();
389
390
	// Reset subsystems.
391
	cmdEngine->sync(time);
392
	resetInit();
393
	spriteChecker->reset(time);
394
	cmdEngine->reset(time);
395
	renderer->reInit();
396
397
	// Tell the subsystems of the new mask values.
398
	resetMasks(time);
399
400
	// Init scheduling.
401
	frameCount = -1;
402
	frameStartTime.reset(time); // no previous frame to carry over from
403
	frameStart(time);
404
	assert(frameCount == 0);
405
}
406
407
void VDP::execVSync(EmuTime time)
408
{
409
	// This frame is finished.
410
	// Inform VDP subcomponents.
411
	// TODO: Do this via VDPVRAM?
412
	renderer->frameEnd(time);
413
	spriteChecker->frameEnd(time);
414
415
	if (isFastBlinkEnabled()) {
416
		// adjust blinkState and blinkCount for next frame
417
		auto next = calculateLineBlinkState(getLinesPerFrame());
418
		blinkState = next.state;
419
		blinkCount = next.count;
420
	}
421
422
	// Finish the previous frame, because access-slot calculations work within a frame.
423
	cmdEngine->sync(time);
424
425
	// Start next frame.
426
	frameStart(time);
427
}
428
429
void VDP::setSlotTableDisplayArea(bool enabled, EmuTime time)
430
{
431
	if (isSlotTableDisplayArea == enabled) return;
432
	// A command must not use the new grid for the accesses it still owes on
433
	// the old one.
434
	if (displayEnabled) cmdEngine->sync(time);
435
	isSlotTableDisplayArea = enabled;
436
}
437
438
void VDP::execDisplayStart(EmuTime time)
439
{
440
	// The access grid normally switched over a line ago. It can still be off
441
	// if the display start moved inside that line, because then the sync
442
	// point that switches it was rescheduled into the past.
443
	setSlotTableDisplayArea(true, time);
444
445
	// Display area starts here, unless we're doing overscan and it
446
	// was already active.
447
	if (!isDisplayArea) {
448
		if (displayEnabled) {
449
			vram->updateDisplayEnabled(true, time);
450
		}
451
		isDisplayArea = true;
452
	}
453
}
454
455
void VDP::execSlotTableStart(EmuTime time)
456
{
457
	// The VDP starts fetching the sprite data of the first display line here,
458
	// and the access grid follows the fetching rather than the display.
459
	setSlotTableDisplayArea(true, time);
460
}
461
462
void VDP::execSlotTableEnd(EmuTime time)
463
{
464
	setSlotTableDisplayArea(false, time);
465
}
466
467
void VDP::execVScan(EmuTime time)
468
{
469
	// Same the other way round: the grid normally switched back 40 cycles
470
	// ago. (In overscan this is not reached at all, and the grid correctly
471
	// stays on across the frame boundary.)
472
	setSlotTableDisplayArea(false, time);
473
474
	// VSCAN is the end of display.
475
	// This will generate a VBLANK IRQ. Typically MSX software will
476
	// poll the keyboard/joystick on this IRQ. So now is a good
477
	// time to also poll for host events.
478
	getReactor().enterMainLoop();
479
480
	if (isDisplayEnabled()) {
481
		vram->updateDisplayEnabled(false, time);
482
	}
483
	isDisplayArea = false;
484
485
	// Vertical scanning occurs.
486
	statusReg0 |= 0x80;
487
	if (controlRegs[1] & 0x20) {
488
		irqVertical.set();
489
	}
490
}
491
492
void VDP::execHScan()
493
{
494
	// Horizontal scanning occurs.
495
	if (controlRegs[0] & 0x10) {
496
		irqHorizontal.set();
497
	}
498
}
499
500
void VDP::execHorAdjust(EmuTime time)
501
{
502
	int newHorAdjust = (controlRegs[18] & 0x0F) ^ 0x07;
503
	if (controlRegs[25] & 0x08) {
504
		newHorAdjust += 4;
505
	}
506
	renderer->updateHorizontalAdjust(newHorAdjust, time);
507
	horizontalAdjust = newHorAdjust;
508
}
509
510
int VDP::getScrollBurstTicks(EmuTime time) const
511
{
512
	// The new value is used from the first 8-pixel group that starts at
513
	// least LEAD ticks after the write (the burst for that group is fetched
514
	// ahead of the beam).
515
	static constexpr int LEAD = 16;
516
	static constexpr int GROUP = 8 * 4; // 8 pixels of 4 ticks
517
	if (!isDisplayEnabled() || !getDisplayMode().isBitmapMode()) return -1;
518
	int ticks = getTicksThisFrame(time);
519
	int lineStart = ticks - ticks % TICKS_PER_LINE;
520
	int x = ticks - lineStart - getLeftSprites(); // ticks into display
521
	if (x + LEAD <= 0) return -1;
522
	return lineStart + getLeftSprites() + (x + LEAD + GROUP - 1) / GROUP * GROUP;
523
}
524
525
void VDP::execSetMode(EmuTime time)
526
{
527
	updateDisplayMode(
528
		DisplayMode(controlRegs[0], controlRegs[1], controlRegs[25]),
529
		getCmdBit(),
530
		time);
531
}
532
533
void VDP::execSetBlank(EmuTime time)
534
{
535
	bool newDisplayEnabled = (controlRegs[1] & 0x40) != 0;
536
	if (isDisplayArea) {
537
		vram->updateDisplayEnabled(newDisplayEnabled, time);
538
	}
539
	displayEnabled = newDisplayEnabled;
540
}
541
542
void VDP::execSetSprites(EmuTime time)
543
{
544
	bool newSpriteEnabled = (controlRegs[8] & 0x02) == 0;
545
	vram->updateSpritesEnabled(newSpriteEnabled, time);
546
	spriteEnabled = newSpriteEnabled;
547
}
548
549
void VDP::execCpuVramAccess(EmuTime time)
550
{
551
	pendingCpuAccess = false;
552
	executeCpuVramAccess(time);
553
	if (secondCpuSlot != EmuTime::infinity()) [[unlikely]] {
554
		// A request accepted just before this access now gets its turn.
555
		cpuVramReqIsRead = secondCpuVramReqIsRead;
556
		cpuVramReqData = secondCpuVramReqData;
557
		pendingCpuAccess = true;
558
		syncCpuVramAccess.setSyncPoint(secondCpuSlot);
559
		secondCpuSlot = EmuTime::infinity();
560
	}
561
}
562
563
void VDP::execSyncCmdDone(EmuTime time)
564
{
565
	cmdEngine->sync(time);
566
}
567
568
// The vertical timing of the V9938 hangs off one display line counter. It is
569
// reset once per frame, in the top border, when the raster line plus the
570
// vertical adjust equals 15 (so at line 8 + getVerticalAdjust()), to a
571
// negative value that depends on the 50/60Hz and 192/212-lines bits of R#9
572
// *as they are at that moment*. From there it counts up once per line:
573
//  - display starts when it reaches 0,
574
//  - the display area ends when it equals 192 or 212, compared against R#9
575
//    bit 7 as it is then (so if the bit is changed after the counter passed
576
//    the new value the display simply does not end: overscan),
577
//  - the line interrupt fires when it equals R#19 - R#23.
578
// Nothing about it changes until the next reset, so it keeps counting into
579
// the top border of the next frame.
580
// This was measured on FS-A1GT and FS-A1ST with the test program from PR
581
// #2191, and matches the V9958 die-shot reconstruction (IKA9958_st.sv,
582
// "vertical-auxiliary counter") in https://github.com/ika-musume/IKA9958.
583
void VDP::scheduleLineCountReset(EmuTime time)
584
{
585
	// The reset happens at the start of the processing of the line, see
586
	// syncAtNextLine().
587
	int ticks = (8 + getVerticalAdjust()) * TICKS_PER_LINE
588
	          + 144 + (horizontalAdjust - 7) * 4;
589
	EmuTime resetTime = frameStartTime + ticks;
590
	if (resetTime <= time) {
591
		// Moved behind us by an adjust change. If a reset is still
592
		// pending at its old position, leave it there.
593
		// (The real chip would then skip the reset for this frame,
594
		// not emulated.)
595
		return;
596
	}
597
	if (lineCountResetSyncTime > time) {
598
		syncLineCountReset.removeSyncPoint();
599
	}
600
	lineCountResetSyncTime = resetTime;
601
	syncLineCountReset.setSyncPoint(resetTime);
602
}
603
604
void VDP::execLineCountReset(EmuTime time)
605
{
606
	// Remove pending DISPLAY_START sync point, if any.
607
	if (displayStartSyncTime > time) {
608
		syncDisplayStart.removeSyncPoint();
609
	}
610
	if (slotTableStartSyncTime > time) {
611
		syncSlotTableStart.removeSyncPoint();
612
	}
613
614
	// Display line zero moves here. In overscan the display is still on,
615
	// so render and check sprites up to this point against the old one.
616
	renderer->updateLineZero(time);
617
	spriteChecker->sync(time);
618
619
	// Lines from the reset to display line zero, i.e. minus the value the
620
	// counter is reset to:
621
	//   60Hz: 17 (212 lines) or 27 (192 lines)
622
	//   50Hz: 44 (212 lines) or 54 (192 lines)
623
	int linesToDisplay = ((controlRegs[9] & 0x02) ? 44 : 17)
624
	                   + ((controlRegs[9] & 0x80) ?  0 : 10);
625
	int resetLine = getTicksThisFrame(time) / TICKS_PER_LINE;
626
	displayStart =
627
		(resetLine + linesToDisplay) * TICKS_PER_LINE
628
		+ 100 + 102; // VR flips at start of left border
629
	displayStartSyncTime = frameStartTime + displayStart;
630
	syncDisplayStart.setSyncPoint(displayStartSyncTime);
631
632
	// The access grid switches over 162 cycles into the line before this one.
633
	slotTableStartSyncTime = displayStartSyncTime -
634
	                         VDPClock::duration(SLOT_TABLE_START_LEAD);
635
	if (slotTableStartSyncTime > time) {
636
		syncSlotTableStart.setSyncPoint(slotTableStartSyncTime);
637
	}
638
639
	// HSCAN and VSCAN are relative to display start.
640
	scheduleHScan(time);
641
	scheduleVScan(time);
642
}
643
644
void VDP::scheduleVScan(EmuTime time)
645
{
646
	// Remove pending VSCAN sync point, if any.
647
	if (vScanSyncTime > time) {
648
		syncVScan.removeSyncPoint();
649
		//cerr << "removing predicted VSCAN sync point\n";
650
	}
651
	if (slotTableEndSyncTime > time) {
652
		syncSlotTableEnd.removeSyncPoint();
653
	}
654
655
	// Display end is where the display line counter equals the number of
656
	// lines selected by R#9 bit 7 as it is right now.
657
	int offset = displayStart + getNumberOfLines() * TICKS_PER_LINE;
658
	if (offset < 0) {
659
		// That moment lies before this frame (the counter is still the
660
		// previous frame's), so there is no display end until the next
661
		// counter reset schedules one.
662
		vScanSyncTime = slotTableEndSyncTime = time;
663
		return;
664
	}
665
	vScanSyncTime = frameStartTime + offset;
666
667
	// Register new VSCAN sync point. If the counter already passed the
668
	// value there is none this frame: overscan.
669
	if (vScanSyncTime > time) {
670
		syncVScan.setSyncPoint(vScanSyncTime);
671
		//cerr << "inserting new VSCAN sync point\n";
672
	}
673
674
	// The access grid switches back 162 cycles into the line after this one.
675
	slotTableEndSyncTime = vScanSyncTime - VDPClock::duration(SLOT_TABLE_END_LEAD);
676
	if (slotTableEndSyncTime > time) {
677
		syncSlotTableEnd.setSyncPoint(slotTableEndSyncTime);
678
	}
679
}
680
681
void VDP::scheduleHScan(EmuTime time)
682
{
683
	// Remove pending HSCAN sync point, if any.
684
	if (hScanSyncTime > time) {
685
		syncHScan.removeSyncPoint();
686
		hScanSyncTime = time;
687
	}
688
689
	// The match is where the display line counter equals R#19 - R#23. The
690
	// counter started at 'displayStart' and keeps counting until the next
691
	// reset, so the match sits at a fixed distance from the display start,
692
	// possibly in the top border of the next frame. If that moment lies at
693
	// or after the next reset, the counter never gets there.
694
	horizontalScanOffset = displayStart - (100 + 102)
695
		+ ((controlRegs[19] - controlRegs[23]) & 0xFF) * TICKS_PER_LINE
696
		+ getRightBorder();
697
	if (horizontalScanOffset < 0) {
698
		// Before this frame, so already behind us. (Possible while the
699
		// counter is still the previous frame's.)
700
		return;
701
	}
702
	EmuTime matchTime = frameStartTime + horizontalScanOffset;
703
	EmuTime nextResetTime = (time < lineCountResetSyncTime)
704
		? lineCountResetSyncTime
705
		// Assumes the adjust and 50/60Hz settings don't change before
706
		// then. Close enough.
707
		: lineCountResetSyncTime + VDPClock::duration(getTicksPerFrame());
708
	if (matchTime >= nextResetTime) {
709
		// This is one way to say "never".
710
		horizontalScanOffset = -1000 * TICKS_PER_LINE;
711
		return;
712
	}
713
714
	// Register new HSCAN sync point if interrupt is enabled.
715
	if ((controlRegs[0] & 0x10) && (matchTime > time)) {
716
		hScanSyncTime = matchTime;
717
		syncHScan.setSyncPoint(hScanSyncTime);
718
	}
719
}
720
721
// TODO: inline?
722
// TODO: Is it possible to get rid of this routine and its sync point?
723
//       VSYNC, HSYNC and DISPLAY_START could be scheduled for the next
724
//       frame when their callback occurs.
725
//       But I'm not sure how to handle the PAL/NTSC setting (which also
726
//       influences the frequency at which E/O toggles).
727
void VDP::frameStart(EmuTime time)
728
{
729
	++frameCount;
730
731
	// Toggle E/O.
732
	// Actually this should occur half a line earlier,
733
	// but for now this is accurate enough.
734
	statusReg2 ^= 0x02;
735
736
	// Settings which are fixed at start of frame.
737
	// Not sure this is how real MSX does it, but close enough for now.
738
	// TODO: Interlace is effectuated in border height, according to
739
	//       the data book. Exactly when is the fixation point?
740
	palTiming = (controlRegs[9] & 0x02) != 0;
741
	interlaced = !isFastBlinkEnabled() && ((controlRegs[9] & 0x08) != 0);
742
743
	// Blinking.
744
	if ((blinkCount != 0) && !isFastBlinkEnabled()) { // counter active?
745
		blinkCount--;
746
		if (blinkCount == 0) {
747
			renderer->updateBlinkState(!blinkState, time);
748
			blinkState = !blinkState;
749
			blinkCount = (blinkState
750
				? controlRegs[13] >> 4 : controlRegs[13] & 0x0F) * 10;
751
		}
752
	}
753
754
	// TODO: Presumably this is done here
755
	// Note that if superimposing is enabled but no external video
756
	// signal is provided then the VDP stops producing a signal
757
	// (at least on an MSX1, VDP(0)=1 produces "signal lost" on my
758
	// monitor)
759
	if (const RawFrame* newSuperimposing = (controlRegs[0] & 1) ? externalVideo : nullptr;
760
	    superimposing != newSuperimposing) {
761
		superimposing = newSuperimposing;
762
		renderer->updateSuperimposing(superimposing, time);
763
	}
764
765
	// The display line counter does not notice the frame boundary: it
766
	// keeps counting from the previous frame's display start until this
767
	// frame's reset (see execLineCountReset()). Keep 'displayStart' and
768
	// 'horizontalScanOffset' pointing at the same moments, now expressed
769
	// relative to the new frame start.
770
	int elapsed = getTicksThisFrame(time);
771
	displayStart -= elapsed;
772
	horizontalScanOffset -= elapsed;
773
774
	// Schedule next VSYNC.
775
	frameStartTime.reset(time);
776
	syncVSync.setSyncPoint(frameStartTime + getTicksPerFrame());
777
	// Schedule the display line counter reset in the top border, which in
778
	// turn schedules DISPLAY_START, VSCAN and HSCAN.
779
	scheduleLineCountReset(time);
780
781
	// Inform VDP subcomponents.
782
	// TODO: Do this via VDPVRAM?
783
	renderer->frameStart(time);
784
	spriteChecker->frameStart(time);
785
786
	/*
787
	   cout << "--> frameStart = " << frameStartTime
788
		<< ", frameEnd = " << (frameStartTime + getTicksPerFrame())
789
		<< ", hscan = " << hScanSyncTime
790
		<< ", displayStart = " << displayStart
791
		<< ", timing: " << (palTiming ? "PAL"sv : "NTSC"sv)
792
		<< "\n";
793
	*/
794
}
795
796
// The I/O functions.
797
798
void VDP::writeIO(uint16_t port, uint8_t value, EmuTime time_)
799
{
800
	EmuTime time = time_;
801
	// This is the (fixed) delay from
802
	// https://github.com/openMSX/openMSX/issues/563 and
803
	// https://github.com/openMSX/openMSX/issues/989
804
	// It seems to originate from the T9769x and for x=C the delay is 1
805
	// cycle and for other x it seems the delay is 2 cycles
806
	if (fixedVDPIOdelayCycles > 0) {
807
		time = cpu.waitCyclesZ80(time, fixedVDPIOdelayCycles);
808
	}
809
810
	assert(isInsideFrame(time));
811
	switch (port & (isMSX1VDP() ? 0x01 : 0x03)) {
812
	case 0: // VRAM data write
813
		vramWrite(value, time);
814
		registerDataStored = false;
815
		break;
816
	case 1: // Register or address write
817
		if (registerDataStored) {
818
			if (value & 0x80) {
819
				if (!(value & 0x40) || isMSX1VDP()) {
820
					// Register write.
821
					changeRegister(
822
						value & controlRegMask,
823
						dataLatch,
824
						time);
825
				} else {
826
					// TODO what happens in this case?
827
					// it's not a register write because
828
					// that breaks "SNOW26" demo
829
				}
830
				if (isMSX1VDP()) {
831
					// For these VDPs the VRAM pointer is modified when
832
					// writing to VDP registers. Without this some demos won't
833
					// run as on real MSX1, e.g. Planet of the Epas, Utopia and
834
					// Waves 1.2. Thanks to dvik for finding this out.
835
					// See also below about not using the latch on MSX1.
836
					// Set read/write address.
837
					vramPointer = (value << 8 | (vramPointer & 0xFF)) & 0x3FFF;
838
				}
839
			} else {
840
				// Set read/write address.
841
				writeAccess = value & 0x40;
842
				vramPointer = (value << 8 | dataLatch) & 0x3FFF;
843
				if (!(value & 0x40)) {
844
					// Read ahead.
845
					(void)vramRead(time);
846
				}
847
			}
848
			registerDataStored = false;
849
		} else {
850
			// Note: on MSX1 there seems to be no
851
			// latch used, but VDP address writes
852
			// are done directly.
853
			// Thanks to hap for finding this out. :)
854
			if (isMSX1VDP()) {
855
				vramPointer = (vramPointer & 0x3F00) | value;
856
			}
857
			dataLatch = value;
858
			registerDataStored = true;
859
		}
860
		break;
861
	case 2: // Palette data write
862
		if (paletteDataStored) {
863
			unsigned index = controlRegs[16];
864
			uint16_t grb = ((value << 8) | dataLatch) & 0x777;
865
			setPalette(index, grb, time);
866
			controlRegs[16] = (index + 1) & 0x0F;
867
			paletteDataStored = false;
868
		} else {
869
			dataLatch = value;
870
			paletteDataStored = true;
871
		}
872
		break;
873
	case 3: { // Indirect register write
874
		dataLatch = value;
875
		// TODO: What happens if reg 17 is written indirectly?
876
		//fprintf(stderr, "VDP indirect register write: %02X\n", value);
877
		uint8_t regNr = controlRegs[17];
878
		changeRegister(regNr & 0x3F, value, time);
879
		if ((regNr & 0x80) == 0) {
880
			// Auto-increment.
881
			controlRegs[17] = (regNr + 1) & 0x3F;
882
		}
883
		break;
884
	}
885
	}
886
}
887
888
std::string_view VDP::getVersionString() const
889
{
890
	// Add VDP type from the config. An alternative is to convert the
891
	// 'version' enum member into some kind of string, but we already
892
	// parsed that string to become this version enum value. So whatever is
893
	// in there, it makes sense. So we can just as well return that then.
894
	const auto* vdpVersionString = getDeviceConfig().findChild("version");
895
	assert(vdpVersionString);
896
	return vdpVersionString->getData();
897
}
898
899
void VDP::getExtraDeviceInfo(TclObject& result) const
900
{
901
	result.addDictKeyValues("version", getVersionString());
902
}
903
904
uint8_t VDP::peekRegister(unsigned address, EmuTime time) const
905
{
906
	if (address < 0x20) {
907
		return controlRegs[address];
908
	} else if (address < 0x2F) {
909
		return cmdEngine->peekCmdReg(narrow<uint8_t>(address - 0x20), time);
910
	} else {
911
		return 0xFF;
912
	}
913
}
914
915
void VDP::setPalette(unsigned index, uint16_t grb, EmuTime time)
916
{
917
	if (palette[index] != grb) {
918
		renderer->updatePalette(index, grb, time);
919
		palette[index] = grb;
920
	}
921
}
922
923
void VDP::vramWrite(uint8_t value, EmuTime time)
924
{
925
	scheduleCpuVramAccess(false, value, time);
926
}
927
928
uint8_t VDP::vramRead(EmuTime time)
929
{
930
	if (pendingCpuAccess) [[unlikely]] {
931
		if (allowTooFastAccess) {
932
			// In this mode no access may be lost, so a read has to see
933
			// the result of the access before it even when that access
934
			// has not had its slot yet.
935
			flushCpuVramAccesses(time);
936
		} else if (cpuVramReqIsRead &&
937
		           (*syncCpuVramAccess.isPending() <=
938
		            (time + VDPClock::duration(CPU_READ_SAMPLE_DELAY)))) {
939
			// The read before this one has its slot only just after
940
			// 'time', but still before the Z80 samples the data bus, so
941
			// this IN does see its result.
942
			syncCpuVramAccess.removeSyncPoint();
943
			execCpuVramAccess(time);
944
		}
945
	}
946
	// Return the result from a previous read.
947
	uint8_t result = cpuVramData;
948
949
	uint8_t dummy = 0;
950
	scheduleCpuVramAccess(true, dummy, time); // schedule next read
951
	return result;
952
}
953
954
void VDP::scheduleCpuVramAccess(bool isRead, uint8_t write, EmuTime time)
955
{
956
	// Tested on real V9938: 'cpuVramData' is shared between read and write.
957
	// E.g. OUT (#98),A followed by IN A,(#98) returns the just written value.
958
	// It is written by the port access itself, so also by a request that the
959
	// VDP goes on to lose.
960
	if (!isRead) cpuVramData = write;
961
962
	if (isMSX1VDP()) {
963
		scheduleTMS99x8VramAccess(isRead, time);
964
	} else {
965
		scheduleV99x8VramAccess(isRead, time);
966
	}
967
}
968
969
void VDP::scheduleTMS99x8VramAccess(bool isRead, EmuTime time)
970
{
971
	if (pendingCpuAccess) [[unlikely]] {
972
		if (!allowTooFastAccess) {
973
			// The old request has been overwritten by the new request!
974
			tooFastCallback.execute();
975
			cpuVramReqIsRead = isRead;
976
			cpuVramReqData = cpuVramData;
977
			return;
978
		}
979
		// In 'ignore' mode no access may be lost, so give the one that is
980
		// still waiting its turn now. (And don't report it: this mode is
981
		// selected precisely to not care.)
982
		flushCpuVramAccesses(time);
983
	}
984
985
	// For TMS99x8 the situation is less clear than for V99x8, see
986
	//    doc/internal/vdp-vram-timing/vdp-timing-2.html
987
	// Additional measurements(*) show that picking either 8 or 9
988
	// TMS cycles (equivalent to 32 or 36 V99x8 cycles) gives the
989
	// same result as on a real MSX. This corresponds to
990
	// respectively 1.49us or 1.68us, the TMS documentation
991
	// specifies 2us for this value.
992
	//  (*) In this test we did a lot of OUT operations (writes to
993
	//  VRAM) that are exactly N cycles apart. After the writes we
994
	//  detect whether all were successful by reading VRAM
995
	//  (slowly). We vary N and found that you get corruption for
996
	//  N<=26 cycles, but no corruption occurs for N>=27. This test
997
	//  was done in screen 2 with 4 sprites visible on one line
998
	//  (though the sprites did not seem to make a difference).
999
	// So this test could not decide between 8 or 9 TMS cycles.
1000
	// To be on the safe side we picked 8.
1001
	//
1002
	// Update: 8 cycles (Delta::D32) causes corruption in
1003
	// 'Chase HQ', see
1004
	//    http://www.msx.org/forum/msx-talk/openmsx/openmsx-about-release-testing-help-wanted
1005
	// lowering it to 7 cycles seems fine. TODO needs more
1006
	// investigation. (Just guessing) possibly there are
1007
	// other variables that influence the exact timing (7
1008
	// vs 8 cycles).
1009
	cpuVramReqIsRead = isRead;
1010
	cpuVramReqData = cpuVramData;
1011
	pendingCpuAccess = true;
1012
	syncCpuVramAccess.setSyncPoint(
1013
		getAccessSlot(time, VDPAccessSlots::Delta::CPU_28));
1014
}
1015
1016
void VDP::scheduleV99x8VramAccess(bool isRead, EmuTime time)
1017
{
1018
	// The V9938 does not see the request at 'time': that is the start of T2
1019
	// of the Z80's I/O machine cycle, while /CSW only rises 3 T-states later.
1020
	// Measured from that edge the VDP registers the request 10.9 +- 0.2
1021
	// cycles later, so 29 cycles after 'time'. Only from there on do the 16
1022
	// cycles of lookahead count. See:
1023
	//    doc/internal/vdp-vram-timing/2026-measurement-analysis.md
1024
	EmuTime request = time + VDPClock::duration(CPU_REQUEST_DELAY);
1025
1026
	if (cpuRequestIsTooEarly(request)) [[unlikely]] {
1027
		if (!allowTooFastAccess) {
1028
			// The VDP has nowhere to put this request, so nothing at all
1029
			// happens for it: no VRAM access, and the VRAM pointer does
1030
			// not advance.
1031
			tooFastCallback.execute();
1032
			return;
1033
		}
1034
		// In 'ignore' mode no access may be lost, so free the buffer by
1035
		// giving the access that is still waiting its turn now. (And don't
1036
		// report it: this mode is selected precisely to not care.)
1037
		flushCpuVramAccesses(time);
1038
	}
1039
1040
	EmuTime slot = getAccessSlot(request, VDPAccessSlots::Delta::CPU_16);
1041
1042
	// The slot the request would have been granted if the CPU could use every
1043
	// slot. When that is a 'tight' slot -- the continuation tick of a two-tick
1044
	// CPU-slot waveform, which the CPU cannot be served in -- the VDP still
1045
	// reserves it and spends it on a dummy read of 0x1FFFF, and serves the CPU
1046
	// at the next slot. Only in a narrow window: taking the request cycles the
1047
	// measurement repository reconstructs, 'request 18 to 21 cycles before
1048
	// that slot' predicts 585 of the 603 dummy reads in the sprites-off
1049
	// captures and 11 that did not happen. The boundary is not sharp -- 19
1050
	// misses 154 of them, 17 invents 134 -- so this is the best integer rule
1051
	// rather than an exact one.
1052
	EmuTime dummy = getAccessSlot(request, VDPAccessSlots::Delta::CPU_16_ANY);
1053
	if (dummy != slot) [[unlikely]] {
1054
		auto margin = VDPClock(request).getTicksTill(dummy);
1055
		if ((DUMMY_WINDOW_LO <= margin) && (margin < DUMMY_WINDOW_HI)) {
1056
			syncCpuVramDummy.setSyncPoint(dummy);
1057
		}
1058
	}
1059
1060
	previousCpuSlot = slot;
1061
	previousCpuSlotIsLate = VDPAccessSlots::isLateCpuSlot(
1062
		getTicksThisFrame(slot) % TICKS_PER_LINE, *this);
1063
1064
	if (pendingCpuAccess) [[unlikely]] {
1065
		// Accepted while the access before it has not happened yet: it gets
1066
		// its sync point when that one fires. This is not rare: the VDP
1067
		// registers the request 29 cycles after 'time', so a request that
1068
		// finds the buffer free right after the previous access has its
1069
		// port access up to 28 cycles before that access in emulated time.
1070
		// The BIOS's OTIR/INIR scroll in TEXT2 does this every line: 138
1071
		// cycles per byte, while a request there can wait 29 + 16 + 99
1072
		// cycles for its slot. There can never be a third one, because
1073
		// the CPU cannot issue two port accesses less than 66 cycles
1074
		// apart while no slot table makes an accepted request wait more
1075
		// than 100 + 18 cycles, so by the time a third one is accepted
1076
		// the first access has taken place. The write data has to travel
1077
		// with the request: the port access already put the new byte in
1078
		// 'cpuVramData', while the access before it has yet to store its
1079
		// own.
1080
		assert(secondCpuSlot == EmuTime::infinity());
1081
		assert(slot > *syncCpuVramAccess.isPending());
1082
		secondCpuSlot = slot;
1083
		secondCpuVramReqIsRead = isRead;
1084
		secondCpuVramReqData = cpuVramData;
1085
	} else {
1086
		cpuVramReqIsRead = isRead;
1087
		cpuVramReqData = cpuVramData;
1088
		pendingCpuAccess = true;
1089
		syncCpuVramAccess.setSyncPoint(slot);
1090
	}
1091
}
1092
1093
/** The V9938 has a single CPU VRAM request buffer, and it is not free again
1094
  * until 'threshold' memory cycles before the access it was granted: +1 for a
1095
  * plain slot, -1 for a late one. A request that arrives earlier than that is
1096
  * lost. */
1097
bool VDP::cpuRequestIsTooEarly(EmuTime request) const
1098
{
1099
	// Almost always the request arrives well after the slot granted to the
1100
	// request before it, and is accepted whatever the padding in between
1101
	// does: a line carries only 4 cycles of it. Only the marginal cases need
1102
	// the exact distance, and those are always within a few cycles, so this
1103
	// also keeps the tick counts below well inside 32 bits.
1104
	if (request >= (previousCpuSlot + VDPClock::duration(5))) [[likely]] {
1105
		return false;
1106
	}
1107
	int tick = getTicksThisFrame(request) % TICKS_PER_LINE;
1108
	int threshold = previousCpuSlotIsLate ? -1 : 1;
1109
1110
	if (request >= previousCpuSlot) {
1111
		// 0 to 4 cycles after it
1112
		int n = narrow<int>(VDPClock(previousCpuSlot).getTicksTill(request));
1113
		int distance = n ? (n - VDPAccessSlots::paddingCycles(
1114
		                            (tick - n + TICKS_PER_LINE) % TICKS_PER_LINE,
1115
		                            n, *this))
1116
		                 : 0;
1117
		return distance < threshold;
1118
	} else {
1119
		// Before it, which only a late slot can accept, and only by one
1120
		// memory cycle. More than 5 cycles early is more than 2 memory
1121
		// cycles early whatever the padding does.
1122
		int n = narrow<int>(VDPClock(request).getTicksTill(previousCpuSlot));
1123
		if (n > VDPAccessSlots::MAX_PADDING_SPAN) return true;
1124
		int distance = -(n - VDPAccessSlots::paddingCycles(tick, n, *this));
1125
		return distance < threshold;
1126
	}
1127
}
1128
1129
void VDP::execCpuVramDummy(EmuTime time)
1130
{
1131
	// The VDP grants this slot to the CPU but cannot serve the CPU in it, so
1132
	// it spends the memory cycle on a read of 0x1FFFF. Nothing is latched and
1133
	// the VRAM pointer does not move; the only thing that shows is that the
1134
	// command engine does not get the slot either.
1135
	cmdEngine->stealAccessSlot(time);
1136
}
1137
1138
void VDP::flushCpuVramAccesses(EmuTime time)
1139
{
1140
	// Only in 'too_fast_vram_access == ignore' mode, where no access may be
1141
	// lost: perform the accesses that have not had their slot yet.
1142
	assert(allowTooFastAccess);
1143
	while (pendingCpuAccess) {
1144
		syncCpuVramAccess.removeSyncPoint();
1145
		execCpuVramAccess(time);
1146
	}
1147
}
1148
1149
void VDP::executeCpuVramAccess(EmuTime time)
1150
{
1151
	int addr = (controlRegs[14] << 14) | vramPointer;
1152
	if (displayMode.isPlanar()) {
1153
		// note: also extended VRAM is interleaved,
1154
		//       because there is only 64kB it's interleaved
1155
		//       with itself (every byte repeated twice)
1156
		addr = ((addr << 16) | (addr >> 1)) & 0x1FFFF;
1157
	}
1158
1159
	bool doAccess = [&] {
1160
		if (!cpuExtendedVram) [[likely]] {
1161
			return true;
1162
		} else if (vram->getSize() == 192 * 1024) [[likely]] {
1163
			addr = 0x20000 | (addr & 0xFFFF);
1164
			return true;
1165
		} else {
1166
			return false;
1167
		}
1168
	}();
1169
	if (doAccess) {
1170
		if (cpuVramReqIsRead) {
1171
			cpuVramData = vram->cpuRead(addr, time);
1172
		} else {
1173
			vram->cpuWrite(addr, cpuVramReqData, time);
1174
		}
1175
	} else {
1176
		if (cpuVramReqIsRead) {
1177
			cpuVramData = 0xFF;
1178
		} else {
1179
			// nothing
1180
		}
1181
	}
1182
1183
	vramPointer = (vramPointer + 1) & 0x3FFF;
1184
	if (vramPointer == 0 && displayMode.isV9938Mode()) {
1185
		// In MSX2 video modes, pointer range is 128K.
1186
		controlRegs[14] = (controlRegs[14] + 1) & 0x07;
1187
	}
1188
}
1189
1190
EmuTime VDP::getAccessSlot(EmuTime time, VDPAccessSlots::Delta delta) const
1191
{
1192
	return VDPAccessSlots::getAccessSlot(
1193
		getFrameStartTime(), time, delta, *this);
1194
}
1195
1196
VDPAccessSlots::Calculator VDP::getAccessSlotCalculator(
1197
	EmuTime time, EmuTime limit) const
1198
{
1199
	return VDPAccessSlots::getCalculator(
1200
		getFrameStartTime(), time, limit, *this);
1201
}
1202
1203
uint8_t VDP::peekStatusReg(uint8_t reg, EmuTime time) const
1204
{
1205
	switch (reg) {
1206
	case 0:
1207
		spriteChecker->sync(time);
1208
		return statusReg0;
1209
	case 1:
1210
		if (controlRegs[0] & 0x10) { // line int enabled
1211
			return statusReg1 | (irqHorizontal.getState() ? 1:0);
1212
		} else { // line int disabled
1213
			// FH goes up at the start of the right border of IL and
1214
			// goes down at the start of the next left border.
1215
			// TODO: Precalc matchLength?
1216
			int afterMatch =
1217
			       getTicksThisFrame(time) - horizontalScanOffset;
1218
			if (afterMatch < 0) {
1219
				afterMatch += getTicksPerFrame();
1220
				// afterMatch can still be negative at this
1221
				// point, see scheduleHScan()
1222
			}
1223
			int matchLength = (displayMode.isTextMode() ? 87 : 59)
1224
			                  + 27 + 100 + 102;
1225
			return statusReg1 |
1226
			       (0 <= afterMatch && afterMatch < matchLength);
1227
		}
1228
	case 2: {
1229
		// TODO: Once VDP keeps display/blanking state, keeping
1230
		//       VR is probably part of that, so use it.
1231
		//       --> Is isDisplayArea actually !VR?
1232
		int ticksThisFrame = getTicksThisFrame(time);
1233
		int displayEnd =
1234
			displayStart + getNumberOfLines() * TICKS_PER_LINE;
1235
		bool vr = ticksThisFrame < displayStart - TICKS_PER_LINE
1236
		       || ticksThisFrame >= displayEnd;
1237
		return statusReg2
1238
			| (getHR(ticksThisFrame) ? 0x20 : 0x00)
1239
			| (vr ? 0x40 : 0x00)
1240
			| cmdEngine->peekStatus2(time);
1241
	}
1242
	case 3:
1243
		return uint8_t(spriteChecker->getCollisionX(time));
1244
	case 4:
1245
		return uint8_t(spriteChecker->getCollisionX(time) >> 8) | 0xFE;
1246
	case 5:
1247
		return uint8_t(spriteChecker->getCollisionY(time));
1248
	case 6:
1249
		return uint8_t(spriteChecker->getCollisionY(time) >> 8) | 0xFC;
1250
	case 7:
1251
		return cmdEngine->readColor(time);
1252
	case 8:
1253
		return uint8_t(cmdEngine->getBorderX(time));
1254
	case 9:
1255
		return uint8_t(cmdEngine->getBorderX(time) >> 8) | 0xFE;
1256
	default: // non-existent status register
1257
		return 0xFF;
1258
	}
1259
}
1260
1261
uint8_t VDP::readStatusReg(uint8_t reg, EmuTime time)
1262
{
1263
	uint8_t ret = peekStatusReg(reg, time);
1264
	switch (reg) {
1265
	case 0:
1266
		spriteChecker->resetStatus();
1267
		statusReg0 &= ~0x80;
1268
		irqVertical.reset();
1269
		break;
1270
	case 1:
1271
		if (controlRegs[0] & 0x10) { // line int enabled
1272
			irqHorizontal.reset();
1273
		}
1274
		break;
1275
	case 5:
1276
		spriteChecker->resetCollision();
1277
		break;
1278
	case 7:
1279
		cmdEngine->resetColor();
1280
		break;
1281
	case 9:
1282
		cmdEngine->resetBD();
1283
		break;
1284
	}
1285
	return ret;
1286
}
1287
1288
uint8_t VDP::readIO(uint16_t port, EmuTime time_)
1289
{
1290
	// See comment in writeIO().
1291
	EmuTime time = time_;
1292
	if (fixedVDPIOdelayCycles > 0) {
1293
		time = cpu.waitCyclesZ80(time, fixedVDPIOdelayCycles);
1294
	}
1295
1296
	assert(isInsideFrame(time));
1297
1298
	registerDataStored = false; // Abort any port #1 writes in progress.
1299
1300
	switch (port & (isMSX1VDP() ? 0x01 : 0x03)) {
1301
	case 0: // VRAM data read
1302
		return vramRead(time);
1303
	case 1: // Status register read
1304
		// Calculate status register contents.
1305
		return readStatusReg(controlRegs[15], time);
1306
	case 2:
1307
	case 3:
1308
		return 0xFF;
1309
	default:
1310
		UNREACHABLE;
1311
	}
1312
}
1313
1314
uint8_t VDP::peekIO(uint16_t /*port*/, EmuTime /*time*/) const
1315
{
1316
	// TODO not implemented
1317
	return 0xFF;
1318
}
1319
1320
void VDP::changeRegister(uint8_t reg, uint8_t val, EmuTime time)
1321
{
1322
	if (reg >= 32) {
1323
		// MXC belongs to CPU interface;
1324
		// other bits in this register belong to command engine.
1325
		if (reg == 45) {
1326
			cpuExtendedVram = (val & 0x40) != 0;
1327
		}
1328
		// Pass command register writes to command engine.
1329
		if (reg < 47) {
1330
			cmdEngine->setCmdReg(reg - 32, val, time);
1331
		}
1332
		return;
1333
	}
1334
1335
	// Make sure only bits that actually exist are written.
1336
	val &= controlValueMasks[reg];
1337
	// Determine the difference between new and old value.
1338
	uint8_t change = val ^ controlRegs[reg];
1339
1340
	// Register 13 is special because writing it resets blinking state,
1341
	// even if the value in the register doesn't change.
1342
	if (reg == 13) {
1343
		// Switch to ON state unless ON period is zero.
1344
		if (blinkState == ((val & 0xF0) == 0)) {
1345
			renderer->updateBlinkState(!blinkState, time);
1346
			blinkState = !blinkState;
1347
		}
1348
1349
		if ((val & 0xF0) && (val & 0x0F)) {
1350
			// Alternating colors, start with ON.
1351
			blinkCount = (val >> 4) * 10;
1352
		} else {
1353
			// Stable color.
1354
			blinkCount = 0;
1355
		}
1356
		// TODO when 'isFastBlinkEnabled()==true' the variables
1357
		// 'blinkState' and 'blinkCount' represent the values at line 0.
1358
		// This implementation is not correct for the partial remaining
1359
		// frame after register 13 got changed.
1360
	}
1361
1362
	if (!change) return;
1363
1364
	// Perform additional tasks before new value becomes active.
1365
	switch (reg) {
1366
	case 0:
1367
		if (change & DisplayMode::REG0_MASK) {
1368
			syncAtNextLine(syncSetMode, time);
1369
		}
1370
		break;
1371
	case 1:
1372
		if (change & 0x03) {
1373
			// Update sprites on size and mag changes.
1374
			spriteChecker->updateSpriteSizeMag(val, time);
1375
		}
1376
		// TODO: Reset vertical IRQ if IE0 is reset?
1377
		if (change & DisplayMode::REG1_MASK) {
1378
			syncAtNextLine(syncSetMode, time);
1379
		}
1380
		if (change & 0x40) {
1381
			syncAtNextLine(syncSetBlank, time);
1382
		}
1383
		break;
1384
	case 2: {
1385
		unsigned base = (val << 10) | ~(~0u << 10);
1386
		// TODO:
1387
		// I reverted this fix.
1388
		// Although the code is correct, there is also a counterpart in the
1389
		// renderer that must be updated. I'm too tired now to find it.
1390
		// Since name table checking is currently disabled anyway, keeping the
1391
		// old code does not hurt.
1392
		// Eventually this line should be re-enabled.
1393
		/*
1394
		if (displayMode.isPlanar()) {
1395
			base = ((base << 16) | (base >> 1)) & 0x1FFFF;
1396
		}
1397
		*/
1398
		renderer->updateNameBase(base, time);
1399
		break;
1400
	}
1401
	case 7:
1402
		if (getDisplayMode().getByte() != DisplayMode::GRAPHIC7) {
1403
			if (change & 0xF0) {
1404
				renderer->updateForegroundColor(val >> 4, time);
1405
			}
1406
			if (change & 0x0F) {
1407
				renderer->updateBackgroundColor(val & 0x0F, time);
1408
			}
1409
		} else {
1410
			renderer->updateBackgroundColor(val, time);
1411
		}
1412
		break;
1413
	case 8:
1414
		if (change & 0x20) {
1415
			renderer->updateTransparency((val & 0x20) == 0, time);
1416
			spriteChecker->updateTransparency((val & 0x20) == 0, time);
1417
		}
1418
		if (change & 0x02) {
1419
			syncAtNextLine(syncSetSprites, time);
1420
		}
1421
		if (change & 0x08) {
1422
			vram->updateVRMode((val & 0x08) != 0, time);
1423
		}
1424
		break;
1425
	case 12:
1426
		if (change & 0xF0) {
1427
			renderer->updateBlinkForegroundColor(val >> 4, time);
1428
		}
1429
		if (change & 0x0F) {
1430
			renderer->updateBlinkBackgroundColor(val & 0x0F, time);
1431
		}
1432
		break;
1433
	case 16:
1434
		// Any half-finished palette loads are aborted.
1435
		paletteDataStored = false;
1436
		break;
1437
	case 18:
1438
		if (change & 0x0F) {
1439
			syncAtNextLine(syncHorAdjust, time);
1440
		}
1441
		break;
1442
	case 23:
1443
		spriteChecker->updateVerticalScroll(val, time);
1444
		renderer->updateVerticalScroll(val, time);
1445
		break;
1446
	case 25:
1447
		if (change & (DisplayMode::REG25_MASK | 0x40)) {
1448
			updateDisplayMode(getDisplayMode().updateReg25(val),
1449
			                  val & 0x40,
1450
			                  time);
1451
		}
1452
		if (change & 0x08) {
1453
			syncAtNextLine(syncHorAdjust, time);
1454
		}
1455
		if (change & 0x02) {
1456
			renderer->updateBorderMask((val & 0x02) != 0, time);
1457
		}
1458
		if (change & 0x01) {
1459
			renderer->updateMultiPage((val & 0x01) != 0, time);
1460
		}
1461
		break;
1462
	case 26:
1463
		renderer->updateHorizontalScrollHigh(val, time);
1464
		break;
1465
	case 27:
1466
		renderer->updateHorizontalScrollLow(val, time);
1467
		break;
1468
	}
1469
1470
	// Commit the change.
1471
	controlRegs[reg] = val;
1472
1473
	// Perform additional tasks after new value became active.
1474
	// Because base masks cannot be read from the VDP, updating them after
1475
	// the commit is equivalent to updating before.
1476
	switch (reg) {
1477
	case 0:
1478
		if (change & 0x10) { // IE1
1479
			if (val & 0x10) {
1480
				scheduleHScan(time);
1481
			} else {
1482
				irqHorizontal.reset();
1483
			}
1484
		}
1485
		break;
1486
	case 1:
1487
		if (change & 0x20) { // IE0
1488
			if (val & 0x20) {
1489
				// This behaviour is important. Without it,
1490
				// the intro music in 'Andonis' is way too slow
1491
				// and the intro logo of 'Zanac' is corrupted.
1492
				if (statusReg0 & 0x80) {
1493
					irqVertical.set();
1494
				}
1495
			} else {
1496
				irqVertical.reset();
1497
			}
1498
		}
1499
		if ((change & 0x80) && isVDPwithVRAMremapping()) {
1500
			// confirmed: VRAM remapping only happens on TMS99xx
1501
			// see VDPVRAM for details on the remapping itself
1502
			vram->change4k8kMapping((val & 0x80) != 0);
1503
		}
1504
		break;
1505
	case 2:
1506
		updateNameBase(time);
1507
		break;
1508
	case 3:
1509
	case 10:
1510
		updateColorBase(time);
1511
		if (vdpHasPatColMirroring()) updatePatternBase(time);
1512
		break;
1513
	case 4:
1514
		updatePatternBase(time);
1515
		break;
1516
	case 5:
1517
	case 11:
1518
		updateSpriteAttributeBase(time);
1519
		break;
1520
	case 6:
1521
		updateSpritePatternBase(time);
1522
		break;
1523
	case 9:
1524
		if ((val & 1) && ! warningPrinted) {
1525
			warningPrinted = true;
1526
			dotClockDirectionCallback.execute();
1527
			// TODO: Emulate such behaviour.
1528
		}
1529
		if (change & 0x80) {
1530
			/*
1531
			cerr << "changed to " << (val & 0x80 ? 212 : 192) << " lines"
1532
				<< " at line " << (getTicksThisFrame(time) / TICKS_PER_LINE) << "\n";
1533
			*/
1534
			// The 192/212 lines bit is sampled at the display line
1535
			// counter reset in the top border, which is where the
1536
			// display start gets fixed (see execLineCountReset()), so
1537
			// a change has no effect on the display start until then.
1538
			// The display end, however, is a comparison against the
1539
			// bit as it is now.
1540
			scheduleVScan(time);
1541
		}
1542
		break;
1543
	case 18:
1544
		if (change & 0xF0) {
1545
			// The display line counter reset moves with the vertical
1546
			// adjust.
1547
			scheduleLineCountReset(time);
1548
		}
1549
		break;
1550
	case 19:
1551
	case 23:
1552
		scheduleHScan(time);
1553
		break;
1554
	case 25:
1555
		if (change & 0x01) {
1556
			updateNameBase(time);
1557
		}
1558
		break;
1559
	}
1560
}
1561
1562
void VDP::syncAtNextLine(SyncBase& type, EmuTime time) const
1563
{
1564
	// The processing of a new line starts in the middle of the left erase,
1565
	// ~144 cycles after the sync signal. Adjust affects it. See issue #1310.
1566
	int offset = 144 + (horizontalAdjust - 7) * 4;
1567
	int line = (getTicksThisFrame(time) + TICKS_PER_LINE - offset) / TICKS_PER_LINE;
1568
	int ticks = line * TICKS_PER_LINE + offset;
1569
	EmuTime nextTime = frameStartTime + ticks;
1570
	type.setSyncPoint(nextTime);
1571
}
1572
1573
void VDP::updateNameBase(EmuTime time)
1574
{
1575
	unsigned base = (controlRegs[2] << 10) | ~(~0u << 10);
1576
	// TODO:
1577
	// I reverted this fix.
1578
	// Although the code is correct, there is also a counterpart in the
1579
	// renderer that must be updated. I'm too tired now to find it.
1580
	// Since name table checking is currently disabled anyway, keeping the
1581
	// old code does not hurt.
1582
	// Eventually this line should be re-enabled.
1583
	/*
1584
	if (displayMode.isPlanar()) {
1585
		base = ((base << 16) | (base >> 1)) & 0x1FFFF;
1586
	}
1587
	*/
1588
	unsigned indexMask =
1589
		  displayMode.isBitmapMode()
1590
		? ~0u << 17 // TODO: Calculate actual value; how to handle planar?
1591
		: ~0u << (displayMode.isTextMode() ? 12 : 10);
1592
	if (controlRegs[25] & 0x01) {
1593
		// Multi page scrolling. The same bit is used in character and
1594
		// (non)planar-bitmap modes.
1595
		// TODO test text modes
1596
		indexMask &= ~0x8000;
1597
	}
1598
	vram->nameTable.setMask(base, indexMask, time);
1599
}
1600
1601
void VDP::updateColorBase(EmuTime time)
1602
{
1603
	unsigned base = (controlRegs[10] << 14) | (controlRegs[3] << 6) | ~(~0u << 6);
1604
	renderer->updateColorBase(base, time);
1605
	switch (displayMode.getBase()) {
1606
	case 0x09: // Text 2.
1607
		// TODO: Enable this only if dual color is actually active.
1608
		vram->colorTable.setMask(base, ~0u << 9, time);
1609
		break;
1610
	case 0x00: // Graphic 1.
1611
		vram->colorTable.setMask(base, ~0u << 6, time);
1612
		break;
1613
	case 0x04: // Graphic 2.
1614
		vram->colorTable.setMask(base | (vdpLacksMirroring() ? 0x1800 : 0), ~0u << 13, time);
1615
		break;
1616
	case 0x08: // Graphic 3.
1617
		vram->colorTable.setMask(base, ~0u << 13, time);
1618
		break;
1619
	default:
1620
		// Other display modes do not use a color table.
1621
		vram->colorTable.disable(time);
1622
	}
1623
}
1624
1625
void VDP::updatePatternBase(EmuTime time)
1626
{
1627
	unsigned base = (controlRegs[4] << 11) | ~(~0u << 11);
1628
	renderer->updatePatternBase(base, time);
1629
	switch (displayMode.getBase()) {
1630
	case 0x01: // Text 1.
1631
	case 0x05: // Text 1 Q.
1632
	case 0x09: // Text 2.
1633
	case 0x00: // Graphic 1.
1634
	case 0x02: // Multicolor.
1635
	case 0x06: // Multicolor Q.
1636
		vram->patternTable.setMask(base, ~0u << 11, time);
1637
		break;
1638
	case 0x04: // Graphic 2.
1639
		if (vdpHasPatColMirroring()) {
1640
			// TMS99XX has weird pattern table behavior: some
1641
			// bits of the color-base register leak into the
1642
			// pattern-base. See also:
1643
			//   http://www.youtube.com/watch?v=XJljSJqzDR0
1644
			base =  (controlRegs[4]         << 11)
1645
			     | ((controlRegs[3] & 0x1f) <<  6)
1646
			     | ~(~0u << 6);
1647
		}
1648
		vram->patternTable.setMask(base | (vdpLacksMirroring() ? 0x1800 : 0), ~0u << 13, time);
1649
		break;
1650
	case 0x08: // Graphic 3.
1651
		vram->patternTable.setMask(base, ~0u << 13, time);
1652
		break;
1653
	default:
1654
		// Other display modes do not use a pattern table.
1655
		vram->patternTable.disable(time);
1656
	}
1657
}
1658
1659
void VDP::updateSpriteAttributeBase(EmuTime time)
1660
{
1661
	int mode = displayMode.getSpriteMode(isMSX1VDP());
1662
	if (mode == 0) {
1663
		vram->spriteAttribTable.disable(time);
1664
		return;
1665
	}
1666
	unsigned baseMask = (controlRegs[11] << 15) | (controlRegs[5] << 7) | ~(~0u << 7);
1667
	unsigned indexMask = mode == 1 ? ~0u << 7 : ~0u << 10;
1668
	if (displayMode.isPlanar()) {
1669
		baseMask = ((baseMask << 16) | (baseMask >> 1)) & 0x1FFFF;
1670
		indexMask = ((indexMask << 16) | ~(1 << 16)) & (indexMask >> 1);
1671
	}
1672
	vram->spriteAttribTable.setMask(baseMask, indexMask, time);
1673
}
1674
1675
void VDP::updateSpritePatternBase(EmuTime time)
1676
{
1677
	if (displayMode.getSpriteMode(isMSX1VDP()) == 0) {
1678
		vram->spritePatternTable.disable(time);
1679
		return;
1680
	}
1681
	unsigned baseMask = (controlRegs[6] << 11) | ~(~0u << 11);
1682
	unsigned indexMask = ~0u << 11;
1683
	if (displayMode.isPlanar()) {
1684
		baseMask = ((baseMask << 16) | (baseMask >> 1)) & 0x1FFFF;
1685
		indexMask = ((indexMask << 16) | ~(1 << 16)) & (indexMask >> 1);
1686
	}
1687
	vram->spritePatternTable.setMask(baseMask, indexMask, time);
1688
}
1689
1690
void VDP::updateDisplayMode(DisplayMode newMode, bool cmdBit, EmuTime time)
1691
{
1692
	// Synchronize subsystems.
1693
	vram->updateDisplayMode(newMode, cmdBit, time);
1694
1695
	// TODO: Is this a useful optimisation, or doesn't it help
1696
	//       in practice?
1697
	// What aspects have changed:
1698
	// Switched from planar to non-planar or vice versa.
1699
	bool planarChange =
1700
		newMode.isPlanar() != displayMode.isPlanar();
1701
	// Sprite mode changed.
1702
	bool msx1 = isMSX1VDP();
1703
	bool spriteModeChange =
1704
		newMode.getSpriteMode(msx1) != displayMode.getSpriteMode(msx1);
1705
1706
	// Commit the new display mode.
1707
	displayMode = newMode;
1708
1709
	// Speed up performance of bitmap/character mode splits:
1710
	// leave last used character mode active.
1711
	// TODO: Disable it if not used for some time.
1712
	if (!displayMode.isBitmapMode()) {
1713
		updateColorBase(time);
1714
		updatePatternBase(time);
1715
	}
1716
	if (planarChange || spriteModeChange) {
1717
		updateSpritePatternBase(time);
1718
		updateSpriteAttributeBase(time);
1719
	}
1720
	updateNameBase(time);
1721
1722
	// To be extremely accurate, reschedule hscan when changing
1723
	// from/to text mode. Text mode has different border width,
1724
	// which affects the moment hscan occurs.
1725
	// TODO: Why didn't I implement this yet?
1726
	//       It's one line of code and overhead is not huge either.
1727
}
1728
1729
void VDP::update(const Setting& setting) noexcept
1730
{
1731
	assert(&setting == one_of(&cmdTiming, &tooFastAccess));
1732
	(void)setting;
1733
	brokenCmdTiming    = cmdTiming    .getEnum();
1734
	allowTooFastAccess = tooFastAccess.getEnum();
1735
1736
	if (allowTooFastAccess && pendingCpuAccess) [[unlikely]] {
1737
		// Switched to 'ignore' while accesses were waiting for their slot.
1738
		flushCpuVramAccesses(getCurrentTime());
1739
	}
1740
}
1741
1742
/*
1743
 * Roughly measured RGB values in volts.
1744
 * Voltages were in range of 1.12-5.04, and had 2 digits accuracy (it seems
1745
 * minimum difference was 0.04 V).
1746
 * Blue component of color 5 and red component of color 9 were higher than
1747
 * the components for white... There are several methods to handle this...
1748
 * 1) clip to values of white
1749
 * 2) scale all colors by min/max of that component (means white is not 3x 255)
1750
 * 3) scale per color if components for that color are beyond those of white
1751
 * 4) assume the analog values are output by a DA converter, derive the digital
1752
 *    values and scale that to the range 0-255 (thanks to FRS for this idea).
1753
 *    This also results in white not being 3x 255, of course.
1754
 *
1755
 * Method 4 results in the table below and seems the most accurate (so far).
1756
 *
1757
 * Thanks to Tiago Valença and Carlos Mansur for measuring on a T7937A.
1758
 */
1759
static constexpr std::array<std::array<uint8_t, 3>, 16> TOSHIBA_PALETTE = {{
1760
	{   0,   0,   0 },
1761
	{   0,   0,   0 },
1762
	{ 102, 204, 102 },
1763
	{ 136, 238, 136 },
1764
	{  68,  68, 221 },
1765
	{ 119, 119, 255 },
1766
	{ 187,  85,  85 },
1767
	{ 119, 221, 221 },
1768
	{ 221, 102, 102 },
1769
	{ 255, 119, 119 },
1770
	{ 204, 204,  85 },
1771
	{ 238, 238, 136 },
1772
	{  85, 170,  85 },
1773
	{ 187,  85, 187 },
1774
	{ 204, 204, 204 },
1775
	{ 238, 238, 238 },
1776
}};
1777
1778
/*
1779
 * Palette for the YM2220 is a crude approximation based on the fact that the
1780
 * pictures of a Yamaha AX-150 (YM2220) and a Philips NMS-8250 (V9938) have a
1781
 * quite similar appearance. See first post here:
1782
 *
1783
 * https://www.msx.org/forum/msx-talk/hardware/unknown-vdp-yamaha-ym2220?page=3
1784
 */
1785
static constexpr std::array<std::array<uint8_t, 3>, 16> YM2220_PALETTE = {{
1786
	{   0,   0,   0 },
1787
	{   0,   0,   0 },
1788
	{  36, 218,  36 },
1789
	{ 200, 255, 109 },
1790
	{  36,  36, 255 },
1791
	{  72, 109, 255 },
1792
	{ 182,  36,  36 },
1793
	{  72, 218, 255 },
1794
	{ 255,  36,  36 },
1795
	{ 255, 175, 175 },
1796
	{ 230, 230,   0 },
1797
	{ 230, 230, 200 },
1798
	{  36, 195,  36 },
1799
	{ 218,  72, 182 },
1800
	{ 182, 182, 182 },
1801
	{ 255, 255, 255 },
1802
}};
1803
1804
/*
1805
How come the FM-X has a distinct palette while it clearly has a TMS9928 VDP?
1806
Because it has an additional circuit that rework the palette for the same one
1807
used in the Fujitsu FM-7. It's encoded in 3-bit RGB.
1808
1809
This seems to be the 24-bit RGB equivalent to the palette output by the FM-X on
1810
its RGB connector:
1811
*/
1812
static constexpr std::array<std::array<uint8_t, 3>, 16> THREE_BIT_RGB_PALETTE = {{
1813
	{   0,   0,   0 },
1814
	{   0,   0,   0 },
1815
	{   0, 255,   0 },
1816
	{   0, 255,   0 },
1817
	{   0,   0, 255 },
1818
	{   0,   0, 255 },
1819
	{ 255,   0,   0 },
1820
	{   0, 255, 255 },
1821
	{ 255,   0,   0 },
1822
	{ 255,   0,   0 },
1823
	{ 255, 255,   0 },
1824
	{ 255, 255,   0 },
1825
	{   0, 255,   0 },
1826
	{ 255,   0, 255 },
1827
	{ 255, 255, 255 },
1828
	{ 255, 255, 255 },
1829
}};
1830
1831
// Source: TMS9918/28/29 Data Book, page 2-17.
1832
1833
static constexpr std::array<std::array<float, 3>, 16> TMS9XXXA_ANALOG_OUTPUT = {
1834
	//           Y     R-Y    B-Y    voltages
1835
	std::array{0.00f, 0.47f, 0.47f},
1836
	std::array{0.00f, 0.47f, 0.47f},
1837
	std::array{0.53f, 0.07f, 0.20f},
1838
	std::array{0.67f, 0.17f, 0.27f},
1839
	std::array{0.40f, 0.40f, 1.00f},
1840
	std::array{0.53f, 0.43f, 0.93f},
1841
	std::array{0.47f, 0.83f, 0.30f},
1842
	std::array{0.73f, 0.00f, 0.70f},
1843
	std::array{0.53f, 0.93f, 0.27f},
1844
	std::array{0.67f, 0.93f, 0.27f},
1845
	std::array{0.73f, 0.57f, 0.07f},
1846
	std::array{0.80f, 0.57f, 0.17f},
1847
	std::array{0.47f, 0.13f, 0.23f},
1848
	std::array{0.53f, 0.73f, 0.67f},
1849
	std::array{0.80f, 0.47f, 0.47f},
1850
	std::array{1.00f, 0.47f, 0.47f},
1851
};
1852
1853
std::array<std::array<uint8_t, 3>, 16> VDP::getMSX1Palette() const
1854
{
1855
	assert(isMSX1VDP());
1856
	if (MSXDevice::getDeviceConfig().findChild("rgboutput3bit") != nullptr) {
1857
		return THREE_BIT_RGB_PALETTE;
1858
	}
1859
	if ((version & VM_TOSHIBA_PALETTE) != 0) {
1860
		return TOSHIBA_PALETTE;
1861
	}
1862
	if ((version & VM_YM2220_PALETTE) != 0) {
1863
		return YM2220_PALETTE;
1864
	}
1865
	std::array<std::array<uint8_t, 3>, 16> tmsPalette;
1866
	for (auto color : xrange(16)) {
1867
		// convert from analog output to YPbPr
1868
		float Y  = TMS9XXXA_ANALOG_OUTPUT[color][0];
1869
		float Pr = TMS9XXXA_ANALOG_OUTPUT[color][1] - 0.5f;
1870
		float Pb = TMS9XXXA_ANALOG_OUTPUT[color][2] - 0.5f;
1871
		// apply the saturation
1872
		Pr *= (narrow<float>(saturationPr) * (1.0f / 100.0f));
1873
		Pb *= (narrow<float>(saturationPb) * (1.0f / 100.0f));
1874
		// convert to RGB as follows:
1875
		/*
1876
		  |R|   | 1  0      1.402 |   |Y |
1877
		  |G| = | 1 -0.344 -0.714 | x |Pb|
1878
		  |B|   | 1  1.722  0     |   |Pr|
1879
		*/
1880
		float R = Y +           0 + 1.402f * Pr;
1881
		float G = Y - 0.344f * Pb - 0.714f * Pr;
1882
		float B = Y + 1.722f * Pb +           0;
1883
		// blow up with factor of 255
1884
		R *= 255;
1885
		G *= 255;
1886
		B *= 255;
1887
		// the final result is that these values
1888
		// are clipped in the [0:255] range.
1889
		// Note: Using roundf instead of std::round because libstdc++ when
1890
		//       built on top of uClibc lacks std::round; uClibc does provide
1891
		//       roundf, but lacks other C99 math functions and that makes
1892
		//       libstdc++ disable all wrappers for C99 math functions.
1893
		tmsPalette[color][0] = Math::clipIntToByte(narrow_cast<int>(roundf(R)));
1894
		tmsPalette[color][1] = Math::clipIntToByte(narrow_cast<int>(roundf(G)));
1895
		tmsPalette[color][2] = Math::clipIntToByte(narrow_cast<int>(roundf(B)));
1896
		// std::cerr << color << " " << int(tmsPalette[color][0]) << " " << int(tmsPalette[color][1]) <<" " << int(tmsPalette[color][2]) << '\n';
1897
	}
1898
	return tmsPalette;
1899
}
1900
1901
const RawFrame* VDP::getWorkingFrame(EmuTime time)
1902
{
1903
	return renderer->getWorkingFrame(time);
1904
}
1905
1906
const RawFrame* VDP::getLastFrame() const
1907
{
1908
	return renderer->getLastFrame();
1909
}
1910
1911
// RegDebug
1912
1913
VDP::RegDebug::RegDebug(const VDP& vdp_)
1914
	: SimpleDebuggable(vdp_.getMotherBoard(),
1915
	                   vdp_.getName() + " regs", "VDP registers.", 0x40)
1916
{
1917
}
1918
1919
uint8_t VDP::RegDebug::read(unsigned address, EmuTime time)
1920
{
1921
	const auto& vdp = OUTER(VDP, vdpRegDebug);
1922
	return vdp.peekRegister(address, time);
1923
}
1924
1925
void VDP::RegDebug::write(unsigned address, uint8_t value, EmuTime time)
1926
{
1927
	auto& vdp = OUTER(VDP, vdpRegDebug);
1928
	// Ignore writes to registers >= 8 on MSX1. An alternative is to only
1929
	// expose 8 registers. But changing that now breaks backwards
1930
	// compatibility with some existing scripts. E.g. script that queries
1931
	// PAL vs NTSC in a VDP agnostic way.
1932
	if ((address >= 8) && vdp.isMSX1VDP()) return;
1933
	vdp.changeRegister(narrow<uint8_t>(address), value, time);
1934
}
1935
1936
1937
// StatusRegDebug
1938
1939
VDP::StatusRegDebug::StatusRegDebug(const VDP& vdp_)
1940
	: SimpleDebuggable(vdp_.getMotherBoard(),
1941
	                   vdp_.getName() + " status regs", "VDP status registers.", 0x10)
1942
{
1943
}
1944
1945
uint8_t VDP::StatusRegDebug::read(unsigned address, EmuTime time)
1946
{
1947
	const auto& vdp = OUTER(VDP, vdpStatusRegDebug);
1948
	return vdp.peekStatusReg(narrow<uint8_t>(address), time);
1949
}
1950
1951
1952
// PaletteDebug
1953
1954
VDP::PaletteDebug::PaletteDebug(const VDP& vdp_)
1955
	: SimpleDebuggable(vdp_.getMotherBoard(),
1956
	                   vdp_.getName() + " palette", "V99x8 palette (RBG format)", 0x20)
1957
{
1958
}
1959
1960
uint8_t VDP::PaletteDebug::read(unsigned address)
1961
{
1962
	const auto& vdp = OUTER(VDP, vdpPaletteDebug);
1963
	uint16_t grb = vdp.getPalette(address / 2);
1964
	return (address & 1) ? narrow_cast<uint8_t>(grb >> 8)
1965
	                     : narrow_cast<uint8_t>(grb & 0xff);
1966
}
1967
1968
void VDP::PaletteDebug::write(unsigned address, uint8_t value, EmuTime time)
1969
{
1970
	auto& vdp = OUTER(VDP, vdpPaletteDebug);
1971
	// Ignore writes on MSX1. An alternative could be to not expose the
1972
	// palette at all, but allowing read-only access could be useful for
1973
	// some scripts.
1974
	if (vdp.isMSX1VDP()) return;
1975
1976
	unsigned index = address / 2;
1977
	uint16_t grb = vdp.getPalette(index);
1978
	grb = (address & 1)
1979
	    ? uint16_t((grb & 0x0077) | ((value & 0x07) << 8))
1980
	    : uint16_t((grb & 0x0700) | ((value & 0x77) << 0));
1981
	vdp.setPalette(index, grb, time);
1982
}
1983
1984
1985
// class VRAMPointerDebug
1986
1987
VDP::VRAMPointerDebug::VRAMPointerDebug(const VDP& vdp_)
1988
	: SimpleDebuggable(vdp_.getMotherBoard(), vdp_.getName() == "VDP" ?
1989
			"VRAM pointer" : vdp_.getName() + " VRAM pointer",
1990
			"VDP VRAM pointer (14 lower bits)", 2)
1991
{
1992
}
1993
1994
uint8_t VDP::VRAMPointerDebug::read(unsigned address)
1995
{
1996
	const auto& vdp = OUTER(VDP, vramPointerDebug);
1997
	if (address & 1) {
1998
		return narrow_cast<uint8_t>(vdp.vramPointer >> 8);  // TODO add read/write mode?
1999
	} else {
2000
		return narrow_cast<uint8_t>(vdp.vramPointer & 0xFF);
2001
	}
2002
}
2003
2004
void VDP::VRAMPointerDebug::write(unsigned address, uint8_t value, EmuTime /*time*/)
2005
{
2006
	auto& vdp = OUTER(VDP, vramPointerDebug);
2007
	int& ptr = vdp.vramPointer;
2008
	if (address & 1) {
2009
		ptr = (ptr & 0x00FF) | ((value & 0x3F) << 8);
2010
	} else {
2011
		ptr = (ptr & 0xFF00) | value;
2012
	}
2013
}
2014
2015
// class RegisterLatchStatusDebug
2016
2017
VDP::RegisterLatchStatusDebug::RegisterLatchStatusDebug(const VDP &vdp_)
2018
	: SimpleDebuggable(vdp_.getMotherBoard(),
2019
			vdp_.getName() + " register latch status", "V99x8 register latch status (0 = expecting a value, 1 = expecting a register)", 1)
2020
{
2021
}
2022
2023
uint8_t VDP::RegisterLatchStatusDebug::read(unsigned /*address*/)
2024
{
2025
	const auto& vdp = OUTER(VDP, registerLatchStatusDebug);
2026
	return uint8_t(vdp.registerDataStored);
2027
}
2028
2029
// class VramAccessStatusDebug
2030
2031
VDP::VramAccessStatusDebug::VramAccessStatusDebug(const VDP &vdp_)
2032
	: SimpleDebuggable(vdp_.getMotherBoard(), vdp_.getName() == "VDP" ?
2033
			"VRAM access status" : vdp_.getName() + " VRAM access status",
2034
			"VDP VRAM access status (0 = ready to read, 1 = ready to write)", 1)
2035
{
2036
}
2037
2038
uint8_t VDP::VramAccessStatusDebug::read(unsigned /*address*/)
2039
{
2040
	const auto& vdp = OUTER(VDP, vramAccessStatusDebug);
2041
	return uint8_t(vdp.writeAccess);
2042
}
2043
2044
// class PaletteLatchStatusDebug
2045
2046
VDP::PaletteLatchStatusDebug::PaletteLatchStatusDebug(const VDP &vdp_)
2047
	: SimpleDebuggable(vdp_.getMotherBoard(),
2048
			vdp_.getName() + " palette latch status", "V99x8 palette latch status (0 = expecting red & blue, 1 = expecting green)", 1)
2049
{
2050
}
2051
2052
uint8_t VDP::PaletteLatchStatusDebug::read(unsigned /*address*/)
2053
{
2054
	const auto& vdp = OUTER(VDP, paletteLatchStatusDebug);
2055
	return uint8_t(vdp.paletteDataStored);
2056
}
2057
2058
// class DataLatchDebug
2059
2060
VDP::DataLatchDebug::DataLatchDebug(const VDP &vdp_)
2061
	: SimpleDebuggable(vdp_.getMotherBoard(),
2062
			vdp_.getName() + " data latch value", "V99x8 data latch value (byte)", 1)
2063
{
2064
}
2065
2066
uint8_t VDP::DataLatchDebug::read(unsigned /*address*/)
2067
{
2068
	const auto& vdp = OUTER(VDP, dataLatchDebug);
2069
	return vdp.dataLatch;
2070
}
2071
2072
// class Info
2073
2074
VDP::Info::Info(VDP& vdp_, const std::string& name_, std::string helpText_)
2075
	: InfoTopic(vdp_.getMotherBoard().getMachineInfoCommand(),
2076
		    strCat(vdp_.getName(), '_', name_))
2077
	, vdp(vdp_)
2078
	, helpText(std::move(helpText_))
2079
{
2080
}
2081
2082
void VDP::Info::execute(std::span<const TclObject> /*tokens*/, TclObject& result) const
2083
{
2084
	result = calc(vdp.getCurrentTime());
2085
}
2086
2087
std::string VDP::Info::help(std::span<const TclObject> /*tokens*/) const
2088
{
2089
	return helpText;
2090
}
2091
2092
2093
// class FrameCountInfo
2094
2095
VDP::FrameCountInfo::FrameCountInfo(VDP& vdp_)
2096
	: Info(vdp_, "frame_count",
2097
	       "The current frame number, starts counting at 0 "
2098
	       "when MSX is powered up or reset.")
2099
{
2100
}
2101
2102
int VDP::FrameCountInfo::calc(EmuTime /*time*/) const
2103
{
2104
	return vdp.frameCount;
2105
}
2106
2107
2108
// class CycleInFrameInfo
2109
2110
VDP::CycleInFrameInfo::CycleInFrameInfo(VDP& vdp_)
2111
	: Info(vdp_, "cycle_in_frame",
2112
	       "The number of VDP cycles since the beginning of "
2113
	       "the current frame. The VDP runs at 6 times the Z80 "
2114
	       "clock frequency, so at approximately 21.5MHz.")
2115
{
2116
}
2117
2118
int VDP::CycleInFrameInfo::calc(EmuTime time) const
2119
{
2120
	return vdp.getTicksThisFrame(time);
2121
}
2122
2123
2124
// class LineInFrameInfo
2125
2126
VDP::LineInFrameInfo::LineInFrameInfo(VDP& vdp_)
2127
	: Info(vdp_, "line_in_frame",
2128
	       "The absolute line number since the beginning of "
2129
	       "the current frame. Goes from 0 till 262 (NTSC) or "
2130
	       "313 (PAL). Note that this number includes the "
2131
	       "border lines, use 'msx_y_pos' to get MSX "
2132
	       "coordinates.")
2133
{
2134
}
2135
2136
int VDP::LineInFrameInfo::calc(EmuTime time) const
2137
{
2138
	return vdp.getTicksThisFrame(time) / VDP::TICKS_PER_LINE;
2139
}
2140
2141
2142
// class CycleInLineInfo
2143
2144
VDP::CycleInLineInfo::CycleInLineInfo(VDP& vdp_)
2145
	: Info(vdp_, "cycle_in_line",
2146
	       "The number of VDP cycles since the beginning of "
2147
	       "the current line. See also 'cycle_in_frame'."
2148
	       "Note that this includes the cycles in the border, "
2149
	       "use 'msx_x256_pos' or 'msx_x512_pos' to get MSX "
2150
	       "coordinates.")
2151
{
2152
}
2153
2154
int VDP::CycleInLineInfo::calc(EmuTime time) const
2155
{
2156
	return vdp.getTicksThisFrame(time) % VDP::TICKS_PER_LINE;
2157
}
2158
2159
2160
// class MsxYPosInfo
2161
2162
VDP::MsxYPosInfo::MsxYPosInfo(VDP& vdp_)
2163
	: Info(vdp_, "msx_y_pos",
2164
	       "Similar to 'line_in_frame', but expressed in MSX "
2165
	       "coordinates. So lines in the top border have "
2166
	       "negative coordinates, lines in the bottom border "
2167
	       "have coordinates bigger or equal to 192 or 212.")
2168
{
2169
}
2170
2171
int VDP::MsxYPosInfo::calc(EmuTime time) const
2172
{
2173
	return vdp.getMSXPos(time).y;
2174
}
2175
2176
2177
// class MsxX256PosInfo
2178
2179
VDP::MsxX256PosInfo::MsxX256PosInfo(VDP& vdp_)
2180
	: Info(vdp_, "msx_x256_pos",
2181
	       "Similar to 'cycle_in_frame', but expressed in MSX "
2182
	       "coordinates. So a position in the left border has "
2183
	       "a negative coordinate and a position in the right "
2184
	       "border has a coordinate bigger or equal to 256. "
2185
	       "See also 'msx_x512_pos'.")
2186
{
2187
}
2188
2189
int VDP::MsxX256PosInfo::calc(EmuTime time) const
2190
{
2191
	return vdp.getMSXPos(time).x / 2;
2192
}
2193
2194
2195
// class MsxX512PosInfo
2196
2197
VDP::MsxX512PosInfo::MsxX512PosInfo(VDP& vdp_)
2198
	: Info(vdp_, "msx_x512_pos",
2199
	       "Similar to 'cycle_in_frame', but expressed in "
2200
	       "'narrow' (screen 7) MSX coordinates. So a position "
2201
	       "in the left border has a negative coordinate and "
2202
	       "a position in the right border has a coordinate "
2203
	       "bigger or equal to 512. See also 'msx_x256_pos'.")
2204
{
2205
}
2206
2207
int VDP::MsxX512PosInfo::calc(EmuTime time) const
2208
{
2209
	return vdp.getMSXPos(time).x;
2210
}
2211
2212
2213
// version 1: initial version
2214
// version 2: added frameCount
2215
// version 3: removed verticalAdjust
2216
// version 4: removed lineZero
2217
// version 5: replace readAhead->cpuVramData, added cpuVramReqIsRead
2218
// version 6: added cpuVramReqAddr to solve too_fast_vram_access issue
2219
// version 7: removed cpuVramReqAddr again, fixed issue in a different way
2220
// version 8: removed 'userData' from Schedulable
2221
// version 9: update sprite-enabled-status only once per line
2222
// version 10: added writeAccess
2223
// version 11: added previousCpuSlot, previousCpuSlotIsLate, secondCpuSlot, secondCpuVramReqIsRead
2224
// version 12: added isSlotTableDisplayArea and its two sync points
2225
// version 13: added cpuVramReqData, secondCpuVramReqData
2226
// version 14: added syncLineCountReset and lineCountResetSyncTime; displayStart
2227
//             is latched there instead of at frame start
2228
template<typename Archive>
2229
void VDP::serialize(Archive& ar, unsigned serVersion)
2230
{
2231
	ar.template serializeBase<MSXDevice>(*this);
2232
2233
	if (ar.versionAtLeast(serVersion, 8)) {
2234
		ar.serialize("syncVSync",         syncVSync,
2235
		             "syncDisplayStart",  syncDisplayStart,
2236
		             "syncVScan",         syncVScan,
2237
		             "syncHScan",         syncHScan,
2238
		             "syncHorAdjust",     syncHorAdjust,
2239
		             "syncSetMode",       syncSetMode,
2240
		             "syncSetBlank",      syncSetBlank,
2241
		             "syncCpuVramAccess", syncCpuVramAccess);
2242
		             // no need for syncCmdDone (only used for probe)
2243
		if (ar.versionAtLeast(serVersion, 11)) {
2244
			ar.serialize("syncCpuVramDummy", syncCpuVramDummy);
2245
		}
2246
		if (ar.versionAtLeast(serVersion, 12)) {
2247
			ar.serialize("syncSlotTableStart", syncSlotTableStart,
2248
			             "syncSlotTableEnd",   syncSlotTableEnd);
2249
		}
2250
		if (ar.versionAtLeast(serVersion, 14)) {
2251
			ar.serialize("syncLineCountReset", syncLineCountReset);
2252
		}
2253
	} else {
2254
		Schedulable::restoreOld(ar,
2255
			{&syncVSync, &syncDisplayStart, &syncVScan,
2256
			 &syncHScan, &syncHorAdjust, &syncSetMode,
2257
			 &syncSetBlank, &syncCpuVramAccess});
2258
	}
2259
2260
	// not serialized
2261
	//    std::unique_ptr<Renderer> renderer;
2262
	//    VdpVersion version;
2263
	//    int controlRegMask;
2264
	//    uint8_t controlValueMasks[32];
2265
	//    bool warningPrinted;
2266
2267
	ar.serialize("irqVertical",          irqVertical,
2268
	             "irqHorizontal",        irqHorizontal,
2269
	             "frameStartTime",       frameStartTime,
2270
	             "displayStartSyncTime", displayStartSyncTime,
2271
	             "vScanSyncTime",        vScanSyncTime,
2272
	             "hScanSyncTime",        hScanSyncTime,
2273
	             "displayStart",         displayStart,
2274
	             "horizontalScanOffset", horizontalScanOffset,
2275
	             "horizontalAdjust",     horizontalAdjust,
2276
	             "registers",            controlRegs,
2277
	             "blinkCount",           blinkCount,
2278
	             "vramPointer",          vramPointer,
2279
	             "palette",              palette,
2280
	             "isDisplayArea",        isDisplayArea,
2281
	             "palTiming",            palTiming,
2282
	             "interlaced",           interlaced,
2283
	             "statusReg0",           statusReg0,
2284
	             "statusReg1",           statusReg1,
2285
	             "statusReg2",           statusReg2,
2286
	             "blinkState",           blinkState,
2287
	             "dataLatch",            dataLatch,
2288
	             "registerDataStored",   registerDataStored,
2289
	             "paletteDataStored",    paletteDataStored);
2290
	if (ar.versionAtLeast(serVersion, 5)) {
2291
		ar.serialize("cpuVramData",      cpuVramData,
2292
		             "cpuVramReqIsRead", cpuVramReqIsRead);
2293
	} else {
2294
		ar.serialize("readAhead", cpuVramData);
2295
	}
2296
	ar.serialize("cpuExtendedVram", cpuExtendedVram,
2297
	             "displayEnabled",  displayEnabled);
2298
	uint8_t mode = displayMode.getByte();
2299
	ar.serialize("displayMode", mode);
2300
	displayMode.setByte(mode);
2301
2302
	ar.serialize("cmdEngine",     *cmdEngine,
2303
	             "spriteChecker", *spriteChecker, // must come after displayMode
2304
	             "vram",          *vram); // must come after controlRegs and after spriteChecker
2305
	if constexpr (Archive::IS_LOADER) {
2306
		pendingCpuAccess = syncCpuVramAccess.isPending().has_value();
2307
		update(tooFastAccess);
2308
	}
2309
2310
	if (ar.versionAtLeast(serVersion, 2)) {
2311
		ar.serialize("frameCount", frameCount);
2312
	} else {
2313
		assert(Archive::IS_LOADER);
2314
		// We could estimate the frameCount (assume framerate was
2315
		// constant the whole time). But I think it's better to have
2316
		// an obviously wrong value than an almost correct value.
2317
		frameCount = 0;
2318
	}
2319
2320
	if (ar.versionAtLeast(serVersion, 9)) {
2321
		ar.serialize("syncSetSprites", syncSetSprites);
2322
		ar.serialize("spriteEnabled", spriteEnabled);
2323
	} else {
2324
		assert(Archive::IS_LOADER);
2325
		spriteEnabled = (controlRegs[8] & 0x02) == 0;
2326
	}
2327
	if (ar.versionAtLeast(serVersion, 10)) {
2328
		ar.serialize("writeAccess", writeAccess);
2329
	} else {
2330
		writeAccess = !cpuVramReqIsRead; // best guess
2331
	}
2332
2333
	if (ar.versionAtLeast(serVersion, 12)) {
2334
		ar.serialize("isSlotTableDisplayArea", isSlotTableDisplayArea);
2335
	} else if constexpr (Archive::IS_LOADER) {
2336
		// Close enough: it differs from 'isDisplayArea' for one line a frame.
2337
		isSlotTableDisplayArea = isDisplayArea;
2338
	}
2339
2340
	if (ar.versionAtLeast(serVersion, 14)) {
2341
		ar.serialize("lineCountResetSyncTime", lineCountResetSyncTime);
2342
	} else if constexpr (Archive::IS_LOADER) {
2343
		// Older states computed displayStart at frame start. That is
2344
		// the value the reset would latch too, so if this frame's reset
2345
		// is still ahead just schedule it; it then only re-schedules
2346
		// what was already pending.
2347
		lineCountResetSyncTime = frameStartTime.getTime();
2348
		scheduleLineCountReset(getCurrentTime());
2349
	}
2350
2351
	if (ar.versionAtLeast(serVersion, 11)) {
2352
		ar.serialize("previousCpuSlot",        previousCpuSlot,
2353
		             "previousCpuSlotIsLate",  previousCpuSlotIsLate,
2354
		             "secondCpuSlot",          secondCpuSlot,
2355
		             "secondCpuVramReqIsRead", secondCpuVramReqIsRead);
2356
	} else if constexpr (Archive::IS_LOADER) {
2357
		// The first CPU-VRAM request after loading is then always
2358
		// accepted, which is at most one request too many.
2359
		previousCpuSlot = EmuTime::zero();
2360
		previousCpuSlotIsLate = false;
2361
		secondCpuSlot = EmuTime::infinity();
2362
	}
2363
	if (ar.versionAtLeast(serVersion, 13)) {
2364
		ar.serialize("cpuVramReqData",       cpuVramReqData,
2365
		             "secondCpuVramReqData", secondCpuVramReqData);
2366
	} else if constexpr (Archive::IS_LOADER) {
2367
		// Right unless a second write was accepted just before saving.
2368
		cpuVramReqData = cpuVramData;
2369
		secondCpuVramReqData = cpuVramData;
2370
	}
2371
2372
	// externalVideo does not need serializing. It is set on load by the
2373
	// external video source (e.g. PioneerLDControl).
2374
	//
2375
	// TODO should superimposing be serialized? It cannot be recalculated
2376
	// from register values (it depends on the register values at the start
2377
	// of this frame). But it will be correct at the start of the next
2378
	// frame. Probably good enough.
2379
2380
	if constexpr (Archive::IS_LOADER) {
2381
		renderer->reInit();
2382
	}
2383
}
2384
INSTANTIATE_SERIALIZE_METHODS(VDP);
2385
REGISTER_MSXDEVICE(VDP, "VDP");
2386
2387
} // namespace openmsx