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