OXYGENHUB
◆ main openmsx / src / video / VDPCmdEngine.cc
91.3 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
/*
2
TODO:
3
- How is 64K VRAM handled?
4
  VRAM size is never inspected by the command engine.
5
  How does a real MSX handle it?
6
  Mirroring of first 64K or empty memory space?
7
- How is extended VRAM handled?
8
  The current VDP implementation does not support it.
9
  Since it is not accessed by the renderer, it is possible allocate
10
  it here.
11
  But maybe it makes more sense to have all RAM managed by the VDP?
12
- Currently all VRAM access is done at the start time of a series of
13
  updates: currentTime is not increased until the very end of the sync
14
  method. It should of course be updated after every read and write.
15
  An acceptable approximation would be an update after every pixel/byte
16
  operation.
17
*/
18
19
/*
20
 About NX, NY
21
  - for block commands NX = 0 is equivalent to NX = 512 (TODO recheck this)
22
    and NY = 0 is equivalent to NY = 1024
23
  - when NX or NY is too large and the VDP command hits the border, the
24
    following happens:
25
     - when the left or right border is hit, the line terminates
26
     - when the top border is hit (line 0) the command terminates
27
     - when the bottom border (line 511 or 1023) the command continues
28
       (wraps to the top)
29
  - in 512 lines modes (e.g. screen 7) NY is NOT limited to 512, so when
30
    NY > 512, part of the screen is overdrawn twice
31
  - in 256 columns modes (e.g. screen 5) when "SX/DX >= 256", only 1 element
32
    (pixel or byte) is processed per horizontal line. The real x-coordinate
33
    is "SX/DX & 255".
34
*/
35
36
#include "VDPCmdEngine.hh"
37
38
#include "VDPVRAM.hh"
39
40
#include "EmuTime.hh"
41
#include "serialize.hh"
42
43
#include "unreachable.hh"
44
45
#include <algorithm>
46
#include <array>
47
#include <cassert>
48
#include <iostream>
49
#include <string_view>
50
51
namespace openmsx {
52
53
using namespace VDPAccessSlots;
54
55
template<typename Mode>
56
static constexpr unsigned clipNX_1_pixel(unsigned DX, unsigned NX, uint8_t ARG)
57
{
58
	if (DX >= Mode::PIXELS_PER_LINE) [[unlikely]] {
59
		return 1;
60
	}
61
	NX = NX ? NX : Mode::PIXELS_PER_LINE;
62
	return (ARG & VDPCmdEngine::DIX)
63
		? std::min(NX, DX + 1)
64
		: std::min(NX, Mode::PIXELS_PER_LINE - DX);
65
}
66
67
template<typename Mode>
68
static constexpr unsigned clipNX_1_byte(unsigned DX, unsigned NX, uint8_t ARG)
69
{
70
	constexpr unsigned BYTES_PER_LINE =
71
		Mode::PIXELS_PER_LINE >> Mode::PIXELS_PER_BYTE_SHIFT;
72
73
	DX >>= Mode::PIXELS_PER_BYTE_SHIFT;
74
	if (BYTES_PER_LINE <= DX) [[unlikely]] {
75
		return 1;
76
	}
77
	NX >>= Mode::PIXELS_PER_BYTE_SHIFT;
78
	NX = NX ? NX : BYTES_PER_LINE;
79
	return (ARG & VDPCmdEngine::DIX)
80
		? std::min(NX, DX + 1)
81
		: std::min(NX, BYTES_PER_LINE - DX);
82
}
83
84
template<typename Mode>
85
static constexpr unsigned clipNX_2_pixel(unsigned SX, unsigned DX, unsigned NX, uint8_t ARG)
86
{
87
	if ((SX >= Mode::PIXELS_PER_LINE) ||
88
	    (DX >= Mode::PIXELS_PER_LINE)) [[unlikely]] {
89
		return 1;
90
	}
91
	NX = NX ? NX : Mode::PIXELS_PER_LINE;
92
	return (ARG & VDPCmdEngine::DIX)
93
		? std::min(NX, std::min(SX, DX) + 1)
94
		: std::min(NX, Mode::PIXELS_PER_LINE - std::max(SX, DX));
95
}
96
97
template<typename Mode>
98
static constexpr unsigned clipNX_2_byte(unsigned SX, unsigned DX, unsigned NX, uint8_t ARG)
99
{
100
	constexpr unsigned BYTES_PER_LINE =
101
		Mode::PIXELS_PER_LINE >> Mode::PIXELS_PER_BYTE_SHIFT;
102
103
	SX >>= Mode::PIXELS_PER_BYTE_SHIFT;
104
	DX >>= Mode::PIXELS_PER_BYTE_SHIFT;
105
	if ((BYTES_PER_LINE <= SX) ||
106
	    (BYTES_PER_LINE <= DX)) [[unlikely]] {
107
		return 1;
108
	}
109
	NX >>= Mode::PIXELS_PER_BYTE_SHIFT;
110
	NX = NX ? NX : BYTES_PER_LINE;
111
	return (ARG & VDPCmdEngine::DIX)
112
		? std::min(NX, std::min(SX, DX) + 1)
113
		: std::min(NX, BYTES_PER_LINE - std::max(SX, DX));
114
}
115
116
static constexpr unsigned clipNY_1(unsigned DY, unsigned NY, uint8_t ARG)
117
{
118
	NY = NY ? NY : 1024;
119
	return (ARG & VDPCmdEngine::DIY) ? std::min(NY, DY + 1) : NY;
120
}
121
122
static constexpr unsigned clipNY_2(unsigned SY, unsigned DY, unsigned NY, uint8_t ARG)
123
{
124
	NY = NY ? NY : 1024;
125
	return (ARG & VDPCmdEngine::DIY) ? std::min(NY, std::min(SY, DY) + 1) : NY;
126
}
127
128
129
//struct IncrByteAddr4;
130
//struct IncrByteAddr5;
131
//struct IncrByteAddr6;
132
//struct IncrByteAddr7;
133
//struct IncrPixelAddr4;
134
//struct IncrPixelAddr5;
135
//struct IncrPixelAddr6;
136
//struct IncrMask4;
137
//struct IncrMask5;
138
//struct IncrMask7;
139
//struct IncrShift4;
140
//struct IncrShift5;
141
//struct IncrShift7;
142
//using IncrPixelAddr7 = IncrByteAddr7;
143
//using IncrMask6  = IncrMask4;
144
//using IncrShift6 = IncrShift4;
145
146
147
template<typename LogOp> static void psetFast(
148
	EmuTime time, VDPVRAM& vram, unsigned addr,
149
	uint8_t color, uint8_t mask, LogOp op)
150
{
151
	uint8_t src = vram.cmdWriteWindow.readNP(addr);
152
	op(time, vram, addr, src, color, mask);
153
}
154
155
/** Represents V9938 Graphic 4 mode (SCREEN5).
156
  */
157
struct Graphic4Mode
158
{
159
	//using IncrByteAddr  = IncrByteAddr4;
160
	//using IncrPixelAddr = IncrPixelAddr4;
161
	//using IncrMask      = IncrMask4;
162
	//using IncrShift     = IncrShift4;
163
	static constexpr uint8_t COLOR_MASK = 0x0F;
164
	static constexpr uint8_t PIXELS_PER_BYTE = 2;
165
	static constexpr uint8_t PIXELS_PER_BYTE_SHIFT = 1;
166
	static constexpr unsigned PIXELS_PER_LINE = 256;
167
	static unsigned addressOf(unsigned x, unsigned y, bool extVRAM);
168
	static uint8_t point(const VDPVRAM& vram, unsigned x, unsigned y, bool extVRAM);
169
	template<typename LogOp>
170
	static void pset(EmuTime time, VDPVRAM& vram,
171
		unsigned x, unsigned addr, uint8_t src, uint8_t color, LogOp op);
172
	static uint8_t duplicate(uint8_t color);
173
};
174
175
inline unsigned Graphic4Mode::addressOf(
176
	unsigned x, unsigned y, bool extVRAM)
177
{
178
	if (!extVRAM) [[likely]] {
179
		return ((y & 1023) << 7) | ((x & 255) >> 1);
180
	} else {
181
		return ((y &  511) << 7) | ((x & 255) >> 1) | 0x20000;
182
	}
183
}
184
185
inline uint8_t Graphic4Mode::point(
186
	const VDPVRAM& vram, unsigned x, unsigned y, bool extVRAM)
187
{
188
	return (vram.cmdReadWindow.readNP(addressOf(x, y, extVRAM))
189
		>> (((~x) & 1) << 2)) & 15;
190
}
191
192
template<typename LogOp>
193
inline void Graphic4Mode::pset(
194
	EmuTime time, VDPVRAM& vram, unsigned x, unsigned addr,
195
	uint8_t src, uint8_t color, LogOp op)
196
{
197
	auto sh = uint8_t(((~x) & 1) << 2);
198
	op(time, vram, addr, src, uint8_t(color << sh), ~uint8_t(15 << sh));
199
}
200
201
inline uint8_t Graphic4Mode::duplicate(uint8_t color)
202
{
203
	assert((color & 0xF0) == 0);
204
	return uint8_t(color | (color << 4));
205
}
206
207
/** Represents V9938 Graphic 5 mode (SCREEN6).
208
  */
209
struct Graphic5Mode
210
{
211
	//using IncrByteAddr  = IncrByteAddr5;
212
	//using IncrPixelAddr = IncrPixelAddr5;
213
	//using IncrMask      = IncrMask5;
214
	//using IncrShift     = IncrShift5;
215
	static constexpr uint8_t COLOR_MASK = 0x03;
216
	static constexpr uint8_t PIXELS_PER_BYTE = 4;
217
	static constexpr uint8_t PIXELS_PER_BYTE_SHIFT = 2;
218
	static constexpr unsigned PIXELS_PER_LINE = 512;
219
	static unsigned addressOf(unsigned x, unsigned y, bool extVRAM);
220
	static uint8_t point(const VDPVRAM& vram, unsigned x, unsigned y, bool extVRAM);
221
	template<typename LogOp>
222
	static void pset(EmuTime time, VDPVRAM& vram,
223
		unsigned x, unsigned addr, uint8_t src, uint8_t color, LogOp op);
224
	static uint8_t duplicate(uint8_t color);
225
};
226
227
inline unsigned Graphic5Mode::addressOf(
228
	unsigned x, unsigned y, bool extVRAM)
229
{
230
	if (!extVRAM) [[likely]] {
231
		return ((y & 1023) << 7) | ((x & 511) >> 2);
232
	} else {
233
		return ((y &  511) << 7) | ((x & 511) >> 2) | 0x20000;
234
	}
235
}
236
237
inline uint8_t Graphic5Mode::point(
238
	const VDPVRAM& vram, unsigned x, unsigned y, bool extVRAM)
239
{
240
	return (vram.cmdReadWindow.readNP(addressOf(x, y, extVRAM))
241
		>> (((~x) & 3) << 1)) & 3;
242
}
243
244
template<typename LogOp>
245
inline void Graphic5Mode::pset(
246
	EmuTime time, VDPVRAM& vram, unsigned x, unsigned addr,
247
	uint8_t src, uint8_t color, LogOp op)
248
{
249
	auto sh = uint8_t(((~x) & 3) << 1);
250
	op(time, vram, addr, src, uint8_t(color << sh), ~uint8_t(3 << sh));
251
}
252
253
inline uint8_t Graphic5Mode::duplicate(uint8_t color)
254
{
255
	assert((color & 0xFC) == 0);
256
	color |= color << 2;
257
	color |= color << 4;
258
	return color;
259
}
260
261
/** Represents V9938 Graphic 6 mode (SCREEN7).
262
  */
263
struct Graphic6Mode
264
{
265
	//using IncrByteAddr  = IncrByteAddr6;
266
	//using IncrPixelAddr = IncrPixelAddr6;
267
	//using IncrMask      = IncrMask6;
268
	//using IncrShift     = IncrShift6;
269
	static constexpr uint8_t COLOR_MASK = 0x0F;
270
	static constexpr uint8_t PIXELS_PER_BYTE = 2;
271
	static constexpr uint8_t PIXELS_PER_BYTE_SHIFT = 1;
272
	static constexpr unsigned PIXELS_PER_LINE = 512;
273
	static unsigned addressOf(unsigned x, unsigned y, bool extVRAM);
274
	static uint8_t point(const VDPVRAM& vram, unsigned x, unsigned y, bool extVRAM);
275
	template<typename LogOp>
276
	static void pset(EmuTime time, VDPVRAM& vram,
277
		unsigned x, unsigned addr, uint8_t src, uint8_t color, LogOp op);
278
	static uint8_t duplicate(uint8_t color);
279
};
280
281
inline unsigned Graphic6Mode::addressOf(
282
	unsigned x, unsigned y, bool extVRAM)
283
{
284
	if (!extVRAM) [[likely]] {
285
		return ((x & 2) << 15) | ((y & 511) << 7) | ((x & 511) >> 2);
286
	} else {
287
		return 0x20000         | ((y & 511) << 7) | ((x & 511) >> 2);
288
	}
289
}
290
291
inline uint8_t Graphic6Mode::point(
292
	const VDPVRAM& vram, unsigned x, unsigned y, bool extVRAM)
293
{
294
	return (vram.cmdReadWindow.readNP(addressOf(x, y, extVRAM))
295
		>> (((~x) & 1) << 2)) & 15;
296
}
297
298
template<typename LogOp>
299
inline void Graphic6Mode::pset(
300
	EmuTime time, VDPVRAM& vram, unsigned x, unsigned addr,
301
	uint8_t src, uint8_t color, LogOp op)
302
{
303
	auto sh = uint8_t(((~x) & 1) << 2);
304
	op(time, vram, addr, src, uint8_t(color << sh), ~uint8_t(15 << sh));
305
}
306
307
inline uint8_t Graphic6Mode::duplicate(uint8_t color)
308
{
309
	assert((color & 0xF0) == 0);
310
	return uint8_t(color | (color << 4));
311
}
312
313
/** Represents V9938 Graphic 7 mode (SCREEN8).
314
  */
315
struct Graphic7Mode
316
{
317
	//using IncrByteAddr  = IncrByteAddr7;
318
	//using IncrPixelAddr = IncrPixelAddr7;
319
	//using IncrMask      = IncrMask7;
320
	//using IncrShift     = IncrShift7;
321
	static constexpr uint8_t COLOR_MASK = 0xFF;
322
	static constexpr uint8_t PIXELS_PER_BYTE = 1;
323
	static constexpr uint8_t PIXELS_PER_BYTE_SHIFT = 0;
324
	static constexpr unsigned PIXELS_PER_LINE = 256;
325
	static unsigned addressOf(unsigned x, unsigned y, bool extVRAM);
326
	static uint8_t point(const VDPVRAM& vram, unsigned x, unsigned y, bool extVRAM);
327
	template<typename LogOp>
328
	static void pset(EmuTime time, VDPVRAM& vram,
329
		unsigned x, unsigned addr, uint8_t src, uint8_t color, LogOp op);
330
	static uint8_t duplicate(uint8_t color);
331
};
332
333
inline unsigned Graphic7Mode::addressOf(
334
	unsigned x, unsigned y, bool extVRAM)
335
{
336
	if (!extVRAM) [[likely]] {
337
		return ((x & 1) << 16) | ((y & 511) << 7) | ((x & 255) >> 1);
338
	} else {
339
		return 0x20000         | ((y & 511) << 7) | ((x & 255) >> 1);
340
	}
341
}
342
343
inline uint8_t Graphic7Mode::point(
344
	const VDPVRAM& vram, unsigned x, unsigned y, bool extVRAM)
345
{
346
	return vram.cmdReadWindow.readNP(addressOf(x, y, extVRAM));
347
}
348
349
template<typename LogOp>
350
inline void Graphic7Mode::pset(
351
	EmuTime time, VDPVRAM& vram, unsigned /*x*/, unsigned addr,
352
	uint8_t src, uint8_t color, LogOp op)
353
{
354
	op(time, vram, addr, src, color, 0);
355
}
356
357
inline uint8_t Graphic7Mode::duplicate(uint8_t color)
358
{
359
	return color;
360
}
361
362
/** Represents V9958 non-bitmap command mode. This uses the Graphic7Mode
363
  * coordinate system, but in non-planar mode.
364
  */
365
struct NonBitmapMode
366
{
367
	//using IncrByteAddr  = IncrByteAddrNonBitMap;
368
	//using IncrPixelAddr = IncrPixelAddrNonBitMap;
369
	//using IncrMask      = IncrMaskNonBitMap;
370
	//using IncrShift     = IncrShiftNonBitMap;
371
	static constexpr uint8_t COLOR_MASK = 0xFF;
372
	static constexpr uint8_t PIXELS_PER_BYTE = 1;
373
	static constexpr uint8_t PIXELS_PER_BYTE_SHIFT = 0;
374
	static constexpr unsigned PIXELS_PER_LINE = 256;
375
	static unsigned addressOf(unsigned x, unsigned y, bool extVRAM);
376
	static uint8_t point(const VDPVRAM& vram, unsigned x, unsigned y, bool extVRAM);
377
	template<typename LogOp>
378
	static void pset(EmuTime time, VDPVRAM& vram,
379
		unsigned x, unsigned addr, uint8_t src, uint8_t color, LogOp op);
380
	static uint8_t duplicate(uint8_t color);
381
};
382
383
inline unsigned NonBitmapMode::addressOf(
384
	unsigned x, unsigned y, bool extVRAM)
385
{
386
	if (!extVRAM) [[likely]] {
387
		return ((y & 511) << 8) | (x & 255);
388
	} else {
389
		return ((y & 255) << 8) | (x & 255) | 0x20000;
390
	}
391
}
392
393
inline uint8_t NonBitmapMode::point(
394
	const VDPVRAM& vram, unsigned x, unsigned y, bool extVRAM)
395
{
396
	return vram.cmdReadWindow.readNP(addressOf(x, y, extVRAM));
397
}
398
399
template<typename LogOp>
400
inline void NonBitmapMode::pset(
401
	EmuTime time, VDPVRAM& vram, unsigned /*x*/, unsigned addr,
402
	uint8_t src, uint8_t color, LogOp op)
403
{
404
	op(time, vram, addr, src, color, 0);
405
}
406
407
inline uint8_t NonBitmapMode::duplicate(uint8_t color)
408
{
409
	return color;
410
}
411
412
/** Incremental address calculation (byte based, no extended VRAM)
413
 */
414
struct IncrByteAddr4
415
{
416
	IncrByteAddr4(unsigned x, unsigned y, int /*tx*/)
417
		: addr(Graphic4Mode::addressOf(x, y, false))
418
	{
419
	}
420
	[[nodiscard]] unsigned getAddr() const
421
	{
422
		return addr;
423
	}
424
	void step(int tx)
425
	{
426
		addr += (tx >> 1);
427
	}
428
429
private:
430
	unsigned addr;
431
};
432
433
struct IncrByteAddr5
434
{
435
	IncrByteAddr5(unsigned x, unsigned y, int /*tx*/)
436
		: addr(Graphic5Mode::addressOf(x, y, false))
437
	{
438
	}
439
	[[nodiscard]] unsigned getAddr() const
440
	{
441
		return addr;
442
	}
443
	void step(int tx)
444
	{
445
		addr += (tx >> 2);
446
	}
447
448
private:
449
	unsigned addr;
450
};
451
452
struct IncrByteAddr7
453
{
454
	IncrByteAddr7(unsigned x, unsigned y, int tx)
455
		: addr(Graphic7Mode::addressOf(x, y, false))
456
		, delta((tx > 0) ? 0x10000 : (0x10000 - 1))
457
		, delta2((tx > 0) ? ( 0x10000 ^ (1 - 0x10000))
458
		                  : (-0x10000 ^ (0x10000 - 1)))
459
	{
460
		if (x & 1) delta ^= delta2;
461
	}
462
	[[nodiscard]] unsigned getAddr() const
463
	{
464
		return addr;
465
	}
466
	void step(int /*tx*/)
467
	{
468
		addr += delta;
469
		delta ^= delta2;
470
	}
471
472
private:
473
	unsigned addr;
474
	unsigned delta;
475
	const unsigned delta2;
476
};
477
478
struct IncrByteAddr6 : IncrByteAddr7
479
{
480
	IncrByteAddr6(unsigned x, unsigned y, int tx)
481
		: IncrByteAddr7(x >> 1, y, tx)
482
	{
483
	}
484
};
485
486
/** Incremental address calculation (pixel-based)
487
 */
488
struct IncrPixelAddr4
489
{
490
	IncrPixelAddr4(unsigned x, unsigned y, int tx)
491
		: addr(Graphic4Mode::addressOf(x, y, false))
492
		, delta((tx == 1) ? (x & 1) : ((x & 1) - 1))
493
	{
494
	}
495
	[[nodiscard]] unsigned getAddr() const { return addr; }
496
	void step(int tx)
497
	{
498
		addr += delta;
499
		delta ^= tx;
500
	}
501
private:
502
	unsigned addr;
503
	unsigned delta;
504
};
505
506
struct IncrPixelAddr5
507
{
508
	IncrPixelAddr5(unsigned x, unsigned y, int tx)
509
		: addr(Graphic5Mode::addressOf(x, y, false))
510
		                       // x |  0 |  1 |  2 |  3
511
		                       //-----------------------
512
		, c1(-(signed(x) & 1)) //   |  0 | -1 |  0 | -1
513
		, c2((x & 2) >> 1)     //   |  0 |  0 |  1 |  1
514
	{
515
		if (tx < 0) {
516
			c1 = ~c1;      //   | -1 |  0 | -1 |  0
517
			c2 -= 1;       //   | -1 | -1 |  0 |  0
518
		}
519
	}
520
	[[nodiscard]] unsigned getAddr() const { return addr; }
521
	void step(int tx)
522
	{
523
		addr += (c1 & c2);
524
		c2 ^= (c1 & tx);
525
		c1 = ~c1;
526
	}
527
private:
528
	unsigned addr;
529
	unsigned c1;
530
	unsigned c2;
531
};
532
533
struct IncrPixelAddr6
534
{
535
	IncrPixelAddr6(unsigned x, unsigned y, int tx)
536
		: addr(Graphic6Mode::addressOf(x, y, false))
537
		, c1(-(signed(x) & 1))
538
		, c3((tx == 1) ? unsigned(0x10000 ^ (1 - 0x10000))   // == -0x1FFFF
539
		               : unsigned(-0x10000 ^ (0x10000 - 1))) // == -1
540
	{
541
		if (tx == 1) {
542
			c2 = (x & 2) ? (1 - 0x10000) :  0x10000;
543
		} else {
544
			c1 = ~c1;
545
			c2 = (x & 2) ? -0x10000 : (0x10000 - 1);
546
		}
547
	}
548
	[[nodiscard]] unsigned getAddr() const { return addr; }
549
	void step(int /*tx*/)
550
	{
551
		addr += (c1 & c2);
552
		c2 ^= (c1 & c3);
553
		c1 = ~c1;
554
	}
555
private:
556
	unsigned addr;
557
	unsigned c1;
558
	unsigned c2;
559
	const unsigned c3;
560
};
561
562
563
/** Incremental mask calculation.
564
 * Mask has 0-bits in the position of the pixel, 1-bits elsewhere.
565
 */
566
struct IncrMask4
567
{
568
	IncrMask4(unsigned x, int /*tx*/)
569
		: mask(uint8_t(0x0F << ((x & 1) << 2)))
570
	{
571
	}
572
	[[nodiscard]] uint8_t getMask() const
573
	{
574
		return mask;
575
	}
576
	void step()
577
	{
578
		mask = ~mask;
579
	}
580
private:
581
	uint8_t mask;
582
};
583
584
struct IncrMask5
585
{
586
	IncrMask5(unsigned x, int tx)
587
		: mask(~uint8_t(0xC0 >> ((x & 3) << 1)))
588
		, shift((tx > 0) ? 6 : 2)
589
	{
590
	}
591
	[[nodiscard]] uint8_t getMask() const
592
	{
593
		return mask;
594
	}
595
	void step()
596
	{
597
		mask = uint8_t((mask << shift) | (mask >> (8 - shift)));
598
	}
599
private:
600
	uint8_t mask;
601
	const uint8_t shift;
602
};
603
604
struct IncrMask7
605
{
606
	IncrMask7(unsigned /*x*/, int /*tx*/) {}
607
	[[nodiscard]] uint8_t getMask() const
608
	{
609
		return 0;
610
	}
611
	void step() const {}
612
};
613
614
615
/* Shift between source and destination pixel for LMMM command.
616
 */
617
struct IncrShift4
618
{
619
	IncrShift4(unsigned sx, unsigned dx)
620
		: shift(((dx - sx) & 1) * 4)
621
	{
622
	}
623
	[[nodiscard]] uint8_t doShift(uint8_t color) const
624
	{
625
		return uint8_t((color >> shift) | (color << shift));
626
	}
627
private:
628
	const uint8_t shift;
629
};
630
631
struct IncrShift5
632
{
633
	IncrShift5(unsigned sx, unsigned dx)
634
		: shift(((dx - sx) & 3) * 2)
635
	{
636
	}
637
	[[nodiscard]] uint8_t doShift(uint8_t color) const
638
	{
639
		return uint8_t((color >> shift) | (color << (8 - shift)));
640
	}
641
private:
642
	const uint8_t shift;
643
};
644
645
struct IncrShift7
646
{
647
	IncrShift7(unsigned /*sx*/, unsigned /*dx*/) {}
648
	[[nodiscard]] uint8_t doShift(uint8_t color) const
649
	{
650
		return color;
651
	}
652
};
653
654
655
// Logical operations:
656
657
struct DummyOp {
658
	void operator()(EmuTime /*time*/, VDPVRAM& /*vram*/, unsigned /*addr*/,
659
	                uint8_t /*src*/, uint8_t /*color*/, uint8_t /*mask*/) const
660
	{
661
		// Undefined logical operations do nothing.
662
	}
663
};
664
665
struct ImpOp {
666
	void operator()(EmuTime time, VDPVRAM& vram, unsigned addr,
667
	                uint8_t src, uint8_t color, uint8_t mask) const
668
	{
669
		vram.cmdWrite(addr, (src & mask) | color, time);
670
	}
671
};
672
673
struct AndOp {
674
	void operator()(EmuTime time, VDPVRAM& vram, unsigned addr,
675
	                uint8_t src, uint8_t color, uint8_t mask) const
676
	{
677
		vram.cmdWrite(addr, src & (color | mask), time);
678
	}
679
};
680
681
struct OrOp {
682
	void operator()(EmuTime time, VDPVRAM& vram, unsigned addr,
683
	                uint8_t src, uint8_t color, uint8_t /*mask*/) const
684
	{
685
		vram.cmdWrite(addr, src | color, time);
686
	}
687
};
688
689
struct XorOp {
690
	void operator()(EmuTime time, VDPVRAM& vram, unsigned addr,
691
	                uint8_t src, uint8_t color, uint8_t /*mask*/) const
692
	{
693
		vram.cmdWrite(addr, src ^ color, time);
694
	}
695
};
696
697
struct NotOp {
698
	void operator()(EmuTime time, VDPVRAM& vram, unsigned addr,
699
	                uint8_t src, uint8_t color, uint8_t mask) const
700
	{
701
		vram.cmdWrite(addr, (src & mask) | ~(color | mask), time);
702
	}
703
};
704
705
template<typename Op>
706
struct TransparentOp : Op {
707
	void operator()(EmuTime time, VDPVRAM& vram, unsigned addr,
708
	                uint8_t src, uint8_t color, uint8_t mask) const
709
	{
710
		// TODO does this skip the write or re-write the original value
711
		//      might make a difference in case the CPU has written
712
		//      the same address between the command read and write
713
		if (color) Op::operator()(time, vram, addr, src, color, mask);
714
	}
715
};
716
using TImpOp = TransparentOp<ImpOp>;
717
using TAndOp = TransparentOp<AndOp>;
718
using TOrOp  = TransparentOp<OrOp>;
719
using TXorOp = TransparentOp<XorOp>;
720
using TNotOp = TransparentOp<NotOp>;
721
722
723
// Commands
724
725
void VDPCmdEngine::setStatusChangeTime(EmuTime t)
726
{
727
	statusChangeTime = t;
728
	if ((t != EmuTime::infinity()) && (executingProbe.anyObservers() || cmdProbe.anyObservers())) {
729
		vdp.scheduleCmdSync(t);
730
	}
731
}
732
733
void VDPCmdEngine::calcFinishTime(unsigned nx, unsigned ny, unsigned ticksPerPixel)
734
{
735
	if (!CMD) return;
736
	if (vdp.getBrokenCmdTiming()) {
737
		setStatusChangeTime(EmuTime::zero()); // will finish soon
738
		return;
739
	}
740
741
	// Underestimation for when the command will be finished. This assumes
742
	// we never have to wait for access slots and that there's no overhead
743
	// per line.
744
	auto t = VDP::VDPClock::duration(ticksPerPixel);
745
	t *= ((nx * (ny - 1)) + (ANX - 1));
746
	setStatusChangeTime(engineTime + t);
747
}
748
749
/** Abort
750
  */
751
void VDPCmdEngine::startAbrt(EmuTime time)
752
{
753
	commandDone(time);
754
}
755
756
/** Point
757
  */
758
void VDPCmdEngine::startPoint(EmuTime time)
759
{
760
	vram.cmdReadWindow.setMask(0x3FFFF, ~0u << 18, time);
761
	vram.cmdWriteWindow.disable(time);
762
	nextAccessSlot(time, VDPAccessSlots::Delta::CMD_START_63);
763
	setStatusChangeTime(EmuTime::zero()); // will finish soon
764
}
765
766
template<typename Mode>
767
void VDPCmdEngine::executePoint(EmuTime limit)
768
{
769
	if (engineTime >= limit) [[unlikely]] return;
770
771
	bool srcExt  = (ARG & MXS) != 0;
772
	if (bool doPoint = !srcExt || hasExtendedVRAM; doPoint) [[likely]] {
773
		COL = Mode::point(vram, SX, SY, srcExt);
774
	} else {
775
		COL = 0xFF;
776
	}
777
	commandDone(engineTime);
778
}
779
780
/** Pset
781
  */
782
void VDPCmdEngine::startPset(EmuTime time)
783
{
784
	vram.cmdReadWindow.disable(time);
785
	vram.cmdWriteWindow.setMask(0x3FFFF, ~0u << 18, time);
786
	nextAccessSlot(time, VDPAccessSlots::Delta::CMD_START_88);
787
	setStatusChangeTime(EmuTime::zero()); // will finish soon
788
	phase = 0;
789
}
790
791
template<typename Mode, typename LogOp>
792
void VDPCmdEngine::executePset(EmuTime limit)
793
{
794
	bool dstExt = (ARG & MXD) != 0;
795
	bool doPset = !dstExt || hasExtendedVRAM;
796
	unsigned addr = Mode::addressOf(DX, DY, dstExt);
797
798
	switch (phase) {
799
	case 0:
800
		if (engineTime >= limit) [[unlikely]] { phase = 0; break; }
801
		if (doPset) [[likely]] {
802
			tmpDst = vram.cmdWriteWindow.readNP(addr);
803
		}
804
		nextAccessSlot(Delta::CMD_24); // TODO
805
		[[fallthrough]];
806
	case 1:
807
		if (engineTime >= limit) [[unlikely]] { phase = 1; break; }
808
		if (doPset) [[likely]] {
809
			uint8_t col = COL & Mode::COLOR_MASK;
810
			Mode::pset(engineTime, vram, DX, addr, tmpDst, col, LogOp());
811
		}
812
		commandDone(engineTime);
813
		break;
814
	default:
815
		UNREACHABLE;
816
	}
817
}
818
819
/** Search a dot.
820
  */
821
void VDPCmdEngine::startSrch(EmuTime time)
822
{
823
	vram.cmdReadWindow.setMask(0x3FFFF, ~0u << 18, time);
824
	vram.cmdWriteWindow.disable(time);
825
	ASX = SX;
826
	nextAccessSlot(time, VDPAccessSlots::Delta::CMD_START_88);
827
	setStatusChangeTime(EmuTime::zero()); // we can find it any moment
828
}
829
830
template<typename Mode>
831
void VDPCmdEngine::executeSrch(EmuTime limit)
832
{
833
	uint8_t CL = COL & Mode::COLOR_MASK;
834
	int TX = (ARG & DIX) ? -1 : 1;
835
	bool AEQ = (ARG & EQ) != 0; // TODO: Do we look for "==" or "!="?
836
837
	// TODO use MXS or MXD here?
838
	//  datasheet says MXD but MXS seems more logical
839
	bool srcExt  = (ARG & MXS) != 0;
840
	bool doPoint = !srcExt || hasExtendedVRAM;
841
	auto calculator = getSlotCalculator(limit);
842
843
	while (!calculator.limitReached()) {
844
		auto p = [&] -> uint8_t {
845
			if (doPoint) [[likely]] {
846
				return Mode::point(vram, ASX, SY, srcExt);
847
			} else {
848
				return 0xFF;
849
			}
850
		}();
851
		if ((p == CL) ^ AEQ) {
852
			status |= BD; // border detected
853
			commandDone(calculator.getTime());
854
			break;
855
		}
856
		ASX += TX;
857
		if (ASX & Mode::PIXELS_PER_LINE) {
858
			// this does NOT reset the BD flag!
859
			commandDone(calculator.getTime());
860
			break;
861
		}
862
		calculator.next(Delta::CMD_88); // TODO
863
	}
864
	engineTime = calculator.getTime();
865
}
866
867
/** Draw a line.
868
  */
869
void VDPCmdEngine::startLine(EmuTime time)
870
{
871
	vram.cmdReadWindow.disable(time);
872
	vram.cmdWriteWindow.setMask(0x3FFFF, ~0u << 18, time);
873
	NY &= 1023;
874
	ASX = ((NX - 1) >> 1);
875
	ADX = DX;
876
	ANX = 0;
877
	nextAccessSlot(time, VDPAccessSlots::Delta::CMD_START_112);
878
	setStatusChangeTime(EmuTime::zero()); // TODO can still be optimized
879
	phase = 0;
880
}
881
882
template<typename Mode, typename LogOp>
883
void VDPCmdEngine::executeLine(EmuTime limit)
884
{
885
	// See doc/line-speed.txt for some background info on the timing.
886
	uint8_t CL = COL & Mode::COLOR_MASK;
887
	int TX = (ARG & DIX) ? -1 : 1;
888
	int TY = (ARG & DIY) ? -1 : 1;
889
	bool dstExt = (ARG & MXD) != 0;
890
	bool doPset = !dstExt || hasExtendedVRAM;
891
	unsigned addr = Mode::addressOf(ADX, DY, dstExt);
892
	auto calculator = getSlotCalculator(limit);
893
894
	switch (phase) {
895
	case 0:
896
loop:		if (calculator.limitReached()) [[unlikely]] { phase = 0; break; }
897
		if (doPset) [[likely]] {
898
			tmpDst = vram.cmdWriteWindow.readNP(addr);
899
		}
900
		calculator.next(Delta::CMD_24);
901
		[[fallthrough]];
902
	case 1: {
903
		if (calculator.limitReached()) [[unlikely]] { phase = 1; break; }
904
		if (doPset) [[likely]] {
905
			Mode::pset(calculator.getTime(), vram, ADX, addr,
906
			           tmpDst, CL, LogOp());
907
		}
908
909
		Delta delta = Delta::CMD_84;
910
		if ((ARG & MAJ) == 0) {
911
			// X-Axis is major direction.
912
			ADX += TX;
913
			// confirmed on real HW:
914
			//  - end-test happens before DY += TY
915
			//  - (ADX & PPL) test only happens after first pixel
916
			//    is drawn. And it does test with 'AND' (not with ==)
917
			if (ANX++ == NX || (ADX & Mode::PIXELS_PER_LINE)) {
918
				commandDone(calculator.getTime());
919
				break;
920
			}
921
			if (ASX < NY) {
922
				ASX += NX;
923
				DY += TY;
924
				delta = Delta::CMD_84_36;
925
				// Advancing above the top border stops the command, but
926
				// advancing below the bottom border wraps to the top.
927
				// Same for the block commands, but those handle it via
928
				// clipNY_1() and clipNY_2().
929
				if ((TY < 0) && (int(DY) < 0)) {
930
					commandDone(calculator.getTime());
931
					break;
932
				}
933
			}
934
			ASX -= NY;
935
			ASX &= 1023; // mask to 10 bits range
936
		} else {
937
			// Y-Axis is major direction.
938
			// confirmed on real HW: DY += TY happens before end-test
939
			DY += TY;
940
			if ((TY < 0) && (int(DY) < 0)) { // see comment above
941
				commandDone(calculator.getTime());
942
				break;
943
			}
944
			if (ASX < NY) {
945
				ASX += NX;
946
				ADX += TX;
947
				delta = Delta::CMD_84_36;
948
			}
949
			ASX -= NY;
950
			ASX &= 1023; // mask to 10 bits range
951
			if (ANX++ == NX || (ADX & Mode::PIXELS_PER_LINE)) {
952
				commandDone(calculator.getTime());
953
				break;
954
			}
955
		}
956
		addr = Mode::addressOf(ADX, DY, dstExt);
957
		calculator.next(delta);
958
		goto loop;
959
	}
960
	default:
961
		UNREACHABLE;
962
	}
963
	engineTime = calculator.getTime();
964
}
965
966
967
/** Logical move VDP -> VRAM.
968
  */
969
template<typename Mode>
970
void VDPCmdEngine::startLmmv(EmuTime time)
971
{
972
	vram.cmdReadWindow.disable(time);
973
	vram.cmdWriteWindow.setMask(0x3FFFF, ~0u << 18, time);
974
	NY &= 1023;
975
	unsigned tmpNX = clipNX_1_pixel<Mode>(DX, NX, ARG);
976
	unsigned tmpNY = clipNY_1(DY, NY, ARG);
977
	ADX = DX;
978
	ANX = tmpNX;
979
	nextAccessSlot(time, VDPAccessSlots::Delta::CMD_START_88);
980
	calcFinishTime(tmpNX, tmpNY, 72 + 24);
981
	phase = 0;
982
}
983
984
template<typename Mode, typename LogOp>
985
void VDPCmdEngine::executeLmmv(EmuTime limit)
986
{
987
	NY &= 1023;
988
	unsigned tmpNX = clipNX_1_pixel<Mode>(DX, NX, ARG);
989
	unsigned tmpNY = clipNY_1(DY, NY, ARG);
990
	int TX = (ARG & DIX) ? -1 : 1;
991
	int TY = (ARG & DIY) ? -1 : 1;
992
	ANX = clipNX_1_pixel<Mode>(ADX, ANX, ARG);
993
	uint8_t CL = COL & Mode::COLOR_MASK;
994
	bool dstExt = (ARG & MXD) != 0;
995
	bool doPset = !dstExt || hasExtendedVRAM;
996
	unsigned addr = Mode::addressOf(ADX, DY, dstExt);
997
	auto calculator = getSlotCalculator(limit);
998
999
	switch (phase) {
1000
	case 0:
1001
loop:		if (calculator.limitReached()) [[unlikely]] { phase = 0; break; }
1002
		if (doPset) [[likely]] {
1003
			tmpDst = vram.cmdWriteWindow.readNP(addr);
1004
		}
1005
		calculator.next(Delta::CMD_24);
1006
		[[fallthrough]];
1007
	case 1: {
1008
		if (calculator.limitReached()) [[unlikely]] { phase = 1; break; }
1009
		if (doPset) [[likely]] {
1010
			Mode::pset(calculator.getTime(), vram, ADX, addr,
1011
			           tmpDst, CL, LogOp());
1012
		}
1013
		ADX += TX;
1014
		Delta delta = Delta::CMD_72;
1015
		if (--ANX == 0) {
1016
			delta = Delta::CMD_72_58;
1017
			DY += TY; --NY;
1018
			ADX = DX; ANX = tmpNX;
1019
			if (--tmpNY == 0) {
1020
				commandDone(calculator.getTime());
1021
				break;
1022
			}
1023
		}
1024
		addr = Mode::addressOf(ADX, DY, dstExt);
1025
		calculator.next(delta);
1026
		goto loop;
1027
	}
1028
	default:
1029
		UNREACHABLE;
1030
	}
1031
	engineTime = calculator.getTime();
1032
	this->calcFinishTime(tmpNX, tmpNY, 72 + 24);
1033
1034
	/*
1035
	if (dstExt) [[unlikely]] {
1036
		bool doPset = !dstExt || hasExtendedVRAM;
1037
		while (engineTime < limit) {
1038
			if (doPset) [[likely]] {
1039
				Mode::pset(engineTime, vram, ADX, DY,
1040
					   dstExt, CL, LogOp());
1041
			}
1042
			engineTime += delta;
1043
			ADX += TX;
1044
			if (--ANX == 0) {
1045
				DY += TY; --NY;
1046
				ADX = DX; ANX = tmpNX;
1047
				if (--tmpNY == 0) {
1048
					commandDone(engineTime);
1049
					break;
1050
				}
1051
			}
1052
		}
1053
	} else {
1054
		// fast-path, no extended VRAM
1055
		CL = Mode::duplicate(CL);
1056
		while (engineTime < limit) {
1057
			typename Mode::IncrPixelAddr dstAddr(ADX, DY, TX);
1058
			typename Mode::IncrMask      dstMask(ADX, TX);
1059
			EmuDuration dur = limit - engineTime;
1060
			unsigned num = (delta != EmuDuration::zero())
1061
			             ? std::min(dur.divUp(delta), ANX)
1062
			             : ANX;
1063
			for (auto i : xrange(num)) {
1064
				uint8_t mask = dstMask.getMask();
1065
				psetFast(engineTime, vram, dstAddr.getAddr(),
1066
					 CL & ~mask, mask, LogOp());
1067
				engineTime += delta;
1068
				dstAddr.step(TX);
1069
				dstMask.step();
1070
			}
1071
			ANX -= num;
1072
			if (ANX == 0) {
1073
				DY += TY;
1074
				NY -= 1;
1075
				ADX = DX;
1076
				ANX = tmpNX;
1077
				if (--tmpNY == 0) {
1078
					commandDone(engineTime);
1079
					break;
1080
				}
1081
			} else {
1082
				ADX += num * TX;
1083
				assert(engineTime >= limit);
1084
				break;
1085
			}
1086
		}
1087
	}
1088
	*/
1089
}
1090
1091
/** Logical move VRAM -> VRAM.
1092
  */
1093
template<typename Mode>
1094
void VDPCmdEngine::startLmmm(EmuTime time)
1095
{
1096
	vram.cmdReadWindow .setMask(0x3FFFF, ~0u << 18, time);
1097
	vram.cmdWriteWindow.setMask(0x3FFFF, ~0u << 18, time);
1098
	NY &= 1023;
1099
	unsigned tmpNX = clipNX_2_pixel<Mode>(SX, DX, NX, ARG);
1100
	unsigned tmpNY = clipNY_2(SY, DY, NY, ARG);
1101
	ASX = SX;
1102
	ADX = DX;
1103
	ANX = tmpNX;
1104
	nextAccessSlot(time, VDPAccessSlots::Delta::CMD_START_64);
1105
	calcFinishTime(tmpNX, tmpNY, 64 + 32 + 24);
1106
	phase = 0;
1107
}
1108
1109
template<typename Mode, typename LogOp>
1110
void VDPCmdEngine::executeLmmm(EmuTime limit)
1111
{
1112
	NY &= 1023;
1113
	unsigned tmpNX = clipNX_2_pixel<Mode>(SX, DX, NX, ARG);
1114
	unsigned tmpNY = clipNY_2(SY, DY, NY, ARG);
1115
	int TX = (ARG & DIX) ? -1 : 1;
1116
	int TY = (ARG & DIY) ? -1 : 1;
1117
	ANX = clipNX_2_pixel<Mode>(ASX, ADX, ANX, ARG);
1118
	bool srcExt  = (ARG & MXS) != 0;
1119
	bool dstExt  = (ARG & MXD) != 0;
1120
	bool doPoint = !srcExt || hasExtendedVRAM;
1121
	bool doPset  = !dstExt || hasExtendedVRAM;
1122
	unsigned dstAddr = Mode::addressOf(ADX, DY, dstExt);
1123
	auto calculator = getSlotCalculator(limit);
1124
1125
	switch (phase) {
1126
	case 0:
1127
loop:		if (calculator.limitReached()) [[unlikely]] { phase = 0; break; }
1128
		if (doPoint) [[likely]] {
1129
		       tmpSrc = Mode::point(vram, ASX, SY, srcExt);
1130
		} else {
1131
		       tmpSrc = 0xFF;
1132
		}
1133
		calculator.next(Delta::CMD_32);
1134
		[[fallthrough]];
1135
	case 1:
1136
		if (calculator.limitReached()) [[unlikely]] { phase = 1; break; }
1137
		if (doPset) [[likely]] {
1138
			tmpDst = vram.cmdWriteWindow.readNP(dstAddr);
1139
		}
1140
		calculator.next(Delta::CMD_24);
1141
		[[fallthrough]];
1142
	case 2: {
1143
		if (calculator.limitReached()) [[unlikely]] { phase = 2; break; }
1144
		if (doPset) [[likely]] {
1145
			Mode::pset(calculator.getTime(), vram, ADX, dstAddr,
1146
			           tmpDst, tmpSrc, LogOp());
1147
		}
1148
		ASX += TX; ADX += TX;
1149
		Delta delta = Delta::CMD_60;
1150
		if (--ANX == 0) {
1151
			delta = Delta::CMD_60_68;
1152
			SY += TY; DY += TY; --NY;
1153
			ASX = SX; ADX = DX; ANX = tmpNX;
1154
			if (--tmpNY == 0) {
1155
				commandDone(calculator.getTime());
1156
				break;
1157
			}
1158
		}
1159
		dstAddr = Mode::addressOf(ADX, DY, dstExt);
1160
		calculator.next(delta);
1161
		goto loop;
1162
	}
1163
	default:
1164
		UNREACHABLE;
1165
	}
1166
	engineTime = calculator.getTime();
1167
	this->calcFinishTime(tmpNX, tmpNY, 64 + 32 + 24);
1168
1169
	/*if (srcExt || dstExt) [[unlikely]] {
1170
		bool doPoint = !srcExt || hasExtendedVRAM;
1171
		bool doPset  = !dstExt || hasExtendedVRAM;
1172
		while (engineTime < limit) {
1173
			if (doPset) [[likely]] {
1174
				auto p = [&] -> uint8_t {
1175
					if (doPoint) [[likely]] {
1176
						return Mode::point(vram, ASX, SY, srcExt);
1177
					} else {
1178
						return 0xFF;
1179
					}
1180
				}();
1181
				Mode::pset(engineTime, vram, ADX, DY,
1182
					   dstExt, p, LogOp());
1183
			}
1184
			engineTime += delta;
1185
			ASX += TX; ADX += TX;
1186
			if (--ANX == 0) {
1187
				SY += TY; DY += TY; --NY;
1188
				ASX = SX; ADX = DX; ANX = tmpNX;
1189
				if (--tmpNY == 0) {
1190
					commandDone(engineTime);
1191
					break;
1192
				}
1193
			}
1194
		}
1195
	} else {
1196
		// fast-path, no extended VRAM
1197
		while (engineTime < limit) {
1198
			typename Mode::IncrPixelAddr srcAddr(ASX, SY, TX);
1199
			typename Mode::IncrPixelAddr dstAddr(ADX, DY, TX);
1200
			typename Mode::IncrMask      dstMask(ADX, TX);
1201
			typename Mode::IncrShift     shift  (ASX, ADX);
1202
			EmuDuration dur = limit - engineTime;
1203
			unsigned num = (delta != EmuDuration::zero())
1204
			             ? std::min(dur.divUp(delta), ANX)
1205
			             : ANX;
1206
			for (auto i : xrange(num)) {
1207
				uint8_t p = vram.cmdReadWindow.readNP(srcAddr.getAddr());
1208
				p = shift.doShift(p);
1209
				uint8_t mask = dstMask.getMask();
1210
				psetFast(engineTime, vram, dstAddr.getAddr(),
1211
					 p & ~mask, mask, LogOp());
1212
				engineTime += delta;
1213
				srcAddr.step(TX);
1214
				dstAddr.step(TX);
1215
				dstMask.step();
1216
			}
1217
			ANX -= num;
1218
			if (ANX == 0) {
1219
				SY += TY;
1220
				DY += TY;
1221
				NY -= 1;
1222
				ASX = SX;
1223
				ADX = DX;
1224
				ANX = tmpNX;
1225
				if (--tmpNY == 0) {
1226
					commandDone(engineTime);
1227
					break;
1228
				}
1229
			} else {
1230
				ASX += num * TX;
1231
				ADX += num * TX;
1232
				assert(engineTime >= limit);
1233
				break;
1234
			}
1235
		}
1236
	}
1237
	*/
1238
}
1239
1240
/** Logical move VRAM -> CPU.
1241
  */
1242
template<typename Mode>
1243
void VDPCmdEngine::startLmcm(EmuTime time)
1244
{
1245
	vram.cmdReadWindow.setMask(0x3FFFF, ~0u << 18, time);
1246
	vram.cmdWriteWindow.disable(time);
1247
	NY &= 1023;
1248
	unsigned tmpNX = clipNX_1_pixel<Mode>(SX, NX, ARG);
1249
	ASX = SX;
1250
	ANX = tmpNX;
1251
	transfer = true;
1252
	status |= TR;
1253
	nextAccessSlot(time, VDPAccessSlots::Delta::CMD_START_76);
1254
	setStatusChangeTime(EmuTime::zero());
1255
}
1256
1257
template<typename Mode>
1258
void VDPCmdEngine::executeLmcm(EmuTime limit)
1259
{
1260
	if (!transfer) return;
1261
	if (engineTime >= limit) [[unlikely]] return;
1262
1263
	NY &= 1023;
1264
	unsigned tmpNX = clipNX_1_pixel<Mode>(SX, NX, ARG);
1265
	unsigned tmpNY = clipNY_1(SY, NY, ARG);
1266
	int TX = (ARG & DIX) ? -1 : 1;
1267
	int TY = (ARG & DIY) ? -1 : 1;
1268
	ANX = clipNX_1_pixel<Mode>(ASX, ANX, ARG);
1269
	bool srcExt  = (ARG & MXS) != 0;
1270
1271
	// TODO we should (most likely) perform the actual read earlier and
1272
	//  buffer it, and on a CPU-IO-read start the next read (just like how
1273
	//  regular reading from VRAM works).
1274
	if (bool doPoint = !srcExt || hasExtendedVRAM; doPoint) [[likely]] {
1275
		COL = Mode::point(vram, ASX, SY, srcExt);
1276
	} else {
1277
		COL = 0xFF;
1278
	}
1279
	transfer = false;
1280
	ASX += TX; --ANX;
1281
	if (ANX == 0) {
1282
		SY += TY; --NY;
1283
		ASX = SX; ANX = tmpNX;
1284
		if (--tmpNY == 0) {
1285
			commandDone(engineTime);
1286
		}
1287
	}
1288
	nextAccessSlot(limit); // TODO
1289
}
1290
1291
/** Logical move CPU -> VRAM.
1292
  */
1293
template<typename Mode>
1294
void VDPCmdEngine::startLmmc(EmuTime time)
1295
{
1296
	vram.cmdReadWindow.disable(time);
1297
	vram.cmdWriteWindow.setMask(0x3FFFF, ~0u << 18, time);
1298
	NY &= 1023;
1299
	unsigned tmpNX = clipNX_1_pixel<Mode>(DX, NX, ARG);
1300
	ADX = DX;
1301
	ANX = tmpNX;
1302
	setStatusChangeTime(EmuTime::zero());
1303
	// do not set 'transfer = true', this fixes bug#1014
1304
	// Baltak Rampage: characters in greetings part are one pixel offset
1305
	status |= TR;
1306
	nextAccessSlot(time, VDPAccessSlots::Delta::CMD_START_88);
1307
}
1308
1309
template<typename Mode, typename LogOp>
1310
void VDPCmdEngine::executeLmmc(EmuTime limit)
1311
{
1312
	NY &= 1023;
1313
	unsigned tmpNX = clipNX_1_pixel<Mode>(DX, NX, ARG);
1314
	unsigned tmpNY = clipNY_1(DY, NY, ARG);
1315
	int TX = (ARG & DIX) ? -1 : 1;
1316
	int TY = (ARG & DIY) ? -1 : 1;
1317
	ANX = clipNX_1_pixel<Mode>(ADX, ANX, ARG);
1318
	bool dstExt = (ARG & MXD) != 0;
1319
	bool doPset  = !dstExt || hasExtendedVRAM;
1320
1321
	if (transfer) {
1322
		uint8_t col = COL & Mode::COLOR_MASK;
1323
		// TODO: timing is inaccurate, this executes the read and write
1324
		//  in the same access slot. Instead we should
1325
		//    - wait for a byte
1326
		//    - in next access slot read
1327
		//    - in next access slot write
1328
		if (doPset) [[likely]] {
1329
			unsigned addr = Mode::addressOf(ADX, DY, dstExt);
1330
			tmpDst = vram.cmdWriteWindow.readNP(addr);
1331
			Mode::pset(limit, vram, ADX, addr,
1332
			           tmpDst, col, LogOp());
1333
		}
1334
		// Execution is emulated as instantaneous, so don't bother
1335
		// with the timing.
1336
		// Note: Correct timing would require currentTime to be set
1337
		//       to the moment transfer becomes true.
1338
		transfer = false;
1339
1340
		ADX += TX; --ANX;
1341
		if (ANX == 0) {
1342
			DY += TY; --NY;
1343
			ADX = DX; ANX = tmpNX;
1344
			if (--tmpNY == 0) {
1345
				commandDone(limit);
1346
			}
1347
		}
1348
	}
1349
	nextAccessSlot(limit); // inaccurate, but avoid assert
1350
}
1351
1352
/** High-speed move VDP -> VRAM.
1353
  */
1354
template<typename Mode>
1355
void VDPCmdEngine::startHmmv(EmuTime time)
1356
{
1357
	vram.cmdReadWindow.disable(time);
1358
	vram.cmdWriteWindow.setMask(0x3FFFF, ~0u << 18, time);
1359
	NY &= 1023;
1360
	unsigned tmpNX = clipNX_1_byte<Mode>(DX, NX, ARG);
1361
	unsigned tmpNY = clipNY_1(DY, NY, ARG);
1362
	ADX = DX;
1363
	ANX = tmpNX;
1364
	nextAccessSlot(time, VDPAccessSlots::Delta::CMD_START_112);
1365
	calcFinishTime(tmpNX, tmpNY, 48);
1366
}
1367
1368
template<typename Mode>
1369
void VDPCmdEngine::executeHmmv(EmuTime limit)
1370
{
1371
	NY &= 1023;
1372
	unsigned tmpNX = clipNX_1_byte<Mode>(DX, NX, ARG);
1373
	unsigned tmpNY = clipNY_1(DY, NY, ARG);
1374
	int TX = (ARG & DIX)
1375
		? -Mode::PIXELS_PER_BYTE : Mode::PIXELS_PER_BYTE;
1376
	int TY = (ARG & DIY) ? -1 : 1;
1377
	ANX = clipNX_1_byte<Mode>(
1378
		ADX, ANX << Mode::PIXELS_PER_BYTE_SHIFT, ARG);
1379
	bool dstExt = (ARG & MXD) != 0;
1380
	bool doPset = !dstExt || hasExtendedVRAM;
1381
	auto calculator = getSlotCalculator(limit);
1382
1383
	while (!calculator.limitReached()) {
1384
		if (doPset) [[likely]] {
1385
			vram.cmdWrite(Mode::addressOf(ADX, DY, dstExt),
1386
			              COL, calculator.getTime());
1387
		}
1388
		ADX += TX;
1389
		Delta delta = Delta::CMD_46;
1390
		if (--ANX == 0) {
1391
			delta = Delta::CMD_46_58;
1392
			DY += TY; --NY;
1393
			ADX = DX; ANX = tmpNX;
1394
			if (--tmpNY == 0) {
1395
				commandDone(calculator.getTime());
1396
				break;
1397
			}
1398
		}
1399
		calculator.next(delta);
1400
	}
1401
	engineTime = calculator.getTime();
1402
	calcFinishTime(tmpNX, tmpNY, 48);
1403
1404
	/*if (dstExt) [[unlikely]] {
1405
		bool doPset = !dstExt || hasExtendedVRAM;
1406
		while (engineTime < limit) {
1407
			if (doPset) [[likely]] {
1408
				vram.cmdWrite(Mode::addressOf(ADX, DY, dstExt),
1409
					      COL, engineTime);
1410
			}
1411
			engineTime += delta;
1412
			ADX += TX;
1413
			if (--ANX == 0) {
1414
				DY += TY; --NY;
1415
				ADX = DX; ANX = tmpNX;
1416
				if (--tmpNY == 0) {
1417
					commandDone(engineTime);
1418
					break;
1419
				}
1420
			}
1421
		}
1422
	} else {
1423
		// fast-path, no extended VRAM
1424
		while (engineTime < limit) {
1425
			typename Mode::IncrByteAddr dstAddr(ADX, DY, TX);
1426
			EmuDuration dur = limit - engineTime;
1427
			unsigned num = (delta != EmuDuration::zero())
1428
			             ? std::min(dur.divUp(delta), ANX)
1429
			             : ANX;
1430
			for (auto i : xrange(num)) {
1431
				vram.cmdWrite(dstAddr.getAddr(), COL,
1432
					      engineTime);
1433
				engineTime += delta;
1434
				dstAddr.step(TX);
1435
			}
1436
			ANX -= num;
1437
			if (ANX == 0) {
1438
				DY += TY;
1439
				NY -= 1;
1440
				ADX = DX;
1441
				ANX = tmpNX;
1442
				if (--tmpNY == 0) {
1443
					commandDone(engineTime);
1444
					break;
1445
				}
1446
			} else {
1447
				ADX += num * TX;
1448
				assert(engineTime >= limit);
1449
				break;
1450
			}
1451
		}
1452
	}
1453
	*/
1454
}
1455
1456
/** High-speed move VRAM -> VRAM.
1457
  */
1458
template<typename Mode>
1459
void VDPCmdEngine::startHmmm(EmuTime time)
1460
{
1461
	vram.cmdReadWindow .setMask(0x3FFFF, ~0u << 18, time);
1462
	vram.cmdWriteWindow.setMask(0x3FFFF, ~0u << 18, time);
1463
	NY &= 1023;
1464
	unsigned tmpNX = clipNX_2_byte<Mode>(SX, DX, NX, ARG);
1465
	unsigned tmpNY = clipNY_2(SY, DY, NY, ARG);
1466
	ASX = SX;
1467
	ADX = DX;
1468
	ANX = tmpNX;
1469
	nextAccessSlot(time, VDPAccessSlots::Delta::CMD_START_100);
1470
	calcFinishTime(tmpNX, tmpNY, 24 + 64);
1471
	phase = 0;
1472
}
1473
1474
template<typename Mode>
1475
void VDPCmdEngine::executeHmmm(EmuTime limit)
1476
{
1477
	NY &= 1023;
1478
	unsigned tmpNX = clipNX_2_byte<Mode>(SX, DX, NX, ARG);
1479
	unsigned tmpNY = clipNY_2(SY, DY, NY, ARG);
1480
	int TX = (ARG & DIX)
1481
	       ? -Mode::PIXELS_PER_BYTE : Mode::PIXELS_PER_BYTE;
1482
	int TY = (ARG & DIY) ? -1 : 1;
1483
	ANX = clipNX_2_byte<Mode>(
1484
		ASX, ADX, ANX << Mode::PIXELS_PER_BYTE_SHIFT, ARG);
1485
	bool srcExt  = (ARG & MXS) != 0;
1486
	bool dstExt  = (ARG & MXD) != 0;
1487
	bool doPoint = !srcExt || hasExtendedVRAM;
1488
	bool doPset  = !dstExt || hasExtendedVRAM;
1489
	auto calculator = getSlotCalculator(limit);
1490
1491
	switch (phase) {
1492
	case 0:
1493
loop:		if (calculator.limitReached()) [[unlikely]] { phase = 0; break; }
1494
		if (doPoint) [[likely]] {
1495
			tmpSrc = vram.cmdReadWindow.readNP(Mode::addressOf(ASX, SY, srcExt));
1496
		} else {
1497
			tmpSrc = 0xFF;
1498
		}
1499
		calculator.next(Delta::CMD_24);
1500
		[[fallthrough]];
1501
	case 1: {
1502
		if (calculator.limitReached()) [[unlikely]] { phase = 1; break; }
1503
		if (doPset) [[likely]] {
1504
			vram.cmdWrite(Mode::addressOf(ADX, DY, dstExt),
1505
			              tmpSrc, calculator.getTime());
1506
		}
1507
		ASX += TX; ADX += TX;
1508
		Delta delta = Delta::CMD_60;
1509
		if (--ANX == 0) {
1510
			delta = Delta::CMD_60_68;
1511
			SY += TY; DY += TY; --NY;
1512
			ASX = SX; ADX = DX; ANX = tmpNX;
1513
			if (--tmpNY == 0) {
1514
				commandDone(calculator.getTime());
1515
				break;
1516
			}
1517
		}
1518
		calculator.next(delta);
1519
		goto loop;
1520
	}
1521
	default:
1522
		UNREACHABLE;
1523
	}
1524
	engineTime = calculator.getTime();
1525
	calcFinishTime(tmpNX, tmpNY, 24 + 64);
1526
1527
	/*if (srcExt || dstExt) [[unlikely]] {
1528
		bool doPoint = !srcExt || hasExtendedVRAM;
1529
		bool doPset  = !dstExt || hasExtendedVRAM;
1530
		while (engineTime < limit) {
1531
			if (doPset) [[likely]] {
1532
				auto p = [&] -> uint8_t {
1533
					if (doPoint) [[likely]] {
1534
						return vram.cmdReadWindow.readNP(Mode::addressOf(ASX, SY, srcExt));
1535
					} else {
1536
						return 0xFF;
1537
					}
1538
				}();
1539
				vram.cmdWrite(Mode::addressOf(ADX, DY, dstExt),
1540
					      p, engineTime);
1541
			}
1542
			engineTime += delta;
1543
			ASX += TX; ADX += TX;
1544
			if (--ANX == 0) {
1545
				SY += TY; DY += TY; --NY;
1546
				ASX = SX; ADX = DX; ANX = tmpNX;
1547
				if (--tmpNY == 0) {
1548
					commandDone(engineTime);
1549
					break;
1550
				}
1551
			}
1552
		}
1553
	} else {
1554
		// fast-path, no extended VRAM
1555
		while (engineTime < limit) {
1556
			typename Mode::IncrByteAddr srcAddr(ASX, SY, TX);
1557
			typename Mode::IncrByteAddr dstAddr(ADX, DY, TX);
1558
			EmuDuration dur = limit - engineTime;
1559
			unsigned num = (delta != EmuDuration::zero())
1560
			             ? std::min(dur.divUp(delta), ANX)
1561
			             : ANX;
1562
			for (auto i : xrange(num)) {
1563
				uint8_t p = vram.cmdReadWindow.readNP(srcAddr.getAddr());
1564
				vram.cmdWrite(dstAddr.getAddr(), p, engineTime);
1565
				engineTime += delta;
1566
				srcAddr.step(TX);
1567
				dstAddr.step(TX);
1568
			}
1569
			ANX -= num;
1570
			if (ANX == 0) {
1571
				SY += TY;
1572
				DY += TY;
1573
				NY -= 1;
1574
				ASX = SX;
1575
				ADX = DX;
1576
				ANX = tmpNX;
1577
				if (--tmpNY == 0) {
1578
					commandDone(engineTime);
1579
					break;
1580
				}
1581
			} else {
1582
				ASX += num * TX;
1583
				ADX += num * TX;
1584
				assert(engineTime >= limit);
1585
				break;
1586
			}
1587
		}
1588
	}
1589
	*/
1590
}
1591
1592
/** High-speed move VRAM -> VRAM (Y direction only).
1593
  */
1594
template<typename Mode>
1595
void VDPCmdEngine::startYmmm(EmuTime time)
1596
{
1597
	vram.cmdReadWindow .setMask(0x3FFFF, ~0u << 18, time);
1598
	vram.cmdWriteWindow.setMask(0x3FFFF, ~0u << 18, time);
1599
	NY &= 1023;
1600
	unsigned tmpNX = clipNX_1_byte<Mode>(DX, 512, ARG);
1601
		// large enough so that it gets clipped
1602
	unsigned tmpNY = clipNY_2(SY, DY, NY, ARG);
1603
	ADX = DX;
1604
	ANX = tmpNX;
1605
	nextAccessSlot(time, VDPAccessSlots::Delta::CMD_START_100);
1606
	calcFinishTime(tmpNX, tmpNY, 24 + 36);
1607
	phase = 0;
1608
}
1609
1610
template<typename Mode>
1611
void VDPCmdEngine::executeYmmm(EmuTime limit)
1612
{
1613
	NY &= 1023;
1614
	unsigned tmpNX = clipNX_1_byte<Mode>(DX, 512, ARG);
1615
		// large enough so that it gets clipped
1616
	unsigned tmpNY = clipNY_2(SY, DY, NY, ARG);
1617
	int TX = (ARG & DIX)
1618
		? -Mode::PIXELS_PER_BYTE : Mode::PIXELS_PER_BYTE;
1619
	int TY = (ARG & DIY) ? -1 : 1;
1620
	ANX = clipNX_1_byte<Mode>(ADX, 512, ARG);
1621
1622
	// TODO does this use MXD for both read and write?
1623
	//  it says so in the datasheet, but it seems illogical
1624
	//  OTOH YMMM also uses DX for both read and write
1625
	bool dstExt = (ARG & MXD) != 0;
1626
	bool doPset  = !dstExt || hasExtendedVRAM;
1627
	auto calculator = getSlotCalculator(limit);
1628
1629
	// Note: we changed the timing
1630
	//    from:  40 R 24 W  +0   (see doc/internal/vdp-vram-timing/vdp-timing.html for notation and background info)
1631
	//    to:    36 R 24 W  +68  (TODO update the above documentation, as it's wrong now)
1632
	// This is based on:
1633
	//    https://github.com/openMSX/openMSX/issues/2057   (measurement tools by bengalack)
1634
	//    https://github.com/sndpl/openMSX/pull/5          (analysis done by Claude (Opus), an AI assistant)
1635
	switch (phase) {
1636
	case 0:
1637
loop:		if (calculator.limitReached()) [[unlikely]] { phase = 0; break; }
1638
		if (doPset) [[likely]] {
1639
			tmpSrc = vram.cmdReadWindow.readNP(
1640
			       Mode::addressOf(ADX, SY, dstExt));
1641
		}
1642
		calculator.next(Delta::CMD_24);
1643
		[[fallthrough]];
1644
	case 1: {
1645
		if (calculator.limitReached()) [[unlikely]] { phase = 1; break; }
1646
		if (doPset) [[likely]] {
1647
			vram.cmdWrite(Mode::addressOf(ADX, DY, dstExt),
1648
			              tmpSrc, calculator.getTime());
1649
		}
1650
		ADX += TX;
1651
		Delta delta = Delta::CMD_36;
1652
		if (--ANX == 0) {
1653
			delta = Delta::CMD_36_68;
1654
			SY += TY; DY += TY; --NY;
1655
			ADX = DX; ANX = tmpNX;
1656
			if (--tmpNY == 0) {
1657
				commandDone(calculator.getTime());
1658
				break;
1659
			}
1660
		}
1661
		calculator.next(delta);
1662
		goto loop;
1663
	}
1664
	default:
1665
		UNREACHABLE;
1666
	}
1667
	engineTime = calculator.getTime();
1668
	calcFinishTime(tmpNX, tmpNY, 24 + 36);
1669
1670
	/*
1671
	if (dstExt) [[unlikely]] {
1672
		bool doPset  = !dstExt || hasExtendedVRAM;
1673
		while (engineTime < limit) {
1674
			if (doPset) [[likely]] {
1675
				uint8_t p = vram.cmdReadWindow.readNP(
1676
					      Mode::addressOf(ADX, SY, dstExt));
1677
				vram.cmdWrite(Mode::addressOf(ADX, DY, dstExt),
1678
					      p, engineTime);
1679
			}
1680
			engineTime += delta;
1681
			ADX += TX;
1682
			if (--ANX == 0) {
1683
				SY += TY; DY += TY; --NY;
1684
				ADX = DX; ANX = tmpNX;
1685
				if (--tmpNY == 0) {
1686
					commandDone(engineTime);
1687
					break;
1688
				}
1689
			}
1690
		}
1691
	} else {
1692
		// fast-path, no extended VRAM
1693
		while (engineTime < limit) {
1694
			typename Mode::IncrByteAddr srcAddr(ADX, SY, TX);
1695
			typename Mode::IncrByteAddr dstAddr(ADX, DY, TX);
1696
			EmuDuration dur = limit - engineTime;
1697
			unsigned num = (delta != EmuDuration::zero())
1698
			             ? std::min(dur.divUp(delta), ANX)
1699
			             : ANX;
1700
			for (auto i : xrange(num)) {
1701
				uint8_t p = vram.cmdReadWindow.readNP(srcAddr.getAddr());
1702
				vram.cmdWrite(dstAddr.getAddr(), p, engineTime);
1703
				engineTime += delta;
1704
				srcAddr.step(TX);
1705
				dstAddr.step(TX);
1706
			}
1707
			ANX -= num;
1708
			if (ANX == 0) {
1709
				SY += TY;
1710
				DY += TY;
1711
				NY -= 1;
1712
				ADX = DX;
1713
				ANX = tmpNX;
1714
				if (--tmpNY == 0) {
1715
					commandDone(engineTime);
1716
					break;
1717
				}
1718
			} else {
1719
				ADX += num * TX;
1720
				assert(engineTime >= limit);
1721
				break;
1722
			}
1723
		}
1724
	}
1725
	*/
1726
}
1727
1728
/** High-speed move CPU -> VRAM.
1729
  */
1730
template<typename Mode>
1731
void VDPCmdEngine::startHmmc(EmuTime time)
1732
{
1733
	vram.cmdReadWindow.disable(time);
1734
	vram.cmdWriteWindow.setMask(0x3FFFF, ~0u << 18, time);
1735
	NY &= 1023;
1736
	unsigned tmpNX = clipNX_1_byte<Mode>(DX, NX, ARG);
1737
	ADX = DX;
1738
	ANX = tmpNX;
1739
	setStatusChangeTime(EmuTime::zero());
1740
	// do not set 'transfer = true', see startLmmc()
1741
	status |= TR;
1742
	nextAccessSlot(time, VDPAccessSlots::Delta::CMD_START_112);
1743
}
1744
1745
template<typename Mode>
1746
void VDPCmdEngine::executeHmmc(EmuTime limit)
1747
{
1748
	NY &= 1023;
1749
	unsigned tmpNX = clipNX_1_byte<Mode>(DX, NX, ARG);
1750
	unsigned tmpNY = clipNY_1(DY, NY, ARG);
1751
	int TX = (ARG & DIX)
1752
		? -Mode::PIXELS_PER_BYTE : Mode::PIXELS_PER_BYTE;
1753
	int TY = (ARG & DIY) ? -1 : 1;
1754
	ANX = clipNX_1_byte<Mode>(
1755
		ADX, ANX << Mode::PIXELS_PER_BYTE_SHIFT, ARG);
1756
	bool dstExt = (ARG & MXD) != 0;
1757
	bool doPset = !dstExt || hasExtendedVRAM;
1758
1759
	if (transfer) {
1760
		// TODO: timing is inaccurate. We should
1761
		//  - wait for a byte
1762
		//  - on the next access slot write that byte
1763
		if (doPset) [[likely]] {
1764
			vram.cmdWrite(Mode::addressOf(ADX, DY, dstExt),
1765
			              COL, limit);
1766
		}
1767
		transfer = false;
1768
1769
		ADX += TX; --ANX;
1770
		if (ANX == 0) {
1771
			DY += TY; --NY;
1772
			ADX = DX; ANX = tmpNX;
1773
			if (--tmpNY == 0) {
1774
				commandDone(limit);
1775
			}
1776
		}
1777
	}
1778
	nextAccessSlot(limit); // inaccurate, but avoid assert
1779
}
1780
1781
1782
VDPCmdEngine::VDPCmdEngine(VDP& vdp_, CommandController& commandController)
1783
	: vdp(vdp_), vram(vdp.getVRAM())
1784
	, cmdInProgressCallback(
1785
		commandController, vdp_.getName() == "VDP" ?
1786
		"vdpcmdinprogress_callback" : vdp_.getName() +
1787
		" vdpcmdinprogress_callback",
1788
	        "Tcl proc to call when a write to the VDP command engine is "
1789
		"detected while the previous command is still in progress.",
1790
		"",
1791
		Setting::Save::YES)
1792
	, cmdProbe(vdp_.getMotherBoard().getDebugger(), strCat(vdp.getName(), '.', "command"))
1793
	, executingProbe(
1794
		vdp_.getMotherBoard().getDebugger(),
1795
		strCat(vdp.getName(), '.', "commandExecuting"),
1796
		"Is the V99x8 VDP is currently executing a command",
1797
		false)
1798
	, hasExtendedVRAM(vram.getSize() == (192 * 1024))
1799
{
1800
}
1801
1802
void VDPCmdEngine::reset(EmuTime time)
1803
{
1804
	for (int i = 14; i >= 0; --i) { // start with ABORT
1805
		setCmdReg(uint8_t(i), 0, time);
1806
	}
1807
	status = 0;
1808
	scrMode = -1;
1809
1810
	updateDisplayMode(vdp.getDisplayMode(), vdp.getCmdBit(), time);
1811
}
1812
1813
void VDPCmdEngine::setCmdReg(uint8_t index, uint8_t value, EmuTime time)
1814
{
1815
	sync(time);
1816
	if (CMD && (index != 12)) {
1817
		cmdInProgressCallback.execute(index, value);
1818
	}
1819
	switch (index) {
1820
	case 0x00: // source X low
1821
		SX = (SX & 0x100) | value;
1822
		break;
1823
	case 0x01: // source X high
1824
		SX = (SX & 0x0FF) | ((value & 0x01) << 8);
1825
		break;
1826
	case 0x02: // source Y low
1827
		SY = (SY & 0x300) | value;
1828
		break;
1829
	case 0x03: // source Y high
1830
		SY = (SY & 0x0FF) | ((value & 0x03) << 8);
1831
		break;
1832
1833
	case 0x04: // destination X low
1834
		DX = (DX & 0x100) | value;
1835
		break;
1836
	case 0x05: // destination X high
1837
		DX = (DX & 0x0FF) | ((value & 0x01) << 8);
1838
		break;
1839
	case 0x06: // destination Y low
1840
		DY = (DY & 0x300) | value;
1841
		break;
1842
	case 0x07: // destination Y high
1843
		DY = (DY & 0x0FF) | ((value & 0x03) << 8);
1844
		break;
1845
1846
	// TODO is DX 9 or 10 bits, at least current implementation needs
1847
	// 10 bits (otherwise texts in UR are screwed)
1848
	case 0x08: // number X low
1849
		NX = (NX & 0x300) | value;
1850
		break;
1851
	case 0x09: // number X high
1852
		NX = (NX & 0x0FF) | ((value & 0x03) << 8);
1853
		break;
1854
	case 0x0A: // number Y low
1855
		NY = (NY & 0x300) | value;
1856
		break;
1857
	case 0x0B: // number Y high
1858
		NY = (NY & 0x0FF) | ((value & 0x03) << 8);
1859
		break;
1860
1861
	case 0x0C: // color
1862
		COL = value;
1863
		// Note: Real VDP always resets TR, but for such a short time
1864
		//       that the MSX won't notice it.
1865
		// TODO: What happens on non-transfer commands?
1866
		if (!CMD) status &= ~TR;
1867
		transfer = true;
1868
		break;
1869
	case 0x0D: // argument
1870
		ARG = value;
1871
		break;
1872
	case 0x0E: // command
1873
		CMD = value;
1874
		executeCommand(time);
1875
		break;
1876
	default:
1877
		UNREACHABLE;
1878
	}
1879
}
1880
1881
uint8_t VDPCmdEngine::peekCmdReg(uint8_t index, EmuTime time)
1882
{
1883
	sync(time);
1884
	switch (index) {
1885
	case 0x00: return narrow_cast<uint8_t>(SX & 0xFF);
1886
	case 0x01: return narrow_cast<uint8_t>(SX >> 8);
1887
	case 0x02: return narrow_cast<uint8_t>(SY & 0xFF);
1888
	case 0x03: return narrow_cast<uint8_t>(SY >> 8);
1889
1890
	case 0x04: return narrow_cast<uint8_t>(DX & 0xFF);
1891
	case 0x05: return narrow_cast<uint8_t>(DX >> 8);
1892
	case 0x06: return narrow_cast<uint8_t>(DY & 0xFF);
1893
	case 0x07: return narrow_cast<uint8_t>(DY >> 8);
1894
1895
	case 0x08: return narrow_cast<uint8_t>(NX & 0xFF);
1896
	case 0x09: return narrow_cast<uint8_t>(NX >> 8);
1897
	case 0x0A: return narrow_cast<uint8_t>(NY & 0xFF);
1898
	case 0x0B: return narrow_cast<uint8_t>(NY >> 8);
1899
1900
	case 0x0C: return COL;
1901
	case 0x0D: return ARG;
1902
	case 0x0E: return CMD;
1903
	default: UNREACHABLE;
1904
	}
1905
}
1906
1907
void VDPCmdEngine::updateDisplayMode(DisplayMode mode, bool cmdBit, EmuTime time)
1908
{
1909
	int newScrMode = [&] {
1910
		switch (mode.getBase()) {
1911
		case DisplayMode::GRAPHIC4:
1912
			return 0;
1913
		case DisplayMode::GRAPHIC5:
1914
			return 1;
1915
		case DisplayMode::GRAPHIC6:
1916
			return 2;
1917
		case DisplayMode::GRAPHIC7:
1918
			return 3;
1919
		default:
1920
			if (cmdBit) {
1921
				return 4; // like GRAPHIC7, but non-planar
1922
				          // TODO timing might be different
1923
			} else {
1924
				return -1; // no commands
1925
			}
1926
		}
1927
	}();
1928
1929
	if (newScrMode != scrMode) {
1930
		sync(time);
1931
		if (CMD) {
1932
			// VDP mode switch while command in progress
1933
			if (newScrMode == -1) {
1934
				// TODO: For now abort cmd in progress,
1935
				//       later find out what really happens.
1936
				//       At least CE remains high for a while,
1937
				//       but it is not yet clear what happens in VRAM.
1938
				commandDone(time);
1939
			}
1940
		}
1941
		scrMode = newScrMode;
1942
	}
1943
}
1944
1945
void VDPCmdEngine::executeCommand(EmuTime time)
1946
{
1947
	// V9938 ops only work in SCREEN 5-8.
1948
	// V9958 ops work in non SCREEN 5-8 when CMD bit is set
1949
	if (scrMode < 0) {
1950
		commandDone(time);
1951
		return;
1952
	}
1953
1954
	// store a copy of the start registers
1955
	lastSX = SX; lastSY = SY;
1956
	lastDX = DX; lastDY = DY;
1957
	lastNX = NX; lastNY = NY;
1958
	lastCOL = COL; lastARG = ARG; lastCMD = CMD;
1959
1960
	// Start command.
1961
	status |= CE;
1962
	executingProbe = true;
1963
	cmdProbe.signal(); // must be after executingProbe
1964
1965
	switch ((scrMode << 4) | (CMD >> 4)) {
1966
	case 0x00: case 0x10: case 0x20: case 0x30: case 0x40:
1967
	case 0x01: case 0x11: case 0x21: case 0x31: case 0x41:
1968
	case 0x02: case 0x12: case 0x22: case 0x32: case 0x42:
1969
	case 0x03: case 0x13: case 0x23: case 0x33: case 0x43:
1970
		startAbrt(time); break;
1971
1972
	case 0x04: case 0x14: case 0x24: case 0x34: case 0x44:
1973
		startPoint(time); break;
1974
	case 0x05: case 0x15: case 0x25: case 0x35: case 0x45:
1975
		startPset(time); break;
1976
	case 0x06: case 0x16: case 0x26: case 0x36: case 0x46:
1977
		startSrch(time); break;
1978
	case 0x07: case 0x17: case 0x27: case 0x37: case 0x47:
1979
		startLine(time); break;
1980
1981
	case 0x08: startLmmv<Graphic4Mode >(time); break;
1982
	case 0x18: startLmmv<Graphic5Mode >(time); break;
1983
	case 0x28: startLmmv<Graphic6Mode >(time); break;
1984
	case 0x38: startLmmv<Graphic7Mode >(time); break;
1985
	case 0x48: startLmmv<NonBitmapMode>(time); break;
1986
1987
	case 0x09: startLmmm<Graphic4Mode >(time); break;
1988
	case 0x19: startLmmm<Graphic5Mode >(time); break;
1989
	case 0x29: startLmmm<Graphic6Mode >(time); break;
1990
	case 0x39: startLmmm<Graphic7Mode >(time); break;
1991
	case 0x49: startLmmm<NonBitmapMode>(time); break;
1992
1993
	case 0x0A: startLmcm<Graphic4Mode >(time); break;
1994
	case 0x1A: startLmcm<Graphic5Mode >(time); break;
1995
	case 0x2A: startLmcm<Graphic6Mode >(time); break;
1996
	case 0x3A: startLmcm<Graphic7Mode >(time); break;
1997
	case 0x4A: startLmcm<NonBitmapMode>(time); break;
1998
1999
	case 0x0B: startLmmc<Graphic4Mode >(time); break;
2000
	case 0x1B: startLmmc<Graphic5Mode >(time); break;
2001
	case 0x2B: startLmmc<Graphic6Mode >(time); break;
2002
	case 0x3B: startLmmc<Graphic7Mode >(time); break;
2003
	case 0x4B: startLmmc<NonBitmapMode>(time); break;
2004
2005
	case 0x0C: startHmmv<Graphic4Mode >(time); break;
2006
	case 0x1C: startHmmv<Graphic5Mode >(time); break;
2007
	case 0x2C: startHmmv<Graphic6Mode >(time); break;
2008
	case 0x3C: startHmmv<Graphic7Mode >(time); break;
2009
	case 0x4C: startHmmv<NonBitmapMode>(time); break;
2010
2011
	case 0x0D: startHmmm<Graphic4Mode >(time); break;
2012
	case 0x1D: startHmmm<Graphic5Mode >(time); break;
2013
	case 0x2D: startHmmm<Graphic6Mode >(time); break;
2014
	case 0x3D: startHmmm<Graphic7Mode >(time); break;
2015
	case 0x4D: startHmmm<NonBitmapMode>(time); break;
2016
2017
	case 0x0E: startYmmm<Graphic4Mode >(time); break;
2018
	case 0x1E: startYmmm<Graphic5Mode >(time); break;
2019
	case 0x2E: startYmmm<Graphic6Mode >(time); break;
2020
	case 0x3E: startYmmm<Graphic7Mode >(time); break;
2021
	case 0x4E: startYmmm<NonBitmapMode>(time); break;
2022
2023
	case 0x0F: startHmmc<Graphic4Mode >(time); break;
2024
	case 0x1F: startHmmc<Graphic5Mode >(time); break;
2025
	case 0x2F: startHmmc<Graphic6Mode >(time); break;
2026
	case 0x3F: startHmmc<Graphic7Mode >(time); break;
2027
	case 0x4F: startHmmc<NonBitmapMode>(time); break;
2028
2029
	default: UNREACHABLE;
2030
	}
2031
}
2032
2033
void VDPCmdEngine::sync2(EmuTime time)
2034
{
2035
	switch ((scrMode << 8) | CMD) {
2036
	case 0x000: case 0x100: case 0x200: case 0x300: case 0x400:
2037
	case 0x001: case 0x101: case 0x201: case 0x301: case 0x401:
2038
	case 0x002: case 0x102: case 0x202: case 0x302: case 0x402:
2039
	case 0x003: case 0x103: case 0x203: case 0x303: case 0x403:
2040
	case 0x004: case 0x104: case 0x204: case 0x304: case 0x404:
2041
	case 0x005: case 0x105: case 0x205: case 0x305: case 0x405:
2042
	case 0x006: case 0x106: case 0x206: case 0x306: case 0x406:
2043
	case 0x007: case 0x107: case 0x207: case 0x307: case 0x407:
2044
	case 0x008: case 0x108: case 0x208: case 0x308: case 0x408:
2045
	case 0x009: case 0x109: case 0x209: case 0x309: case 0x409:
2046
	case 0x00A: case 0x10A: case 0x20A: case 0x30A: case 0x40A:
2047
	case 0x00B: case 0x10B: case 0x20B: case 0x30B: case 0x40B:
2048
	case 0x00C: case 0x10C: case 0x20C: case 0x30C: case 0x40C:
2049
	case 0x00D: case 0x10D: case 0x20D: case 0x30D: case 0x40D:
2050
	case 0x00E: case 0x10E: case 0x20E: case 0x30E: case 0x40E:
2051
	case 0x00F: case 0x10F: case 0x20F: case 0x30F: case 0x40F:
2052
	case 0x010: case 0x110: case 0x210: case 0x310: case 0x410:
2053
	case 0x011: case 0x111: case 0x211: case 0x311: case 0x411:
2054
	case 0x012: case 0x112: case 0x212: case 0x312: case 0x412:
2055
	case 0x013: case 0x113: case 0x213: case 0x313: case 0x413:
2056
	case 0x014: case 0x114: case 0x214: case 0x314: case 0x414:
2057
	case 0x015: case 0x115: case 0x215: case 0x315: case 0x415:
2058
	case 0x016: case 0x116: case 0x216: case 0x316: case 0x416:
2059
	case 0x017: case 0x117: case 0x217: case 0x317: case 0x417:
2060
	case 0x018: case 0x118: case 0x218: case 0x318: case 0x418:
2061
	case 0x019: case 0x119: case 0x219: case 0x319: case 0x419:
2062
	case 0x01A: case 0x11A: case 0x21A: case 0x31A: case 0x41A:
2063
	case 0x01B: case 0x11B: case 0x21B: case 0x31B: case 0x41B:
2064
	case 0x01C: case 0x11C: case 0x21C: case 0x31C: case 0x41C:
2065
	case 0x01D: case 0x11D: case 0x21D: case 0x31D: case 0x41D:
2066
	case 0x01E: case 0x11E: case 0x21E: case 0x31E: case 0x41E:
2067
	case 0x01F: case 0x11F: case 0x21F: case 0x31F: case 0x41F:
2068
	case 0x020: case 0x120: case 0x220: case 0x320: case 0x420:
2069
	case 0x021: case 0x121: case 0x221: case 0x321: case 0x421:
2070
	case 0x022: case 0x122: case 0x222: case 0x322: case 0x422:
2071
	case 0x023: case 0x123: case 0x223: case 0x323: case 0x423:
2072
	case 0x024: case 0x124: case 0x224: case 0x324: case 0x424:
2073
	case 0x025: case 0x125: case 0x225: case 0x325: case 0x425:
2074
	case 0x026: case 0x126: case 0x226: case 0x326: case 0x426:
2075
	case 0x027: case 0x127: case 0x227: case 0x327: case 0x427:
2076
	case 0x028: case 0x128: case 0x228: case 0x328: case 0x428:
2077
	case 0x029: case 0x129: case 0x229: case 0x329: case 0x429:
2078
	case 0x02A: case 0x12A: case 0x22A: case 0x32A: case 0x42A:
2079
	case 0x02B: case 0x12B: case 0x22B: case 0x32B: case 0x42B:
2080
	case 0x02C: case 0x12C: case 0x22C: case 0x32C: case 0x42C:
2081
	case 0x02D: case 0x12D: case 0x22D: case 0x32D: case 0x42D:
2082
	case 0x02E: case 0x12E: case 0x22E: case 0x32E: case 0x42E:
2083
	case 0x02F: case 0x12F: case 0x22F: case 0x32F: case 0x42F:
2084
	case 0x030: case 0x130: case 0x230: case 0x330: case 0x430:
2085
	case 0x031: case 0x131: case 0x231: case 0x331: case 0x431:
2086
	case 0x032: case 0x132: case 0x232: case 0x332: case 0x432:
2087
	case 0x033: case 0x133: case 0x233: case 0x333: case 0x433:
2088
	case 0x034: case 0x134: case 0x234: case 0x334: case 0x434:
2089
	case 0x035: case 0x135: case 0x235: case 0x335: case 0x435:
2090
	case 0x036: case 0x136: case 0x236: case 0x336: case 0x436:
2091
	case 0x037: case 0x137: case 0x237: case 0x337: case 0x437:
2092
	case 0x038: case 0x138: case 0x238: case 0x338: case 0x438:
2093
	case 0x039: case 0x139: case 0x239: case 0x339: case 0x439:
2094
	case 0x03A: case 0x13A: case 0x23A: case 0x33A: case 0x43A:
2095
	case 0x03B: case 0x13B: case 0x23B: case 0x33B: case 0x43B:
2096
	case 0x03C: case 0x13C: case 0x23C: case 0x33C: case 0x43C:
2097
	case 0x03D: case 0x13D: case 0x23D: case 0x33D: case 0x43D:
2098
	case 0x03E: case 0x13E: case 0x23E: case 0x33E: case 0x43E:
2099
	case 0x03F: case 0x13F: case 0x23F: case 0x33F: case 0x43F:
2100
		UNREACHABLE; break;
2101
2102
	case 0x040: case 0x041: case 0x042: case 0x043:
2103
	case 0x044: case 0x045: case 0x046: case 0x047:
2104
	case 0x048: case 0x049: case 0x04A: case 0x04B:
2105
	case 0x04C: case 0x04D: case 0x04E: case 0x04F:
2106
		executePoint<Graphic4Mode>(time); break;
2107
	case 0x140: case 0x141: case 0x142: case 0x143:
2108
	case 0x144: case 0x145: case 0x146: case 0x147:
2109
	case 0x148: case 0x149: case 0x14A: case 0x14B:
2110
	case 0x14C: case 0x14D: case 0x14E: case 0x14F:
2111
		executePoint<Graphic5Mode>(time); break;
2112
	case 0x240: case 0x241: case 0x242: case 0x243:
2113
	case 0x244: case 0x245: case 0x246: case 0x247:
2114
	case 0x248: case 0x249: case 0x24A: case 0x24B:
2115
	case 0x24C: case 0x24D: case 0x24E: case 0x24F:
2116
		executePoint<Graphic6Mode>(time); break;
2117
	case 0x340: case 0x341: case 0x342: case 0x343:
2118
	case 0x344: case 0x345: case 0x346: case 0x347:
2119
	case 0x348: case 0x349: case 0x34A: case 0x34B:
2120
	case 0x34C: case 0x34D: case 0x34E: case 0x34F:
2121
		executePoint<Graphic7Mode>(time); break;
2122
	case 0x440: case 0x441: case 0x442: case 0x443:
2123
	case 0x444: case 0x445: case 0x446: case 0x447:
2124
	case 0x448: case 0x449: case 0x44A: case 0x44B:
2125
	case 0x44C: case 0x44D: case 0x44E: case 0x44F:
2126
		executePoint<NonBitmapMode>(time); break;
2127
2128
	case 0x050: executePset<Graphic4Mode,  ImpOp>(time); break;
2129
	case 0x051: executePset<Graphic4Mode,  AndOp>(time); break;
2130
	case 0x052: executePset<Graphic4Mode,  OrOp >(time); break;
2131
	case 0x053: executePset<Graphic4Mode,  XorOp>(time); break;
2132
	case 0x054: executePset<Graphic4Mode,  NotOp>(time); break;
2133
	case 0x058: executePset<Graphic4Mode, TImpOp>(time); break;
2134
	case 0x059: executePset<Graphic4Mode, TAndOp>(time); break;
2135
	case 0x05A: executePset<Graphic4Mode, TOrOp >(time); break;
2136
	case 0x05B: executePset<Graphic4Mode, TXorOp>(time); break;
2137
	case 0x05C: executePset<Graphic4Mode, TNotOp>(time); break;
2138
	case 0x055: case 0x056: case 0x057: case 0x05D: case 0x05E: case 0x05F:
2139
		executePset<Graphic4Mode, DummyOp>(time); break;
2140
	case 0x150: executePset<Graphic5Mode,  ImpOp>(time); break;
2141
	case 0x151: executePset<Graphic5Mode,  AndOp>(time); break;
2142
	case 0x152: executePset<Graphic5Mode,  OrOp >(time); break;
2143
	case 0x153: executePset<Graphic5Mode,  XorOp>(time); break;
2144
	case 0x154: executePset<Graphic5Mode,  NotOp>(time); break;
2145
	case 0x158: executePset<Graphic5Mode, TImpOp>(time); break;
2146
	case 0x159: executePset<Graphic5Mode, TAndOp>(time); break;
2147
	case 0x15A: executePset<Graphic5Mode, TOrOp >(time); break;
2148
	case 0x15B: executePset<Graphic5Mode, TXorOp>(time); break;
2149
	case 0x15C: executePset<Graphic5Mode, TNotOp>(time); break;
2150
	case 0x155: case 0x156: case 0x157: case 0x15D: case 0x15E: case 0x15F:
2151
		executePset<Graphic5Mode, DummyOp>(time); break;
2152
	case 0x250: executePset<Graphic6Mode,  ImpOp>(time); break;
2153
	case 0x251: executePset<Graphic6Mode,  AndOp>(time); break;
2154
	case 0x252: executePset<Graphic6Mode,  OrOp >(time); break;
2155
	case 0x253: executePset<Graphic6Mode,  XorOp>(time); break;
2156
	case 0x254: executePset<Graphic6Mode,  NotOp>(time); break;
2157
	case 0x258: executePset<Graphic6Mode, TImpOp>(time); break;
2158
	case 0x259: executePset<Graphic6Mode, TAndOp>(time); break;
2159
	case 0x25A: executePset<Graphic6Mode, TOrOp >(time); break;
2160
	case 0x25B: executePset<Graphic6Mode, TXorOp>(time); break;
2161
	case 0x25C: executePset<Graphic6Mode, TNotOp>(time); break;
2162
	case 0x255: case 0x256: case 0x257: case 0x25D: case 0x25E: case 0x25F:
2163
		executePset<Graphic6Mode, DummyOp>(time); break;
2164
	case 0x350: executePset<Graphic7Mode,  ImpOp>(time); break;
2165
	case 0x351: executePset<Graphic7Mode,  AndOp>(time); break;
2166
	case 0x352: executePset<Graphic7Mode,  OrOp >(time); break;
2167
	case 0x353: executePset<Graphic7Mode,  XorOp>(time); break;
2168
	case 0x354: executePset<Graphic7Mode,  NotOp>(time); break;
2169
	case 0x358: executePset<Graphic7Mode, TImpOp>(time); break;
2170
	case 0x359: executePset<Graphic7Mode, TAndOp>(time); break;
2171
	case 0x35A: executePset<Graphic7Mode, TOrOp >(time); break;
2172
	case 0x35B: executePset<Graphic7Mode, TXorOp>(time); break;
2173
	case 0x35C: executePset<Graphic7Mode, TNotOp>(time); break;
2174
	case 0x355: case 0x356: case 0x357: case 0x35D: case 0x35E: case 0x35F:
2175
		executePset<Graphic7Mode, DummyOp>(time); break;
2176
	case 0x450: executePset<NonBitmapMode,  ImpOp>(time); break;
2177
	case 0x451: executePset<NonBitmapMode,  AndOp>(time); break;
2178
	case 0x452: executePset<NonBitmapMode,  OrOp >(time); break;
2179
	case 0x453: executePset<NonBitmapMode,  XorOp>(time); break;
2180
	case 0x454: executePset<NonBitmapMode,  NotOp>(time); break;
2181
	case 0x458: executePset<NonBitmapMode, TImpOp>(time); break;
2182
	case 0x459: executePset<NonBitmapMode, TAndOp>(time); break;
2183
	case 0x45A: executePset<NonBitmapMode, TOrOp >(time); break;
2184
	case 0x45B: executePset<NonBitmapMode, TXorOp>(time); break;
2185
	case 0x45C: executePset<NonBitmapMode, TNotOp>(time); break;
2186
	case 0x455: case 0x456: case 0x457: case 0x45D: case 0x45E: case 0x45F:
2187
		executePset<NonBitmapMode, DummyOp>(time); break;
2188
2189
	case 0x060: case 0x061: case 0x062: case 0x063:
2190
	case 0x064: case 0x065: case 0x066: case 0x067:
2191
	case 0x068: case 0x069: case 0x06A: case 0x06B:
2192
	case 0x06C: case 0x06D: case 0x06E: case 0x06F:
2193
		executeSrch<Graphic4Mode>(time); break;
2194
	case 0x160: case 0x161: case 0x162: case 0x163:
2195
	case 0x164: case 0x165: case 0x166: case 0x167:
2196
	case 0x168: case 0x169: case 0x16A: case 0x16B:
2197
	case 0x16C: case 0x16D: case 0x16E: case 0x16F:
2198
		executeSrch<Graphic5Mode>(time); break;
2199
	case 0x260: case 0x261: case 0x262: case 0x263:
2200
	case 0x264: case 0x265: case 0x266: case 0x267:
2201
	case 0x268: case 0x269: case 0x26A: case 0x26B:
2202
	case 0x26C: case 0x26D: case 0x26E: case 0x26F:
2203
		executeSrch<Graphic6Mode>(time); break;
2204
	case 0x360: case 0x361: case 0x362: case 0x363:
2205
	case 0x364: case 0x365: case 0x366: case 0x367:
2206
	case 0x368: case 0x369: case 0x36A: case 0x36B:
2207
	case 0x36C: case 0x36D: case 0x36E: case 0x36F:
2208
		executeSrch<Graphic7Mode>(time); break;
2209
	case 0x460: case 0x461: case 0x462: case 0x463:
2210
	case 0x464: case 0x465: case 0x466: case 0x467:
2211
	case 0x468: case 0x469: case 0x46A: case 0x46B:
2212
	case 0x46C: case 0x46D: case 0x46E: case 0x46F:
2213
		executeSrch<NonBitmapMode>(time); break;
2214
2215
	case 0x070: executeLine<Graphic4Mode,  ImpOp>(time); break;
2216
	case 0x071: executeLine<Graphic4Mode,  AndOp>(time); break;
2217
	case 0x072: executeLine<Graphic4Mode,  OrOp >(time); break;
2218
	case 0x073: executeLine<Graphic4Mode,  XorOp>(time); break;
2219
	case 0x074: executeLine<Graphic4Mode,  NotOp>(time); break;
2220
	case 0x078: executeLine<Graphic4Mode, TImpOp>(time); break;
2221
	case 0x079: executeLine<Graphic4Mode, TAndOp>(time); break;
2222
	case 0x07A: executeLine<Graphic4Mode, TOrOp >(time); break;
2223
	case 0x07B: executeLine<Graphic4Mode, TXorOp>(time); break;
2224
	case 0x07C: executeLine<Graphic4Mode, TNotOp>(time); break;
2225
	case 0x075: case 0x076: case 0x077: case 0x07D: case 0x07E: case 0x07F:
2226
		executeLine<Graphic4Mode, DummyOp>(time); break;
2227
	case 0x170: executeLine<Graphic5Mode,  ImpOp>(time); break;
2228
	case 0x171: executeLine<Graphic5Mode,  AndOp>(time); break;
2229
	case 0x172: executeLine<Graphic5Mode,  OrOp >(time); break;
2230
	case 0x173: executeLine<Graphic5Mode,  XorOp>(time); break;
2231
	case 0x174: executeLine<Graphic5Mode,  NotOp>(time); break;
2232
	case 0x178: executeLine<Graphic5Mode, TImpOp>(time); break;
2233
	case 0x179: executeLine<Graphic5Mode, TAndOp>(time); break;
2234
	case 0x17A: executeLine<Graphic5Mode, TOrOp >(time); break;
2235
	case 0x17B: executeLine<Graphic5Mode, TXorOp>(time); break;
2236
	case 0x17C: executeLine<Graphic5Mode, TNotOp>(time); break;
2237
	case 0x175: case 0x176: case 0x177: case 0x17D: case 0x17E: case 0x17F:
2238
		executeLine<Graphic5Mode, DummyOp>(time); break;
2239
	case 0x270: executeLine<Graphic6Mode,  ImpOp>(time); break;
2240
	case 0x271: executeLine<Graphic6Mode,  AndOp>(time); break;
2241
	case 0x272: executeLine<Graphic6Mode,  OrOp >(time); break;
2242
	case 0x273: executeLine<Graphic6Mode,  XorOp>(time); break;
2243
	case 0x274: executeLine<Graphic6Mode,  NotOp>(time); break;
2244
	case 0x278: executeLine<Graphic6Mode, TImpOp>(time); break;
2245
	case 0x279: executeLine<Graphic6Mode, TAndOp>(time); break;
2246
	case 0x27A: executeLine<Graphic6Mode, TOrOp >(time); break;
2247
	case 0x27B: executeLine<Graphic6Mode, TXorOp>(time); break;
2248
	case 0x27C: executeLine<Graphic6Mode, TNotOp>(time); break;
2249
	case 0x275: case 0x276: case 0x277: case 0x27D: case 0x27E: case 0x27F:
2250
		executeLine<Graphic6Mode, DummyOp>(time); break;
2251
	case 0x370: executeLine<Graphic7Mode,  ImpOp>(time); break;
2252
	case 0x371: executeLine<Graphic7Mode,  AndOp>(time); break;
2253
	case 0x372: executeLine<Graphic7Mode,  OrOp >(time); break;
2254
	case 0x373: executeLine<Graphic7Mode,  XorOp>(time); break;
2255
	case 0x374: executeLine<Graphic7Mode,  NotOp>(time); break;
2256
	case 0x378: executeLine<Graphic7Mode, TImpOp>(time); break;
2257
	case 0x379: executeLine<Graphic7Mode, TAndOp>(time); break;
2258
	case 0x37A: executeLine<Graphic7Mode, TOrOp >(time); break;
2259
	case 0x37B: executeLine<Graphic7Mode, TXorOp>(time); break;
2260
	case 0x37C: executeLine<Graphic7Mode, TNotOp>(time); break;
2261
	case 0x375: case 0x376: case 0x377: case 0x37D: case 0x37E: case 0x37F:
2262
		executeLine<Graphic7Mode, DummyOp>(time); break;
2263
	case 0x470: executeLine<NonBitmapMode,  ImpOp>(time); break;
2264
	case 0x471: executeLine<NonBitmapMode,  AndOp>(time); break;
2265
	case 0x472: executeLine<NonBitmapMode,  OrOp >(time); break;
2266
	case 0x473: executeLine<NonBitmapMode,  XorOp>(time); break;
2267
	case 0x474: executeLine<NonBitmapMode,  NotOp>(time); break;
2268
	case 0x478: executeLine<NonBitmapMode, TImpOp>(time); break;
2269
	case 0x479: executeLine<NonBitmapMode, TAndOp>(time); break;
2270
	case 0x47A: executeLine<NonBitmapMode, TOrOp >(time); break;
2271
	case 0x47B: executeLine<NonBitmapMode, TXorOp>(time); break;
2272
	case 0x47C: executeLine<NonBitmapMode, TNotOp>(time); break;
2273
	case 0x475: case 0x476: case 0x477: case 0x47D: case 0x47E: case 0x47F:
2274
		executeLine<NonBitmapMode, DummyOp>(time); break;
2275
2276
	case 0x080: executeLmmv<Graphic4Mode,  ImpOp>(time); break;
2277
	case 0x081: executeLmmv<Graphic4Mode,  AndOp>(time); break;
2278
	case 0x082: executeLmmv<Graphic4Mode,  OrOp >(time); break;
2279
	case 0x083: executeLmmv<Graphic4Mode,  XorOp>(time); break;
2280
	case 0x084: executeLmmv<Graphic4Mode,  NotOp>(time); break;
2281
	case 0x088: executeLmmv<Graphic4Mode, TImpOp>(time); break;
2282
	case 0x089: executeLmmv<Graphic4Mode, TAndOp>(time); break;
2283
	case 0x08A: executeLmmv<Graphic4Mode, TOrOp >(time); break;
2284
	case 0x08B: executeLmmv<Graphic4Mode, TXorOp>(time); break;
2285
	case 0x08C: executeLmmv<Graphic4Mode, TNotOp>(time); break;
2286
	case 0x085: case 0x086: case 0x087: case 0x08D: case 0x08E: case 0x08F:
2287
		executeLmmv<Graphic4Mode, DummyOp>(time); break;
2288
	case 0x180: executeLmmv<Graphic5Mode,  ImpOp>(time); break;
2289
	case 0x181: executeLmmv<Graphic5Mode,  AndOp>(time); break;
2290
	case 0x182: executeLmmv<Graphic5Mode,  OrOp >(time); break;
2291
	case 0x183: executeLmmv<Graphic5Mode,  XorOp>(time); break;
2292
	case 0x184: executeLmmv<Graphic5Mode,  NotOp>(time); break;
2293
	case 0x188: executeLmmv<Graphic5Mode, TImpOp>(time); break;
2294
	case 0x189: executeLmmv<Graphic5Mode, TAndOp>(time); break;
2295
	case 0x18A: executeLmmv<Graphic5Mode, TOrOp >(time); break;
2296
	case 0x18B: executeLmmv<Graphic5Mode, TXorOp>(time); break;
2297
	case 0x18C: executeLmmv<Graphic5Mode, TNotOp>(time); break;
2298
	case 0x185: case 0x186: case 0x187: case 0x18D: case 0x18E: case 0x18F:
2299
		executeLmmv<Graphic5Mode, DummyOp>(time); break;
2300
	case 0x280: executeLmmv<Graphic6Mode,  ImpOp>(time); break;
2301
	case 0x281: executeLmmv<Graphic6Mode,  AndOp>(time); break;
2302
	case 0x282: executeLmmv<Graphic6Mode,  OrOp >(time); break;
2303
	case 0x283: executeLmmv<Graphic6Mode,  XorOp>(time); break;
2304
	case 0x284: executeLmmv<Graphic6Mode,  NotOp>(time); break;
2305
	case 0x288: executeLmmv<Graphic6Mode, TImpOp>(time); break;
2306
	case 0x289: executeLmmv<Graphic6Mode, TAndOp>(time); break;
2307
	case 0x28A: executeLmmv<Graphic6Mode, TOrOp >(time); break;
2308
	case 0x28B: executeLmmv<Graphic6Mode, TXorOp>(time); break;
2309
	case 0x28C: executeLmmv<Graphic6Mode, TNotOp>(time); break;
2310
	case 0x285: case 0x286: case 0x287: case 0x28D: case 0x28E: case 0x28F:
2311
		executeLmmv<Graphic6Mode, DummyOp>(time); break;
2312
	case 0x380: executeLmmv<Graphic7Mode,  ImpOp>(time); break;
2313
	case 0x381: executeLmmv<Graphic7Mode,  AndOp>(time); break;
2314
	case 0x382: executeLmmv<Graphic7Mode,  OrOp >(time); break;
2315
	case 0x383: executeLmmv<Graphic7Mode,  XorOp>(time); break;
2316
	case 0x384: executeLmmv<Graphic7Mode,  NotOp>(time); break;
2317
	case 0x388: executeLmmv<Graphic7Mode, TImpOp>(time); break;
2318
	case 0x389: executeLmmv<Graphic7Mode, TAndOp>(time); break;
2319
	case 0x38A: executeLmmv<Graphic7Mode, TOrOp >(time); break;
2320
	case 0x38B: executeLmmv<Graphic7Mode, TXorOp>(time); break;
2321
	case 0x38C: executeLmmv<Graphic7Mode, TNotOp>(time); break;
2322
	case 0x385: case 0x386: case 0x387: case 0x38D: case 0x38E: case 0x38F:
2323
		executeLmmv<Graphic7Mode, DummyOp>(time); break;
2324
	case 0x480: executeLmmv<NonBitmapMode,  ImpOp>(time); break;
2325
	case 0x481: executeLmmv<NonBitmapMode,  AndOp>(time); break;
2326
	case 0x482: executeLmmv<NonBitmapMode,  OrOp >(time); break;
2327
	case 0x483: executeLmmv<NonBitmapMode,  XorOp>(time); break;
2328
	case 0x484: executeLmmv<NonBitmapMode,  NotOp>(time); break;
2329
	case 0x488: executeLmmv<NonBitmapMode, TImpOp>(time); break;
2330
	case 0x489: executeLmmv<NonBitmapMode, TAndOp>(time); break;
2331
	case 0x48A: executeLmmv<NonBitmapMode, TOrOp >(time); break;
2332
	case 0x48B: executeLmmv<NonBitmapMode, TXorOp>(time); break;
2333
	case 0x48C: executeLmmv<NonBitmapMode, TNotOp>(time); break;
2334
	case 0x485: case 0x486: case 0x487: case 0x48D: case 0x48E: case 0x48F:
2335
		executeLmmv<NonBitmapMode, DummyOp>(time); break;
2336
2337
	case 0x090: executeLmmm<Graphic4Mode,  ImpOp>(time); break;
2338
	case 0x091: executeLmmm<Graphic4Mode,  AndOp>(time); break;
2339
	case 0x092: executeLmmm<Graphic4Mode,  OrOp >(time); break;
2340
	case 0x093: executeLmmm<Graphic4Mode,  XorOp>(time); break;
2341
	case 0x094: executeLmmm<Graphic4Mode,  NotOp>(time); break;
2342
	case 0x098: executeLmmm<Graphic4Mode, TImpOp>(time); break;
2343
	case 0x099: executeLmmm<Graphic4Mode, TAndOp>(time); break;
2344
	case 0x09A: executeLmmm<Graphic4Mode, TOrOp >(time); break;
2345
	case 0x09B: executeLmmm<Graphic4Mode, TXorOp>(time); break;
2346
	case 0x09C: executeLmmm<Graphic4Mode, TNotOp>(time); break;
2347
	case 0x095: case 0x096: case 0x097: case 0x09D: case 0x09E: case 0x09F:
2348
		executeLmmm<Graphic4Mode, DummyOp>(time); break;
2349
	case 0x190: executeLmmm<Graphic5Mode,  ImpOp>(time); break;
2350
	case 0x191: executeLmmm<Graphic5Mode,  AndOp>(time); break;
2351
	case 0x192: executeLmmm<Graphic5Mode,  OrOp >(time); break;
2352
	case 0x193: executeLmmm<Graphic5Mode,  XorOp>(time); break;
2353
	case 0x194: executeLmmm<Graphic5Mode,  NotOp>(time); break;
2354
	case 0x198: executeLmmm<Graphic5Mode, TImpOp>(time); break;
2355
	case 0x199: executeLmmm<Graphic5Mode, TAndOp>(time); break;
2356
	case 0x19A: executeLmmm<Graphic5Mode, TOrOp >(time); break;
2357
	case 0x19B: executeLmmm<Graphic5Mode, TXorOp>(time); break;
2358
	case 0x19C: executeLmmm<Graphic5Mode, TNotOp>(time); break;
2359
	case 0x195: case 0x196: case 0x197: case 0x19D: case 0x19E: case 0x19F:
2360
		executeLmmm<Graphic5Mode, DummyOp>(time); break;
2361
	case 0x290: executeLmmm<Graphic6Mode,  ImpOp>(time); break;
2362
	case 0x291: executeLmmm<Graphic6Mode,  AndOp>(time); break;
2363
	case 0x292: executeLmmm<Graphic6Mode,  OrOp >(time); break;
2364
	case 0x293: executeLmmm<Graphic6Mode,  XorOp>(time); break;
2365
	case 0x294: executeLmmm<Graphic6Mode,  NotOp>(time); break;
2366
	case 0x298: executeLmmm<Graphic6Mode, TImpOp>(time); break;
2367
	case 0x299: executeLmmm<Graphic6Mode, TAndOp>(time); break;
2368
	case 0x29A: executeLmmm<Graphic6Mode, TOrOp >(time); break;
2369
	case 0x29B: executeLmmm<Graphic6Mode, TXorOp>(time); break;
2370
	case 0x29C: executeLmmm<Graphic6Mode, TNotOp>(time); break;
2371
	case 0x295: case 0x296: case 0x297: case 0x29D: case 0x29E: case 0x29F:
2372
		executeLmmm<Graphic6Mode, DummyOp>(time); break;
2373
	case 0x390: executeLmmm<Graphic7Mode,  ImpOp>(time); break;
2374
	case 0x391: executeLmmm<Graphic7Mode,  AndOp>(time); break;
2375
	case 0x392: executeLmmm<Graphic7Mode,  OrOp >(time); break;
2376
	case 0x393: executeLmmm<Graphic7Mode,  XorOp>(time); break;
2377
	case 0x394: executeLmmm<Graphic7Mode,  NotOp>(time); break;
2378
	case 0x398: executeLmmm<Graphic7Mode, TImpOp>(time); break;
2379
	case 0x399: executeLmmm<Graphic7Mode, TAndOp>(time); break;
2380
	case 0x39A: executeLmmm<Graphic7Mode, TOrOp >(time); break;
2381
	case 0x39B: executeLmmm<Graphic7Mode, TXorOp>(time); break;
2382
	case 0x39C: executeLmmm<Graphic7Mode, TNotOp>(time); break;
2383
	case 0x395: case 0x396: case 0x397: case 0x39D: case 0x39E: case 0x39F:
2384
		executeLmmm<Graphic7Mode, DummyOp>(time); break;
2385
	case 0x490: executeLmmm<NonBitmapMode,  ImpOp>(time); break;
2386
	case 0x491: executeLmmm<NonBitmapMode,  AndOp>(time); break;
2387
	case 0x492: executeLmmm<NonBitmapMode,  OrOp >(time); break;
2388
	case 0x493: executeLmmm<NonBitmapMode,  XorOp>(time); break;
2389
	case 0x494: executeLmmm<NonBitmapMode,  NotOp>(time); break;
2390
	case 0x498: executeLmmm<NonBitmapMode, TImpOp>(time); break;
2391
	case 0x499: executeLmmm<NonBitmapMode, TAndOp>(time); break;
2392
	case 0x49A: executeLmmm<NonBitmapMode, TOrOp >(time); break;
2393
	case 0x49B: executeLmmm<NonBitmapMode, TXorOp>(time); break;
2394
	case 0x49C: executeLmmm<NonBitmapMode, TNotOp>(time); break;
2395
	case 0x495: case 0x496: case 0x497: case 0x49D: case 0x49E: case 0x49F:
2396
		executeLmmm<NonBitmapMode, DummyOp>(time); break;
2397
2398
	case 0x0A0: case 0x0A1: case 0x0A2: case 0x0A3:
2399
	case 0x0A4: case 0x0A5: case 0x0A6: case 0x0A7:
2400
	case 0x0A8: case 0x0A9: case 0x0AA: case 0x0AB:
2401
	case 0x0AC: case 0x0AD: case 0x0AE: case 0x0AF:
2402
		executeLmcm<Graphic4Mode>(time); break;
2403
	case 0x1A0: case 0x1A1: case 0x1A2: case 0x1A3:
2404
	case 0x1A4: case 0x1A5: case 0x1A6: case 0x1A7:
2405
	case 0x1A8: case 0x1A9: case 0x1AA: case 0x1AB:
2406
	case 0x1AC: case 0x1AD: case 0x1AE: case 0x1AF:
2407
		executeLmcm<Graphic5Mode>(time); break;
2408
	case 0x2A0: case 0x2A1: case 0x2A2: case 0x2A3:
2409
	case 0x2A4: case 0x2A5: case 0x2A6: case 0x2A7:
2410
	case 0x2A8: case 0x2A9: case 0x2AA: case 0x2AB:
2411
	case 0x2AC: case 0x2AD: case 0x2AE: case 0x2AF:
2412
		executeLmcm<Graphic6Mode>(time); break;
2413
	case 0x3A0: case 0x3A1: case 0x3A2: case 0x3A3:
2414
	case 0x3A4: case 0x3A5: case 0x3A6: case 0x3A7:
2415
	case 0x3A8: case 0x3A9: case 0x3AA: case 0x3AB:
2416
	case 0x3AC: case 0x3AD: case 0x3AE: case 0x3AF:
2417
		executeLmcm<Graphic7Mode>(time); break;
2418
	case 0x4A0: case 0x4A1: case 0x4A2: case 0x4A3:
2419
	case 0x4A4: case 0x4A5: case 0x4A6: case 0x4A7:
2420
	case 0x4A8: case 0x4A9: case 0x4AA: case 0x4AB:
2421
	case 0x4AC: case 0x4AD: case 0x4AE: case 0x4AF:
2422
		executeLmcm<NonBitmapMode>(time); break;
2423
2424
	case 0x0B0: executeLmmc<Graphic4Mode,  ImpOp>(time); break;
2425
	case 0x0B1: executeLmmc<Graphic4Mode,  AndOp>(time); break;
2426
	case 0x0B2: executeLmmc<Graphic4Mode,  OrOp >(time); break;
2427
	case 0x0B3: executeLmmc<Graphic4Mode,  XorOp>(time); break;
2428
	case 0x0B4: executeLmmc<Graphic4Mode,  NotOp>(time); break;
2429
	case 0x0B8: executeLmmc<Graphic4Mode, TImpOp>(time); break;
2430
	case 0x0B9: executeLmmc<Graphic4Mode, TAndOp>(time); break;
2431
	case 0x0BA: executeLmmc<Graphic4Mode, TOrOp >(time); break;
2432
	case 0x0BB: executeLmmc<Graphic4Mode, TXorOp>(time); break;
2433
	case 0x0BC: executeLmmc<Graphic4Mode, TNotOp>(time); break;
2434
	case 0x0B5: case 0x0B6: case 0x0B7: case 0x0BD: case 0x0BE: case 0x0BF:
2435
		executeLmmc<Graphic4Mode, DummyOp>(time); break;
2436
	case 0x1B0: executeLmmc<Graphic5Mode,  ImpOp>(time); break;
2437
	case 0x1B1: executeLmmc<Graphic5Mode,  AndOp>(time); break;
2438
	case 0x1B2: executeLmmc<Graphic5Mode,  OrOp >(time); break;
2439
	case 0x1B3: executeLmmc<Graphic5Mode,  XorOp>(time); break;
2440
	case 0x1B4: executeLmmc<Graphic5Mode,  NotOp>(time); break;
2441
	case 0x1B8: executeLmmc<Graphic5Mode, TImpOp>(time); break;
2442
	case 0x1B9: executeLmmc<Graphic5Mode, TAndOp>(time); break;
2443
	case 0x1BA: executeLmmc<Graphic5Mode, TOrOp >(time); break;
2444
	case 0x1BB: executeLmmc<Graphic5Mode, TXorOp>(time); break;
2445
	case 0x1BC: executeLmmc<Graphic5Mode, TNotOp>(time); break;
2446
	case 0x1B5: case 0x1B6: case 0x1B7: case 0x1BD: case 0x1BE: case 0x1BF:
2447
		executeLmmc<Graphic5Mode, DummyOp>(time); break;
2448
	case 0x2B0: executeLmmc<Graphic6Mode,  ImpOp>(time); break;
2449
	case 0x2B1: executeLmmc<Graphic6Mode,  AndOp>(time); break;
2450
	case 0x2B2: executeLmmc<Graphic6Mode,  OrOp >(time); break;
2451
	case 0x2B3: executeLmmc<Graphic6Mode,  XorOp>(time); break;
2452
	case 0x2B4: executeLmmc<Graphic6Mode,  NotOp>(time); break;
2453
	case 0x2B8: executeLmmc<Graphic6Mode, TImpOp>(time); break;
2454
	case 0x2B9: executeLmmc<Graphic6Mode, TAndOp>(time); break;
2455
	case 0x2BA: executeLmmc<Graphic6Mode, TOrOp >(time); break;
2456
	case 0x2BB: executeLmmc<Graphic6Mode, TXorOp>(time); break;
2457
	case 0x2BC: executeLmmc<Graphic6Mode, TNotOp>(time); break;
2458
	case 0x2B5: case 0x2B6: case 0x2B7: case 0x2BD: case 0x2BE: case 0x2BF:
2459
		executeLmmc<Graphic6Mode, DummyOp>(time); break;
2460
	case 0x3B0: executeLmmc<Graphic7Mode,  ImpOp>(time); break;
2461
	case 0x3B1: executeLmmc<Graphic7Mode,  AndOp>(time); break;
2462
	case 0x3B2: executeLmmc<Graphic7Mode,  OrOp >(time); break;
2463
	case 0x3B3: executeLmmc<Graphic7Mode,  XorOp>(time); break;
2464
	case 0x3B4: executeLmmc<Graphic7Mode,  NotOp>(time); break;
2465
	case 0x3B8: executeLmmc<Graphic7Mode, TImpOp>(time); break;
2466
	case 0x3B9: executeLmmc<Graphic7Mode, TAndOp>(time); break;
2467
	case 0x3BA: executeLmmc<Graphic7Mode, TOrOp >(time); break;
2468
	case 0x3BB: executeLmmc<Graphic7Mode, TXorOp>(time); break;
2469
	case 0x3BC: executeLmmc<Graphic7Mode, TNotOp>(time); break;
2470
	case 0x3B5: case 0x3B6: case 0x3B7: case 0x3BD: case 0x3BE: case 0x3BF:
2471
		executeLmmc<Graphic7Mode, DummyOp>(time); break;
2472
	case 0x4B0: executeLmmc<NonBitmapMode,  ImpOp>(time); break;
2473
	case 0x4B1: executeLmmc<NonBitmapMode,  AndOp>(time); break;
2474
	case 0x4B2: executeLmmc<NonBitmapMode,  OrOp >(time); break;
2475
	case 0x4B3: executeLmmc<NonBitmapMode,  XorOp>(time); break;
2476
	case 0x4B4: executeLmmc<NonBitmapMode,  NotOp>(time); break;
2477
	case 0x4B8: executeLmmc<NonBitmapMode, TImpOp>(time); break;
2478
	case 0x4B9: executeLmmc<NonBitmapMode, TAndOp>(time); break;
2479
	case 0x4BA: executeLmmc<NonBitmapMode, TOrOp >(time); break;
2480
	case 0x4BB: executeLmmc<NonBitmapMode, TXorOp>(time); break;
2481
	case 0x4BC: executeLmmc<NonBitmapMode, TNotOp>(time); break;
2482
	case 0x4B5: case 0x4B6: case 0x4B7: case 0x4BD: case 0x4BE: case 0x4BF:
2483
		executeLmmc<NonBitmapMode, DummyOp>(time); break;
2484
2485
	case 0x0C0: case 0x0C1: case 0x0C2: case 0x0C3:
2486
	case 0x0C4: case 0x0C5: case 0x0C6: case 0x0C7:
2487
	case 0x0C8: case 0x0C9: case 0x0CA: case 0x0CB:
2488
	case 0x0CC: case 0x0CD: case 0x0CE: case 0x0CF:
2489
		executeHmmv<Graphic4Mode>(time); break;
2490
	case 0x1C0: case 0x1C1: case 0x1C2: case 0x1C3:
2491
	case 0x1C4: case 0x1C5: case 0x1C6: case 0x1C7:
2492
	case 0x1C8: case 0x1C9: case 0x1CA: case 0x1CB:
2493
	case 0x1CC: case 0x1CD: case 0x1CE: case 0x1CF:
2494
		executeHmmv<Graphic5Mode>(time); break;
2495
	case 0x2C0: case 0x2C1: case 0x2C2: case 0x2C3:
2496
	case 0x2C4: case 0x2C5: case 0x2C6: case 0x2C7:
2497
	case 0x2C8: case 0x2C9: case 0x2CA: case 0x2CB:
2498
	case 0x2CC: case 0x2CD: case 0x2CE: case 0x2CF:
2499
		executeHmmv<Graphic6Mode>(time); break;
2500
	case 0x3C0: case 0x3C1: case 0x3C2: case 0x3C3:
2501
	case 0x3C4: case 0x3C5: case 0x3C6: case 0x3C7:
2502
	case 0x3C8: case 0x3C9: case 0x3CA: case 0x3CB:
2503
	case 0x3CC: case 0x3CD: case 0x3CE: case 0x3CF:
2504
		executeHmmv<Graphic7Mode>(time); break;
2505
	case 0x4C0: case 0x4C1: case 0x4C2: case 0x4C3:
2506
	case 0x4C4: case 0x4C5: case 0x4C6: case 0x4C7:
2507
	case 0x4C8: case 0x4C9: case 0x4CA: case 0x4CB:
2508
	case 0x4CC: case 0x4CD: case 0x4CE: case 0x4CF:
2509
		executeHmmv<NonBitmapMode>(time); break;
2510
2511
	case 0x0D0: case 0x0D1: case 0x0D2: case 0x0D3:
2512
	case 0x0D4: case 0x0D5: case 0x0D6: case 0x0D7:
2513
	case 0x0D8: case 0x0D9: case 0x0DA: case 0x0DB:
2514
	case 0x0DC: case 0x0DD: case 0x0DE: case 0x0DF:
2515
		executeHmmm<Graphic4Mode>(time); break;
2516
	case 0x1D0: case 0x1D1: case 0x1D2: case 0x1D3:
2517
	case 0x1D4: case 0x1D5: case 0x1D6: case 0x1D7:
2518
	case 0x1D8: case 0x1D9: case 0x1DA: case 0x1DB:
2519
	case 0x1DC: case 0x1DD: case 0x1DE: case 0x1DF:
2520
		executeHmmm<Graphic5Mode>(time); break;
2521
	case 0x2D0: case 0x2D1: case 0x2D2: case 0x2D3:
2522
	case 0x2D4: case 0x2D5: case 0x2D6: case 0x2D7:
2523
	case 0x2D8: case 0x2D9: case 0x2DA: case 0x2DB:
2524
	case 0x2DC: case 0x2DD: case 0x2DE: case 0x2DF:
2525
		executeHmmm<Graphic6Mode>(time); break;
2526
	case 0x3D0: case 0x3D1: case 0x3D2: case 0x3D3:
2527
	case 0x3D4: case 0x3D5: case 0x3D6: case 0x3D7:
2528
	case 0x3D8: case 0x3D9: case 0x3DA: case 0x3DB:
2529
	case 0x3DC: case 0x3DD: case 0x3DE: case 0x3DF:
2530
		executeHmmm<Graphic7Mode>(time); break;
2531
	case 0x4D0: case 0x4D1: case 0x4D2: case 0x4D3:
2532
	case 0x4D4: case 0x4D5: case 0x4D6: case 0x4D7:
2533
	case 0x4D8: case 0x4D9: case 0x4DA: case 0x4DB:
2534
	case 0x4DC: case 0x4DD: case 0x4DE: case 0x4DF:
2535
		executeHmmm<NonBitmapMode>(time); break;
2536
2537
	case 0x0E0: case 0x0E1: case 0x0E2: case 0x0E3:
2538
	case 0x0E4: case 0x0E5: case 0x0E6: case 0x0E7:
2539
	case 0x0E8: case 0x0E9: case 0x0EA: case 0x0EB:
2540
	case 0x0EC: case 0x0ED: case 0x0EE: case 0x0EF:
2541
		executeYmmm<Graphic4Mode>(time); break;
2542
	case 0x1E0: case 0x1E1: case 0x1E2: case 0x1E3:
2543
	case 0x1E4: case 0x1E5: case 0x1E6: case 0x1E7:
2544
	case 0x1E8: case 0x1E9: case 0x1EA: case 0x1EB:
2545
	case 0x1EC: case 0x1ED: case 0x1EE: case 0x1EF:
2546
		executeYmmm<Graphic5Mode>(time); break;
2547
	case 0x2E0: case 0x2E1: case 0x2E2: case 0x2E3:
2548
	case 0x2E4: case 0x2E5: case 0x2E6: case 0x2E7:
2549
	case 0x2E8: case 0x2E9: case 0x2EA: case 0x2EB:
2550
	case 0x2EC: case 0x2ED: case 0x2EE: case 0x2EF:
2551
		executeYmmm<Graphic6Mode>(time); break;
2552
	case 0x3E0: case 0x3E1: case 0x3E2: case 0x3E3:
2553
	case 0x3E4: case 0x3E5: case 0x3E6: case 0x3E7:
2554
	case 0x3E8: case 0x3E9: case 0x3EA: case 0x3EB:
2555
	case 0x3EC: case 0x3ED: case 0x3EE: case 0x3EF:
2556
		executeYmmm<Graphic7Mode>(time); break;
2557
	case 0x4E0: case 0x4E1: case 0x4E2: case 0x4E3:
2558
	case 0x4E4: case 0x4E5: case 0x4E6: case 0x4E7:
2559
	case 0x4E8: case 0x4E9: case 0x4EA: case 0x4EB:
2560
	case 0x4EC: case 0x4ED: case 0x4EE: case 0x4EF:
2561
		executeYmmm<NonBitmapMode>(time); break;
2562
2563
	case 0x0F0: case 0x0F1: case 0x0F2: case 0x0F3:
2564
	case 0x0F4: case 0x0F5: case 0x0F6: case 0x0F7:
2565
	case 0x0F8: case 0x0F9: case 0x0FA: case 0x0FB:
2566
	case 0x0FC: case 0x0FD: case 0x0FE: case 0x0FF:
2567
		executeHmmc<Graphic4Mode>(time); break;
2568
	case 0x1F0: case 0x1F1: case 0x1F2: case 0x1F3:
2569
	case 0x1F4: case 0x1F5: case 0x1F6: case 0x1F7:
2570
	case 0x1F8: case 0x1F9: case 0x1FA: case 0x1FB:
2571
	case 0x1FC: case 0x1FD: case 0x1FE: case 0x1FF:
2572
		executeHmmc<Graphic5Mode>(time); break;
2573
	case 0x2F0: case 0x2F1: case 0x2F2: case 0x2F3:
2574
	case 0x2F4: case 0x2F5: case 0x2F6: case 0x2F7:
2575
	case 0x2F8: case 0x2F9: case 0x2FA: case 0x2FB:
2576
	case 0x2FC: case 0x2FD: case 0x2FE: case 0x2FF:
2577
		executeHmmc<Graphic6Mode>(time); break;
2578
	case 0x3F0: case 0x3F1: case 0x3F2: case 0x3F3:
2579
	case 0x3F4: case 0x3F5: case 0x3F6: case 0x3F7:
2580
	case 0x3F8: case 0x3F9: case 0x3FA: case 0x3FB:
2581
	case 0x3FC: case 0x3FD: case 0x3FE: case 0x3FF:
2582
		executeHmmc<Graphic7Mode>(time); break;
2583
	case 0x4F0: case 0x4F1: case 0x4F2: case 0x4F3:
2584
	case 0x4F4: case 0x4F5: case 0x4F6: case 0x4F7:
2585
	case 0x4F8: case 0x4F9: case 0x4FA: case 0x4FB:
2586
	case 0x4FC: case 0x4FD: case 0x4FE: case 0x4FF:
2587
		executeHmmc<NonBitmapMode>(time); break;
2588
2589
	default:
2590
		UNREACHABLE;
2591
	}
2592
}
2593
2594
void VDPCmdEngine::commandDone(EmuTime time)
2595
{
2596
	// Note: TR is not reset yet; it is reset when S#2 is read next.
2597
	status &= ~CE;
2598
	executingProbe = false;
2599
	cmdProbe.signal(); // must be after executingProbe
2600
	CMD = 0;
2601
	setStatusChangeTime(EmuTime::infinity());
2602
	vram.cmdReadWindow.disable(time);
2603
	vram.cmdWriteWindow.disable(time);
2604
}
2605
2606
// Take VDPCmdEngine info like {sx,sy} (or {dx,dy}), {nx,ny}, ...
2607
// And turn that into a rectangle that's properly clipped horizontally for the
2608
// given screen mode. Taking into account pixel vs byte mode.
2609
2610
// Usually this returns just 1 retangle, but in case of vertical wrapping this
2611
// can be split over two (disjunct) rectangles. In that case the order in which
2612
// the VDP handles these rectangles (via 'diy') is preserved.
2613
//
2614
// Also within a single rectangle the VDP order (via 'dix' and 'diy') is
2615
// preserved. So start at corner 'p1', end at corner 'p2'.
2616
//
2617
// Both start and end points of the rectangle(s) are inclusive.
2618
// TODO unittest
2619
[[nodiscard]] static static_vector<VDPCmdEngine::Rect, 2> rectFromVdpCmd(
2620
	int x, int y, // either sx,sy or dx,dy
2621
	int nx, int ny,
2622
	bool dix, bool diy,
2623
	int screenMode,
2624
	bool byteMode) // Lxxx or Hxxx command
2625
{
2626
	const auto [width, height, pixelsPerByte] = [&]{
2627
		switch (screenMode) {
2628
		case 0:  return std::tuple{256, 1024, 2}; // screen 5
2629
		case 1:  return std::tuple{512, 1024, 4}; // screen 6
2630
		case 2:  return std::tuple{512,  512, 2}; // screen 7
2631
		default: return std::tuple{256,  512, 1}; // screen 8, 11, 12  (and fallback for non-bitmap)
2632
		}
2633
	}();
2634
2635
	// clamp/wrap start-point
2636
	x = std::clamp(x, 0, width - 1); // Clamp to border. This is different from the real VDP behavior.
2637
	                            // But for debugging it's visually less confusing??
2638
	y &= (height - 1); // wrap around
2639
2640
	if (nx <= 0) nx = width;
2641
	if (ny <= 0) ny = height;
2642
	if (byteMode) { // round to byte positions
2643
		auto mask = ~(pixelsPerByte - 1);
2644
		x &= mask;
2645
		nx &= mask;
2646
	}
2647
	nx -= 1; // because coordinates are inclusive
2648
	ny -= 1;
2649
2650
	// horizontally clamp to left/right border
2651
	auto endX = std::clamp(x + (dix ? -nx : nx), 0, width - 1);
2652
2653
	// but vertically wrap around, possibly that splits the rectangle in two parts
2654
	using Point = VDPCmdEngine::Point;
2655
	using Rect = VDPCmdEngine::Rect;
2656
	if (diy) {
2657
		auto endY = y - ny;
2658
		if (endY >= 0) {
2659
			return {Rect{Point{x, y}, Point{endX, endY}}};
2660
		} else {
2661
			return {Rect{Point{x, y}, Point{endX, 0}},
2662
			        Rect{Point{x, height - 1},
2663
			             Point{endX, endY & (height - 1)}}};
2664
		}
2665
	} else {
2666
		auto endY = (y + ny);
2667
		if (endY < height) {
2668
			return {Rect{Point{x, y}, Point{endX, endY}}};
2669
		} else {
2670
			return {Rect{Point{x, y}, Point{endX, height - 1}},
2671
			        Rect{Point{x, 0},
2672
			             Point{endX, endY & (height - 1)}}};
2673
		}
2674
	}
2675
}
2676
2677
VDPCmdEngine::FormatCmdResult VDPCmdEngine::formatCommand(const VDPCmdEngine::CmdRegs& r, int mode)
2678
{
2679
	FormatCmdResult result;
2680
2681
	bool dix = r.arg & VDPCmdEngine::DIX;
2682
	bool diy = r.arg & VDPCmdEngine::DIY;
2683
2684
	static constexpr std::array<const char*, 16> logOps = {
2685
		"",     ",AND", ",OR", ",XOR", ",NOT", "","","",
2686
		",TIMP",",TAND",",TOR",",TXOR",",TNOT","","",""
2687
	};
2688
	const char* logOp = logOps[r.cmd & 15];
2689
2690
	auto printRect = [&](std::string_view s, const static_vector<Rect, 2>& rct, auto... args) {
2691
		// front/back for the (unlikely) case of vertical wrapping
2692
		strAppend(result.str, s, '(', rct.front().p1.x, ',', rct.front().p1.y, ")"
2693
		                        "-(", rct.back ().p2.x, ',', rct.back().p2.y, ')', args...);
2694
	};
2695
	switch (r.cmd >> 4) {
2696
	case 0: case 1: case 2: case 3:
2697
		result.str = "ABORT"sv;
2698
		break;
2699
	case 4:
2700
		result.str = strCat("POINT (", r.sx, ',', r.sy, ')');
2701
		break;
2702
	case 5:
2703
		result.str = strCat("PSET (", r.dx, ',', r.dy, "),", r.col, logOp);
2704
		break;
2705
	case 6:
2706
		result.str = strCat("SRCH (", r.sx, ',', r.sy, ") ",
2707
		                    ((r.arg & VDPCmdEngine::EQ) ? "== " : "!= "), r.col);
2708
		break;
2709
	case 7: {
2710
		auto nx2 = r.nx; auto ny2 = r.ny;
2711
		if (r.arg & VDPCmdEngine::MAJ) std::swap(nx2, ny2);
2712
		auto x = int(r.dx) + (dix ? -int(nx2) : int(nx2));
2713
		auto y = int(r.dy) + (diy ? -int(ny2) : int(ny2));
2714
		result.str = strCat("LINE (", r.dx, ',', r.dy, ")"
2715
		                        "-(",    x, ',',    y, "),", r.col, logOp);
2716
		break;
2717
	}
2718
	case 8:
2719
		result.dstRect = rectFromVdpCmd(r.dx, r.dy, r.nx, r.ny, dix, diy, mode, false);
2720
		printRect("LMMV ", *result.dstRect, ',', r.col, logOp);
2721
		break;
2722
	case 9:
2723
		result.srcRect = rectFromVdpCmd(r.sx, r.sy, r.nx, r.ny, dix, diy, mode, false);
2724
		result.dstRect = rectFromVdpCmd(r.dx, r.dy, r.nx, r.ny, dix, diy, mode, false);
2725
		printRect("LMMM ", *result.srcRect);
2726
		printRect(" TO ", *result.dstRect, logOp);
2727
		break;
2728
	case 10:
2729
		result.srcRect = rectFromVdpCmd(r.sx, r.sy, r.nx, r.ny, dix, diy, mode, false);
2730
		printRect("LMCM ", *result.srcRect);
2731
		break;
2732
	case 11:
2733
		result.dstRect = rectFromVdpCmd(r.dx, r.dy, r.nx, r.ny, dix, diy, mode, false);
2734
		printRect("LMMC ", *result.dstRect, logOp);
2735
		break;
2736
	case 12:
2737
		result.dstRect = rectFromVdpCmd(r.dx, r.dy, r.nx, r.ny, dix, diy, mode, true);
2738
		printRect("HMMV ", *result.dstRect, ',', r.col);
2739
		break;
2740
	case 13:
2741
		result.srcRect = rectFromVdpCmd(r.sx, r.sy, r.nx, r.ny, dix, diy, mode, true);
2742
		result.dstRect = rectFromVdpCmd(r.dx, r.dy, r.nx, r.ny, dix, diy, mode, true);
2743
		printRect("HMMM ", *result.srcRect);
2744
		printRect(" TO ", *result.dstRect);
2745
		break;
2746
	case 14:
2747
		// different from normal: NO 'sx', and NO 'nx'
2748
		result.srcRect = rectFromVdpCmd(r.dx, r.sy, 512, r.ny, dix, diy, mode, true);
2749
		result.dstRect = rectFromVdpCmd(r.dx, r.dy, 512, r.ny, dix, diy, mode, true);
2750
		printRect("YMMM", *result.srcRect);
2751
		printRect(" TO ", *result.dstRect);
2752
		break;
2753
	case 15:
2754
		result.dstRect = rectFromVdpCmd(r.dx, r.dy, r.nx, r.ny, dix, diy, mode, true);
2755
		printRect("HMMC ", *result.dstRect);
2756
		break;
2757
	default:
2758
		UNREACHABLE;
2759
	}
2760
	return result;
2761
}
2762
2763
VDPCmdEngine::CmdProbe::CmdProbe(Debugger& debugger_, std::string name_)
2764
	: ProbeBase(debugger_, std::move(name_), "String representation of the currently executing command")
2765
{
2766
}
2767
2768
TclObject VDPCmdEngine::CmdProbe::getValue() const
2769
{
2770
	auto& engine = OUTER(VDPCmdEngine, cmdProbe);
2771
	if (!engine.executingProbe) return TclObject{};
2772
	auto regs = engine.getLastCommand();
2773
	auto formatted = VDPCmdEngine::formatCommand(regs, engine.scrMode);
2774
	return TclObject{formatted.str};
2775
}
2776
2777
TraceValue VDPCmdEngine::CmdProbe::getTraceValue() const
2778
{
2779
	auto& engine = OUTER(VDPCmdEngine, cmdProbe);
2780
	if (!engine.executingProbe) return TraceValue{std::monostate{}};
2781
	auto regs = engine.getLastCommand();
2782
	auto formatted = VDPCmdEngine::formatCommand(regs, engine.scrMode);
2783
	return TraceValue{formatted.str};
2784
}
2785
2786
// version 1: initial version
2787
// version 2: replaced member 'Clock<> clock' with 'EmuTime time'
2788
// version 3: added 'phase', 'tmpSrc', 'tmpDst'
2789
// version 4: added 'lastXXX'. This is only used for debugging, so you could ask
2790
//            why this needs to be serialized. Maybe for savestate/replay-files
2791
//            it's not super useful, but it is important for reverse during a
2792
//            debug session.
2793
template<typename Archive>
2794
void VDPCmdEngine::serialize(Archive& ar, unsigned version)
2795
{
2796
	// In older (development) versions CMD was split in a CMD and a LOG
2797
	// member, though it was combined for the savestate. Only the CMD part
2798
	// was guaranteed to be zero when no command was executing. So when
2799
	// loading an older savestate this can still be the case.
2800
2801
	if (ar.versionAtLeast(version, 2)) {
2802
		ar.serialize("time", engineTime);
2803
	} else {
2804
		// in version 1, the 'engineTime' member had type 'Clock<>'
2805
		assert(Archive::IS_LOADER);
2806
		VDP::VDPClock clock(EmuTime::dummy());
2807
		ar.serialize("clock", clock);
2808
		engineTime = clock.getTime();
2809
	}
2810
	ar.serialize("statusChangeTime", statusChangeTime,
2811
	             "scrMode",          scrMode,
2812
	             "status",           status,
2813
	             "transfer",         transfer,
2814
	             "SX",  SX,
2815
	             "SY",  SY,
2816
	             "DX",  DX,
2817
	             "DY",  DY,
2818
	             "NX",  NX,
2819
	             "NY",  NY,
2820
	             "ASX", ASX,
2821
	             "ADX", ADX,
2822
	             "ANX", ANX,
2823
	             "COL", COL,
2824
	             "ARG", ARG,
2825
	             "CMD", CMD);
2826
2827
	if (ar.versionAtLeast(version, 3)) {
2828
		ar.serialize("phase",  phase,
2829
		             "tmpSrc", tmpSrc,
2830
		             "tmpDst", tmpDst);
2831
	} else {
2832
		assert(Archive::IS_LOADER);
2833
		phase = tmpSrc = tmpDst = 0;
2834
	}
2835
2836
	if constexpr (Archive::IS_LOADER) {
2837
		if (CMD & 0xF0) {
2838
			assert(scrMode >= 0);
2839
		} else {
2840
			CMD = 0; // see note above
2841
		}
2842
	}
2843
2844
	if (ar.versionAtLeast(version, 4)) {
2845
		ar.serialize("lastSX",  lastSX,
2846
		             "lastSY",  lastSY,
2847
		             "lastDX",  lastDX,
2848
		             "lastDY",  lastDY,
2849
		             "lastNX",  lastNX,
2850
		             "lastNY",  lastNY,
2851
		             "lastCOL", lastCOL,
2852
		             "lastARG", lastARG,
2853
		             "lastCMD", lastCMD);
2854
	} else {
2855
		assert(Archive::IS_LOADER);
2856
		lastSX = SX;
2857
		lastSY = SY;
2858
		lastDX = DX;
2859
		lastDY = DY;
2860
		lastNX = NX;
2861
		lastNY = NY;
2862
		lastCOL = COL;
2863
		lastARG = ARG;
2864
		lastCMD = CMD;
2865
	}
2866
}
2867
INSTANTIATE_SERIALIZE_METHODS(VDPCmdEngine);
2868
2869
} // namespace openmsx