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