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