OXYGENHUB
◆ main openmsx / src / video / v9990 / V9990.cc
26.7 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
e8555c89 Andy McCall 1
#include "V9990.hh"
2
3
#include "Display.hh"
4
#include "Reactor.hh"
5
#include "RendererFactory.hh"
6
#include "V9990Renderer.hh"
7
8
#include "narrow.hh"
9
#include "serialize.hh"
10
#include "unreachable.hh"
11
#include "xrange.hh"
12
13
#include <algorithm>
14
#include <array>
15
#include <cassert>
16
#include <memory>
17
18
namespace openmsx {
19
20
static constexpr uint8_t ALLOW_READ  = 1;
21
static constexpr uint8_t ALLOW_WRITE = 2;
22
static constexpr uint8_t NO_ACCESS = 0;
23
static constexpr uint8_t RD_ONLY   = ALLOW_READ;
24
static constexpr uint8_t WR_ONLY   = ALLOW_WRITE;
25
static constexpr uint8_t RD_WR     = ALLOW_READ | ALLOW_WRITE;
26
static constexpr std::array<uint8_t, 64> regAccess = {
27
	WR_ONLY, WR_ONLY, WR_ONLY,          // VRAM Write Address
28
	WR_ONLY, WR_ONLY, WR_ONLY,          // VRAM Read Address
29
	RD_WR, RD_WR,                       // Screen Mode
30
	RD_WR,                              // Control
31
	RD_WR, RD_WR, RD_WR, RD_WR,         // Interrupt
32
	WR_ONLY,                            // Palette Control
33
	WR_ONLY,                            // Palette Pointer
34
	RD_WR,                              // Back Drop Color
35
	RD_WR,                              // Display Adjust
36
	RD_WR, RD_WR, RD_WR, RD_WR,         // Scroll Control A
37
	RD_WR, RD_WR, RD_WR, RD_WR,         // Scroll Control B
38
	RD_WR,                              // Sprite Pattern Table Address
39
	RD_WR,                              // LCD Control
40
	RD_WR,                              // Priority Control
41
	WR_ONLY,                            // Sprite Palette Control
42
	NO_ACCESS, NO_ACCESS, NO_ACCESS,    // 3x not used
43
	WR_ONLY, WR_ONLY, WR_ONLY, WR_ONLY, // Cmd Parameter Src XY
44
	WR_ONLY, WR_ONLY, WR_ONLY, WR_ONLY, // Cmd Parameter Dest XY
45
	WR_ONLY, WR_ONLY, WR_ONLY, WR_ONLY, // Cmd Parameter Size XY
46
	WR_ONLY, WR_ONLY, WR_ONLY, WR_ONLY, // Cmd Parameter Arg, LogOp, WrtMask
47
	WR_ONLY, WR_ONLY, WR_ONLY, WR_ONLY, // Cmd Parameter Font Color
48
	WR_ONLY, RD_ONLY, RD_ONLY,          // Cmd Parameter OpCode, Border X
49
	NO_ACCESS, NO_ACCESS, NO_ACCESS,    // registers 55-63
50
	NO_ACCESS, NO_ACCESS, NO_ACCESS,
51
	NO_ACCESS, NO_ACCESS, NO_ACCESS
52
};
53
54
// -------------------------------------------------------------------------
55
// Constructor & Destructor
56
// -------------------------------------------------------------------------
57
58
V9990::V9990(const DeviceConfig& config)
59
	: MSXDevice(config)
60
	, syncVSync(*this)
61
	, syncDisplayStart(*this)
62
	, syncVScan(*this)
63
	, syncHScan(*this)
64
	, syncSetMode(*this)
65
	, syncCmdEnd(*this)
66
	, v9990RegDebug(*this)
67
	, v9990PalDebug(*this)
68
	, irq(getMotherBoard(), getName() + ".IRQ")
69
	, display(getReactor().getDisplay())
70
	, invalidRegisterReadCallback(getCommandController(),
71
		tmpStrCat(getName(), "_invalid_register_read_callback"),
72
		"Tcl proc to call when a write-only register was read from. "
73
		"Input: register number (0-63)",
74
                {}, Setting::Save::YES)
75
	, invalidRegisterWriteCallback(getCommandController(),
76
		tmpStrCat(getName(), "_invalid_register_write_callback"),
77
		"Tcl proc to call when a read-only register was written to. "
78
		"Input: register number (0-63), 8-bit data",
79
		{}, Setting::Save::YES)
80
	, vram(*this, getCurrentTime())
81
	, cmdEngine(*this, getCurrentTime(), display.getRenderSettings())
82
	, frameStartTime(getCurrentTime())
83
	, hScanSyncTime(getCurrentTime())
84
{
85
	// clear regs TODO find realistic init values
86
	setDisplayMode(calcDisplayMode());
87
88
	// initialize palette
89
	for (auto i : xrange(64)) {
90
		palette[4 * i + 0] = 0x9F;
91
		palette[4 * i + 1] = 0x1F;
92
		palette[4 * i + 2] = 0x1F;
93
		palette[4 * i + 3] = 0x00;
94
	}
95
96
	vram.setCmdEngine(cmdEngine);
97
98
	// Start with NTSC timing
99
	setVerticalTiming();
100
101
	// Initialise rendering system
102
	EmuTime time = getCurrentTime();
103
	createRenderer(time);
104
105
	powerUp(time);
106
	display.attach(*this);
107
}
108
109
V9990::~V9990()
110
{
111
	display.detach(*this);
112
}
113
114
PostProcessor* V9990::getPostProcessor() const
115
{
116
	return renderer->getPostProcessor();
117
}
118
119
// -------------------------------------------------------------------------
120
// MSXDevice
121
// -------------------------------------------------------------------------
122
123
void V9990::powerUp(EmuTime time)
124
{
125
	vram.clear();
126
	reset(time);
127
}
128
129
void V9990::reset(EmuTime time)
130
{
131
	syncVSync       .removeSyncPoint();
132
	syncDisplayStart.removeSyncPoint();
133
	syncVScan       .removeSyncPoint();
134
	syncHScan       .removeSyncPoint();
135
	syncSetMode     .removeSyncPoint();
136
	syncCmdEnd      .removeSyncPoint();
137
138
	// Clear registers / ports
139
	std::ranges::fill(regs, 0);
140
	status = 0;
141
	regSelect = 0xFF; // TODO check value for power-on and reset
142
	vramWritePtr = 0;
143
	vramReadPtr = 0;
144
	vramReadBuffer = 0;
145
	systemReset = false; // verified on real MSX
146
	setDisplayMode(calcDisplayMode());
147
148
	isDisplayArea = false;
149
	displayEnabled = false;
150
	superimposing = false;
151
152
	// Reset IRQs
153
	writeIO(INTERRUPT_FLAG, 0xFF, time);
154
155
	palTiming = false;
156
	// Reset sub-systems
157
	cmdEngine.sync(time);
158
	renderer->reset(time);
159
	cmdEngine.reset(time);
160
161
	// Init scheduling
162
	frameStart(time);
163
}
164
165
uint8_t V9990::readIO(uint16_t port, EmuTime time)
166
{
167
	port &= 0x0F;
168
169
	// calculate return value (mostly uses peekIO)
170
	uint8_t result = [&] {
171
		switch (port) {
172
		case COMMAND_DATA:
173
			return cmdEngine.getCmdData(time);
174
		case VRAM_DATA:
175
		case PALETTE_DATA:
176
		case REGISTER_DATA:
177
		case INTERRUPT_FLAG:
178
		case STATUS:
179
		case KANJI_ROM_0:
180
		case KANJI_ROM_1:
181
		case KANJI_ROM_2:
182
		case KANJI_ROM_3:
183
		case REGISTER_SELECT:
184
		case SYSTEM_CONTROL:
185
		default:
186
			return peekIO(port, time);
187
		}
188
	}();
189
	// TODO verify this, especially REGISTER_DATA
190
	if (systemReset) return result; // no side-effects
191
192
	// execute side-effects
193
	switch (port) {
194
	case VRAM_DATA:
195
		if (!(regs[VRAM_READ_ADDRESS_2] & 0x80)) {
196
			vramReadPtr = getVRAMAddr(VRAM_READ_ADDRESS_0) + 1;
197
			setVRAMAddr(VRAM_READ_ADDRESS_0, vramReadPtr);
198
			// Update read buffer. TODO: timing?
199
			vramReadBuffer = vram.readVRAMCPU(vramReadPtr, time);
200
		}
201
		break;
202
203
	case PALETTE_DATA:
204
		if (!(regs[PALETTE_CONTROL] & 0x10)) {
205
			uint8_t& palPtr = regs[PALETTE_POINTER];
206
			switch (palPtr & 3) {
207
			case 0:  palPtr += 1; break; // red
208
			case 1:  palPtr += 1; break; // green
209
			case 2:  palPtr += 2; break; // blue
210
			default: palPtr -= 3; break; // checked on real V9990
211
			}
212
		}
213
		break;
214
215
	case REGISTER_DATA:
216
		if (!(regSelect & 0x40)) {
217
			//regSelect = ( regSelect      & 0xC0) |
218
			//            ((regSelect + 1) & 0x3F);
219
			regSelect = (regSelect + 1) & ~0x40;
220
		}
221
		break;
222
	}
223
	return result;
224
}
225
226
uint8_t V9990::peekIO(uint16_t port, EmuTime time) const
227
{
228
	switch (port & 0x0F) {
229
	case VRAM_DATA: {
230
		// TODO in 'systemReset' mode, this seems to hang the MSX
231
		// V9990 fetches from read buffer instead of directly from VRAM.
232
		// The read buffer is the reason why it is impossible to fill
233
		// vram by copying a block from "addr" to "addr+1".
234
		return vramReadBuffer;
235
	}
236
	case PALETTE_DATA:
237
		return palette[regs[PALETTE_POINTER]];
238
239
	case COMMAND_DATA:
240
		return cmdEngine.peekCmdData(time);
241
242
	case REGISTER_DATA:
243
		return readRegister(regSelect & 0x3F, time);
244
245
	case INTERRUPT_FLAG:
246
		return pendingIRQs;
247
248
	case STATUS: {
249
		unsigned left   = getLeftBorder();
250
		unsigned right  = getRightBorder();
251
		unsigned top    = getTopBorder();
252
		unsigned bottom = getBottomBorder();
253
		unsigned ticks = getUCTicksThisFrame(time);
254
		unsigned x = ticks % V9990DisplayTiming::UC_TICKS_PER_LINE;
255
		unsigned y = ticks / V9990DisplayTiming::UC_TICKS_PER_LINE;
256
		bool hr = (x < left) || (right  <= x);
257
		bool vr = (y < top)  || (bottom <= y);
258
259
		return cmdEngine.getStatus(time) |
260
		       (vr ? 0x40 : 0x00) |
261
		       (hr ? 0x20 : 0x00) |
262
		       (status & 0x06);
263
	}
264
	case KANJI_ROM_1:
265
	case KANJI_ROM_3:
266
		// not used in Gfx9000
267
		return 0xFF; // TODO check
268
269
	case REGISTER_SELECT:
270
	case SYSTEM_CONTROL:
271
	case KANJI_ROM_0:
272
	case KANJI_ROM_2:
273
	default:
274
		// write-only
275
		return 0xFF;
276
	}
277
}
278
279
void V9990::writeIO(uint16_t port, uint8_t val, EmuTime time)
280
{
281
	port &= 0x0F;
282
	switch (port) {
283
		case VRAM_DATA: {
284
			// write VRAM
285
			if (systemReset) {
286
				// TODO writes in systemReset mode seem to have
287
				//  'some' effect but it's not immediately clear
288
				//  what the exact behaviour is
289
				return;
290
			}
291
			unsigned addr = getVRAMAddr(VRAM_WRITE_ADDRESS_0);
292
			vram.writeVRAMCPU(addr, val, time);
293
			if (!(regs[VRAM_WRITE_ADDRESS_2] & 0x80)) {
294
				setVRAMAddr(VRAM_WRITE_ADDRESS_0, addr + 1);
295
			}
296
			break;
297
		}
298
		case PALETTE_DATA: {
299
			if (systemReset) {
300
				// Equivalent to writing 0 and keeping palPtr = 0
301
				// The above interpretation makes it similar to
302
				// writes to REGISTER_DATA, REGISTER_SELECT.
303
				writePaletteRegister(0, 0, time);
304
				return;
305
			}
306
			uint8_t& palPtr = regs[PALETTE_POINTER];
307
			writePaletteRegister(palPtr, val, time);
308
			switch (palPtr & 3) {
309
				case 0:  palPtr += 1; break; // red
310
				case 1:  palPtr += 1; break; // green
311
				case 2:  palPtr += 2; break; // blue
312
				default: palPtr -= 3; break; // checked on real V9990
313
			}
314
			break;
315
		}
316
		case COMMAND_DATA:
317
			// systemReset state doesn't matter:
318
			//   command below has no effect in systemReset mode
319
			//assert(cmdEngine);
320
			cmdEngine.setCmdData(val, time);
321
			break;
322
323
		case REGISTER_DATA: {
324
			// write register
325
			if (systemReset) {
326
				// In systemReset mode, write has no effect,
327
				// but 'regSelect' is increased.
328
				// I don't know if write is ignored or a write
329
				// with val=0 is executed. Though both have the
330
				// same effect and the latter is more in line
331
				// with writes to PALETTE_DATA and
332
				// REGISTER_SELECT.
333
				val = 0;
334
			}
335
			writeRegister(regSelect & 0x3F, val, time);
336
			if (!(regSelect & 0x80)) {
337
				regSelect = ( regSelect      & 0xC0) |
338
				            ((regSelect + 1) & 0x3F);
339
			}
340
			break;
341
		}
342
		case REGISTER_SELECT:
343
			if (systemReset) {
344
				// Tested on real MSX. Also when no write is done
345
				// to this port, regSelect is not RESET when
346
				// entering/leaving systemReset mode.
347
				// This behavior is similar to PALETTE_DATA and
348
				// REGISTER_DATA.
349
				val = 0;
350
			}
351
			regSelect = val;
352
			break;
353
354
		case STATUS:
355
			// read-only, ignore writes
356
			break;
357
358
		case INTERRUPT_FLAG:
359
			// systemReset state doesn't matter:
360
			//   stuff below has no effect in systemReset mode
361
			pendingIRQs &= ~val;
362
			if (!(pendingIRQs & regs[INTERRUPT_0])) {
363
				irq.reset();
364
			}
365
			scheduleHscan(time);
366
			break;
367
368
		case SYSTEM_CONTROL: {
369
			// TODO investigate: does switching overscan mode
370
			//      happen at next line or next frame
371
			status = uint8_t((status & 0xFB) | ((val & 1) << 2));
372
			syncAtNextLine(syncSetMode, time);
373
374
			if (bool newSystemReset = (val & 2) != 0;
375
			    newSystemReset != systemReset) {
376
				systemReset = newSystemReset;
377
				if (systemReset) {
378
					// Enter systemReset mode
379
					//   Verified on real MSX: palette data
380
					//   and VRAM content are NOT reset.
381
					for (auto i : xrange(uint8_t(64))) {
382
						writeRegister(i, 0, time);
383
					}
384
					// TODO verify IRQ behaviour
385
					writeIO(INTERRUPT_FLAG, 0xFF, time);
386
				}
387
			}
388
			break;
389
		}
390
		case KANJI_ROM_0:
391
		case KANJI_ROM_1:
392
		case KANJI_ROM_2:
393
		case KANJI_ROM_3:
394
			// not used in Gfx9000, ignore
395
			break;
396
397
		default:
398
			// ignore
399
			break;
400
	}
401
}
402
403
// =========================================================================
404
// Private stuff
405
// =========================================================================
406
407
// -------------------------------------------------------------------------
408
// Schedulable
409
// -------------------------------------------------------------------------
410
411
void V9990::execVSync(EmuTime time)
412
{
413
	// Transition from one frame to the next
414
	renderer->frameEnd(time);
415
	frameStart(time);
416
}
417
418
void V9990::execDisplayStart(EmuTime time)
419
{
420
	if (displayEnabled) {
421
		renderer->updateDisplayEnabled(true, time);
422
	}
423
	isDisplayArea = true;
424
}
425
426
void V9990::execVScan(EmuTime time)
427
{
428
	if (isDisplayEnabled()) {
429
		renderer->updateDisplayEnabled(false, time);
430
	}
431
	isDisplayArea = false;
432
	raiseIRQ(VER_IRQ);
433
}
434
435
void V9990::execHScan()
436
{
437
	raiseIRQ(HOR_IRQ);
438
}
439
440
void V9990::execSetMode(EmuTime time)
441
{
442
	auto newMode = calcDisplayMode();
443
	renderer->setDisplayMode(newMode, time);
444
	renderer->setColorMode(getColorMode(), time);
445
	setDisplayMode(newMode);
446
}
447
448
void V9990::execCheckCmdEnd(EmuTime time)
449
{
450
	cmdEngine.sync(time);
451
	scheduleCmdEnd(time); // in case of underestimation
452
}
453
454
void V9990::scheduleCmdEnd(EmuTime time)
455
{
456
	if (regs[INTERRUPT_0] & 4) {
457
		auto next = cmdEngine.estimateCmdEnd();
458
		if (next > time) {
459
			syncCmdEnd.setSyncPoint(next);
460
		}
461
	}
462
}
463
464
// -------------------------------------------------------------------------
465
// VideoSystemChangeListener
466
// -------------------------------------------------------------------------
467
468
void V9990::preVideoSystemChange() noexcept
469
{
470
	renderer.reset();
471
}
472
473
void V9990::postVideoSystemChange() noexcept
474
{
475
	EmuTime time = getCurrentTime();
476
	createRenderer(time);
477
	renderer->frameStart(time);
478
}
479
480
// -------------------------------------------------------------------------
481
// RegDebug
482
// -------------------------------------------------------------------------
483
484
V9990::RegDebug::RegDebug(const V9990& v9990_)
485
	: SimpleDebuggable(v9990_.getMotherBoard(),
486
	                   v9990_.getName() + " regs", "V9990 registers", 0x40)
487
{
488
}
489
490
uint8_t V9990::RegDebug::read(unsigned address)
491
{
492
	auto& v9990 = OUTER(V9990, v9990RegDebug);
493
	return v9990.regs[address];
494
}
495
496
void V9990::RegDebug::readBlock(unsigned start, std::span<uint8_t> output)
497
{
498
	auto& v9990 = OUTER(V9990, v9990RegDebug);
499
	copy_to_range(std::span{v9990.regs}.subspan(start, output.size()), output);
500
}
501
502
void V9990::RegDebug::write(unsigned address, uint8_t value, EmuTime time)
503
{
504
	auto& v9990 = OUTER(V9990, v9990RegDebug);
505
	v9990.writeRegister(narrow<uint8_t>(address), value, time);
506
}
507
508
// -------------------------------------------------------------------------
509
// PalDebug
510
// -------------------------------------------------------------------------
511
512
V9990::PalDebug::PalDebug(const V9990& v9990_)
513
	: SimpleDebuggable(v9990_.getMotherBoard(),
514
	                   v9990_.getName() + " palette",
515
	                   "V9990 palette (format is R, G, B, 0).", 0x100)
516
{
517
}
518
519
uint8_t V9990::PalDebug::read(unsigned address)
520
{
521
	auto& v9990 = OUTER(V9990, v9990PalDebug);
522
	return v9990.palette[address];
523
}
524
525
void V9990::PalDebug::readBlock(unsigned start, std::span<uint8_t> output)
526
{
527
	auto& v9990 = OUTER(V9990, v9990PalDebug);
528
	copy_to_range(std::span{v9990.palette}.subspan(start, output.size()), output);
529
}
530
531
void V9990::PalDebug::write(unsigned address, uint8_t value, EmuTime time)
532
{
533
	auto& v9990 = OUTER(V9990, v9990PalDebug);
534
	v9990.writePaletteRegister(narrow<uint8_t>(address), value, time);
535
}
536
537
// -------------------------------------------------------------------------
538
// Private methods
539
// -------------------------------------------------------------------------
540
541
inline unsigned V9990::getVRAMAddr(RegisterId base) const
542
{
543
	return   regs[base + 0] +
544
	        (regs[base + 1] << 8) +
545
	       ((regs[base + 2] & 0x07) << 16);
546
}
547
548
inline void V9990::setVRAMAddr(RegisterId base, unsigned addr)
549
{
550
	regs[base + 0] =   addr &     0xFF;
551
	regs[base + 1] =  (addr &   0xFF00) >> 8;
552
	regs[base + 2] = ((addr & 0x070000) >> 16) | (regs[base + 2] & 0x80);
553
	// TODO check
554
}
555
556
uint8_t V9990::readRegister(uint8_t reg, EmuTime time) const
557
{
558
	// TODO sync(time) (if needed at all)
559
	if (systemReset) return 255; // verified on real MSX
560
561
	assert(reg < 64);
562
	if (regAccess[reg] & ALLOW_READ) {
563
		if (reg < CMD_PARAM_BORDER_X_0) {
564
			return regs[reg];
565
		} else {
566
			uint16_t borderX = cmdEngine.getBorderX(time);
567
			return (reg == CMD_PARAM_BORDER_X_0)
568
			       ? narrow_cast<uint8_t>(borderX & 0xFF)
569
			       : narrow_cast<uint8_t>(borderX >> 8);
570
		}
571
	} else {
572
		invalidRegisterReadCallback.execute(reg);
573
		return 0xFF;
574
	}
575
}
576
577
void V9990::syncAtNextLine(SyncBase& type, EmuTime time) const
578
{
579
	int line = getUCTicksThisFrame(time) / V9990DisplayTiming::UC_TICKS_PER_LINE;
580
	int ticks = (line + 1) * V9990DisplayTiming::UC_TICKS_PER_LINE;
581
	EmuTime nextTime = frameStartTime + ticks;
582
	type.setSyncPoint(nextTime);
583
}
584
585
void V9990::writeRegister(uint8_t reg, uint8_t val, EmuTime time)
586
{
587
	// Found this table by writing 0xFF to a register and reading
588
	// back the value (only works for read/write registers)
589
	static constexpr std::array<uint8_t, 32> regWriteMask = {
590
		0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
591
		0xFF, 0x87, 0xFF, 0x83, 0x0F, 0xFF, 0xFF, 0xFF,
592
		0xFF, 0xFF, 0xDF, 0x07, 0xFF, 0xFF, 0xC1, 0x07,
593
		0x3F, 0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
594
	};
595
596
	assert(reg < 64);
597
	if (!(regAccess[reg] & ALLOW_WRITE)) {
598
		// register not writable
599
		invalidRegisterWriteCallback.execute(reg, val);
600
		return;
601
	}
602
	if (reg >= CMD_PARAM_SRC_ADDRESS_0) {
603
		cmdEngine.setCmdReg(reg, val, time);
604
		if (reg == CMD_PARAM_OPCODE) {
605
			scheduleCmdEnd(time);
606
		}
607
		return;
608
	}
609
610
	val &= regWriteMask[reg];
611
612
	// This optimization is not valid for the vertical scroll registers
613
	// TODO is this optimization still useful for other registers?
614
	//if (!change) return;
615
616
	// Perform additional tasks before new value becomes active
617
	// note: no update for SCROLL_CONTROL_AY1, SCROLL_CONTROL_BY1
618
	switch (reg) {
619
		case SCREEN_MODE_0:
620
		case SCREEN_MODE_1:
621
			// TODO verify this on real V9990
622
			syncAtNextLine(syncSetMode, time);
623
			break;
624
		case PALETTE_CONTROL:
625
			renderer->setColorMode(getColorMode(val), time);
626
			break;
627
		case BACK_DROP_COLOR:
628
			renderer->updateBackgroundColor(val & 63, time);
629
			break;
630
		case SCROLL_CONTROL_AY0:
631
			renderer->updateScrollAYLow(time);
632
			break;
633
		case SCROLL_CONTROL_BY0:
634
			renderer->updateScrollBYLow(time);
635
			break;
636
		case SCROLL_CONTROL_AX0:
637
		case SCROLL_CONTROL_AX1:
638
			renderer->updateScrollAX(time);
639
			break;
640
		case SCROLL_CONTROL_BX0:
641
		case SCROLL_CONTROL_BX1:
642
			renderer->updateScrollBX(time);
643
			break;
644
		case DISPLAY_ADJUST:
645
			// TODO verify on real V9990: when exactly does a
646
			//  change in horizontal/vertical adjust take place
647
			break;
648
	}
649
	// commit the change
650
	regs[reg] = val;
651
652
	// Perform additional tasks after new value became active
653
	switch (reg) {
654
		case VRAM_WRITE_ADDRESS_2:
655
			// write pointer is only updated on R#2 write
656
			vramWritePtr = getVRAMAddr(VRAM_WRITE_ADDRESS_0);
657
			break;
658
		case VRAM_READ_ADDRESS_2:
659
			// write pointer is only updated on R#5 write
660
			vramReadPtr = getVRAMAddr(VRAM_READ_ADDRESS_0);
661
			// update read buffer immediately after read pointer changes. TODO: timing?
662
			vramReadBuffer = vram.readVRAMCPU(vramReadPtr, time);
663
			break;
664
		case SCREEN_MODE_0:
665
		case SCREEN_MODE_1:
666
		case CONTROL:
667
			// These influence the command timing
668
			scheduleCmdEnd(time);
669
			break;
670
		case INTERRUPT_0:
671
			irq.set((pendingIRQs & val) != 0);
672
			scheduleCmdEnd(time);
673
			break;
674
		case INTERRUPT_1:
675
		case INTERRUPT_2:
676
		case INTERRUPT_3:
677
			scheduleHscan(time);
678
			break;
679
	}
680
}
681
682
void V9990::writePaletteRegister(uint8_t reg, uint8_t val, EmuTime time)
683
{
684
	switch (reg & 3) {
685
		case 0: val &= 0x9F; break;
686
		case 1: val &= 0x1F; break;
687
		case 2: val &= 0x1F; break;
688
		case 3: val  = 0x00; break;
689
	}
690
	palette[reg] = val;
691
	reg &= ~3;
692
	uint8_t index = reg / 4;
693
	bool ys = isSuperimposing() && (palette[reg] & 0x80);
694
	renderer->updatePalette(index, palette[reg + 0] & 0x1F, palette[reg + 1],
695
	                        palette[reg + 2], ys, time);
696
	if (index == regs[BACK_DROP_COLOR]) {
697
		renderer->updateBackgroundColor(index, time);
698
	}
699
}
700
701
V9990::GetPaletteResult V9990::getPalette(int index) const
702
{
703
	uint8_t r = palette[4 * index + 0] & 0x1F;
704
	uint8_t g = palette[4 * index + 1];
705
	uint8_t b = palette[4 * index + 2];
706
	bool ys = isSuperimposing() && (palette[4 * index + 0] & 0x80);
707
	return {.r = r, .g = g, .b = b, .ys = ys};
708
}
709
710
void V9990::createRenderer(EmuTime time)
711
{
712
	assert(!renderer);
713
	renderer = RendererFactory::createV9990Renderer(*this, display);
714
	renderer->reset(time);
715
}
716
717
void V9990::frameStart(EmuTime time)
718
{
719
	// Update setings that are fixed at the start of a frame
720
	displayEnabled = (regs[CONTROL]       & 0x80) != 0;
721
	palTiming      = (regs[SCREEN_MODE_1] & 0x08) != 0;
722
	interlaced     = (regs[SCREEN_MODE_1] & 0x02) != 0;
723
	scrollAYHigh   = regs[SCROLL_CONTROL_AY1];
724
	scrollBYHigh   = regs[SCROLL_CONTROL_BY1];
725
	setVerticalTiming();
726
	status ^= 0x02; // flip EO bit
727
728
	if (bool newSuperimposing = (regs[CONTROL] & 0x20) && externalVideoSource;
729
	    superimposing != newSuperimposing) {
730
		superimposing = newSuperimposing;
731
		renderer->updateSuperimposing(superimposing, time);
732
	}
733
734
	frameStartTime.reset(time);
735
736
	// schedule next VSYNC
737
	syncVSync.setSyncPoint(
738
		frameStartTime + V9990DisplayTiming::getUCTicksPerFrame(palTiming));
739
740
	// schedule DISPLAY_START
741
	syncDisplayStart.setSyncPoint(
742
	    frameStartTime + getTopBorder() * V9990DisplayTiming::UC_TICKS_PER_LINE);
743
744
	// schedule VSCAN
745
	syncVScan.setSyncPoint(
746
	    frameStartTime + getBottomBorder() * V9990DisplayTiming::UC_TICKS_PER_LINE);
747
748
	renderer->frameStart(time);
749
}
750
751
void V9990::raiseIRQ(IRQType irqType)
752
{
753
	pendingIRQs |= irqType;
754
	if (pendingIRQs & regs[INTERRUPT_0]) {
755
		irq.set();
756
	}
757
}
758
759
void V9990::setHorizontalTiming()
760
{
761
	switch (mode) {
762
	using enum V9990DisplayMode;
763
	case P1: case P2:
764
	case B1: case B3: case B7:
765
		horTiming = &V9990DisplayTiming::lineMCLK;
766
		break;
767
	case B0: case B2: case B4:
768
		horTiming = &V9990DisplayTiming::lineXTAL;
769
		break;
770
	case B5: case B6:
771
		break;
772
	default:
773
		UNREACHABLE;
774
	}
775
}
776
777
void V9990::setVerticalTiming()
778
{
779
	switch (mode) {
780
	using enum V9990DisplayMode;
781
	case P1: case P2:
782
	case B1: case B3: case B7:
783
		verTiming = isPalTiming()
784
		          ? &V9990DisplayTiming::displayPAL_MCLK
785
		          : &V9990DisplayTiming::displayNTSC_MCLK;
786
		break;
787
	case B0: case B2: case B4:
788
		verTiming = isPalTiming()
789
		          ? &V9990DisplayTiming::displayPAL_XTAL
790
		          : &V9990DisplayTiming::displayNTSC_XTAL;
791
		break;
792
	case B5: case B6:
793
		break;
794
	default:
795
		UNREACHABLE;
796
	}
797
}
798
799
V9990ColorMode V9990::getColorMode(uint8_t pal_ctrl) const
800
{
801
	using enum V9990ColorMode;
802
	if (!(regs[SCREEN_MODE_0] & 0x80)) {
803
		return BP4;
804
	} else {
805
		switch (regs[SCREEN_MODE_0] & 0x03) {
806
			case 0x00: return BP2;
807
			case 0x01: return BP4;
808
			case 0x02:
809
				switch (pal_ctrl & 0xC0) {
810
					case 0x00: return BP6;
811
					case 0x40: return BD8;
812
					case 0x80: return BYJK;
813
					case 0xC0: return BYUV;
814
				}
815
				break;
816
			case 0x03: return BD16;
817
		}
818
	}
819
	UNREACHABLE;
820
}
821
822
V9990ColorMode V9990::getColorMode() const
823
{
824
	return getColorMode(regs[PALETTE_CONTROL]);
825
}
826
827
V9990DisplayMode V9990::calcDisplayMode() const
828
{
829
	using enum V9990DisplayMode;
830
	switch (regs[SCREEN_MODE_0] & 0xC0) {
831
		case 0x00:
832
			return P1;
833
		case 0x40:
834
			return P2;
835
		case 0x80:
836
			if (status & 0x04) { // MCLK timing
837
				switch(regs[SCREEN_MODE_0] & 0x30) {
838
					case 0x00: return B0;
839
					case 0x10: return B2;
840
					case 0x20: return B4;
841
				}
842
			} else { // XTAL1 timing
843
				switch(regs[SCREEN_MODE_0] & 0x30) {
844
					case 0x00: return B1;
845
					case 0x10: return B3;
846
					case 0x20: return B7;
847
				}
848
			}
849
			break;
850
	}
851
	// invalid display mode
852
	return P1; // TODO Check
853
}
854
855
void V9990::setDisplayMode(V9990DisplayMode newMode)
856
{
857
	mode = newMode;
858
	setHorizontalTiming();
859
}
860
861
void V9990::scheduleHscan(EmuTime time)
862
{
863
	// remove pending HSCAN, if any
864
	if (hScanSyncTime > time) {
865
		syncHScan.removeSyncPoint();
866
		hScanSyncTime = time;
867
	}
868
869
	if (pendingIRQs & HOR_IRQ) {
870
		// flag already set, no need to schedule
871
		return;
872
	}
873
874
	int ticks = narrow<int>(frameStartTime.getTicksTill(time));
875
	bool perLine = regs[INTERRUPT_2] & 0x80;
876
	int offset = [&] {
877
		if (perLine) {
878
			// every line
879
			return ticks - (ticks % V9990DisplayTiming::UC_TICKS_PER_LINE);
880
		} else {
881
			int line = regs[INTERRUPT_1] + 256 * (regs[INTERRUPT_2] & 3) +
882
				   getTopBorder();
883
			return line * V9990DisplayTiming::UC_TICKS_PER_LINE;
884
		}
885
	}();
886
	int mult = (status & 0x04) ? 3 : 2; // MCLK / XTAL1
887
	offset += (regs[INTERRUPT_3] & 0x0F) * 64 * mult;
888
	if (offset <= ticks) {
889
		offset += perLine ? V9990DisplayTiming::UC_TICKS_PER_LINE
890
		                  : V9990DisplayTiming::getUCTicksPerFrame(palTiming);
891
	}
892
893
	hScanSyncTime = frameStartTime + offset;
894
	syncHScan.setSyncPoint(hScanSyncTime);
895
}
896
897
static constexpr auto displayModeInfo = std::to_array<enum_string<V9990DisplayMode>>({
898
	{ "P1", V9990DisplayMode::P1 }, { "P2", V9990DisplayMode::P2 },
899
	{ "B0", V9990DisplayMode::B0 }, { "B1", V9990DisplayMode::B1 },
900
	{ "B2", V9990DisplayMode::B2 }, { "B3", V9990DisplayMode::B3 },
901
	{ "B4", V9990DisplayMode::B4 }, { "B5", V9990DisplayMode::B5 },
902
	{ "B6", V9990DisplayMode::B6 }, { "B7", V9990DisplayMode::B7 },
903
});
904
SERIALIZE_ENUM(V9990DisplayMode, displayModeInfo);
905
906
// version 1: initial version
907
// version 2: added systemReset
908
// version 3: added vramReadPtr, vramWritePtr, vramReadBuffer
909
// version 4: removed 'userData' from Schedulable
910
// version 5: added syncCmdEnd
911
template<typename Archive>
912
void V9990::serialize(Archive& ar, unsigned version)
913
{
914
	ar.template serializeBase<MSXDevice>(*this);
915
916
	if (ar.versionAtLeast(version, 4)) {
917
		ar.serialize("syncVSync",        syncVSync,
918
		             "syncDisplayStart", syncDisplayStart,
919
		             "syncVScan",        syncVScan,
920
		             "syncHScan",        syncHScan,
921
		             "syncSetMode",      syncSetMode);
922
	} else {
923
		Schedulable::restoreOld(ar,
924
			{&syncVSync, &syncDisplayStart, &syncVScan,
925
			 &syncHScan, &syncSetMode});
926
	}
927
	if (ar.versionAtLeast(version, 5)) {
928
		ar.serialize("syncCmdEnd", syncCmdEnd);
929
	}
930
931
	ar.serialize("displayMode", mode); // must be deserialized before cmdEngine (because it's used to restore some derived state in cmdEngine)
932
	ar.serialize("vram",           vram,
933
	             "cmdEngine",      cmdEngine,
934
	             "irq",            irq,
935
	             "frameStartTime", frameStartTime,
936
	             "hScanSyncTime",  hScanSyncTime);
937
	ar.serialize_blob("palette", palette);
938
	ar.serialize("status",      status,
939
	             "pendingIRQs", pendingIRQs);
940
	ar.serialize_blob("registers", regs);
941
	ar.serialize("regSelect",      regSelect,
942
	             "palTiming",      palTiming,
943
	             "interlaced",     interlaced,
944
	             "isDisplayArea",  isDisplayArea,
945
	             "displayEnabled", displayEnabled,
946
	             "scrollAYHigh",   scrollAYHigh,
947
	             "scrollBYHigh",   scrollBYHigh);
948
949
	if (ar.versionBelow(version, 2)) {
950
		systemReset = false;
951
	} else {
952
		ar.serialize("systemReset", systemReset);
953
	}
954
955
	if (ar.versionBelow(version, 3)) {
956
		vramReadPtr = getVRAMAddr(VRAM_READ_ADDRESS_0);
957
		vramWritePtr = getVRAMAddr(VRAM_WRITE_ADDRESS_0);
958
		vramReadBuffer = vram.readVRAMCPU(vramReadPtr, getCurrentTime());
959
	} else {
960
		ar.serialize("vramReadPtr",    vramReadPtr,
961
		             "vramWritePtr",   vramWritePtr,
962
		             "vramReadBuffer", vramReadBuffer);
963
	}
964
965
	// No need to serialize 'externalVideoSource', it will be restored when
966
	// the external peripheral (e.g. Video9000) is de-serialized.
967
	// TODO should 'superimposing' be serialized? It can't be recalculated
968
	// from register values (it depends on the register values at the start
969
	// of this frame). But it will be correct at the start of the next
970
	// frame. Good enough?
971
972
	if constexpr (Archive::IS_LOADER) {
973
		// TODO This uses 'mode' to calculate 'horTiming' and
974
		//      'verTiming'. Are these always in sync? Or can for
975
		//      example one change at any time and the other only
976
		//      at start of frame (or next line)? Does this matter?
977
		setHorizontalTiming();
978
		setVerticalTiming();
979
980
		renderer->reset(getCurrentTime());
981
	}
982
}
983
INSTANTIATE_SERIALIZE_METHODS(V9990);
984
REGISTER_MSXDEVICE(V9990, "V9990");
985
986
} // namespace openmsx