OXYGENHUB
◆ main openmsx / src / video / v9990 / V9990CmdEngine.cc
55.7 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#include "V9990CmdEngine.hh"
2
3
#include "V9990.hh"
4
#include "V9990DisplayTiming.hh"
5
#include "V9990VRAM.hh"
6
7
#include "BooleanSetting.hh"
8
#include "Clock.hh"
9
#include "EnumSetting.hh"
10
#include "MSXMotherBoard.hh"
11
#include "MemBuffer.hh"
12
#include "RenderSettings.hh"
13
#include "serialize.hh"
14
15
#include "checked_cast.hh"
16
#include "narrow.hh"
17
#include "stl.hh"
18
#include "unreachable.hh"
19
#include "xrange.hh"
20
21
#include <array>
22
#include <cassert>
23
#include <iostream>
24
#include <string_view>
25
26
namespace openmsx {
27
28
static constexpr unsigned maxLength = 171; // The maximum value from the xxx_TIMING tables below
29
static constexpr EmuDuration d_(unsigned x)
30
{
31
	assert(x <= maxLength);
32
	return Clock<V9990DisplayTiming::UC_TICKS_PER_SECOND>::duration(x);
33
}
34
using EDStorage = EmuDurationStorageFor<d_(maxLength).toUint64()>;
35
static constexpr EDStorage d(unsigned x) { return EDStorage{d_(x)}; }
36
using A  = std::array<const EDStorage, 4>;
37
using A2 = std::array<const A, 3>;
38
using TimingTable = std::span<const A2, 4>;
39
40
// 1st index  B0/2/4, B1/3/7, P1, P2
41
// 2nd index  sprites-ON, sprites-OFF, display-OFF
42
// 3th index  2bpp, 4bpp, 8bpp, 16bpp
43
//            (for P1/P2 fill in the same value 4 times)
44
static constexpr std::array LMMV_TIMING = {
45
	A2{A{ d( 8), d(11), d(15), d(30)}, A{d( 7), d(10), d(13), d(26)}, A{d( 7), d(10), d(13), d(25)}},
46
	A2{A{ d( 5), d( 7), d( 9), d(18)}, A{d( 5), d( 6), d( 8), d(17)}, A{d( 5), d( 6), d( 8), d(17)}},
47
	A2{A{ d(56), d(56), d(56), d(56)}, A{d(25), d(25), d(25), d(25)}, A{d( 9), d( 9), d( 9), d( 9)}},
48
	A2{A{ d(28), d(28), d(28), d(28)}, A{d(15), d(15), d(15), d(15)}, A{d( 6), d( 6), d( 6), d( 6)}}
49
};
50
static constexpr std::array LMMM_TIMING = {
51
	A2{A{d (10),d (16),d( 32),d( 66)}, A{d( 8), d(14), d(28), d(57)}, A{d( 8), d(13), d(27), d(54)}},
52
	A2{A{d ( 6),d( 10),d( 20),d( 39)}, A{d( 5), d( 9), d(18), d(35)}, A{d( 5), d( 9), d(17), d(35)}},
53
	A2{A{d(115),d(115),d(115),d(115)}, A{d(52), d(52), d(52), d(52)}, A{d(18), d(18), d(18), d(18)}},
54
	A2{A{d( 57),d( 57),d( 57),d( 57)}, A{d(25), d(25), d(25), d(25)}, A{d( 9), d( 9), d( 9), d( 9)}}
55
};
56
static constexpr std::array BMXL_TIMING = { // NOTE: values are BYTE based here!
57
	A2{A{d( 38),d( 33),d( 32),d( 33)}, A{d(33), d(28), d(28), d(28)}, A{d(33), d(27), d(27), d(27)}}, // identical to LMMM (b)
58
	A2{A{d( 24),d( 20),d( 20),d (19)}, A{d(22), d(18), d(18), d(18)}, A{d(21), d(17), d(17), d(17)}}, // identical to LMMM (b)
59
	A2{A{d(171),d(171),d(171),d(171)}, A{d(82), d(82), d(82), d(82)}, A{d(29), d(29), d(29), d(29)}},
60
	A2{A{d(114),d(114),d(114),d(114)}, A{d(50), d(50), d(50), d(50)}, A{d(18), d(18), d(18), d(18)}}
61
};
62
static constexpr std::array BMLX_TIMING = {
63
	A2{A{ d(10), d(16), d(32), d(66)}, A{d( 8), d(14), d(28), d(57)}, A{d( 8), d(13), d(27), d(54)}}, // identical to LMMM
64
	A2{A{ d( 6), d(10), d(20), d(39)}, A{d( 5), d( 9), d(18), d(35)}, A{d( 5), d( 9), d(17), d(35)}}, // identical to LMMM
65
	A2{A{ d(84), d(84), d(84), d(84)}, A{d(44), d(44), d(44), d(44)}, A{d(17), d(17), d(17), d(17)}},
66
	A2{A{ d(57), d(57), d(57), d(57)}, A{d(25), d(25), d(25), d(25)}, A{d( 9), d( 9), d( 9), d( 9)}}
67
};
68
static constexpr std::array BMLL_TIMING = {
69
	A2{A{d( 33),d( 33),d( 33),d( 33)}, A{d(28), d(28), d(28), d(28)}, A{d(27), d(27), d(27), d(27)}},
70
	A2{A{d( 20),d( 20),d( 20),d( 20)}, A{d(18), d(18), d(18), d(18)}, A{d(18), d(18), d(18), d(18)}},
71
	A2{A{d(118),d(118),d(118),d(118)}, A{d(52), d(52), d(52), d(52)}, A{d(18), d(18), d(18), d(18)}},
72
	A2{A{d(118),d(118),d(118),d(118)}, A{d(52), d(52), d(52), d(52)}, A{d(18), d(18), d(18), d(18)}}
73
};
74
static constexpr std::array CMMM_TIMING = {
75
	A2{A{ d(24), d(24), d(24), d(24)}, A{d(24), d(24), d(24), d(24)}, A{d(24), d(24), d(24), d(24)}}, // TODO
76
	A2{A{ d(24), d(24), d(24), d(24)}, A{d(24), d(24), d(24), d(24)}, A{d(24), d(24), d(24), d(24)}}, // TODO
77
	A2{A{ d(24), d(24), d(24), d(24)}, A{d(24), d(24), d(24), d(24)}, A{d(24), d(24), d(24), d(24)}}, // TODO
78
	A2{A{ d(24), d(24), d(24), d(24)}, A{d(24), d(24), d(24), d(24)}, A{d(24), d(24), d(24), d(24)}}  // TODO
79
};
80
static constexpr std::array LINE_TIMING = {
81
	A2{A{ d(24), d(24), d(24), d(24)}, A{d(24), d(24), d(24), d(24)}, A{d(24), d(24), d(24), d(24)}}, // TODO
82
	A2{A{ d(24), d(24), d(24), d(24)}, A{d(24), d(24), d(24), d(24)}, A{d(24), d(24), d(24), d(24)}}, // TODO
83
	A2{A{ d(24), d(24), d(24), d(24)}, A{d(24), d(24), d(24), d(24)}, A{d(24), d(24), d(24), d(24)}}, // TODO
84
	A2{A{ d(24), d(24), d(24), d(24)}, A{d(24), d(24), d(24), d(24)}, A{d(24), d(24), d(24), d(24)}}  // TODO
85
};
86
static constexpr std::array SRCH_TIMING = {
87
	A2{A{ d(24), d(24), d(24), d(24)}, A{d(24), d(24), d(24), d(24)}, A{d(24), d(24), d(24), d(24)}}, // TODO
88
	A2{A{ d(24), d(24), d(24), d(24)}, A{d(24), d(24), d(24), d(24)}, A{d(24), d(24), d(24), d(24)}}, // TODO
89
	A2{A{ d(24), d(24), d(24), d(24)}, A{d(24), d(24), d(24), d(24)}, A{d(24), d(24), d(24), d(24)}}, // TODO
90
	A2{A{ d(24), d(24), d(24), d(24)}, A{d(24), d(24), d(24), d(24)}, A{d(24), d(24), d(24), d(24)}}  // TODO
91
};
92
93
[[nodiscard]] static EmuDuration getTiming(const V9990CmdEngine& cmdEngine, TimingTable table)
94
{
95
	if (cmdEngine.getBrokenTiming()) [[unlikely]] return {};
96
97
	const auto& vdp = cmdEngine.getVDP();
98
	auto mode = vdp.getDisplayMode();
99
	unsigned idx1 = (mode == V9990DisplayMode::P1) ? 2 :
100
	                (mode == V9990DisplayMode::P2) ? 3 :
101
	                (vdp.isOverScan()) ? 0 : 1;
102
	unsigned idx2 = vdp.isDisplayEnabled() ? (vdp.spritesEnabled() ? 0 : 1)
103
	                                       : 2;
104
	unsigned idx3 = vdp.getColorDepth();
105
	return EmuDuration(table[idx1][idx2][idx3]);
106
}
107
108
109
110
// Lazily initialized LUT to speed up logical operations:
111
//  - 1st index is the mode: 2,4,8 bpp or 'not-transparent'
112
//  - 2nd index is the logical operation: one of the 16 possible binary functions
113
// * Each entry contains a 256x256 byte array, that array is indexed using
114
//   destination and source byte (in that order).
115
// * A fully populated logOpLUT would take 4MB, however the vast majority of
116
//   this table is (almost) never used. So we save quite some memory (and
117
//   startup time) by lazily initializing this table.
118
enum class Log : uint8_t {
119
	NO_T, BPP2, BPP4, BPP8,
120
	NUM
121
};
122
static array_with_enum_index<Log, std::array<MemBuffer<uint8_t>, 16>> logOpLUT;
123
124
// to speedup calculating logOpLUT
125
static constexpr auto bitLUT = [] {
126
	std::array<std::array<std::array<std::array<uint8_t, 2>, 2>, 16>, 8> result = {};
127
	for (auto op : xrange(16)) {
128
		unsigned tmp = op;
129
		for (auto src : xrange(2)) {
130
			for (auto dst : xrange(2)) {
131
				unsigned b = tmp & 1;
132
				for (auto bit : xrange(8)) {
133
					result[bit][op][src][dst] = narrow<uint8_t>(b << bit);
134
				}
135
				tmp >>= 1;
136
			}
137
		}
138
	}
139
	return result;
140
}();
141
142
[[nodiscard]] static constexpr uint8_t func01(unsigned op, unsigned src, unsigned dst)
143
{
144
	if ((src & 0x03) == 0) return dst & 0x03;
145
	uint8_t res = 0;
146
	res |= bitLUT[0][op][(src & 0x01) >> 0][(dst & 0x01) >> 0];
147
	res |= bitLUT[1][op][(src & 0x02) >> 1][(dst & 0x02) >> 1];
148
	return res;
149
}
150
[[nodiscard]] static constexpr uint8_t func23(unsigned op, unsigned src, unsigned dst)
151
{
152
	if ((src & 0x0C) == 0) return dst & 0x0C;
153
	uint8_t res = 0;
154
	res |= bitLUT[2][op][(src & 0x04) >> 2][(dst & 0x04) >> 2];
155
	res |= bitLUT[3][op][(src & 0x08) >> 3][(dst & 0x08) >> 3];
156
	return res;
157
}
158
[[nodiscard]] static constexpr uint8_t func45(unsigned op, unsigned src, unsigned dst)
159
{
160
	if ((src & 0x30) == 0) return dst & 0x30;
161
	uint8_t res = 0;
162
	res |= bitLUT[4][op][(src & 0x10) >> 4][(dst & 0x10) >> 4];
163
	res |= bitLUT[5][op][(src & 0x20) >> 5][(dst & 0x20) >> 5];
164
	return res;
165
}
166
[[nodiscard]] static constexpr uint8_t func67(unsigned op, unsigned src, unsigned dst)
167
{
168
	if ((src & 0xC0) == 0) return dst & 0xC0;
169
	uint8_t res = 0;
170
	res |= bitLUT[6][op][(src & 0x40) >> 6][(dst & 0x40) >> 6];
171
	res |= bitLUT[7][op][(src & 0x80) >> 7][(dst & 0x80) >> 7];
172
	return res;
173
}
174
175
[[nodiscard]] static constexpr uint8_t func03(unsigned op, unsigned src, unsigned dst)
176
{
177
	if ((src & 0x0F) == 0) return dst & 0x0F;
178
	uint8_t res = 0;
179
	res |= bitLUT[0][op][(src & 0x01) >> 0][(dst & 0x01) >> 0];
180
	res |= bitLUT[1][op][(src & 0x02) >> 1][(dst & 0x02) >> 1];
181
	res |= bitLUT[2][op][(src & 0x04) >> 2][(dst & 0x04) >> 2];
182
	res |= bitLUT[3][op][(src & 0x08) >> 3][(dst & 0x08) >> 3];
183
	return res;
184
}
185
[[nodiscard]] static constexpr uint8_t func47(unsigned op, unsigned src, unsigned dst)
186
{
187
	if ((src & 0xF0) == 0) return dst & 0xF0;
188
	uint8_t res = 0;
189
	res |= bitLUT[4][op][(src & 0x10) >> 4][(dst & 0x10) >> 4];
190
	res |= bitLUT[5][op][(src & 0x20) >> 5][(dst & 0x20) >> 5];
191
	res |= bitLUT[6][op][(src & 0x40) >> 6][(dst & 0x40) >> 6];
192
	res |= bitLUT[7][op][(src & 0x80) >> 7][(dst & 0x80) >> 7];
193
	return res;
194
}
195
196
[[nodiscard]] static constexpr uint8_t func07(unsigned op, unsigned src, unsigned dst)
197
{
198
	// if (src == 0) return dst;  // handled in fillTable8
199
	uint8_t res = 0;
200
	res |= bitLUT[0][op][(src & 0x01) >> 0][(dst & 0x01) >> 0];
201
	res |= bitLUT[1][op][(src & 0x02) >> 1][(dst & 0x02) >> 1];
202
	res |= bitLUT[2][op][(src & 0x04) >> 2][(dst & 0x04) >> 2];
203
	res |= bitLUT[3][op][(src & 0x08) >> 3][(dst & 0x08) >> 3];
204
	res |= bitLUT[4][op][(src & 0x10) >> 4][(dst & 0x10) >> 4];
205
	res |= bitLUT[5][op][(src & 0x20) >> 5][(dst & 0x20) >> 5];
206
	res |= bitLUT[6][op][(src & 0x40) >> 6][(dst & 0x40) >> 6];
207
	res |= bitLUT[7][op][(src & 0x80) >> 7][(dst & 0x80) >> 7];
208
	return res;
209
}
210
211
static constexpr void fillTableNoT(unsigned op, std::span<uint8_t, 256 * 256> table)
212
{
213
	for (auto dst : xrange(256)) {
214
		for (auto src : xrange(256)) {
215
			table[dst * 256 + src] = func07(op, src, dst);
216
		}
217
	}
218
}
219
220
static constexpr void fillTable2(unsigned op, std::span<uint8_t, 256 * 256> table)
221
{
222
	for (auto dst : xrange(256)) {
223
		for (auto src : xrange(256)) {
224
			uint8_t res = 0;
225
			res |= func01(op, src, dst);
226
			res |= func23(op, src, dst);
227
			res |= func45(op, src, dst);
228
			res |= func67(op, src, dst);
229
			table[dst * 256 + src] = res;
230
		}
231
	}
232
}
233
234
static constexpr void fillTable4(unsigned op, std::span<uint8_t, 256 * 256> table)
235
{
236
	for (auto dst : xrange(256)) {
237
		for (auto src : xrange(256)) {
238
			uint8_t res = 0;
239
			res |= func03(op, src, dst);
240
			res |= func47(op, src, dst);
241
			table[dst * 256 + src] = res;
242
		}
243
	}
244
}
245
246
static constexpr void fillTable8(unsigned op, std::span<uint8_t, 256 * 256> table)
247
{
248
	for (auto dst : xrange(256)) {
249
		{ // src == 0
250
			table[dst * 256 + 0  ] = narrow_cast<uint8_t>(dst);
251
		}
252
		for (auto src : xrange(1, 256)) { // src != 0
253
			table[dst * 256 + src] = func07(op, src, dst);
254
		}
255
	}
256
}
257
258
[[nodiscard]] static std::span<const uint8_t, 256 * 256> getLogOpImpl(Log mode, unsigned op)
259
{
260
	op &= 0x0f;
261
	auto& lut = logOpLUT[mode][op];
262
	if (!lut.data()) {
263
		lut.resize(256 * 256);
264
		std::span<uint8_t, 256 * 256> s{lut};
265
		switch (mode) {
266
		using enum Log;
267
		case NO_T:
268
			fillTableNoT(op, s);
269
			break;
270
		case BPP2:
271
			fillTable2(op, s);
272
			break;
273
		case BPP4:
274
			fillTable4(op, s);
275
			break;
276
		case BPP8:
277
			fillTable8(op, s);
278
			break;
279
		default:
280
			UNREACHABLE;
281
		}
282
	}
283
	return std::span<uint8_t, 256 * 256>{lut};
284
}
285
286
287
static constexpr uint8_t DIY = 0x08;
288
static constexpr uint8_t DIX = 0x04;
289
static constexpr uint8_t NEQ = 0x02;
290
static constexpr uint8_t MAJ = 0x01;
291
292
// P1 --------------------------------------------------------------
293
inline unsigned V9990CmdEngine::V9990P1::getPitch(unsigned width)
294
{
295
	return width / 2;
296
}
297
298
inline unsigned V9990CmdEngine::V9990P1::addressOf(
299
	unsigned x, unsigned y, unsigned pitch)
300
{
301
	//return V9990VRAM::transformP1(((x / 2) & (pitch - 1)) + y * pitch) & 0x7FFFF;
302
	// TODO figure out exactly how the coordinate system maps to vram in P1
303
	unsigned addr = V9990VRAM::transformP1(((x / 2) & (pitch - 1)) + y * pitch);
304
	return (addr & 0x3FFFF) | ((x & 0x200) << 9);
305
}
306
307
inline uint8_t V9990CmdEngine::V9990P1::point(
308
	const V9990VRAM& vram, unsigned x, unsigned y, unsigned pitch)
309
{
310
	return vram.readVRAMDirect(addressOf(x, y, pitch));
311
}
312
313
inline uint8_t V9990CmdEngine::V9990P1::shift(
314
	uint8_t value, unsigned fromX, unsigned toX)
315
{
316
	int shift = 4 * (narrow<int>(toX & 1) - narrow<int>(fromX & 1));
317
	return (shift > 0) ? uint8_t(value >> shift) : uint8_t(value << -shift);
318
}
319
320
inline uint8_t V9990CmdEngine::V9990P1::shiftMask(unsigned x)
321
{
322
	return (x & 1) ? 0x0F : 0xF0;
323
}
324
325
inline std::span<const uint8_t, 256 * 256> V9990CmdEngine::V9990P1::getLogOpLUT(uint8_t op)
326
{
327
	return getLogOpImpl((op & 0x10) ? Log::BPP4 : Log::NO_T, op);
328
}
329
330
inline uint8_t V9990CmdEngine::V9990P1::logOp(
331
	std::span<const uint8_t, 256 * 256> lut, uint8_t src, uint8_t dst)
332
{
333
	return lut[256 * dst + src];
334
}
335
336
inline void V9990CmdEngine::V9990P1::pset(
337
	V9990VRAM& vram, unsigned x, unsigned y, unsigned pitch,
338
	uint8_t srcColor, uint16_t mask, std::span<const uint8_t, 256 * 256> lut, uint8_t /*op*/)
339
{
340
	auto addr = addressOf(x, y, pitch);
341
	auto dstColor = vram.readVRAMDirect(addr);
342
	auto newColor = logOp(lut, srcColor, dstColor);
343
	auto mask1 = narrow_cast<uint8_t>((addr & 0x40000) ? (mask >> 8) : (mask & 0xFF));
344
	uint8_t mask2 = mask1 & shiftMask(x);
345
	uint8_t result = (dstColor & ~mask2) | (newColor & mask2);
346
	vram.writeVRAMDirect(addr, result);
347
}
348
inline void V9990CmdEngine::V9990P1::psetColor(
349
	V9990VRAM& vram, unsigned x, unsigned y, unsigned pitch,
350
	uint16_t color, uint16_t mask, std::span<const uint8_t, 256 * 256> lut, uint8_t /*op*/)
351
{
352
	auto addr = addressOf(x, y, pitch);
353
	auto srcColor = narrow_cast<uint8_t>((addr & 0x40000) ? (color >> 8) : (color & 0xFF));
354
	auto dstColor = vram.readVRAMDirect(addr);
355
	auto newColor = logOp(lut, srcColor, dstColor);
356
	auto mask1 = narrow_cast<uint8_t>((addr & 0x40000) ? (mask >> 8) : (mask & 0xFF));
357
	uint8_t mask2 = mask1 & (0xF0 >> (4 * (x & 1)));
358
	uint8_t result = (dstColor & ~mask2) | (newColor & mask2);
359
	vram.writeVRAMDirect(addr, result);
360
}
361
362
// P2 --------------------------------------------------------------
363
inline unsigned V9990CmdEngine::V9990P2::getPitch(unsigned width)
364
{
365
	return width / 2;
366
}
367
368
inline unsigned V9990CmdEngine::V9990P2::addressOf(
369
	unsigned x, unsigned y, unsigned pitch)
370
{
371
	// TODO check
372
	return V9990VRAM::transformP2(((x / 2) & (pitch - 1)) + y * pitch) & 0x7FFFF;
373
}
374
375
inline uint8_t V9990CmdEngine::V9990P2::point(
376
	const V9990VRAM& vram, unsigned x, unsigned y, unsigned pitch)
377
{
378
	return vram.readVRAMDirect(addressOf(x, y, pitch));
379
}
380
381
inline uint8_t V9990CmdEngine::V9990P2::shift(
382
	uint8_t value, unsigned fromX, unsigned toX)
383
{
384
	int shift = 4 * (narrow<int>(toX & 1) - narrow<int>(fromX & 1));
385
	return (shift > 0) ? uint8_t(value >> shift) : uint8_t(value << -shift);
386
}
387
388
inline uint8_t V9990CmdEngine::V9990P2::shiftMask(unsigned x)
389
{
390
	return (x & 1) ? 0x0F : 0xF0;
391
}
392
393
inline std::span<const uint8_t, 256 * 256> V9990CmdEngine::V9990P2::getLogOpLUT(uint8_t op)
394
{
395
	return getLogOpImpl((op & 0x10) ? Log::BPP4 : Log::NO_T, op);
396
}
397
398
inline uint8_t V9990CmdEngine::V9990P2::logOp(
399
	std::span<const uint8_t, 256 * 256> lut, uint8_t src, uint8_t dst)
400
{
401
	return lut[256 * dst + src];
402
}
403
404
inline void V9990CmdEngine::V9990P2::pset(
405
	V9990VRAM& vram, unsigned x, unsigned y, unsigned pitch,
406
	uint8_t srcColor, uint16_t mask, std::span<const uint8_t, 256 * 256> lut, uint8_t /*op*/)
407
{
408
	auto addr = addressOf(x, y, pitch);
409
	auto dstColor = vram.readVRAMDirect(addr);
410
	auto newColor = logOp(lut, srcColor, dstColor);
411
	auto mask1 = narrow_cast<uint8_t>((addr & 0x40000) ? (mask >> 8) : (mask & 0xFF));
412
	uint8_t mask2 = mask1 & shiftMask(x);
413
	uint8_t result = (dstColor & ~mask2) | (newColor & mask2);
414
	vram.writeVRAMDirect(addr, result);
415
}
416
417
inline void V9990CmdEngine::V9990P2::psetColor(
418
	V9990VRAM& vram, unsigned x, unsigned y, unsigned pitch,
419
	uint16_t color, uint16_t mask, std::span<const uint8_t, 256 * 256> lut, uint8_t /*op*/)
420
{
421
	auto addr = addressOf(x, y, pitch);
422
	auto srcColor = narrow_cast<uint8_t>((addr & 0x40000) ? (color >> 8) : (color & 0xFF));
423
	auto dstColor = vram.readVRAMDirect(addr);
424
	auto newColor = logOp(lut, srcColor, dstColor);
425
	auto mask1 = narrow_cast<uint8_t>((addr & 0x40000) ? (mask >> 8) : (mask & 0xFF));
426
	uint8_t mask2 = mask1 & (0xF0 >> (4 * (x & 1)));
427
	uint8_t result = (dstColor & ~mask2) | (newColor & mask2);
428
	vram.writeVRAMDirect(addr, result);
429
}
430
431
// 2 bpp --------------------------------------------------------------
432
inline unsigned V9990CmdEngine::V9990Bpp2::getPitch(unsigned width)
433
{
434
	return width / 4;
435
}
436
437
inline unsigned V9990CmdEngine::V9990Bpp2::addressOf(
438
	unsigned x, unsigned y, unsigned pitch)
439
{
440
	return V9990VRAM::transformBx(((x / 4) & (pitch - 1)) + y * pitch) & 0x7FFFF;
441
}
442
443
inline uint8_t V9990CmdEngine::V9990Bpp2::point(
444
	const V9990VRAM& vram, unsigned x, unsigned y, unsigned pitch)
445
{
446
	return vram.readVRAMDirect(addressOf(x, y, pitch));
447
}
448
449
inline uint8_t V9990CmdEngine::V9990Bpp2::shift(
450
	uint8_t value, unsigned fromX, unsigned toX)
451
{
452
	int shift = 2 * (narrow<int>(toX & 3) - narrow<int>(fromX & 3));
453
	return (shift > 0) ? uint8_t(value >> shift) : uint8_t(value << -shift);
454
}
455
456
inline uint8_t V9990CmdEngine::V9990Bpp2::shiftMask(unsigned x)
457
{
458
	return 0xC0 >> (2 * (x & 3));
459
}
460
461
inline std::span<const uint8_t, 256 * 256> V9990CmdEngine::V9990Bpp2::getLogOpLUT(uint8_t op)
462
{
463
	return getLogOpImpl((op & 0x10) ? Log::BPP2 : Log::NO_T, op);
464
}
465
466
inline uint8_t V9990CmdEngine::V9990Bpp2::logOp(
467
	std::span<const uint8_t, 256 * 256> lut, uint8_t src, uint8_t dst)
468
{
469
	return lut[256 * dst + src];
470
}
471
472
inline void V9990CmdEngine::V9990Bpp2::pset(
473
	V9990VRAM& vram, unsigned x, unsigned y, unsigned pitch,
474
	uint8_t srcColor, uint16_t mask, std::span<const uint8_t, 256 * 256> lut, uint8_t /*op*/)
475
{
476
	auto addr = addressOf(x, y, pitch);
477
	auto dstColor = vram.readVRAMDirect(addr);
478
	auto newColor = logOp(lut, srcColor, dstColor);
479
	auto mask1 = narrow_cast<uint8_t>((addr & 0x40000) ? (mask >> 8) : (mask & 0xFF));
480
	uint8_t mask2 = mask1 & shiftMask(x);
481
	uint8_t result = (dstColor & ~mask2) | (newColor & mask2);
482
	vram.writeVRAMDirect(addr, result);
483
}
484
485
inline void V9990CmdEngine::V9990Bpp2::psetColor(
486
	V9990VRAM& vram, unsigned x, unsigned y, unsigned pitch,
487
	uint16_t color, uint16_t mask, std::span<const uint8_t, 256 * 256> lut, uint8_t /*op*/)
488
{
489
	auto addr = addressOf(x, y, pitch);
490
	auto srcColor = narrow_cast<uint8_t>((addr & 0x40000) ? (color >> 8) : (color & 0xFF));
491
	auto dstColor = vram.readVRAMDirect(addr);
492
	auto newColor = logOp(lut, srcColor, dstColor);
493
	auto mask1 = narrow_cast<uint8_t>((addr & 0x40000) ? (mask >> 8) : (mask & 0xFF));
494
	uint8_t mask2 = mask1 & (0xC0 >> (2 * (x & 3)));
495
	uint8_t result = (dstColor & ~mask2) | (newColor & mask2);
496
	vram.writeVRAMDirect(addr, result);
497
}
498
499
// 4 bpp --------------------------------------------------------------
500
inline unsigned V9990CmdEngine::V9990Bpp4::getPitch(unsigned width)
501
{
502
	return width / 2;
503
}
504
505
inline unsigned V9990CmdEngine::V9990Bpp4::addressOf(
506
	unsigned x, unsigned y, unsigned pitch)
507
{
508
	return V9990VRAM::transformBx(((x / 2) & (pitch - 1)) + y * pitch) & 0x7FFFF;
509
}
510
511
inline uint8_t V9990CmdEngine::V9990Bpp4::point(
512
	const V9990VRAM& vram, unsigned x, unsigned y, unsigned pitch)
513
{
514
	return vram.readVRAMDirect(addressOf(x, y, pitch));
515
}
516
517
inline uint8_t V9990CmdEngine::V9990Bpp4::shift(
518
	uint8_t value, unsigned fromX, unsigned toX)
519
{
520
	int shift = 4 * (narrow<int>(toX & 1) - narrow<int>(fromX & 1));
521
	return (shift > 0) ? uint8_t(value >> shift) : uint8_t(value << -shift);
522
}
523
524
inline uint8_t V9990CmdEngine::V9990Bpp4::shiftMask(unsigned x)
525
{
526
	return (x & 1) ? 0x0F : 0xF0;
527
}
528
529
inline std::span<const uint8_t, 256 * 256> V9990CmdEngine::V9990Bpp4::getLogOpLUT(uint8_t op)
530
{
531
	return getLogOpImpl((op & 0x10) ? Log::BPP4 : Log::NO_T, op);
532
}
533
534
inline uint8_t V9990CmdEngine::V9990Bpp4::logOp(
535
	std::span<const uint8_t, 256 * 256> lut, uint8_t src, uint8_t dst)
536
{
537
	return lut[256 * dst + src];
538
}
539
540
inline void V9990CmdEngine::V9990Bpp4::pset(
541
	V9990VRAM& vram, unsigned x, unsigned y, unsigned pitch,
542
	uint8_t srcColor, uint16_t mask, std::span<const uint8_t, 256 * 256> lut, uint8_t /*op*/)
543
{
544
	auto addr = addressOf(x, y, pitch);
545
	auto dstColor = vram.readVRAMDirect(addr);
546
	auto newColor = logOp(lut, srcColor, dstColor);
547
	auto mask1 = narrow_cast<uint8_t>((addr & 0x40000) ? (mask >> 8) : (mask & 0xFF));
548
	uint8_t mask2 = mask1 & shiftMask(x);
549
	uint8_t result = (dstColor & ~mask2) | (newColor & mask2);
550
	vram.writeVRAMDirect(addr, result);
551
}
552
553
inline void V9990CmdEngine::V9990Bpp4::psetColor(
554
	V9990VRAM& vram, unsigned x, unsigned y, unsigned pitch,
555
	uint16_t color, uint16_t mask, std::span<const uint8_t, 256 * 256> lut, uint8_t /*op*/)
556
{
557
	auto addr = addressOf(x, y, pitch);
558
	auto srcColor = narrow_cast<uint8_t>((addr & 0x40000) ? (color >> 8) : (color & 0xFF));
559
	auto dstColor = vram.readVRAMDirect(addr);
560
	auto newColor = logOp(lut, srcColor, dstColor);
561
	auto mask1 = narrow_cast<uint8_t>((addr & 0x40000) ? (mask >> 8) : (mask & 0xFF));
562
	uint8_t mask2 = mask1 & (0xF0 >> (4 * (x & 1)));
563
	uint8_t result = (dstColor & ~mask2) | (newColor & mask2);
564
	vram.writeVRAMDirect(addr, result);
565
}
566
567
// 8 bpp --------------------------------------------------------------
568
inline unsigned V9990CmdEngine::V9990Bpp8::getPitch(unsigned width)
569
{
570
	return width;
571
}
572
573
inline unsigned V9990CmdEngine::V9990Bpp8::addressOf(
574
	unsigned x, unsigned y, unsigned pitch)
575
{
576
	return V9990VRAM::transformBx((x & (pitch - 1)) + y * pitch) & 0x7FFFF;
577
}
578
579
inline uint8_t V9990CmdEngine::V9990Bpp8::point(
580
	const V9990VRAM& vram, unsigned x, unsigned y, unsigned pitch)
581
{
582
	return vram.readVRAMDirect(addressOf(x, y, pitch));
583
}
584
585
inline uint8_t V9990CmdEngine::V9990Bpp8::shift(
586
	uint8_t value, unsigned /*fromX*/, unsigned /*toX*/)
587
{
588
	return value;
589
}
590
591
inline uint8_t V9990CmdEngine::V9990Bpp8::shiftMask(unsigned /*x*/)
592
{
593
	return 0xFF;
594
}
595
596
inline std::span<const uint8_t, 256 * 256> V9990CmdEngine::V9990Bpp8::getLogOpLUT(uint8_t op)
597
{
598
	return getLogOpImpl((op & 0x10) ? Log::BPP8 : Log::NO_T, op);
599
}
600
601
inline uint8_t V9990CmdEngine::V9990Bpp8::logOp(
602
	std::span<const uint8_t, 256 * 256> lut, uint8_t src, uint8_t dst)
603
{
604
	return lut[256 * dst + src];
605
}
606
607
inline void V9990CmdEngine::V9990Bpp8::pset(
608
	V9990VRAM& vram, unsigned x, unsigned y, unsigned pitch,
609
	uint8_t srcColor, uint16_t mask, std::span<const uint8_t, 256 * 256> lut, uint8_t /*op*/)
610
{
611
	auto addr = addressOf(x, y, pitch);
612
	auto dstColor = vram.readVRAMDirect(addr);
613
	auto newColor = logOp(lut, srcColor, dstColor);
614
	auto mask1 = narrow_cast<uint8_t>((addr & 0x40000) ? (mask >> 8) : (mask & 0xFF));
615
	uint8_t result = (dstColor & ~mask1) | (newColor & mask1);
616
	vram.writeVRAMDirect(addr, result);
617
}
618
619
inline void V9990CmdEngine::V9990Bpp8::psetColor(
620
	V9990VRAM& vram, unsigned x, unsigned y, unsigned pitch,
621
	uint16_t color, uint16_t mask, std::span<const uint8_t, 256 * 256> lut, uint8_t /*op*/)
622
{
623
	auto addr = addressOf(x, y, pitch);
624
	auto srcColor = narrow_cast<uint8_t>((addr & 0x40000) ? (color >> 8) : (color & 0xFF));
625
	auto dstColor = vram.readVRAMDirect(addr);
626
	auto newColor = logOp(lut, srcColor, dstColor);
627
	auto mask1 = narrow_cast<uint8_t>((addr & 0x40000) ? (mask >> 8) : (mask & 0xFF));
628
	uint8_t result = (dstColor & ~mask1) | (newColor & mask1);
629
	vram.writeVRAMDirect(addr, result);
630
}
631
632
// 16 bpp -------------------------------------------------------------
633
inline unsigned V9990CmdEngine::V9990Bpp16::getPitch(unsigned width)
634
{
635
	//return width * 2;
636
	return width;
637
}
638
639
inline unsigned V9990CmdEngine::V9990Bpp16::addressOf(
640
	unsigned x, unsigned y, unsigned pitch)
641
{
642
	//return V9990VRAM::transformBx(((x * 2) & (pitch - 1)) + y * pitch) & 0x7FFFF;
643
	return ((x & (pitch - 1)) + y * pitch) & 0x3FFFF;
644
}
645
646
inline uint16_t V9990CmdEngine::V9990Bpp16::point(
647
	const V9990VRAM& vram, unsigned x, unsigned y, unsigned pitch)
648
{
649
	unsigned addr = addressOf(x, y, pitch);
650
	return uint16_t(vram.readVRAMDirect(addr + 0x00000) +
651
	                vram.readVRAMDirect(addr + 0x40000) * 256);
652
}
653
654
inline uint16_t V9990CmdEngine::V9990Bpp16::shift(
655
	uint16_t value, unsigned /*fromX*/, unsigned /*toX*/)
656
{
657
	return value;
658
}
659
660
inline uint16_t V9990CmdEngine::V9990Bpp16::shiftMask(unsigned /*x*/)
661
{
662
	return 0xFFFF;
663
}
664
665
inline std::span<const uint8_t, 256 * 256> V9990CmdEngine::V9990Bpp16::getLogOpLUT(uint8_t op)
666
{
667
	return getLogOpImpl(Log::NO_T, op);
668
}
669
670
inline uint16_t V9990CmdEngine::V9990Bpp16::logOp(
671
	std::span<const uint8_t, 256 * 256> lut, uint16_t src, uint16_t dst, bool transp)
672
{
673
	if (transp && (src == 0)) return dst;
674
	return uint16_t((lut[((dst & 0x00FF) << 8) + ((src & 0x00FF) >> 0)] << 0) +
675
	                (lut[((dst & 0xFF00) << 0) + ((src & 0xFF00) >> 8)] << 8));
676
}
677
678
inline void V9990CmdEngine::V9990Bpp16::pset(
679
	V9990VRAM& vram, unsigned x, unsigned y, unsigned pitch,
680
	uint16_t srcColor, uint16_t mask, std::span<const uint8_t, 256 * 256> lut, uint8_t op)
681
{
682
	auto addr = addressOf(x, y, pitch);
683
	auto dstColor = uint16_t(vram.readVRAMDirect(addr + 0x00000) +
684
	                         vram.readVRAMDirect(addr + 0x40000) * 256);
685
	auto newColor = logOp(lut, srcColor, dstColor, (op & 0x10) != 0);
686
	uint16_t result = (dstColor & ~mask) | (newColor & mask);
687
	vram.writeVRAMDirect(addr + 0x00000, narrow_cast<uint8_t>(result & 0xFF));
688
	vram.writeVRAMDirect(addr + 0x40000, narrow_cast<uint8_t>(result >> 8));
689
}
690
691
inline void V9990CmdEngine::V9990Bpp16::psetColor(
692
	V9990VRAM& vram, unsigned x, unsigned y, unsigned pitch,
693
	uint16_t srcColor, uint16_t mask, std::span<const uint8_t, 256 * 256> lut, uint8_t op)
694
{
695
	auto addr = addressOf(x, y, pitch);
696
	auto dstColor = uint16_t(vram.readVRAMDirect(addr + 0x00000) +
697
	                         vram.readVRAMDirect(addr + 0x40000) * 256);
698
	auto newColor = logOp(lut, srcColor, dstColor, (op & 0x10) != 0);
699
	uint16_t result = (dstColor & ~mask) | (newColor & mask);
700
	vram.writeVRAMDirect(addr + 0x00000, narrow_cast<uint8_t>(result & 0xFF));
701
	vram.writeVRAMDirect(addr + 0x40000, narrow_cast<uint8_t>(result >> 8));
702
}
703
704
// ====================================================================
705
/** Constructor
706
  */
707
V9990CmdEngine::V9990CmdEngine(V9990& vdp_, EmuTime time_,
708
                               RenderSettings& settings_)
709
	: settings(settings_), vdp(vdp_), vram(vdp.getVRAM()), engineTime(time_)
710
{
711
	cmdTraceSetting = vdp.getMotherBoard().getSharedStuff<BooleanSetting>(
712
		"v9990cmdtrace",
713
		vdp.getCommandController(), "v9990cmdtrace",
714
		"V9990 command tracing on/off", false);
715
716
	auto& cmdTimingSetting = settings.getCmdTimingSetting();
717
	update(cmdTimingSetting);
718
	cmdTimingSetting.attach(*this);
719
720
	reset(time_);
721
722
	// avoid UMR on savestate
723
	srcAddress = dstAddress = nbBytes = 0;
724
	ASX = ADX = ANX = ANY = 0;
725
	SX = SY = DX = DY = NX = NY = 0;
726
	WM = fgCol = bgCol = 0;
727
	ARG = LOG = 0;
728
	data = bitsLeft = partial = 0;
729
}
730
731
V9990CmdEngine::~V9990CmdEngine()
732
{
733
	settings.getCmdTimingSetting().detach(*this);
734
}
735
736
void V9990CmdEngine::reset(EmuTime /*time*/)
737
{
738
	CMD = 0;
739
	status = 0;
740
	borderX = 0;
741
	endAfterRead = false;
742
}
743
744
void V9990CmdEngine::setCmdReg(uint8_t reg, uint8_t value, EmuTime time)
745
{
746
	sync(time);
747
	switch(reg - 32) {
748
	case  0: // SX low
749
		SX = uint16_t((SX & 0x0700) | ((value & 0xFF) << 0));
750
		break;
751
	case  1: // SX high
752
		SX = uint16_t((SX & 0x00FF) | ((value & 0x07) << 8));
753
		break;
754
	case  2: // SY low
755
		SY = uint16_t((SY & 0x0F00) | ((value & 0xFF) << 0));
756
		break;
757
	case  3: // SY high
758
		SY = uint16_t((SY & 0x00FF) | ((value & 0x0F) << 8));
759
		break;
760
	case  4: // DX low
761
		DX = uint16_t((DX & 0x0700) | ((value & 0xFF) << 0));
762
		break;
763
	case  5: // DX high
764
		DX = uint16_t((DX & 0x00FF) | ((value & 0x07) << 8));
765
		break;
766
	case  6: // DY low
767
		DY = uint16_t((DY & 0x0F00) | ((value & 0xFF) << 0));
768
		break;
769
	case  7: // DY high
770
		DY = uint16_t((DY & 0x00FF) | ((value & 0x0F) << 8));
771
		break;
772
	case  8: // NX low
773
		NX = uint16_t((NX & 0x0F00) | ((value & 0xFF) << 0));
774
		break;
775
	case  9: // NX high
776
		NX = uint16_t((NX & 0x00FF) | ((value & 0x0F) << 8));
777
		break;
778
	case 10: // NY low
779
		NY = uint16_t((NY & 0x0F00) | ((value & 0xFF) << 0));
780
		break;
781
	case 11: // NY high
782
		NY = uint16_t((NY & 0x00FF) | ((value & 0x0F) << 8));
783
		break;
784
	case 12: // ARG
785
		ARG = value & 0x0F;
786
		break;
787
	case 13: // LOGOP
788
		LOG = value & 0x1F;
789
		break;
790
	case 14: // write mask low
791
		WM = uint16_t((WM & 0xFF00) | (value << 0));
792
		break;
793
	case 15: // write mask high
794
		WM = uint16_t((WM & 0x00FF) | (value << 8));
795
		break;
796
	case 16: // Font color - FG low
797
		fgCol = uint16_t((fgCol & 0xFF00) | (value << 0));
798
		break;
799
	case 17: // Font color - FG high
800
		fgCol = uint16_t((fgCol & 0x00FF) | (value << 8));
801
		break;
802
	case 18: // Font color - BG low
803
		bgCol = uint16_t((bgCol & 0xFF00) | (value << 0));
804
		break;
805
	case 19: // Font color - BG high
806
		bgCol = uint16_t((bgCol & 0x00FF) | (value << 8));
807
		break;
808
	case 20: { // CMD
809
		CMD = value;
810
		if (cmdTraceSetting->getBoolean()) {
811
			reportV9990Command();
812
		}
813
		status |= CE;
814
815
		// TODO do this when mode changes instead of at the start of a command.
816
		setCommandMode();
817
818
		//currentCommand->start(time);
819
		switch (cmdMode | (CMD >> 4)) {
820
			case 0x00: case 0x10: case 0x20: case 0x30: case 0x40: case 0x50:
821
				startSTOP(time); break;
822
823
			case 0x01: case 0x11: case 0x21: case 0x31: case 0x41:
824
				startLMMC  (time); break;
825
			case 0x51:
826
				startLMMC16(time); break;
827
828
			case 0x02: case 0x12: case 0x22: case 0x32: case 0x42: case 0x52:
829
				startLMMV(time); break;
830
831
			case 0x03: case 0x13: case 0x23: case 0x33: case 0x43:
832
				startLMCM  (time); break;
833
			case 0x53:
834
				startLMCM16(time); break;
835
836
			case 0x04: case 0x14: case 0x24: case 0x34: case 0x44: case 0x54:
837
				startLMMM(time); break;
838
839
			case 0x05: case 0x15: case 0x25: case 0x35: case 0x45: case 0x55:
840
				startCMMC(time); break;
841
842
			case 0x06: case 0x16: case 0x26: case 0x36: case 0x46: case 0x56:
843
				startCMMK(time); break;
844
845
			case 0x07: case 0x17: case 0x27: case 0x37: case 0x47: case 0x57:
846
				startCMMM(time); break;
847
848
			case 0x08: case 0x18: case 0x28: case 0x38: case 0x48: case 0x58:
849
				startBMXL(time); break;
850
851
			case 0x09: case 0x19: case 0x29: case 0x39: case 0x49: case 0x59:
852
				startBMLX(time); break;
853
854
			case 0x0A: case 0x1A: case 0x2A: case 0x3A: case 0x4A:
855
				startBMLL  (time); break;
856
			case 0x5A:
857
				startBMLL16(time); break;
858
859
			case 0x0B: case 0x1B: case 0x2B: case 0x3B: case 0x4B: case 0x5B:
860
				startLINE(time); break;
861
862
			case 0x0C: case 0x1C: case 0x2C: case 0x3C: case 0x4C: case 0x5C:
863
				startSRCH(time); break;
864
865
			case 0x0D: startPOINT<V9990P1   >(time); break;
866
			case 0x1D: startPOINT<V9990P2   >(time); break;
867
			case 0x2D: startPOINT<V9990Bpp2 >(time); break;
868
			case 0x3D: startPOINT<V9990Bpp4 >(time); break;
869
			case 0x4D: startPOINT<V9990Bpp8 >(time); break;
870
			case 0x5D: startPOINT<V9990Bpp16>(time); break;
871
872
			case 0x0E: startPSET<V9990P1   >(time); break;
873
			case 0x1E: startPSET<V9990P2   >(time); break;
874
			case 0x2E: startPSET<V9990Bpp2 >(time); break;
875
			case 0x3E: startPSET<V9990Bpp4 >(time); break;
876
			case 0x4E: startPSET<V9990Bpp8 >(time); break;
877
			case 0x5E: startPSET<V9990Bpp16>(time); break;
878
879
			case 0x0F: case 0x1F: case 0x2F: case 0x3F: case 0x4F: case 0x5F:
880
				startADVN(time); break;
881
882
			default: UNREACHABLE;
883
		}
884
885
		// Finish command now if instantaneous command timing is active.
886
		if (brokenTiming) {
887
			sync(time);
888
		}
889
		break;
890
	}
891
	}
892
}
893
894
void V9990CmdEngine::setCommandMode()
895
{
896
	auto dispMode = vdp.getDisplayMode();
897
	if (dispMode == V9990DisplayMode::P1) {
898
		cmdMode = 0 << 4; // P1;
899
	} else if (dispMode == V9990DisplayMode::P2) {
900
		cmdMode = 1 << 4; // P2;
901
	} else { // Bx
902
		switch (vdp.getColorMode()) {
903
			using enum V9990ColorMode;
904
			default:
905
				UNREACHABLE; break;
906
			case BP2:
907
				cmdMode = 2 << 4; // BPP2;
908
				break;
909
			case PP:
910
			case BP4:
911
				cmdMode = 3 << 4; // BPP4;
912
				break;
913
			case BYUV:
914
			case BYUVP:
915
			case BYJK:
916
			case BYJKP:
917
			case BD8:
918
			case BP6:
919
				cmdMode = 4 << 4; // BPP8;
920
				break;
921
			case BD16:
922
				cmdMode = 5 << 4; // BPP16;
923
				break;
924
		}
925
	}
926
}
927
928
void V9990CmdEngine::reportV9990Command() const
929
{
930
	static constexpr std::array<std::string_view, 16> COMMANDS = {
931
		"STOP", "LMMC", "LMMV", "LMCM",
932
		"LMMM", "CMMC", "CMMK", "CMMM",
933
		"BMXL", "BMLX", "BMLL", "LINE",
934
		"SRCH", "POINT","PSET", "ADVN"
935
	};
936
	std::cerr << "V9990Cmd " << COMMANDS[CMD >> 4]
937
	          << " SX="  << std::dec << SX
938
	          << " SY="  << std::dec << SY
939
	          << " DX="  << std::dec << DX
940
	          << " DY="  << std::dec << DY
941
	          << " NX="  << std::dec << NX
942
	          << " NY="  << std::dec << NY
943
	          << " ARG=" << std::hex << int(ARG)
944
	          << " LOG=" << std::hex << int(LOG)
945
	          << " WM="  << std::hex << WM
946
	          << " FC="  << std::hex << fgCol
947
	          << " BC="  << std::hex << bgCol
948
	          << " CMD=" << std::hex << int(CMD)
949
	          << '\n';
950
}
951
952
void V9990CmdEngine::update(const Setting& setting) noexcept
953
{
954
	brokenTiming = checked_cast<const EnumSetting<bool>&>(setting).getEnum();
955
}
956
957
// STOP
958
void V9990CmdEngine::startSTOP(EmuTime time)
959
{
960
	cmdReady(time);
961
}
962
963
void V9990CmdEngine::executeSTOP(EmuTime /*limit*/)
964
{
965
	UNREACHABLE;
966
}
967
968
// LMMC
969
void V9990CmdEngine::startLMMC(EmuTime /*time*/)
970
{
971
	ANX = getWrappedNX();
972
	ANY = getWrappedNY();
973
	status |= TR;
974
}
975
void V9990CmdEngine::startLMMC16(EmuTime time)
976
{
977
	bitsLeft = 1;
978
	startLMMC(time);
979
}
980
981
template<>
982
void V9990CmdEngine::executeLMMC<V9990CmdEngine::V9990Bpp16>(EmuTime limit)
983
{
984
	if (!(status & TR)) {
985
		status |= TR;
986
		if (bitsLeft) {
987
			bitsLeft = 0;
988
			partial = data;
989
		} else {
990
			bitsLeft = 1;
991
			auto value = uint16_t((data << 8) | partial);
992
			unsigned pitch = V9990Bpp16::getPitch(vdp.getImageWidth());
993
			auto lut = V9990Bpp16::getLogOpLUT(LOG);
994
			V9990Bpp16::pset(vram, DX, DY, pitch, value, WM, lut, LOG);
995
			uint16_t dx = (ARG & DIX) ? uint16_t(-1) : 1;
996
			DX += dx;
997
			if (!--ANX) {
998
				uint16_t dy = (ARG & DIY) ? uint16_t(-1) : 1;
999
				DX -= uint16_t(NX * dx);
1000
				DY += dy;
1001
				if (!--ANY) {
1002
					cmdReady(limit);
1003
				} else {
1004
					ANX = getWrappedNX();
1005
				}
1006
			}
1007
		}
1008
	}
1009
}
1010
1011
template<typename Mode>
1012
void V9990CmdEngine::executeLMMC(EmuTime limit)
1013
{
1014
	if (!(status & TR)) {
1015
		status |= TR;
1016
		unsigned pitch = Mode::getPitch(vdp.getImageWidth());
1017
		auto lut = Mode::getLogOpLUT(LOG);
1018
		for (int i = 0; (ANY > 0) && (i < Mode::PIXELS_PER_BYTE); ++i) {
1019
			uint8_t d = Mode::shift(data, i, DX);
1020
			Mode::pset(vram, DX, DY, pitch, d, WM, lut, LOG);
1021
1022
			uint16_t dx = (ARG & DIX) ? uint16_t(-1) : 1;
1023
			DX += dx;
1024
			if (!--ANX) {
1025
				uint16_t dy = (ARG & DIY) ? uint16_t(-1) : 1;
1026
				DX -= uint16_t(NX * dx);
1027
				DY += dy;
1028
				if (!--ANY) {
1029
					cmdReady(limit);
1030
				} else {
1031
					ANX = NX;
1032
				}
1033
			}
1034
		}
1035
	}
1036
}
1037
1038
// LMMV
1039
void V9990CmdEngine::startLMMV(EmuTime time)
1040
{
1041
	engineTime = time;
1042
	ANX = getWrappedNX();
1043
	ANY = getWrappedNY();
1044
}
1045
1046
template<typename Mode>
1047
void V9990CmdEngine::executeLMMV(EmuTime limit)
1048
{
1049
	// TODO can be optimized a lot
1050
1051
	auto delta = getTiming(*this, LMMV_TIMING);
1052
	unsigned pitch = Mode::getPitch(vdp.getImageWidth());
1053
	uint16_t dx = (ARG & DIX) ? uint16_t(-1) : 1;
1054
	uint16_t dy = (ARG & DIY) ? uint16_t(-1) : 1;
1055
	auto lut = Mode::getLogOpLUT(LOG);
1056
	while (engineTime < limit) {
1057
		engineTime += delta;
1058
		Mode::psetColor(vram, DX, DY, pitch, fgCol, WM, lut, LOG);
1059
1060
		DX += dx;
1061
		if (!--ANX) {
1062
			DX -= uint16_t(NX * dx);
1063
			DY += dy;
1064
			if (!--ANY) {
1065
				cmdReady(engineTime);
1066
				return;
1067
			} else {
1068
				ANX = getWrappedNX();
1069
			}
1070
		}
1071
	}
1072
}
1073
1074
// LMCM
1075
void V9990CmdEngine::startLMCM(EmuTime /*time*/)
1076
{
1077
	ANX = getWrappedNX();
1078
	ANY = getWrappedNY();
1079
	status &= ~TR;
1080
	endAfterRead = false;
1081
}
1082
void V9990CmdEngine::startLMCM16(EmuTime time)
1083
{
1084
	bitsLeft = 0;
1085
	startLMCM(time);
1086
}
1087
1088
template<typename Mode>
1089
void V9990CmdEngine::executeLMCM(EmuTime /*limit*/)
1090
{
1091
	if (!(status & TR)) {
1092
		status |= TR;
1093
		if ((Mode::BITS_PER_PIXEL == 16) && bitsLeft) {
1094
			bitsLeft = 0;
1095
			data = partial;
1096
			return;
1097
		}
1098
		unsigned pitch = Mode::getPitch(vdp.getImageWidth());
1099
		using Type = typename Mode::Type;
1100
		Type d = 0;
1101
		for (int i = 0; (ANY > 0) && (i < Mode::PIXELS_PER_BYTE); ++i) {
1102
			auto src = Mode::point(vram, SX, SY, pitch);
1103
			d |= Type(Mode::shift(src, SX, i) & Mode::shiftMask(i));
1104
1105
			uint16_t dx = (ARG & DIX) ? uint16_t(-1) : 1;
1106
			SX += dx;
1107
			if (!--ANX) {
1108
				uint16_t dy = (ARG & DIY) ? uint16_t(-1) : 1;
1109
				SX -= uint16_t(NX * dx);
1110
				SY += dy;
1111
				if (!--ANY) {
1112
					endAfterRead = true;
1113
				} else {
1114
					ANX = getWrappedNX();
1115
				}
1116
			}
1117
		}
1118
		if constexpr (Mode::BITS_PER_PIXEL == 16) {
1119
			unsigned tmp = d; // workaround for VC++ warning C4333
1120
			                  // (in case Mode::Type == uint8_t and
1121
			                  //          Mode::BITS_PER_PIXEL == 8)
1122
			data = narrow_cast<uint8_t>(tmp & 0xff);
1123
			partial = narrow_cast<uint8_t>(tmp >> 8);
1124
			bitsLeft = 1;
1125
		} else {
1126
			data = uint8_t(d);
1127
		}
1128
	}
1129
}
1130
1131
// LMMM
1132
void V9990CmdEngine::startLMMM(EmuTime time)
1133
{
1134
	engineTime = time;
1135
	ANX = getWrappedNX();
1136
	ANY = getWrappedNY();
1137
}
1138
1139
template<typename Mode>
1140
void V9990CmdEngine::executeLMMM(EmuTime limit)
1141
{
1142
	// TODO can be optimized a lot
1143
1144
	auto delta = getTiming(*this, LMMM_TIMING);
1145
	unsigned pitch = Mode::getPitch(vdp.getImageWidth());
1146
	uint16_t dx = (ARG & DIX) ? uint16_t(-1) : 1;
1147
	uint16_t dy = (ARG & DIY) ? uint16_t(-1) : 1;
1148
	auto lut = Mode::getLogOpLUT(LOG);
1149
	while (engineTime < limit) {
1150
		engineTime += delta;
1151
		auto src = Mode::point(vram, SX, SY, pitch);
1152
		src = Mode::shift(src, SX, DX);
1153
		Mode::pset(vram, DX, DY, pitch, src, WM, lut, LOG);
1154
1155
		DX += dx;
1156
		SX += dx;
1157
		if (!--ANX) {
1158
			DX -= uint16_t(NX * dx);
1159
			SX -= uint16_t(NX * dx);
1160
			DY += dy;
1161
			SY += dy;
1162
			if (!--ANY) {
1163
				cmdReady(engineTime);
1164
				return;
1165
			} else {
1166
				ANX = getWrappedNX();
1167
			}
1168
		}
1169
	}
1170
}
1171
1172
// CMMC
1173
void V9990CmdEngine::startCMMC(EmuTime /*time*/)
1174
{
1175
	ANX = getWrappedNX();
1176
	ANY = getWrappedNY();
1177
	status |= TR;
1178
}
1179
1180
template<typename Mode>
1181
void V9990CmdEngine::executeCMMC(EmuTime limit)
1182
{
1183
	if (!(status & TR)) {
1184
		status |= TR;
1185
1186
		unsigned pitch = Mode::getPitch(vdp.getImageWidth());
1187
		uint16_t dx = (ARG & DIX) ? uint16_t(-1) : 1;
1188
		uint16_t dy = (ARG & DIY) ? uint16_t(-1) : 1;
1189
		auto lut = Mode::getLogOpLUT(LOG);
1190
		for (auto i : xrange(8)) {
1191
			(void)i;
1192
			bool bit = (data & 0x80) != 0;
1193
			data <<= 1;
1194
1195
			uint16_t src = bit ? fgCol : bgCol;
1196
			Mode::psetColor(vram, DX, DY, pitch, src, WM, lut, LOG);
1197
1198
			DX += dx;
1199
			if (!--ANX) {
1200
				DX -= uint16_t(NX * dx);
1201
				DY += dy;
1202
				if (!--ANY) {
1203
					cmdReady(limit);
1204
					return;
1205
				} else {
1206
					ANX = getWrappedNX();
1207
				}
1208
			}
1209
		}
1210
	}
1211
}
1212
1213
// CMMK
1214
void V9990CmdEngine::startCMMK(EmuTime time)
1215
{
1216
	std::cout << "V9990: CMMK not yet implemented\n";
1217
	cmdReady(time); // TODO dummy implementation
1218
}
1219
1220
void V9990CmdEngine::executeCMMK(EmuTime /*limit*/)
1221
{
1222
	UNREACHABLE;
1223
}
1224
1225
// CMMM
1226
void V9990CmdEngine::startCMMM(EmuTime time)
1227
{
1228
	engineTime = time;
1229
	srcAddress = (SX & 0xFF) + ((SY & 0x7FF) << 8);
1230
	ANX = getWrappedNX();
1231
	ANY = getWrappedNY();
1232
	bitsLeft = 0;
1233
}
1234
1235
template<typename Mode>
1236
void V9990CmdEngine::executeCMMM(EmuTime limit)
1237
{
1238
	// TODO can be optimized a lot
1239
1240
	auto delta = getTiming(*this, CMMM_TIMING);
1241
	unsigned pitch = Mode::getPitch(vdp.getImageWidth());
1242
	uint16_t dx = (ARG & DIX) ? uint16_t(-1) : 1;
1243
	uint16_t dy = (ARG & DIY) ? uint16_t(-1) : 1;
1244
	auto lut = Mode::getLogOpLUT(LOG);
1245
	while (engineTime < limit) {
1246
		engineTime += delta;
1247
		if (!bitsLeft) {
1248
			data = vram.readVRAMBx(srcAddress++);
1249
			bitsLeft = 8;
1250
		}
1251
		--bitsLeft;
1252
		bool bit = (data & 0x80) != 0;
1253
		data <<= 1;
1254
1255
		uint16_t color = bit ? fgCol : bgCol;
1256
		Mode::psetColor(vram, DX, DY, pitch, color, WM, lut, LOG);
1257
1258
		DX += dx;
1259
		if (!--ANX) {
1260
			DX -= uint16_t(NX * dx);
1261
			DY += dy;
1262
			if (!--ANY) {
1263
				cmdReady(engineTime);
1264
				return;
1265
			} else {
1266
				ANX = getWrappedNX();
1267
			}
1268
		}
1269
	}
1270
}
1271
1272
// BMXL
1273
void V9990CmdEngine::startBMXL(EmuTime time)
1274
{
1275
	engineTime = time;
1276
	srcAddress = (SX & 0xFF) + ((SY & 0x7FF) << 8);
1277
	ANX = getWrappedNX();
1278
	ANY = getWrappedNY();
1279
}
1280
1281
template<>
1282
void V9990CmdEngine::executeBMXL<V9990CmdEngine::V9990Bpp16>(EmuTime limit)
1283
{
1284
	// timing value is times 2, because it does 2 bytes per iteration:
1285
	auto delta = getTiming(*this, BMXL_TIMING) * 2;
1286
	unsigned pitch = V9990Bpp16::getPitch(vdp.getImageWidth());
1287
	uint16_t dx = (ARG & DIX) ? uint16_t(-1) : 1;
1288
	uint16_t dy = (ARG & DIY) ? uint16_t(-1) : 1;
1289
	auto lut = V9990Bpp16::getLogOpLUT(LOG);
1290
1291
	while (engineTime < limit) {
1292
		engineTime += delta;
1293
		auto src = uint16_t(vram.readVRAMBx(srcAddress + 0) +
1294
		                    vram.readVRAMBx(srcAddress + 1) * 256);
1295
		srcAddress += 2;
1296
		V9990Bpp16::pset(vram, DX, DY, pitch, src, WM, lut, LOG);
1297
		DX += dx;
1298
		if (!--ANX) {
1299
			DX -= uint16_t(NX * dx);
1300
			DY += dy;
1301
			if (!--ANY) {
1302
				cmdReady(engineTime);
1303
				return;
1304
			} else {
1305
				ANX = getWrappedNX();
1306
			}
1307
		}
1308
	}
1309
}
1310
1311
template<typename Mode>
1312
void V9990CmdEngine::executeBMXL(EmuTime limit)
1313
{
1314
	auto delta = getTiming(*this, BMXL_TIMING);
1315
	unsigned pitch = Mode::getPitch(vdp.getImageWidth());
1316
	uint16_t dx = (ARG & DIX) ? uint16_t(-1) : 1;
1317
	uint16_t dy = (ARG & DIY) ? uint16_t(-1) : 1;
1318
	auto lut = Mode::getLogOpLUT(LOG);
1319
1320
	while (engineTime < limit) {
1321
		engineTime += delta;
1322
		uint8_t d = vram.readVRAMBx(srcAddress++);
1323
		for (int i = 0; (ANY > 0) && (i < Mode::PIXELS_PER_BYTE); ++i) {
1324
			auto d2 = Mode::shift(d, i, DX);
1325
			Mode::pset(vram, DX, DY, pitch, d2, WM, lut, LOG);
1326
			DX += dx;
1327
			if (!--ANX) {
1328
				DX -= uint16_t(NX * dx);
1329
				DY += dy;
1330
				if (!--ANY) {
1331
					cmdReady(engineTime);
1332
					return;
1333
				} else {
1334
					ANX = getWrappedNX();
1335
				}
1336
			}
1337
		}
1338
	}
1339
}
1340
1341
// BMLX
1342
void V9990CmdEngine::startBMLX(EmuTime time)
1343
{
1344
	engineTime = time;
1345
	dstAddress = (DX & 0xFF) + ((DY & 0x7FF) << 8);
1346
	ANX = getWrappedNX();
1347
	ANY = getWrappedNY();
1348
}
1349
1350
template<>
1351
void V9990CmdEngine::executeBMLX<V9990CmdEngine::V9990Bpp16>(EmuTime limit)
1352
{
1353
	// TODO test corner cases, timing
1354
	auto delta = getTiming(*this, BMLX_TIMING);
1355
	unsigned pitch = V9990Bpp16::getPitch(vdp.getImageWidth());
1356
	uint16_t dx = (ARG & DIX) ? uint16_t(-1) : 1;
1357
	uint16_t dy = (ARG & DIY) ? uint16_t(-1) : 1;
1358
1359
	while (engineTime < limit) {
1360
		engineTime += delta;
1361
		auto src = V9990Bpp16::point(vram, SX, SY, pitch);
1362
		vram.writeVRAMBx(dstAddress++, narrow_cast<uint8_t>(src & 0xFF));
1363
		vram.writeVRAMBx(dstAddress++, narrow_cast<uint8_t>(src >> 8));
1364
		SX += dx;
1365
		if (!--ANX) {
1366
			SX -= uint16_t(NX * dx);
1367
			SY += dy;
1368
			if (!--ANY) {
1369
				cmdReady(engineTime);
1370
				return;
1371
			} else {
1372
				ANX = getWrappedNX();
1373
			}
1374
		}
1375
	}
1376
}
1377
template<typename Mode>
1378
void V9990CmdEngine::executeBMLX(EmuTime limit)
1379
{
1380
	// TODO test corner cases, timing
1381
	auto delta = getTiming(*this, BMLX_TIMING);
1382
	unsigned pitch = Mode::getPitch(vdp.getImageWidth());
1383
	uint16_t dx = (ARG & DIX) ? uint16_t(-1) : 1;
1384
	uint16_t dy = (ARG & DIY) ? uint16_t(-1) : 1;
1385
1386
	while (engineTime < limit) {
1387
		engineTime += delta;
1388
		uint8_t d = 0;
1389
		for (auto i : xrange(Mode::PIXELS_PER_BYTE)) {
1390
			auto src = Mode::point(vram, SX, SY, pitch);
1391
			d |= uint8_t(Mode::shift(src, SX, i) & Mode::shiftMask(i));
1392
			SX += dx;
1393
			if (!--ANX) {
1394
				SX -= uint16_t(NX * dx);
1395
				SY += dy;
1396
				if (!--ANY) {
1397
					vram.writeVRAMBx(dstAddress++, d);
1398
					cmdReady(engineTime);
1399
					return;
1400
				} else {
1401
					ANX = getWrappedNX();
1402
				}
1403
			}
1404
		}
1405
		vram.writeVRAMBx(dstAddress++, d);
1406
	}
1407
}
1408
1409
// BMLL
1410
void V9990CmdEngine::startBMLL(EmuTime time)
1411
{
1412
	engineTime = time;
1413
	srcAddress = (SX & 0xFF) + ((SY & 0x7FF) << 8);
1414
	dstAddress = (DX & 0xFF) + ((DY & 0x7FF) << 8);
1415
	nbBytes    = (NX & 0xFF) + ((NY & 0x7FF) << 8);
1416
	if (nbBytes == 0) {
1417
		nbBytes = 0x80000;
1418
	}
1419
}
1420
void V9990CmdEngine::startBMLL16(EmuTime time)
1421
{
1422
	startBMLL(time);
1423
	// TODO is this correct???
1424
	// drop last bit
1425
	srcAddress >>= 1;
1426
	dstAddress >>= 1;
1427
	nbBytes    >>= 1;
1428
}
1429
1430
template<>
1431
void V9990CmdEngine::executeBMLL<V9990CmdEngine::V9990Bpp16>(EmuTime limit)
1432
{
1433
	// TODO DIX DIY?
1434
	// timing value is times 2, because it does 2 bytes per iteration:
1435
	auto delta = getTiming(*this, BMLL_TIMING) * 2;
1436
	auto lut = V9990Bpp16::getLogOpLUT(LOG);
1437
	bool transp = (LOG & 0x10) != 0;
1438
	while (engineTime < limit) {
1439
		engineTime += delta;
1440
		// VRAM always mapped as in Bx modes
1441
		auto srcColor = uint16_t(vram.readVRAMDirect(srcAddress + 0x00000) +
1442
		                         vram.readVRAMDirect(srcAddress + 0x40000) * 256);
1443
		auto dstColor = uint16_t(vram.readVRAMDirect(dstAddress + 0x00000) +
1444
		                         vram.readVRAMDirect(dstAddress + 0x40000) * 256);
1445
		uint16_t newColor = V9990Bpp16::logOp(lut, srcColor, dstColor, transp);
1446
		uint16_t result = (dstColor & ~WM) | (newColor & WM);
1447
		vram.writeVRAMDirect(dstAddress + 0x00000, narrow_cast<uint8_t>(result & 0xFF));
1448
		vram.writeVRAMDirect(dstAddress + 0x40000, narrow_cast<uint8_t>(result >> 8));
1449
		srcAddress = (srcAddress + 1) & 0x3FFFF;
1450
		dstAddress = (dstAddress + 1) & 0x3FFFF;
1451
		if (!--nbBytes) {
1452
			cmdReady(engineTime);
1453
			return;
1454
		}
1455
	}
1456
}
1457
1458
template<typename Mode>
1459
void V9990CmdEngine::executeBMLL(EmuTime limit)
1460
{
1461
	// TODO DIX DIY?
1462
	auto delta = getTiming(*this, BMLL_TIMING);
1463
	auto lut = Mode::getLogOpLUT(LOG);
1464
	while (engineTime < limit) {
1465
		engineTime += delta;
1466
		// VRAM always mapped as in Bx modes
1467
		auto srcColor = vram.readVRAMBx(srcAddress);
1468
		auto addr = V9990VRAM::transformBx(dstAddress);
1469
		auto dstColor = vram.readVRAMDirect(addr);
1470
		auto newColor = Mode::logOp(lut, srcColor, dstColor);
1471
		auto mask = narrow_cast<uint8_t>((addr & 0x40000) ? (WM >> 8) : (WM & 0xFF));
1472
		uint8_t result = (dstColor & ~mask) | (newColor & mask);
1473
		vram.writeVRAMDirect(addr, result);
1474
		srcAddress = (srcAddress + 1) & 0x7FFFF;
1475
		dstAddress = (dstAddress + 1) & 0x7FFFF;
1476
		if (!--nbBytes) {
1477
			cmdReady(engineTime);
1478
			return;
1479
		}
1480
	}
1481
}
1482
1483
// LINE
1484
void V9990CmdEngine::startLINE(EmuTime time)
1485
{
1486
	engineTime = time;
1487
	ASX = uint16_t((NX - 1) / 2);
1488
	ADX = DX;
1489
	ANX = 0;
1490
}
1491
1492
template<typename Mode>
1493
void V9990CmdEngine::executeLINE(EmuTime limit)
1494
{
1495
	auto delta = getTiming(*this, LINE_TIMING);
1496
	unsigned width = vdp.getImageWidth();
1497
	unsigned pitch = Mode::getPitch(width);
1498
1499
	uint16_t TX = (ARG & DIX) ? uint16_t(-1) : 1;
1500
	uint16_t TY = (ARG & DIY) ? uint16_t(-1) : 1;
1501
	auto lut = Mode::getLogOpLUT(LOG);
1502
1503
	if ((ARG & MAJ) == 0) {
1504
		// X-Axis is major direction.
1505
		while (engineTime < limit) {
1506
			engineTime += delta;
1507
			Mode::psetColor(vram, ADX, DY, pitch, fgCol, WM, lut, LOG);
1508
1509
			ADX += TX;
1510
			if (ASX < NY) {
1511
				ASX += NX;
1512
				DY += TY;
1513
			}
1514
			ASX -= NY;
1515
			//ASX &= 1023; // mask to 10 bits range
1516
			if (ANX++ == NX || (ADX & width)) {
1517
				cmdReady(engineTime);
1518
				break;
1519
			}
1520
		}
1521
	} else {
1522
		// Y-Axis is major direction.
1523
		while (engineTime < limit) {
1524
			engineTime += delta;
1525
			Mode::psetColor(vram, ADX, DY, pitch, fgCol, WM, lut, LOG);
1526
			DY += TY;
1527
			if (ASX < NY) {
1528
				ASX += NX;
1529
				ADX += TX;
1530
			}
1531
			ASX -= NY;
1532
			//ASX &= 1023; // mask to 10 bits range
1533
			if (ANX++ == NX || (ADX & width)) {
1534
				cmdReady(engineTime);
1535
				break;
1536
			}
1537
		}
1538
	}
1539
}
1540
1541
// SRCH
1542
void V9990CmdEngine::startSRCH(EmuTime time)
1543
{
1544
	engineTime = time;
1545
	ASX = SX;
1546
}
1547
1548
template<typename Mode>
1549
void V9990CmdEngine::executeSRCH(EmuTime limit)
1550
{
1551
	using Type = typename Mode::Type;
1552
	auto delta = getTiming(*this, SRCH_TIMING);
1553
	unsigned width = vdp.getImageWidth();
1554
	unsigned pitch = Mode::getPitch(width);
1555
	Type mask = (1 << Mode::BITS_PER_PIXEL) -1;
1556
1557
	uint16_t TX = (ARG & DIX) ? uint16_t(-1) : 1;
1558
	bool AEQ = (ARG & NEQ) != 0;
1559
1560
	while (engineTime < limit) {
1561
		engineTime += delta;
1562
		Type value;
1563
		Type col;
1564
		Type mask2;
1565
		if constexpr (Mode::BITS_PER_PIXEL == 16) {
1566
			value = Mode::point(vram, ASX, SY, pitch);
1567
			col = static_cast<Type>(fgCol);
1568
			mask2 = static_cast<Type>(~0);
1569
		} else {
1570
			// TODO check
1571
			unsigned addr = Mode::addressOf(ASX, SY, pitch);
1572
			value = vram.readVRAMDirect(addr);
1573
			col = narrow_cast<uint8_t>((addr & 0x40000) ? (fgCol >> 8) : (fgCol & 0xFF));
1574
			mask2 = Mode::shift(mask, 3, ASX);
1575
		}
1576
		if (((value & mask2) == (col & mask2)) ^ AEQ) {
1577
			status |= BD; // border detected
1578
			cmdReady(engineTime);
1579
			borderX = ASX;
1580
			break;
1581
		}
1582
		ASX += TX;
1583
		if (ASX & width) {
1584
			status &= ~BD; // border not detected
1585
			cmdReady(engineTime);
1586
			borderX = ASX;
1587
			break;
1588
		}
1589
	}
1590
}
1591
1592
// POINT
1593
template<typename Mode>
1594
void V9990CmdEngine::startPOINT(EmuTime /*time*/)
1595
{
1596
	unsigned pitch = Mode::getPitch(vdp.getImageWidth());
1597
	auto d = Mode::point(vram, SX, SY, pitch);
1598
1599
	if constexpr (Mode::BITS_PER_PIXEL != 16) {
1600
		data = uint8_t(d);
1601
		endAfterRead = true;
1602
	} else {
1603
		unsigned tmp = d; // workaround for VC++ warning C4333
1604
				  // (in case Mode::Type == uint8_t and
1605
				  //          Mode::BITS_PER_PIXEL == 8)
1606
		data = narrow_cast<uint8_t>(tmp & 0xff);
1607
		partial = narrow_cast<uint8_t>(tmp >> 8);
1608
		endAfterRead = false;
1609
	}
1610
	status |= TR;
1611
}
1612
1613
template<typename Mode>
1614
void V9990CmdEngine::executePOINT(EmuTime /*limit*/)
1615
{
1616
	if (status & TR) return;
1617
1618
	assert(Mode::BITS_PER_PIXEL == 16);
1619
	status |= TR;
1620
	data = partial;
1621
	endAfterRead = true;
1622
}
1623
1624
// PSET
1625
template<typename Mode>
1626
void V9990CmdEngine::startPSET(EmuTime time)
1627
{
1628
	unsigned pitch = Mode::getPitch(vdp.getImageWidth());
1629
	auto lut = Mode::getLogOpLUT(LOG);
1630
	Mode::psetColor(vram, DX, DY, pitch, fgCol, WM, lut, LOG);
1631
1632
	// TODO advance DX DY
1633
1634
	cmdReady(time);
1635
}
1636
1637
void V9990CmdEngine::executePSET(EmuTime /*limit*/)
1638
{
1639
	UNREACHABLE;
1640
}
1641
1642
// ADVN
1643
void V9990CmdEngine::startADVN(EmuTime time)
1644
{
1645
	std::cout << "V9990: ADVN not yet implemented\n";
1646
	cmdReady(time); // TODO dummy implementation
1647
}
1648
1649
void V9990CmdEngine::executeADVN(EmuTime /*limit*/)
1650
{
1651
	UNREACHABLE;
1652
}
1653
1654
// ====================================================================
1655
// CmdEngine methods
1656
1657
void V9990CmdEngine::sync2(EmuTime time)
1658
{
1659
	switch (cmdMode | (CMD >> 4)) {
1660
		case 0x00: case 0x10: case 0x20: case 0x30: case 0x40: case 0x50:
1661
			executeSTOP(time); break;
1662
1663
		case 0x01: executeLMMC<V9990P1   >(time); break;
1664
		case 0x11: executeLMMC<V9990P2   >(time); break;
1665
		case 0x21: executeLMMC<V9990Bpp2 >(time); break;
1666
		case 0x31: executeLMMC<V9990Bpp4 >(time); break;
1667
		case 0x41: executeLMMC<V9990Bpp8 >(time); break;
1668
		case 0x51: executeLMMC<V9990Bpp16>(time); break;
1669
1670
		case 0x02: executeLMMV<V9990P1   >(time); break;
1671
		case 0x12: executeLMMV<V9990P2   >(time); break;
1672
		case 0x22: executeLMMV<V9990Bpp2 >(time); break;
1673
		case 0x32: executeLMMV<V9990Bpp4 >(time); break;
1674
		case 0x42: executeLMMV<V9990Bpp8 >(time); break;
1675
		case 0x52: executeLMMV<V9990Bpp16>(time); break;
1676
1677
		case 0x03: executeLMCM<V9990P1   >(time); break;
1678
		case 0x13: executeLMCM<V9990P2   >(time); break;
1679
		case 0x23: executeLMCM<V9990Bpp2 >(time); break;
1680
		case 0x33: executeLMCM<V9990Bpp4 >(time); break;
1681
		case 0x43: executeLMCM<V9990Bpp8 >(time); break;
1682
		case 0x53: executeLMCM<V9990Bpp16>(time); break;
1683
1684
		case 0x04: executeLMMM<V9990P1   >(time); break;
1685
		case 0x14: executeLMMM<V9990P2   >(time); break;
1686
		case 0x24: executeLMMM<V9990Bpp2 >(time); break;
1687
		case 0x34: executeLMMM<V9990Bpp4 >(time); break;
1688
		case 0x44: executeLMMM<V9990Bpp8 >(time); break;
1689
		case 0x54: executeLMMM<V9990Bpp16>(time); break;
1690
1691
		case 0x05: executeCMMC<V9990P1   >(time); break;
1692
		case 0x15: executeCMMC<V9990P2   >(time); break;
1693
		case 0x25: executeCMMC<V9990Bpp2 >(time); break;
1694
		case 0x35: executeCMMC<V9990Bpp4 >(time); break;
1695
		case 0x45: executeCMMC<V9990Bpp8 >(time); break;
1696
		case 0x55: executeCMMC<V9990Bpp16>(time); break;
1697
1698
		case 0x06: case 0x16: case 0x26: case 0x36: case 0x46: case 0x56:
1699
			executeCMMK(time); break;
1700
1701
		case 0x07: executeCMMM<V9990P1   >(time); break;
1702
		case 0x17: executeCMMM<V9990P2   >(time); break;
1703
		case 0x27: executeCMMM<V9990Bpp2 >(time); break;
1704
		case 0x37: executeCMMM<V9990Bpp4 >(time); break;
1705
		case 0x47: executeCMMM<V9990Bpp8 >(time); break;
1706
		case 0x57: executeCMMM<V9990Bpp16>(time); break;
1707
1708
		case 0x08: executeBMXL<V9990P1   >(time); break;
1709
		case 0x18: executeBMXL<V9990P2   >(time); break;
1710
		case 0x28: executeBMXL<V9990Bpp2 >(time); break;
1711
		case 0x38: executeBMXL<V9990Bpp4 >(time); break;
1712
		case 0x48: executeBMXL<V9990Bpp8 >(time); break;
1713
		case 0x58: executeBMXL<V9990Bpp16>(time); break;
1714
1715
		case 0x09: executeBMLX<V9990P1   >(time); break;
1716
		case 0x19: executeBMLX<V9990P2   >(time); break;
1717
		case 0x29: executeBMLX<V9990Bpp2 >(time); break;
1718
		case 0x39: executeBMLX<V9990Bpp4 >(time); break;
1719
		case 0x49: executeBMLX<V9990Bpp8 >(time); break;
1720
		case 0x59: executeBMLX<V9990Bpp16>(time); break;
1721
1722
		case 0x0A: executeBMLL<V9990P1   >(time); break;
1723
		case 0x1A: executeBMLL<V9990P2   >(time); break;
1724
		case 0x2A: executeBMLL<V9990Bpp2 >(time); break;
1725
		case 0x3A: executeBMLL<V9990Bpp4 >(time); break;
1726
		case 0x4A: executeBMLL<V9990Bpp8 >(time); break;
1727
		case 0x5A: executeBMLL<V9990Bpp16>(time); break;
1728
1729
		case 0x0B: executeLINE<V9990P1   >(time); break;
1730
		case 0x1B: executeLINE<V9990P2   >(time); break;
1731
		case 0x2B: executeLINE<V9990Bpp2 >(time); break;
1732
		case 0x3B: executeLINE<V9990Bpp4 >(time); break;
1733
		case 0x4B: executeLINE<V9990Bpp8 >(time); break;
1734
		case 0x5B: executeLINE<V9990Bpp16>(time); break;
1735
1736
		case 0x0C: executeSRCH<V9990P1   >(time); break;
1737
		case 0x1C: executeSRCH<V9990P2   >(time); break;
1738
		case 0x2C: executeSRCH<V9990Bpp2 >(time); break;
1739
		case 0x3C: executeSRCH<V9990Bpp4 >(time); break;
1740
		case 0x4C: executeSRCH<V9990Bpp8 >(time); break;
1741
		case 0x5C: executeSRCH<V9990Bpp16>(time); break;
1742
1743
		case 0x0D: executePOINT<V9990P1   >(time); break;
1744
		case 0x1D: executePOINT<V9990P2   >(time); break;
1745
		case 0x2D: executePOINT<V9990Bpp2 >(time); break;
1746
		case 0x3D: executePOINT<V9990Bpp4 >(time); break;
1747
		case 0x4D: executePOINT<V9990Bpp8 >(time); break;
1748
		case 0x5D: executePOINT<V9990Bpp16>(time); break;
1749
1750
		case 0x0E: case 0x1E: case 0x2E: case 0x3E: case 0x4E: case 0x5E:
1751
			executePSET(time); break;
1752
1753
		case 0x0F: case 0x1F: case 0x2F: case 0x3F: case 0x4F: case 0x5F:
1754
			executeADVN(time); break;
1755
1756
		default: UNREACHABLE;
1757
	}
1758
}
1759
1760
void V9990CmdEngine::setCmdData(uint8_t value, EmuTime time)
1761
{
1762
	sync(time);
1763
	data = value;
1764
	status &= ~TR;
1765
}
1766
1767
uint8_t V9990CmdEngine::getCmdData(EmuTime time)
1768
{
1769
	sync(time);
1770
1771
	uint8_t value = 0xFF;
1772
	if (status & TR) {
1773
		value = data;
1774
		status &= ~TR;
1775
		if (endAfterRead) {
1776
			endAfterRead = false;
1777
			cmdReady(time);
1778
		}
1779
	}
1780
	return value;
1781
}
1782
1783
uint8_t V9990CmdEngine::peekCmdData(EmuTime time) const
1784
{
1785
	const_cast<V9990CmdEngine*>(this)->sync(time);
1786
	return (status & TR) ? data : 0xFF;
1787
}
1788
1789
void V9990CmdEngine::cmdReady(EmuTime /*time*/)
1790
{
1791
	CMD = 0; // for deserialize
1792
	status &= ~(CE | TR);
1793
	vdp.cmdReady();
1794
}
1795
1796
EmuTime V9990CmdEngine::estimateCmdEnd() const
1797
{
1798
	EmuDuration delta;
1799
	switch (CMD >> 4) {
1800
		case 0x00: // STOP
1801
			delta = EmuDuration::zero();
1802
			break;
1803
1804
		case 0x01: // LMMC
1805
		case 0x05: // CMMC
1806
			// command terminates when CPU writes data, no need for extra sync
1807
			delta = EmuDuration::zero();
1808
			break;
1809
1810
		case 0x03: // LMCM
1811
		case 0x0D: // POINT
1812
			// command terminates when CPU reads data, no need for extra sync
1813
			delta = EmuDuration::zero();
1814
			break;
1815
1816
		case 0x02: // LMMV
1817
			// Block commands.
1818
			delta = getTiming(*this, LMMV_TIMING) * (ANX + (ANY - 1) * getWrappedNX());
1819
			break;
1820
		case 0x04: // LMMM
1821
			delta = getTiming(*this, LMMM_TIMING) * (ANX + (ANY - 1) * getWrappedNX());
1822
			break;
1823
		case 0x07: // CMMM
1824
			delta = getTiming(*this, CMMM_TIMING) * (ANX + (ANY - 1) * getWrappedNX());
1825
			break;
1826
		case 0x08: // BMXL
1827
			delta = getTiming(*this, BMXL_TIMING) * (ANX + (ANY - 1) * getWrappedNX()); // TODO correct?
1828
			break;
1829
		case 0x09: // BMLX
1830
			delta = getTiming(*this, BMLX_TIMING) * (ANX + (ANY - 1) * getWrappedNX()); // TODO correct?
1831
			break;
1832
1833
		case 0x06: // CMMK
1834
			// Not yet implemented.
1835
			delta = EmuDuration::zero();
1836
			break;
1837
1838
		case 0x0A: // BMLL
1839
			delta = getTiming(*this, BMLL_TIMING) * nbBytes;
1840
			break;
1841
1842
		case 0x0B: // LINE
1843
			delta = getTiming(*this, LINE_TIMING) * (NX - ANX + 1); // TODO ignores clipping
1844
			break;
1845
1846
		case 0x0C: // SRCH
1847
			// Can end at any next pixel.
1848
			delta = getTiming(*this, SRCH_TIMING);
1849
			break;
1850
1851
		case 0x0E: // PSET
1852
		case 0x0F: // ADVN
1853
			// Current implementation of these commands execute instantly, no need for extra sync.
1854
			delta = EmuDuration::zero();
1855
			break;
1856
1857
		default: UNREACHABLE;
1858
	}
1859
	return engineTime + delta;
1860
}
1861
1862
// version 1: initial version
1863
// version 2: we forgot to serialize the time (or clock) member
1864
template<typename Archive>
1865
void V9990CmdEngine::serialize(Archive& ar, unsigned version)
1866
{
1867
	// note: V9990Cmd objects are stateless
1868
	if (ar.versionAtLeast(version, 2)) {
1869
		ar.serialize("time", engineTime);
1870
	} else {
1871
		// In version 1 we forgot to serialize the time member (it was
1872
		// also still a Clock back then). The best we can do is
1873
		// initialize it with the current time, that's already done in
1874
		// the constructor.
1875
	}
1876
	ar.serialize("srcAddress",   srcAddress,
1877
	             "dstAddress",   dstAddress,
1878
	             "nbBytes",      nbBytes,
1879
	             "borderX",      borderX,
1880
	             "ASX",          ASX,
1881
	             "ADX",          ADX,
1882
	             "ANX",          ANX,
1883
	             "ANY",          ANY,
1884
	             "SX",           SX,
1885
	             "SY",           SY,
1886
	             "DX",           DX,
1887
	             "DY",           DY,
1888
	             "NX",           NX,
1889
	             "NY",           NY,
1890
	             "WM",           WM,
1891
	             "fgCol",        fgCol,
1892
	             "bgCol",        bgCol,
1893
	             "ARG",          ARG,
1894
	             "LOG",          LOG,
1895
	             "CMD",          CMD,
1896
	             "status",       status,
1897
	             "data",         data,
1898
	             "bitsLeft",     bitsLeft,
1899
	             "partial",      partial,
1900
	             "endAfterRead", endAfterRead);
1901
1902
	if constexpr (Archive::IS_LOADER) {
1903
		setCommandMode();
1904
	}
1905
}
1906
INSTANTIATE_SERIALIZE_METHODS(V9990CmdEngine);
1907
1908
} // namespace openmsx