openMSX
VDPCmdEngine.cc
Go to the documentation of this file.
1 /*
2 TODO:
3 - How is 64K VRAM handled?
4  VRAM size is never inspected by the command engine.
5  How does a real MSX handle it?
6  Mirroring of first 64K or empty memory space?
7 - How is extended VRAM handled?
8  The current VDP implementation does not support it.
9  Since it is not accessed by the renderer, it is possible allocate
10  it here.
11  But maybe it makes more sense to have all RAM managed by the VDP?
12 - Currently all VRAM access is done at the start time of a series of
13  updates: currentTime is not increased until the very end of the sync
14  method. It should ofcourse be updated after every read and write.
15  An acceptable approximation would be an update after every pixel/byte
16  operation.
17 */
18 
19 /*
20  About NX, NY
21  - for block commands NX = 0 is equivalent to NX = 512 (TODO recheck this)
22  and NY = 0 is equivalent to NY = 1024
23  - when NX or NY is too large and the VDP command hits the border, the
24  following happens:
25  - when the left or right border is hit, the line terminates
26  - when the top border is hit (line 0) the command terminates
27  - when the bottom border (line 511 or 1023) the command continues
28  (wraps to the top)
29  - in 512 lines modes (e.g. screen 7) NY is NOT limited to 512, so when
30  NY > 512, part of the screen is overdrawn twice
31  - in 256 columns modes (e.g. screen 5) when "SX/DX >= 256", only 1 element
32  (pixel or byte) is processed per horizontal line. The real x-ccordinate
33  is "SX/DX & 255".
34 */
35 
36 #include "VDPCmdEngine.hh"
37 #include "EmuTime.hh"
38 #include "VDPVRAM.hh"
39 #include "BooleanSetting.hh"
40 #include "TclCallback.hh"
41 #include "EnumSetting.hh"
42 #include "RenderSettings.hh"
43 #include "CommandController.hh"
44 #include "TclObject.hh"
45 #include "CommandException.hh"
46 #include "CliComm.hh"
47 #include "serialize.hh"
48 #include "unreachable.hh"
49 #include "memory.hh"
50 #include <iostream>
51 #include <cassert>
52 #include <algorithm>
53 
54 using std::min;
55 using std::max;
56 
57 namespace openmsx {
58 
59 // Constants:
60 const byte MXD = 0x20;
61 const byte MXS = 0x10;
62 const byte DIY = 0x08;
63 const byte DIX = 0x04;
64 const byte EQ = 0x02;
65 const byte MAJ = 0x01;
66 
67 // Inline methods first, to make sure they are actually inlined:
68 
69 template <typename Mode>
70 static inline unsigned clipNX_1_pixel(unsigned DX, unsigned NX, byte ARG)
71 {
72  if (unlikely(DX >= Mode::PIXELS_PER_LINE)) {
73  return 1;
74  }
75  NX = NX ? NX : Mode::PIXELS_PER_LINE;
76  return (ARG & DIX)
77  ? min(NX, DX + 1)
78  : min(NX, Mode::PIXELS_PER_LINE - DX);
79 }
80 
81 template <typename Mode>
82 static inline unsigned clipNX_1_byte(unsigned DX, unsigned NX, byte ARG)
83 {
84  static const unsigned BYTES_PER_LINE =
85  Mode::PIXELS_PER_LINE >> Mode::PIXELS_PER_BYTE_SHIFT;
86 
87  DX >>= Mode::PIXELS_PER_BYTE_SHIFT;
88  if (unlikely(BYTES_PER_LINE <= DX)) {
89  return 1;
90  }
91  NX >>= Mode::PIXELS_PER_BYTE_SHIFT;
92  NX = NX ? NX : BYTES_PER_LINE;
93  return (ARG & DIX)
94  ? min(NX, DX + 1)
95  : min(NX, BYTES_PER_LINE - DX);
96 }
97 
98 template <typename Mode>
99 static inline unsigned clipNX_2_pixel(unsigned SX, unsigned DX, unsigned NX, byte ARG)
100 {
101  if (unlikely(SX >= Mode::PIXELS_PER_LINE) ||
102  unlikely(DX >= Mode::PIXELS_PER_LINE)) {
103  return 1;
104  }
105  NX = NX ? NX : Mode::PIXELS_PER_LINE;
106  return (ARG & DIX)
107  ? min(NX, min(SX, DX) + 1)
108  : min(NX, Mode::PIXELS_PER_LINE - max(SX, DX));
109 }
110 
111 template <typename Mode>
112 static inline unsigned clipNX_2_byte(unsigned SX, unsigned DX, unsigned NX, byte ARG)
113 {
114  static const unsigned BYTES_PER_LINE =
115  Mode::PIXELS_PER_LINE >> Mode::PIXELS_PER_BYTE_SHIFT;
116 
117  SX >>= Mode::PIXELS_PER_BYTE_SHIFT;
118  DX >>= Mode::PIXELS_PER_BYTE_SHIFT;
119  if (unlikely(BYTES_PER_LINE <= SX) ||
120  unlikely(BYTES_PER_LINE <= DX)) {
121  return 1;
122  }
123  NX >>= Mode::PIXELS_PER_BYTE_SHIFT;
124  NX = NX ? NX : BYTES_PER_LINE;
125  return (ARG & DIX)
126  ? min(NX, min(SX, DX) + 1)
127  : min(NX, BYTES_PER_LINE - max(SX, DX));
128 }
129 
130 static inline unsigned clipNY_1(unsigned DY, unsigned NY, byte ARG)
131 {
132  NY = NY ? NY : 1024;
133  return (ARG & DIY) ? min(NY, DY + 1) : NY;
134 }
135 
136 static inline unsigned clipNY_2(unsigned SY, unsigned DY, unsigned NY, byte ARG)
137 {
138  NY = NY ? NY : 1024;
139  return (ARG & DIY) ? min(NY, min(SY, DY) + 1) : NY;
140 }
141 
142 
143 struct IncrByteAddr4;
144 struct IncrByteAddr5;
145 struct IncrByteAddr6;
146 struct IncrByteAddr7;
147 struct IncrPixelAddr4;
148 struct IncrPixelAddr5;
149 struct IncrPixelAddr6;
150 struct IncrMask4;
151 struct IncrMask5;
152 struct IncrMask7;
153 struct IncrShift4;
154 struct IncrShift5;
155 struct IncrShift7;
159 
160 
161 template <typename LogOp> static void psetFast(
162  EmuTime::param time, VDPVRAM& vram, unsigned addr,
163  byte color, byte mask, LogOp op)
164 {
165  byte src = vram.cmdWriteWindow.readNP(addr);
166  op(time, vram, addr, src, color, mask);
167 }
168 
172 {
177  static const byte COLOR_MASK = 0x0F;
178  static const byte PIXELS_PER_BYTE = 2;
179  static const byte PIXELS_PER_BYTE_SHIFT = 1;
180  static const unsigned PIXELS_PER_LINE = 256;
181  static inline unsigned addressOf(unsigned x, unsigned y, bool extVRAM);
182  static inline byte point(VDPVRAM& vram, unsigned x, unsigned y, bool extVRAM);
183  template <typename LogOp>
184  static inline void pset(EmuTime::param time, VDPVRAM& vram,
185  unsigned x, unsigned addr, byte src, byte color, LogOp op);
186  static inline byte duplicate(byte color);
187 };
188 
189 inline unsigned Graphic4Mode::addressOf(
190  unsigned x, unsigned y, bool extVRAM)
191 {
192  return likely(!extVRAM)
193  ? (((y & 1023) << 7) | ((x & 255) >> 1))
194  : (((y & 511) << 7) | ((x & 255) >> 1) | 0x20000);
195 }
196 
198  VDPVRAM& vram, unsigned x, unsigned y, bool extVRAM)
199 {
200  return ( vram.cmdReadWindow.readNP(addressOf(x, y, extVRAM))
201  >> (((~x) & 1) << 2) ) & 15;
202 }
203 
204 template <typename LogOp>
205 inline void Graphic4Mode::pset(
206  EmuTime::param time, VDPVRAM& vram, unsigned x, unsigned addr,
207  byte src, byte color, LogOp op)
208 {
209  byte sh = ((~x) & 1) << 2;
210  op(time, vram, addr, src, color << sh, ~(15 << sh));
211 }
212 
214 {
215  assert((color & 0xF0) == 0);
216  return color | (color << 4);
217 }
218 
222 {
227  static const byte COLOR_MASK = 0x03;
228  static const byte PIXELS_PER_BYTE = 4;
229  static const byte PIXELS_PER_BYTE_SHIFT = 2;
230  static const unsigned PIXELS_PER_LINE = 512;
231  static inline unsigned addressOf(unsigned x, unsigned y, bool extVRAM);
232  static inline byte point(VDPVRAM& vram, unsigned x, unsigned y, bool extVRAM);
233  template <typename LogOp>
234  static inline void pset(EmuTime::param time, VDPVRAM& vram,
235  unsigned x, unsigned addr, byte src, byte color, LogOp op);
236  static inline byte duplicate(byte color);
237 };
238 
239 inline unsigned Graphic5Mode::addressOf(
240  unsigned x, unsigned y, bool extVRAM)
241 {
242  return likely(!extVRAM)
243  ? (((y & 1023) << 7) | ((x & 511) >> 2))
244  : (((y & 511) << 7) | ((x & 511) >> 2) | 0x20000);
245 }
246 
248  VDPVRAM& vram, unsigned x, unsigned y, bool extVRAM)
249 {
250  return ( vram.cmdReadWindow.readNP(addressOf(x, y, extVRAM))
251  >> (((~x) & 3) << 1) ) & 3;
252 }
253 
254 template <typename LogOp>
255 inline void Graphic5Mode::pset(
256  EmuTime::param time, VDPVRAM& vram, unsigned x, unsigned addr,
257  byte src, byte color, LogOp op)
258 {
259  byte sh = ((~x) & 3) << 1;
260  op(time, vram, addr, src, color << sh, ~(3 << sh));
261 }
262 
264 {
265  assert((color & 0xFC) == 0);
266  color |= color << 2;
267  color |= color << 4;
268  return color;
269 }
270 
274 {
279  static const byte COLOR_MASK = 0x0F;
280  static const byte PIXELS_PER_BYTE = 2;
281  static const byte PIXELS_PER_BYTE_SHIFT = 1;
282  static const unsigned PIXELS_PER_LINE = 512;
283  static inline unsigned addressOf(unsigned x, unsigned y, bool extVRAM);
284  static inline byte point(VDPVRAM& vram, unsigned x, unsigned y, bool extVRAM);
285  template <typename LogOp>
286  static inline void pset(EmuTime::param time, VDPVRAM& vram,
287  unsigned x, unsigned addr, byte src, byte color, LogOp op);
288  static inline byte duplicate(byte color);
289 };
290 
291 inline unsigned Graphic6Mode::addressOf(
292  unsigned x, unsigned y, bool extVRAM)
293 {
294  return likely(!extVRAM)
295  ? (((x & 2) << 15) | ((y & 511) << 7) | ((x & 511) >> 2))
296  : (0x20000 | ((y & 511) << 7) | ((x & 511) >> 2));
297 }
298 
300  VDPVRAM& vram, unsigned x, unsigned y, bool extVRAM)
301 {
302  return ( vram.cmdReadWindow.readNP(addressOf(x, y, extVRAM))
303  >> (((~x) & 1) << 2) ) & 15;
304 }
305 
306 template <typename LogOp>
307 inline void Graphic6Mode::pset(
308  EmuTime::param time, VDPVRAM& vram, unsigned x, unsigned addr,
309  byte src, byte color, LogOp op)
310 {
311  byte sh = ((~x) & 1) << 2;
312  op(time, vram, addr, src, color << sh, ~(15 << sh));
313 }
314 
316 {
317  assert((color & 0xF0) == 0);
318  return color | (color << 4);
319 }
320 
324 {
329  static const byte COLOR_MASK = 0xFF;
330  static const byte PIXELS_PER_BYTE = 1;
331  static const byte PIXELS_PER_BYTE_SHIFT = 0;
332  static const unsigned PIXELS_PER_LINE = 256;
333  static inline unsigned addressOf(unsigned x, unsigned y, bool extVRAM);
334  static inline byte point(VDPVRAM& vram, unsigned x, unsigned y, bool extVRAM);
335  template <typename LogOp>
336  static inline void pset(EmuTime::param time, VDPVRAM& vram,
337  unsigned x, unsigned addr, byte src, byte color, LogOp op);
338  static inline byte duplicate(byte color);
339 };
340 
341 inline unsigned Graphic7Mode::addressOf(
342  unsigned x, unsigned y, bool extVRAM)
343 {
344  return likely(!extVRAM)
345  ? (((x & 1) << 16) | ((y & 511) << 7) | ((x & 255) >> 1))
346  : (0x20000 | ((y & 511) << 7) | ((x & 255) >> 1));
347 }
348 
350  VDPVRAM& vram, unsigned x, unsigned y, bool extVRAM)
351 {
352  return vram.cmdReadWindow.readNP(addressOf(x, y, extVRAM));
353 }
354 
355 template <typename LogOp>
356 inline void Graphic7Mode::pset(
357  EmuTime::param time, VDPVRAM& vram, unsigned /*x*/, unsigned addr,
358  byte src, byte color, LogOp op)
359 {
360  op(time, vram, addr, src, color, 0);
361 }
362 
364 {
365  return color;
366 }
367 
371 {
372  IncrByteAddr4(unsigned x, unsigned y, int /*tx*/)
373  {
374  addr = Graphic4Mode::addressOf(x, y, false);
375  }
376  unsigned getAddr() const
377  {
378  return addr;
379  }
380  void step(int tx)
381  {
382  addr += (tx >> 1);
383  }
384 
385 private:
386  unsigned addr;
387 };
388 
390 {
391  IncrByteAddr5(unsigned x, unsigned y, int /*tx*/)
392  {
393  addr = Graphic5Mode::addressOf(x, y, false);
394  }
395  unsigned getAddr() const
396  {
397  return addr;
398  }
399  void step(int tx)
400  {
401  addr += (tx >> 2);
402  }
403 
404 private:
405  unsigned addr;
406 };
407 
409 {
410  IncrByteAddr7(unsigned x, unsigned y, int tx)
411  : delta2((tx > 0) ? ( 0x10000 ^ (1 - 0x10000))
412  : (-0x10000 ^ (0x10000 - 1)))
413  {
414  addr = Graphic7Mode::addressOf(x, y, false);
415  delta = (tx > 0) ? 0x10000 : (0x10000 - 1);
416  if (x & 1) delta ^= delta2;
417  }
418  unsigned getAddr() const
419  {
420  return addr;
421  }
422  void step(int /*tx*/)
423  {
424  addr += delta;
425  delta ^= delta2;
426  }
427 
428 private:
429  unsigned addr;
430  unsigned delta;
431  const unsigned delta2;
432 };
433 
435 {
436  IncrByteAddr6(unsigned x, unsigned y, int tx)
437  : IncrByteAddr7(x >> 1, y, tx)
438  {
439  }
440 };
441 
445 {
446  IncrPixelAddr4(unsigned x, unsigned y, int tx)
447  {
448  addr = Graphic4Mode::addressOf(x, y, false);
449  delta = (tx == 1) ? (x & 1) : ((x & 1) - 1);
450  }
451  unsigned getAddr() const { return addr; }
452  void step(int tx)
453  {
454  addr += delta;
455  delta ^= tx;
456  }
457 private:
458  unsigned addr;
459  unsigned delta;
460 };
461 
463 {
464  IncrPixelAddr5(unsigned x, unsigned y, int tx)
465  {
466  addr = Graphic5Mode::addressOf(x, y, false);
467  // x | 0 | 1 | 2 | 3
468  //-----------------------
469  c1 = -(signed(x) & 1); // | 0 | -1 | 0 | -1
470  c2 = (x & 2) >> 1; // | 0 | 0 | 1 | 1
471  if (tx < 0) {
472  c1 = ~c1; // | -1 | 0 | -1 | 0
473  c2 -= 1; // | -1 | -1 | 0 | 0
474  }
475  }
476  unsigned getAddr() const { return addr; }
477  void step(int tx)
478  {
479  addr += (c1 & c2);
480  c2 ^= (c1 & tx);
481  c1 = ~c1;
482  }
483 private:
484  unsigned addr;
485  unsigned c1;
486  unsigned c2;
487 };
488 
490 {
491  IncrPixelAddr6(unsigned x, unsigned y, int tx)
492  : c3((tx == 1) ? unsigned(0x10000 ^ (1 - 0x10000)) // == -0x1FFFF
493  : unsigned(-0x10000 ^ (0x10000 - 1))) // == -1
494  {
495  addr = Graphic6Mode::addressOf(x, y, false);
496  c1 = -(signed(x) & 1);
497  if (tx == 1) {
498  c2 = (x & 2) ? (1 - 0x10000) : 0x10000;
499  } else {
500  c1 = ~c1;
501  c2 = (x & 2) ? -0x10000 : (0x10000 - 1);
502  }
503  }
504  unsigned getAddr() const { return addr; }
505  void step(int /*tx*/)
506  {
507  addr += (c1 & c2);
508  c2 ^= (c1 & c3);
509  c1 = ~c1;
510  }
511 private:
512  unsigned addr;
513  unsigned c1;
514  unsigned c2;
515  const unsigned c3;
516 };
517 
518 
522 struct IncrMask4
523 {
524  IncrMask4(unsigned x, int /*tx*/)
525  {
526  mask = 0x0F << ((x & 1) << 2);
527  }
528  byte getMask() const
529  {
530  return mask;
531  }
532  void step()
533  {
534  mask = ~mask;
535  }
536 private:
537  byte mask;
538 };
539 
540 struct IncrMask5
541 {
542  IncrMask5(unsigned x, int tx)
543  : shift((tx > 0) ? 6 : 2)
544  {
545  mask = ~(0xC0 >> ((x & 3) << 1));
546  }
547  byte getMask() const
548  {
549  return mask;
550  }
551  void step()
552  {
553  mask = (mask << shift) | (mask >> (8 - shift));
554  }
555 private:
556  byte mask;
557  const byte shift;
558 };
559 
560 struct IncrMask7
561 {
562  IncrMask7(unsigned /*x*/, int /*tx*/) {}
563  byte getMask() const
564  {
565  return 0;
566  }
567  void step() {}
568 };
569 
570 
571 /* Shift between source and destination pixel for LMMM command.
572  */
574 {
575  IncrShift4(unsigned sx, unsigned dx)
576  : shift(((dx - sx) & 1) * 4)
577  {
578  };
579  byte doShift(byte color) const
580  {
581  return (color >> shift) | (color << shift);
582  }
583 private:
584  const byte shift;
585 };
586 
588 {
589  IncrShift5(unsigned sx, unsigned dx)
590  : shift(((dx - sx) & 3) * 2)
591  {
592  };
593  byte doShift(byte color) const
594  {
595  return (color >> shift) | (color << (8 - shift));
596  }
597 private:
598  const byte shift;
599 };
600 
602 {
603  IncrShift7(unsigned /*sx*/, unsigned /*dx*/) {}
604  byte doShift(byte color) const
605  {
606  return color;
607  }
608 };
609 
610 
611 // Logical operations:
612 
613 struct DummyOp {
614  void operator()(EmuTime::param /*time*/, VDPVRAM& /*vram*/, unsigned /*addr*/,
615  byte /*src*/, byte /*color*/, byte /*mask*/) const
616  {
617  // Undefined logical operations do nothing.
618  }
619 };
620 
621 struct ImpOp {
622  void operator()(EmuTime::param time, VDPVRAM& vram, unsigned addr,
623  byte src, byte color, byte mask) const
624  {
625  vram.cmdWrite(addr, (src & mask) | color, time);
626  }
627 };
628 
629 struct AndOp {
630  void operator()(EmuTime::param time, VDPVRAM& vram, unsigned addr,
631  byte src, byte color, byte mask) const
632  {
633  vram.cmdWrite(addr, src & (color | mask), time);
634  }
635 };
636 
637 struct OrOp {
638  void operator()(EmuTime::param time, VDPVRAM& vram, unsigned addr,
639  byte src, byte color, byte /*mask*/) const
640  {
641  vram.cmdWrite(addr, src | color, time);
642  }
643 };
644 
645 struct XorOp {
646  void operator()(EmuTime::param time, VDPVRAM& vram, unsigned addr,
647  byte src, byte color, byte /*mask*/) const
648  {
649  vram.cmdWrite(addr, src ^ color, time);
650  }
651 };
652 
653 struct NotOp {
654  void operator()(EmuTime::param time, VDPVRAM& vram, unsigned addr,
655  byte src, byte color, byte mask) const
656  {
657  vram.cmdWrite(addr, (src & mask) | ~(color | mask), time);
658  }
659 };
660 
661 template <typename Op>
662 struct TransparentOp : Op {
663  void operator()(EmuTime::param time, VDPVRAM& vram, unsigned addr,
664  byte src, byte color, byte mask) const
665  {
666  // TODO does this skip the write or re-write the original value
667  // might make a difference in case the CPU has written
668  // the same address inbetween the command read and write
669  if (color) Op::operator()(time, vram, addr, src, color, mask);
670  }
671 };
677 
678 
679 // Commands
680 
683 struct AbortCmd : public VDPCmd
684 {
685  virtual void start(EmuTime::param time, VDPCmdEngine& engine);
686  virtual void execute(EmuTime::param limit, VDPCmdEngine& engine);
687 };
688 
690 {
691  engine.commandDone(time);
692 }
693 void AbortCmd::execute(EmuTime::param /*limit*/, VDPCmdEngine& /*engine*/)
694 {
695  UNREACHABLE;
696 }
697 
700 struct PointBaseCmd : public VDPCmd
701 {
702  virtual void start(EmuTime::param time, VDPCmdEngine& engine);
703 };
704 template<typename Mode> struct PointCmd : public PointBaseCmd
705 {
706  virtual void execute(EmuTime::param limit, VDPCmdEngine& engine);
707 };
708 
710 {
711  VDPVRAM& vram = engine.vram;
712  vram.cmdReadWindow.setMask(0x3FFFF, -1 << 18, time);
713  vram.cmdWriteWindow.disable(time);
714  engine.time = time; engine.nextAccessSlot(0);
715  engine.statusChangeTime = EmuTime::zero; // will finish soon
716 }
717 
718 template<typename Mode>
720 {
721  if (unlikely(engine.time >= limit)) return;
722 
723  VDPVRAM& vram = engine.vram;
724  bool srcExt = (engine.ARG & MXS) != 0;
725  bool doPoint = !srcExt || engine.hasExtendedVRAM;
726  engine.COL = likely(doPoint)
727  ? Mode::point(vram, engine.SX, engine.SY, srcExt)
728  : 0xFF;
729  engine.commandDone(engine.time);
730 }
731 
734 struct PsetBaseCmd : public VDPCmd
735 {
736  virtual void start(EmuTime::param time, VDPCmdEngine& engine);
737 };
738 template<typename Mode, typename LogOp> struct PsetCmd : public PsetBaseCmd
739 {
740  virtual void execute(EmuTime::param limit, VDPCmdEngine& engine);
741 };
742 
744 {
745  VDPVRAM& vram = engine.vram;
746  vram.cmdReadWindow.disable(time);
747  vram.cmdWriteWindow.setMask(0x3FFFF, -1 << 18, time);
748  engine.time = time; engine.nextAccessSlot(0);
749  engine.statusChangeTime = EmuTime::zero; // will finish soon
750  engine.phase = 0;
751 }
752 
753 template<typename Mode, typename LogOp>
755 {
756  VDPVRAM& vram = engine.vram;
757  bool dstExt = (engine.ARG & MXD) != 0;
758  bool doPset = !dstExt || engine.hasExtendedVRAM;
759  unsigned addr = Mode::addressOf(engine.DX, engine.DY, dstExt);
760 
761  switch (engine.phase) {
762  case 0:
763  if (unlikely(engine.time >= limit)) { engine.phase = 0; break; }
764  if (likely(doPset)) {
765  engine.tmpDst = vram.cmdWriteWindow.readNP(addr);
766  }
767  engine.nextAccessSlot(24); // TODO
768  // fall-through
769  case 1:
770  if (unlikely(engine.time >= limit)) { engine.phase = 1; break; }
771  if (likely(doPset)) {
772  byte col = engine.COL & Mode::COLOR_MASK;
773  Mode::pset(engine.time, vram, engine.DX, addr,
774  engine.tmpDst, col, LogOp());
775  }
776  engine.commandDone(engine.time);
777  break;
778  default:
779  UNREACHABLE;
780  }
781 }
782 
785 struct SrchBaseCmd : public VDPCmd
786 {
787  virtual void start(EmuTime::param time, VDPCmdEngine& engine);
788 };
789 template <typename Mode> struct SrchCmd : public SrchBaseCmd
790 {
791  virtual void execute(EmuTime::param limit, VDPCmdEngine& engine);
792 };
793 
795 {
796  VDPVRAM& vram = engine.vram;
797  vram.cmdReadWindow.setMask(0x3FFFF, -1 << 18, time);
798  vram.cmdWriteWindow.disable(time);
799  engine.ASX = engine.SX;
800  engine.time = time; engine.nextAccessSlot(0);
801  engine.statusChangeTime = EmuTime::zero; // we can find it any moment
802 }
803 
804 template <typename Mode>
806 {
807  VDPVRAM& vram = engine.vram;
808  byte CL = engine.COL & Mode::COLOR_MASK;
809  int TX = (engine.ARG & DIX) ? -1 : 1;
810  bool AEQ = (engine.ARG & EQ) != 0; // TODO: Do we look for "==" or "!="?
811 
812  // TODO use MXS or MXD here?
813  // datasheet says MXD but MXS seems more logical
814  bool srcExt = (engine.ARG & MXS) != 0;
815  bool doPoint = !srcExt || engine.hasExtendedVRAM;
816  auto calculator = engine.getSlotCalculator();
817 
818  while (engine.time < limit) {
819  byte p = likely(doPoint)
820  ? Mode::point(vram, engine.ASX, engine.SY, srcExt)
821  : 0xFF;
822  if ((p == CL) ^ AEQ) {
823  engine.status |= 0x10; // border detected
824  engine.commandDone(engine.time);
825  break;
826  }
827  if ((engine.ASX += TX) & Mode::PIXELS_PER_LINE) {
828  engine.status &= 0xEF; // border not detected
829  engine.commandDone(engine.time);
830  break;
831  }
832  engine.nextAccessSlot(calculator, 88); // TODO
833  }
834 }
835 
838 struct LineBaseCmd : public VDPCmd
839 {
840  virtual void start(EmuTime::param time, VDPCmdEngine& engine);
841 };
842 template <typename Mode, typename LogOp> struct LineCmd : public LineBaseCmd
843 {
844  virtual void execute(EmuTime::param limit, VDPCmdEngine& engine);
845 };
846 
848 {
849  VDPVRAM& vram = engine.vram;
850  vram.cmdReadWindow.disable(time);
851  vram.cmdWriteWindow.setMask(0x3FFFF, -1 << 18, time);
852  engine.NY &= 1023;
853  engine.ASX = ((engine.NX - 1) >> 1);
854  engine.ADX = engine.DX;
855  engine.ANX = 0;
856  engine.time = time; engine.nextAccessSlot(0);
857  engine.statusChangeTime = EmuTime::zero; // TODO can still be optimized
858  engine.phase = 0;
859 }
860 
861 template <typename Mode, typename LogOp>
863 {
864  // See doc/line-speed.txt for some background info on the timing.
865  VDPVRAM& vram = engine.vram;
866  byte CL = engine.COL & Mode::COLOR_MASK;
867  int TX = (engine.ARG & DIX) ? -1 : 1;
868  int TY = (engine.ARG & DIY) ? -1 : 1;
869  bool dstExt = (engine.ARG & MXD) != 0;
870  bool doPset = !dstExt || engine.hasExtendedVRAM;
871  unsigned addr = Mode::addressOf(engine.ADX, engine.DY, dstExt);
872  auto calculator = engine.getSlotCalculator();
873 
874  switch (engine.phase) {
875  case 0:
876 loop: if (unlikely(engine.time >= limit)) { engine.phase = 0; break; }
877  if (likely(doPset)) {
878  engine.tmpDst = vram.cmdWriteWindow.readNP(addr);
879  }
880  engine.nextAccessSlot(calculator, 24);
881  // fall-through
882  case 1: {
883  if (unlikely(engine.time >= limit)) { engine.phase = 1; break; }
884  if (likely(doPset)) {
885  Mode::pset(engine.time, vram, engine.ADX, addr,
886  engine.tmpDst, CL, LogOp());
887  }
888 
889  int ticks = 88;
890  if ((engine.ARG & MAJ) == 0) {
891  // X-Axis is major direction.
892  engine.ADX += TX;
893  // confirmed on real HW:
894  // - end-test happens before DY += TY
895  // - (ADX & PPL) test only happens after first pixel
896  // is drawn. And it does test with 'AND' (not with ==)
897  if (engine.ANX++ == engine.NX || (engine.ADX & Mode::PIXELS_PER_LINE)) {
898  engine.commandDone(engine.time);
899  break;
900  }
901  if (engine.ASX < engine.NY) {
902  engine.ASX += engine.NX;
903  engine.DY += TY;
904  ticks += 32;
905  }
906  engine.ASX -= engine.NY;
907  engine.ASX &= 1023; // mask to 10 bits range
908  } else {
909  // Y-Axis is major direction.
910  // confirmed on real HW: DY += TY happens before end-test
911  engine.DY += TY;
912  if (engine.ASX < engine.NY) {
913  engine.ASX += engine.NX;
914  engine.ADX += TX;
915  ticks += 32;
916  }
917  engine.ASX -= engine.NY;
918  engine.ASX &= 1023; // mask to 10 bits range
919  if (engine.ANX++ == engine.NX || (engine.ADX & Mode::PIXELS_PER_LINE)) {
920  engine.commandDone(engine.time);
921  break;
922  }
923  }
924  addr = Mode::addressOf(engine.ADX, engine.DY, dstExt);
925  engine.nextAccessSlot(calculator, ticks);
926  goto loop;
927  }
928  default:
929  UNREACHABLE;
930  }
931 }
932 
935 class BlockCmd : public VDPCmd
936 {
937 protected:
938  void calcFinishTime(VDPCmdEngine& engine,
939  unsigned NX, unsigned NY, unsigned ticksPerPixel);
940 };
941 
943  unsigned NX, unsigned NY, unsigned ticksPerPixel)
944 {
945  if (!engine.currentCommand) return;
946 
947  // Underestimation for when the command will be finished. This assumes
948  // we never have to wait for access slots and that there's no overhead
949  // per line.
950  auto t = VDP::VDPClock::duration(ticksPerPixel);
951  t *= ((NX * (NY - 1)) + engine.ANX);
952  engine.statusChangeTime = engine.time + t;
953 }
954 
957 template <typename Mode> struct LmmvBaseCmd : public BlockCmd
958 {
959  virtual void start(EmuTime::param time, VDPCmdEngine& engine);
960 };
961 template <typename Mode, typename LogOp> struct LmmvCmd : public LmmvBaseCmd<Mode>
962 {
963  virtual void execute(EmuTime::param limit, VDPCmdEngine& engine);
964 };
965 
966 template <typename Mode>
968 {
969  VDPVRAM& vram = engine.vram;
970  vram.cmdReadWindow.disable(time);
971  vram.cmdWriteWindow.setMask(0x3FFFF, -1 << 18, time);
972  engine.NY &= 1023;
973  unsigned NX = clipNX_1_pixel<Mode>(engine.DX, engine.NX, engine.ARG);
974  unsigned NY = clipNY_1(engine.DY, engine.NY, engine.ARG);
975  engine.ADX = engine.DX;
976  engine.ANX = NX;
977  engine.time = time; engine.nextAccessSlot(0);
978  calcFinishTime(engine, NX, NY, 72 + 24);
979  engine.phase = 0;
980 }
981 
982 template <typename Mode, typename LogOp>
984 {
985  VDPVRAM& vram = engine.vram;
986  engine.NY &= 1023;
987  unsigned NX = clipNX_1_pixel<Mode>(engine.DX, engine.NX, engine.ARG);
988  unsigned NY = clipNY_1(engine.DY, engine.NY, engine.ARG);
989  int TX = (engine.ARG & DIX) ? -1 : 1;
990  int TY = (engine.ARG & DIY) ? -1 : 1;
991  engine.ANX = clipNX_1_pixel<Mode>(engine.ADX, engine.ANX, engine.ARG);
992  byte CL = engine.COL & Mode::COLOR_MASK;
993  bool dstExt = (engine.ARG & MXD) != 0;
994  bool doPset = !dstExt || engine.hasExtendedVRAM;
995  unsigned addr = Mode::addressOf(engine.ADX, engine.DY, dstExt);
996  auto calculator = engine.getSlotCalculator();
997 
998  switch (engine.phase) {
999  case 0:
1000 loop: if (unlikely(engine.time >= limit)) { engine.phase = 0; break; }
1001  if (likely(doPset)) {
1002  engine.tmpDst = vram.cmdWriteWindow.readNP(addr);
1003  }
1004  engine.nextAccessSlot(calculator, 24);
1005  // fall-through
1006  case 1: {
1007  if (unlikely(engine.time >= limit)) { engine.phase = 1; break; }
1008  if (likely(doPset)) {
1009  Mode::pset(engine.time, vram, engine.ADX, addr,
1010  engine.tmpDst, CL, LogOp());
1011  }
1012  engine.ADX += TX;
1013  int ticks = 72;
1014  if (--engine.ANX == 0) {
1015  ticks += 64;
1016  engine.DY += TY; --(engine.NY);
1017  engine.ADX = engine.DX; engine.ANX = NX;
1018  if (--NY == 0) {
1019  engine.commandDone(engine.time);
1020  break;
1021  }
1022  }
1023  addr = Mode::addressOf(engine.ADX, engine.DY, dstExt);
1024  engine.nextAccessSlot(calculator, ticks);
1025  goto loop;
1026  }
1027  default:
1028  UNREACHABLE;
1029  }
1030  this->calcFinishTime(engine, NX, NY, 72 + 24);
1031 
1032  /*
1033  if (unlikely(dstExt)) {
1034  bool doPset = !dstExt || engine.hasExtendedVRAM;
1035  while (engine.time < limit) {
1036  if (likely(doPset)) {
1037  Mode::pset(engine.time, vram, engine.ADX, engine.DY,
1038  dstExt, CL, LogOp());
1039  }
1040  engine.time += delta;
1041  engine.ADX += TX;
1042  if (--engine.ANX == 0) {
1043  engine.DY += TY; --(engine.NY);
1044  engine.ADX = engine.DX; engine.ANX = NX;
1045  if (--NY == 0) {
1046  engine.commandDone(engine.time);
1047  break;
1048  }
1049  }
1050  }
1051  } else {
1052  // fast-path, no extended VRAM
1053  CL = Mode::duplicate(CL);
1054  while (engine.time < limit) {
1055  typename Mode::IncrPixelAddr dstAddr(engine.ADX, engine.DY, TX);
1056  typename Mode::IncrMask dstMask(engine.ADX, TX);
1057  EmuDuration dur = time - engine.time;
1058  unsigned num = (delta != EmuDuration::zero)
1059  ? std::min(dur.divUp(delta), engine.ANX)
1060  : engine.ANX;
1061  for (unsigned i = 0; i < num; ++i) {
1062  byte mask = dstMask.getMask();
1063  psetFast(engine.time, vram, dstAddr.getAddr(),
1064  CL & ~mask, mask, LogOp());
1065  engine.time += delta;
1066  dstAddr.step(TX);
1067  dstMask.step();
1068  }
1069  engine.ANX -= num;
1070  if (engine.ANX == 0) {
1071  engine.DY += TY;
1072  engine.NY -= 1;
1073  engine.ADX = engine.DX;
1074  engine.ANX = NX;
1075  if (--NY == 0) {
1076  engine.commandDone(engine.time);
1077  break;
1078  }
1079  } else {
1080  engine.ADX += num * TX;
1081  assert(engine.time >= limit);
1082  break;
1083  }
1084  }
1085  }
1086  */
1087 }
1088 
1091 template <typename Mode> struct LmmmBaseCmd : public BlockCmd
1092 {
1093  virtual void start(EmuTime::param time, VDPCmdEngine& engine);
1094 };
1095 template <typename Mode, typename LogOp> struct LmmmCmd : public LmmmBaseCmd<Mode>
1096 {
1097  virtual void execute(EmuTime::param limit, VDPCmdEngine& engine);
1098 };
1099 
1100 template <typename Mode>
1102 {
1103  VDPVRAM& vram = engine.vram;
1104  vram.cmdReadWindow.setMask(0x3FFFF, -1 << 18, time);
1105  vram.cmdWriteWindow.setMask(0x3FFFF, -1 << 18, time);
1106  engine.NY &= 1023;
1107  unsigned NX = clipNX_2_pixel<Mode>(
1108  engine.SX, engine.DX, engine.NX, engine.ARG );
1109  unsigned NY = clipNY_2(engine.SY, engine.DY, engine.NY, engine.ARG);
1110  engine.ASX = engine.SX;
1111  engine.ADX = engine.DX;
1112  engine.ANX = NX;
1113  engine.time = time; engine.nextAccessSlot(0);
1114  calcFinishTime(engine, NX, NY, 64 + 32 + 24);
1115  engine.phase = 0;
1116 }
1117 
1118 template <typename Mode, typename LogOp>
1120 {
1121  VDPVRAM& vram = engine.vram;
1122  engine.NY &= 1023;
1123  unsigned NX = clipNX_2_pixel<Mode>(
1124  engine.SX, engine.DX, engine.NX, engine.ARG );
1125  unsigned NY = clipNY_2(engine.SY, engine.DY, engine.NY, engine.ARG);
1126  int TX = (engine.ARG & DIX) ? -1 : 1;
1127  int TY = (engine.ARG & DIY) ? -1 : 1;
1128  engine.ANX = clipNX_2_pixel<Mode>(engine.ASX, engine.ADX, engine.ANX, engine.ARG);
1129  bool srcExt = (engine.ARG & MXS) != 0;
1130  bool dstExt = (engine.ARG & MXD) != 0;
1131  bool doPoint = !srcExt || engine.hasExtendedVRAM;
1132  bool doPset = !dstExt || engine.hasExtendedVRAM;
1133  unsigned dstAddr = Mode::addressOf(engine.ADX, engine.DY, dstExt);
1134  auto calculator = engine.getSlotCalculator();
1135 
1136  switch (engine.phase) {
1137  case 0:
1138 loop: if (unlikely(engine.time >= limit)) { engine.phase = 0; break; }
1139  engine.tmpSrc = likely(doPoint)
1140  ? Mode::point(vram, engine.ASX, engine.SY, srcExt)
1141  : 0xFF;
1142  engine.nextAccessSlot(calculator, 32);
1143  // fall-through
1144  case 1:
1145  if (unlikely(engine.time >= limit)) { engine.phase = 1; break; }
1146  if (likely(doPset)) {
1147  engine.tmpDst = vram.cmdWriteWindow.readNP(dstAddr);
1148  }
1149  engine.nextAccessSlot(calculator, 24);
1150  // fall-through
1151  case 2: {
1152  if (unlikely(engine.time >= limit)) { engine.phase = 2; break; }
1153  if (likely(doPset)) {
1154  Mode::pset(engine.time, vram, engine.ADX, dstAddr,
1155  engine.tmpDst, engine.tmpSrc, LogOp());
1156  }
1157  engine.ASX += TX; engine.ADX += TX;
1158  int ticks = 64;
1159  if (--engine.ANX == 0) {
1160  ticks += 64;
1161  engine.SY += TY; engine.DY += TY; --(engine.NY);
1162  engine.ASX = engine.SX; engine.ADX = engine.DX; engine.ANX = NX;
1163  if (--NY == 0) {
1164  engine.commandDone(engine.time);
1165  break;
1166  }
1167  }
1168  dstAddr = Mode::addressOf(engine.ADX, engine.DY, dstExt);
1169  engine.nextAccessSlot(calculator, ticks);
1170  goto loop;
1171  }
1172  default:
1173  UNREACHABLE;
1174  }
1175  this->calcFinishTime(engine, NX, NY, 64 + 32 + 24);
1176 
1177  /*if (unlikely(srcExt) || unlikely(dstExt)) {
1178  bool doPoint = !srcExt || engine.hasExtendedVRAM;
1179  bool doPset = !dstExt || engine.hasExtendedVRAM;
1180  while (engine.time < limit) {
1181  if (likely(doPset)) {
1182  byte p = likely(doPoint)
1183  ? Mode::point(vram, engine.ASX, engine.SY, srcExt)
1184  : 0xFF;
1185  Mode::pset(engine.time, vram, engine.ADX, engine.DY,
1186  dstExt, p, LogOp());
1187  }
1188  engine.time += delta;
1189  engine.ASX += TX; engine.ADX += TX;
1190  if (--engine.ANX == 0) {
1191  engine.SY += TY; engine.DY += TY; --(engine.NY);
1192  engine.ASX = engine.SX; engine.ADX = engine.DX; engine.ANX = NX;
1193  if (--NY == 0) {
1194  engine.commandDone(engine.time);
1195  break;
1196  }
1197  }
1198  }
1199  } else {
1200  // fast-path, no extended VRAM
1201  while (engine.time < limit) {
1202  typename Mode::IncrPixelAddr srcAddr(engine.ASX, engine.SY, TX);
1203  typename Mode::IncrPixelAddr dstAddr(engine.ADX, engine.DY, TX);
1204  typename Mode::IncrMask dstMask(engine.ADX, TX);
1205  typename Mode::IncrShift shift (engine.ASX, engine.ADX);
1206  EmuDuration dur = limit - engine.time;
1207  unsigned num = (delta != EmuDuration::zero)
1208  ? std::min(dur.divUp(delta), engine.ANX)
1209  : engine.ANX;
1210  for (unsigned i = 0; i < num; ++i) {
1211  byte p = vram.cmdReadWindow.readNP(srcAddr.getAddr());
1212  p = shift.doShift(p);
1213  byte mask = dstMask.getMask();
1214  psetFast(engine.time, vram, dstAddr.getAddr(),
1215  p & ~mask, mask, LogOp());
1216  engine.time += delta;
1217  srcAddr.step(TX);
1218  dstAddr.step(TX);
1219  dstMask.step();
1220  }
1221  engine.ANX -= num;
1222  if (engine.ANX == 0) {
1223  engine.SY += TY;
1224  engine.DY += TY;
1225  engine.NY -= 1;
1226  engine.ASX = engine.SX;
1227  engine.ADX = engine.DX;
1228  engine.ANX = NX;
1229  if (--NY == 0) {
1230  engine.commandDone(engine.time);
1231  break;
1232  }
1233  } else {
1234  engine.ASX += num * TX;
1235  engine.ADX += num * TX;
1236  assert(engine.time >= limit);
1237  break;
1238  }
1239  }
1240  }
1241  */
1242 }
1243 
1246 template <typename Mode> struct LmcmCmd : public BlockCmd
1247 {
1248  virtual void start(EmuTime::param time, VDPCmdEngine& engine);
1249  virtual void execute(EmuTime::param limit, VDPCmdEngine& engine);
1250 };
1251 
1252 template <typename Mode>
1254 {
1255  VDPVRAM& vram = engine.vram;
1256  vram.cmdReadWindow.setMask(0x3FFFF, -1 << 18, time);
1257  vram.cmdWriteWindow.disable(time);
1258  engine.NY &= 1023;
1259  unsigned NX = clipNX_1_pixel<Mode>(engine.SX, engine.NX, engine.ARG);
1260  engine.ASX = engine.SX;
1261  engine.ANX = NX;
1262  engine.transfer = true;
1263  engine.status |= 0x80;
1264  engine.time = time; engine.nextAccessSlot(0);
1265  engine.statusChangeTime = EmuTime::zero;
1266 }
1267 
1268 template <typename Mode>
1270 {
1271  if (!engine.transfer) return;
1272  if (unlikely(engine.time >= limit)) return;
1273 
1274  VDPVRAM& vram = engine.vram;
1275  engine.NY &= 1023;
1276  unsigned NX = clipNX_1_pixel<Mode>(engine.SX, engine.NX, engine.ARG);
1277  unsigned NY = clipNY_1(engine.SY, engine.NY, engine.ARG);
1278  int TX = (engine.ARG & DIX) ? -1 : 1;
1279  int TY = (engine.ARG & DIY) ? -1 : 1;
1280  engine.ANX = clipNX_1_pixel<Mode>(engine.ASX, engine.ANX, engine.ARG);
1281  bool srcExt = (engine.ARG & MXS) != 0;
1282  bool doPoint = !srcExt || engine.hasExtendedVRAM;
1283 
1284  // TODO we should (most likely) perform the actual read earlier and
1285  // buffer it, and on a CPU-IO-read start the next read (just like how
1286  // regular reading from VRAM works).
1287  engine.COL = likely(doPoint)
1288  ? Mode::point(vram, engine.ASX, engine.SY, srcExt)
1289  : 0xFF;
1290  engine.transfer = false;
1291  engine.ASX += TX; --engine.ANX;
1292  if (engine.ANX == 0) {
1293  engine.SY += TY; --(engine.NY);
1294  engine.ASX = engine.SX; engine.ANX = NX;
1295  if (--NY == 0) {
1296  engine.commandDone(engine.time);
1297  }
1298  }
1299  engine.time = limit; engine.nextAccessSlot(0); // TODO
1300 }
1301 
1304 template <typename Mode> struct LmmcBaseCmd : public BlockCmd
1305 {
1306  virtual void start(EmuTime::param time, VDPCmdEngine& engine);
1307 };
1308 template <typename Mode, typename LogOp> struct LmmcCmd : public LmmcBaseCmd<Mode>
1309 {
1310  virtual void execute(EmuTime::param limit, VDPCmdEngine& engine);
1311 };
1312 
1313 template <typename Mode>
1315 {
1316  VDPVRAM& vram = engine.vram;
1317  vram.cmdReadWindow.disable(time);
1318  vram.cmdWriteWindow.setMask(0x3FFFF, -1 << 18, time);
1319  engine.NY &= 1023;
1320  unsigned NX = clipNX_1_pixel<Mode>(engine.DX, engine.NX, engine.ARG);
1321  engine.ADX = engine.DX;
1322  engine.ANX = NX;
1323  engine.statusChangeTime = EmuTime::zero;
1324  engine.transfer = true;
1325  engine.status |= 0x80;
1326  engine.time = time; engine.nextAccessSlot(0);
1327 }
1328 
1329 template <typename Mode, typename LogOp>
1331 {
1332  VDPVRAM& vram = engine.vram;
1333  engine.NY &= 1023;
1334  unsigned NX = clipNX_1_pixel<Mode>(engine.DX, engine.NX, engine.ARG);
1335  unsigned NY = clipNY_1(engine.DY, engine.NY, engine.ARG);
1336  int TX = (engine.ARG & DIX) ? -1 : 1;
1337  int TY = (engine.ARG & DIY) ? -1 : 1;
1338  engine.ANX = clipNX_1_pixel<Mode>(engine.ADX, engine.ANX, engine.ARG);
1339  bool dstExt = (engine.ARG & MXD) != 0;
1340  bool doPset = !dstExt || engine.hasExtendedVRAM;
1341 
1342  if (engine.transfer) {
1343  byte col = engine.COL & Mode::COLOR_MASK;
1344  // TODO: timing is inaccurate, this executes the read and write
1345  // in the same access slot. Instead we should
1346  // - wait for a byte
1347  // - in next access slot read
1348  // - in next access slot write
1349  if (likely(doPset)) {
1350  unsigned addr = Mode::addressOf(engine.ADX, engine.DY, dstExt);
1351  engine.tmpDst = vram.cmdWriteWindow.readNP(addr);
1352  Mode::pset(limit, vram, engine.ADX, addr,
1353  engine.tmpDst, col, LogOp());
1354  }
1355  // Execution is emulated as instantaneous, so don't bother
1356  // with the timing.
1357  // Note: Correct timing would require currentTime to be set
1358  // to the moment transfer becomes true.
1359  engine.transfer = false;
1360 
1361  engine.ADX += TX; --engine.ANX;
1362  if (engine.ANX == 0) {
1363  engine.DY += TY; --(engine.NY);
1364  engine.ADX = engine.DX; engine.ANX = NX;
1365  if (--NY == 0) {
1366  engine.commandDone(limit);
1367  }
1368  }
1369  }
1370 }
1371 
1374 template <typename Mode> struct HmmvCmd : public BlockCmd
1375 {
1376  virtual void start(EmuTime::param time, VDPCmdEngine& engine);
1377  virtual void execute(EmuTime::param limit, VDPCmdEngine& engine);
1378 };
1379 
1380 template <typename Mode>
1382 {
1383  VDPVRAM& vram = engine.vram;
1384  vram.cmdReadWindow.disable(time);
1385  vram.cmdWriteWindow.setMask(0x3FFFF, -1 << 18, time);
1386  engine.NY &= 1023;
1387  unsigned NX = clipNX_1_byte<Mode>(engine.DX, engine.NX, engine.ARG);
1388  unsigned NY = clipNY_1(engine.DY, engine.NY, engine.ARG);
1389  engine.ADX = engine.DX;
1390  engine.ANX = NX;
1391  engine.time = time; engine.nextAccessSlot(0);
1392  calcFinishTime(engine, NX, NY, 48);
1393 }
1394 
1395 template <typename Mode>
1397 {
1398  VDPVRAM& vram = engine.vram;
1399  engine.NY &= 1023;
1400  unsigned NX = clipNX_1_byte<Mode>(engine.DX, engine.NX, engine.ARG);
1401  unsigned NY = clipNY_1(engine.DY, engine.NY, engine.ARG);
1402  int TX = (engine.ARG & DIX)
1403  ? -Mode::PIXELS_PER_BYTE : Mode::PIXELS_PER_BYTE;
1404  int TY = (engine.ARG & DIY) ? -1 : 1;
1405  engine.ANX = clipNX_1_byte<Mode>(
1406  engine.ADX, engine.ANX << Mode::PIXELS_PER_BYTE_SHIFT, engine.ARG );
1407  bool dstExt = (engine.ARG & MXD) != 0;
1408  bool doPset = !dstExt || engine.hasExtendedVRAM;
1409  auto calculator = engine.getSlotCalculator();
1410 
1411  while (engine.time < limit) {
1412  if (likely(doPset)) {
1413  vram.cmdWrite(Mode::addressOf(engine.ADX, engine.DY, dstExt),
1414  engine.COL, engine.time);
1415  }
1416  engine.ADX += TX;
1417  int ticks = 48;
1418  if (--engine.ANX == 0) {
1419  ticks += 56;
1420  engine.DY += TY; --(engine.NY);
1421  engine.ADX = engine.DX; engine.ANX = NX;
1422  if (--NY == 0) {
1423  engine.commandDone(engine.time);
1424  break;
1425  }
1426  }
1427  engine.nextAccessSlot(calculator, ticks);
1428  }
1429  calcFinishTime(engine, NX, NY, 48);
1430 
1431  /*if (unlikely(dstExt)) {
1432  bool doPset = !dstExt || engine.hasExtendedVRAM;
1433  while (engine.time < limit) {
1434  if (likely(doPset)) {
1435  vram.cmdWrite(Mode::addressOf(engine.ADX, engine.DY, dstExt),
1436  engine.COL, engine.time);
1437  }
1438  engine.time += delta;
1439  engine.ADX += TX;
1440  if (--engine.ANX == 0) {
1441  engine.DY += TY; --(engine.NY);
1442  engine.ADX = engine.DX; engine.ANX = NX;
1443  if (--NY == 0) {
1444  engine.commandDone(engine.time);
1445  break;
1446  }
1447  }
1448  }
1449  } else {
1450  // fast-path, no extended VRAM
1451  while (engine.time < limit) {
1452  typename Mode::IncrByteAddr dstAddr(engine.ADX, engine.DY, TX);
1453  EmuDuration dur = limit - engine.time;
1454  unsigned num = (delta != EmuDuration::zero)
1455  ? std::min(dur.divUp(delta), engine.ANX)
1456  : engine.ANX;
1457  for (unsigned i = 0; i < num; ++i) {
1458  vram.cmdWrite(dstAddr.getAddr(), engine.COL,
1459  engine.time);
1460  engine.time += delta;
1461  dstAddr.step(TX);
1462  }
1463  engine.ANX -= num;
1464  if (engine.ANX == 0) {
1465  engine.DY += TY;
1466  engine.NY -= 1;
1467  engine.ADX = engine.DX;
1468  engine.ANX = NX;
1469  if (--NY == 0) {
1470  engine.commandDone(engine.time);
1471  break;
1472  }
1473  } else {
1474  engine.ADX += num * TX;
1475  assert(engine.time >= limit);
1476  break;
1477  }
1478  }
1479  }
1480  */
1481 }
1482 
1485 template <typename Mode> struct HmmmCmd : public BlockCmd
1486 {
1487  virtual void start(EmuTime::param time, VDPCmdEngine& engine);
1488  virtual void execute(EmuTime::param limit, VDPCmdEngine& engine);
1489 };
1490 
1491 template <typename Mode>
1493 {
1494  VDPVRAM& vram = engine.vram;
1495  vram.cmdReadWindow.setMask(0x3FFFF, -1 << 18, time);
1496  vram.cmdWriteWindow.setMask(0x3FFFF, -1 << 18, time);
1497  engine.NY &= 1023;
1498  unsigned NX = clipNX_2_byte<Mode>(
1499  engine.SX, engine.DX, engine.NX, engine.ARG );
1500  unsigned NY = clipNY_2(engine.SY, engine.DY, engine.NY, engine.ARG);
1501  engine.ASX = engine.SX;
1502  engine.ADX = engine.DX;
1503  engine.ANX = NX;
1504  engine.time = time; engine.nextAccessSlot(0);
1505  calcFinishTime(engine, NX, NY, 24 + 64);
1506  engine.phase = 0;
1507 }
1508 
1509 template <typename Mode>
1511 {
1512  VDPVRAM& vram = engine.vram;
1513  engine.NY &= 1023;
1514  unsigned NX = clipNX_2_byte<Mode>(
1515  engine.SX, engine.DX, engine.NX, engine.ARG);
1516  unsigned NY = clipNY_2(engine.SY, engine.DY, engine.NY, engine.ARG);
1517  int TX = (engine.ARG & DIX)
1518  ? -Mode::PIXELS_PER_BYTE : Mode::PIXELS_PER_BYTE;
1519  int TY = (engine.ARG & DIY) ? -1 : 1;
1520  engine.ANX = clipNX_2_byte<Mode>(
1521  engine.ASX, engine.ADX, engine.ANX << Mode::PIXELS_PER_BYTE_SHIFT, engine.ARG );
1522  bool srcExt = (engine.ARG & MXS) != 0;
1523  bool dstExt = (engine.ARG & MXD) != 0;
1524  bool doPoint = !srcExt || engine.hasExtendedVRAM;
1525  bool doPset = !dstExt || engine.hasExtendedVRAM;
1526  auto calculator = engine.getSlotCalculator();
1527 
1528  switch (engine.phase) {
1529  case 0:
1530 loop: if (unlikely(engine.time >= limit)) { engine.phase = 0; break; }
1531  engine.tmpSrc = likely(doPoint)
1532  ? vram.cmdReadWindow.readNP(
1533  Mode::addressOf(engine.ASX, engine.SY, srcExt))
1534  : 0xFF;
1535  engine.nextAccessSlot(calculator, 24);
1536  // fall-through
1537  case 1: {
1538  if (unlikely(engine.time >= limit)) { engine.phase = 1; break; }
1539  if (likely(doPset)) {
1540  vram.cmdWrite(Mode::addressOf(engine.ADX, engine.DY, dstExt),
1541  engine.tmpSrc, engine.time);
1542  }
1543  engine.ASX += TX; engine.ADX += TX;
1544  int ticks = 64;
1545  if (--engine.ANX == 0) {
1546  ticks += 64;
1547  engine.SY += TY; engine.DY += TY; --(engine.NY);
1548  engine.ASX = engine.SX; engine.ADX = engine.DX; engine.ANX = NX;
1549  if (--NY == 0) {
1550  engine.commandDone(engine.time);
1551  break;
1552  }
1553  }
1554  engine.nextAccessSlot(calculator, ticks);
1555  goto loop;
1556  }
1557  default:
1558  UNREACHABLE;
1559  }
1560 
1561  calcFinishTime(engine, NX, NY, 24 + 64);
1562 
1563  /*if (unlikely(srcExt || dstExt)) {
1564  bool doPoint = !srcExt || engine.hasExtendedVRAM;
1565  bool doPset = !dstExt || engine.hasExtendedVRAM;
1566  while (engine.time < limit) {
1567  if (likely(doPset)) {
1568  byte p = likely(doPoint)
1569  ? vram.cmdReadWindow.readNP(
1570  Mode::addressOf(engine.ASX, engine.SY, srcExt))
1571  : 0xFF;
1572  vram.cmdWrite(Mode::addressOf(engine.ADX, engine.DY, dstExt),
1573  p, engine.time);
1574  }
1575  engine.time += delta;
1576  engine.ASX += TX; engine.ADX += TX;
1577  if (--engine.ANX == 0) {
1578  engine.SY += TY; engine.DY += TY; --(engine.NY);
1579  engine.ASX = engine.SX; engine.ADX = engine.DX; engine.ANX = NX;
1580  if (--NY == 0) {
1581  engine.commandDone(engine.time);
1582  break;
1583  }
1584  }
1585  }
1586  } else {
1587  // fast-path, no extended VRAM
1588  while (engine.time < limit) {
1589  typename Mode::IncrByteAddr srcAddr(engine.ASX, engine.SY, TX);
1590  typename Mode::IncrByteAddr dstAddr(engine.ADX, engine.DY, TX);
1591  EmuDuration dur = limit - engine.time;
1592  unsigned num = (delta != EmuDuration::zero)
1593  ? std::min(dur.divUp(delta), engine.ANX)
1594  : engine.ANX;
1595  for (unsigned i = 0; i < num; ++i) {
1596  byte p = vram.cmdReadWindow.readNP(srcAddr.getAddr());
1597  vram.cmdWrite(dstAddr.getAddr(), p, engine.time);
1598  engine.time += delta;
1599  srcAddr.step(TX);
1600  dstAddr.step(TX);
1601  }
1602  engine.ANX -= num;
1603  if (engine.ANX == 0) {
1604  engine.SY += TY;
1605  engine.DY += TY;
1606  engine.NY -= 1;
1607  engine.ASX = engine.SX;
1608  engine.ADX = engine.DX;
1609  engine.ANX = NX;
1610  if (--NY == 0) {
1611  engine.commandDone(engine.time);
1612  break;
1613  }
1614  } else {
1615  engine.ASX += num * TX;
1616  engine.ADX += num * TX;
1617  assert(engine.time >= limit);
1618  break;
1619  }
1620  }
1621  }
1622  */
1623 }
1624 
1627 template <typename Mode> struct YmmmCmd : public BlockCmd
1628 {
1629  virtual void start(EmuTime::param time, VDPCmdEngine& engine);
1630  virtual void execute(EmuTime::param limit, VDPCmdEngine& engine);
1631 };
1632 
1633 template <typename Mode>
1635 {
1636  VDPVRAM& vram = engine.vram;
1637  vram.cmdReadWindow.setMask(0x3FFFF, -1 << 18, time);
1638  vram.cmdWriteWindow.setMask(0x3FFFF, -1 << 18, time);
1639  engine.NY &= 1023;
1640  unsigned NX = clipNX_1_byte<Mode>(engine.DX, 512, engine.ARG);
1641  // large enough so that it gets clipped
1642  unsigned NY = clipNY_2(engine.SY, engine.DY, engine.NY, engine.ARG);
1643  engine.ADX = engine.DX;
1644  engine.ANX = NX;
1645  engine.time = time; engine.nextAccessSlot(0);
1646  calcFinishTime(engine, NX, NY, 24 + 40);
1647  engine.phase = 0;
1648 }
1649 
1650 template <typename Mode>
1652 {
1653  VDPVRAM& vram = engine.vram;
1654  engine.NY &= 1023;
1655  unsigned NX = clipNX_1_byte<Mode>(engine.DX, 512, engine.ARG);
1656  // large enough so that it gets clipped
1657  unsigned NY = clipNY_2(engine.SY, engine.DY, engine.NY, engine.ARG);
1658  int TX = (engine.ARG & DIX)
1659  ? -Mode::PIXELS_PER_BYTE : Mode::PIXELS_PER_BYTE;
1660  int TY = (engine.ARG & DIY) ? -1 : 1;
1661  engine.ANX = clipNX_1_byte<Mode>(engine.ADX, 512, engine.ARG);
1662 
1663  // TODO does this use MXD for both read and write?
1664  // it says so in the datasheet, but it seems unlogical
1665  // OTOH YMMM also uses DX for both read and write
1666  bool dstExt = (engine.ARG & MXD) != 0;
1667  bool doPset = !dstExt || engine.hasExtendedVRAM;
1668  auto calculator = engine.getSlotCalculator();
1669 
1670  switch (engine.phase) {
1671  case 0:
1672 loop: if (unlikely(engine.time >= limit)) { engine.phase = 0; break; }
1673  if (likely(doPset)) {
1674  engine.tmpSrc = vram.cmdReadWindow.readNP(
1675  Mode::addressOf(engine.ADX, engine.SY, dstExt));
1676  }
1677  engine.nextAccessSlot(calculator, 24);
1678  // fall-through
1679  case 1:
1680  if (unlikely(engine.time >= limit)) { engine.phase = 1; break; }
1681  if (likely(doPset)) {
1682  vram.cmdWrite(Mode::addressOf(engine.ADX, engine.DY, dstExt),
1683  engine.tmpSrc, engine.time);
1684  }
1685  engine.ADX += TX;
1686  if (--engine.ANX == 0) {
1687  // note: going to the next line does not take extra time
1688  engine.SY += TY; engine.DY += TY; --(engine.NY);
1689  engine.ADX = engine.DX; engine.ANX = NX;
1690  if (--NY == 0) {
1691  engine.commandDone(engine.time);
1692  break;
1693  }
1694  }
1695  engine.nextAccessSlot(calculator, 40);
1696  goto loop;
1697  default:
1698  UNREACHABLE;
1699  }
1700 
1701  calcFinishTime(engine, NX, NY, 24 + 40);
1702 
1703  /*
1704  if (unlikely(dstExt)) {
1705  bool doPset = !dstExt || engine.hasExtendedVRAM;
1706  while (engine.time < limit) {
1707  if (likely(doPset)) {
1708  byte p = vram.cmdReadWindow.readNP(
1709  Mode::addressOf(engine.ADX, engine.SY, dstExt));
1710  vram.cmdWrite(Mode::addressOf(engine.ADX, engine.DY, dstExt),
1711  p, engine.time);
1712  }
1713  engine.time += delta;
1714  engine.ADX += TX;
1715  if (--engine.ANX == 0) {
1716  engine.SY += TY; engine.DY += TY; --(engine.NY);
1717  engine.ADX = engine.DX; engine.ANX = NX;
1718  if (--NY == 0) {
1719  engine.commandDone(engine.time);
1720  break;
1721  }
1722  }
1723  }
1724  } else {
1725  // fast-path, no extended VRAM
1726  while (engine.time < limit) {
1727  typename Mode::IncrByteAddr srcAddr(engine.ADX, engine.SY, TX);
1728  typename Mode::IncrByteAddr dstAddr(engine.ADX, engine.DY, TX);
1729  EmuDuration dur = limit - engine.time;
1730  unsigned num = (delta != EmuDuration::zero)
1731  ? std::min(dur.divUp(delta), engine.ANX)
1732  : engine.ANX;
1733  for (unsigned i = 0; i < num; ++i) {
1734  byte p = vram.cmdReadWindow.readNP(srcAddr.getAddr());
1735  vram.cmdWrite(dstAddr.getAddr(), p, engine.time);
1736  engine.time += delta;
1737  srcAddr.step(TX);
1738  dstAddr.step(TX);
1739  }
1740  engine.ANX -= num;
1741  if (engine.ANX == 0) {
1742  engine.SY += TY;
1743  engine.DY += TY;
1744  engine.NY -= 1;
1745  engine.ADX = engine.DX;
1746  engine.ANX = NX;
1747  if (--NY == 0) {
1748  engine.commandDone(engine.time);
1749  break;
1750  }
1751  } else {
1752  engine.ADX += num * TX;
1753  assert(engine.time >= limit);
1754  break;
1755  }
1756  }
1757  }
1758  */
1759 }
1760 
1763 template <typename Mode> struct HmmcCmd : public BlockCmd
1764 {
1765  virtual void start(EmuTime::param time, VDPCmdEngine& engine);
1766  virtual void execute(EmuTime::param limit, VDPCmdEngine& engine);
1767 };
1768 
1769 template <typename Mode>
1771 {
1772  VDPVRAM& vram = engine.vram;
1773  vram.cmdReadWindow.disable(time);
1774  vram.cmdWriteWindow.setMask(0x3FFFF, -1 << 18, time);
1775  engine.NY &= 1023;
1776  unsigned NX = clipNX_1_byte<Mode>(engine.DX, engine.NX, engine.ARG);
1777  engine.ADX = engine.DX;
1778  engine.ANX = NX;
1779  engine.statusChangeTime = EmuTime::zero;
1780  engine.transfer = true;
1781  engine.status |= 0x80;
1782  engine.time = time; engine.nextAccessSlot(0);
1783 }
1784 
1785 template <typename Mode>
1787 {
1788  VDPVRAM& vram = engine.vram;
1789  engine.NY &= 1023;
1790  unsigned NX = clipNX_1_byte<Mode>(engine.DX, engine.NX, engine.ARG);
1791  unsigned NY = clipNY_1(engine.DY, engine.NY, engine.ARG);
1792  int TX = (engine.ARG & DIX)
1793  ? -Mode::PIXELS_PER_BYTE : Mode::PIXELS_PER_BYTE;
1794  int TY = (engine.ARG & DIY) ? -1 : 1;
1795  engine.ANX = clipNX_1_byte<Mode>(
1796  engine.ADX, engine.ANX << Mode::PIXELS_PER_BYTE_SHIFT, engine.ARG );
1797  bool dstExt = (engine.ARG & MXD) != 0;
1798  bool doPset = !dstExt || engine.hasExtendedVRAM;
1799 
1800  if (engine.transfer) {
1801  // TODO: timing is inaccurate. We should
1802  // - wait for a byte
1803  // - on the next access slot write that byte
1804  if (likely(doPset)) {
1805  vram.cmdWrite(Mode::addressOf(engine.ADX, engine.DY, dstExt),
1806  engine.COL, limit);
1807  }
1808  engine.transfer = false;
1809 
1810  engine.ADX += TX; --engine.ANX;
1811  if (engine.ANX == 0) {
1812  engine.DY += TY; --(engine.NY);
1813  engine.ADX = engine.DX; engine.ANX = NX;
1814  if (--NY == 0) {
1815  engine.commandDone(limit);
1816  }
1817  }
1818  }
1819 }
1820 
1821 
1822 // Construction and destruction:
1823 
1824 template <template <typename Mode> class Command>
1825 void VDPCmdEngine::createHEngines(unsigned cmd)
1826 {
1827  commands[cmd + 0][0] = new Command<Graphic4Mode>();
1828  commands[cmd + 0][1] = new Command<Graphic5Mode>();
1829  commands[cmd + 0][2] = new Command<Graphic6Mode>();
1830  commands[cmd + 0][3] = new Command<Graphic7Mode>();
1831  for (int i = 1; i < 16; ++i) {
1832  for (int j = 0; j < 4; ++j) {
1833  commands[cmd + i][j] = commands[cmd + 0][j];
1834  }
1835  }
1836 }
1837 void VDPCmdEngine::deleteHEngines(unsigned cmd)
1838 {
1839  for (int j = 0; j < 4; ++j) {
1840  delete commands[cmd + 0][j];
1841  }
1842 }
1843 
1844 template <template <typename Mode, typename LopOp> class Command>
1845 void VDPCmdEngine::createLEngines(unsigned cmd, VDPCmd* dummy)
1846 {
1847  commands[cmd + 0][0] = new Command<Graphic4Mode, ImpOp >();
1848  commands[cmd + 0][1] = new Command<Graphic5Mode, ImpOp >();
1849  commands[cmd + 0][2] = new Command<Graphic6Mode, ImpOp >();
1850  commands[cmd + 0][3] = new Command<Graphic7Mode, ImpOp >();
1851 
1852  commands[cmd + 1][0] = new Command<Graphic4Mode, AndOp >();
1853  commands[cmd + 1][1] = new Command<Graphic5Mode, AndOp >();
1854  commands[cmd + 1][2] = new Command<Graphic6Mode, AndOp >();
1855  commands[cmd + 1][3] = new Command<Graphic7Mode, AndOp >();
1856 
1857  commands[cmd + 2][0] = new Command<Graphic4Mode, OrOp >();
1858  commands[cmd + 2][1] = new Command<Graphic5Mode, OrOp >();
1859  commands[cmd + 2][2] = new Command<Graphic6Mode, OrOp >();
1860  commands[cmd + 2][3] = new Command<Graphic7Mode, OrOp >();
1861 
1862  commands[cmd + 3][0] = new Command<Graphic4Mode, XorOp >();
1863  commands[cmd + 3][1] = new Command<Graphic5Mode, XorOp >();
1864  commands[cmd + 3][2] = new Command<Graphic6Mode, XorOp >();
1865  commands[cmd + 3][3] = new Command<Graphic7Mode, XorOp >();
1866 
1867  commands[cmd + 4][0] = new Command<Graphic4Mode, NotOp >();
1868  commands[cmd + 4][1] = new Command<Graphic5Mode, NotOp >();
1869  commands[cmd + 4][2] = new Command<Graphic6Mode, NotOp >();
1870  commands[cmd + 4][3] = new Command<Graphic7Mode, NotOp >();
1871 
1872  commands[cmd + 5][0] = dummy;
1873  commands[cmd + 5][1] = dummy;
1874  commands[cmd + 5][2] = dummy;
1875  commands[cmd + 5][3] = dummy;
1876 
1877  commands[cmd + 6][0] = dummy;
1878  commands[cmd + 6][1] = dummy;
1879  commands[cmd + 6][2] = dummy;
1880  commands[cmd + 6][3] = dummy;
1881 
1882  commands[cmd + 7][0] = dummy;
1883  commands[cmd + 7][1] = dummy;
1884  commands[cmd + 7][2] = dummy;
1885  commands[cmd + 7][3] = dummy;
1886 
1887  commands[cmd + 8][0] = new Command<Graphic4Mode, TImpOp >();
1888  commands[cmd + 8][1] = new Command<Graphic5Mode, TImpOp >();
1889  commands[cmd + 8][2] = new Command<Graphic6Mode, TImpOp >();
1890  commands[cmd + 8][3] = new Command<Graphic7Mode, TImpOp >();
1891 
1892  commands[cmd + 9][0] = new Command<Graphic4Mode, TAndOp >();
1893  commands[cmd + 9][1] = new Command<Graphic5Mode, TAndOp >();
1894  commands[cmd + 9][2] = new Command<Graphic6Mode, TAndOp >();
1895  commands[cmd + 9][3] = new Command<Graphic7Mode, TAndOp >();
1896 
1897  commands[cmd + 10][0] = new Command<Graphic4Mode, TOrOp >();
1898  commands[cmd + 10][1] = new Command<Graphic5Mode, TOrOp >();
1899  commands[cmd + 10][2] = new Command<Graphic6Mode, TOrOp >();
1900  commands[cmd + 10][3] = new Command<Graphic7Mode, TOrOp >();
1901 
1902  commands[cmd + 11][0] = new Command<Graphic4Mode, TXorOp >();
1903  commands[cmd + 11][1] = new Command<Graphic5Mode, TXorOp >();
1904  commands[cmd + 11][2] = new Command<Graphic6Mode, TXorOp >();
1905  commands[cmd + 11][3] = new Command<Graphic7Mode, TXorOp >();
1906 
1907  commands[cmd + 12][0] = new Command<Graphic4Mode, TNotOp >();
1908  commands[cmd + 12][1] = new Command<Graphic5Mode, TNotOp >();
1909  commands[cmd + 12][2] = new Command<Graphic6Mode, TNotOp >();
1910  commands[cmd + 12][3] = new Command<Graphic7Mode, TNotOp >();
1911 
1912  commands[cmd + 13][0] = dummy;
1913  commands[cmd + 13][1] = dummy;
1914  commands[cmd + 13][2] = dummy;
1915  commands[cmd + 13][3] = dummy;
1916 
1917  commands[cmd + 14][0] = dummy;
1918  commands[cmd + 14][1] = dummy;
1919  commands[cmd + 14][2] = dummy;
1920  commands[cmd + 14][3] = dummy;
1921 
1922  commands[cmd + 15][0] = dummy;
1923  commands[cmd + 15][1] = dummy;
1924  commands[cmd + 15][2] = dummy;
1925  commands[cmd + 15][3] = dummy;
1926 }
1927 
1928 void VDPCmdEngine::deleteLEngines(unsigned cmd)
1929 {
1930  for (int i = 0; i < 5; ++i) {
1931  for (int j = 0; j < 4; ++j) {
1932  delete commands[cmd + i + 0][j];
1933  delete commands[cmd + i + 8][j];
1934  }
1935  }
1936 }
1937 
1938 
1940  CommandController& commandController)
1941  : vdp(vdp_), vram(vdp.getVRAM())
1942  , renderSettings(renderSettings_)
1943  , cmdTraceSetting(make_unique<BooleanSetting>(
1944  commandController, vdp_.getName() == "VDP" ? "vdpcmdtrace" :
1945  vdp_.getName() + " vdpcmdtrace", "VDP command tracing on/off",
1946  false))
1947  , cmdInProgressCallback(make_unique<TclCallback>(
1948  commandController, vdp_.getName() == "VDP" ?
1949  "vdpcmdinprogress_callback" : vdp_.getName() +
1950  " vdpcmdinprogress_callback",
1951  "Tcl proc to call when a write to the VDP command engine is "
1952  "detected while the previous command is still in progress."))
1953  , time(EmuTime::zero)
1954  , statusChangeTime(EmuTime::infinity)
1955  , hasExtendedVRAM(vram.getSize() == (192 * 1024))
1956 {
1957  status = 0;
1958  transfer = false;
1959  SX = SY = DX = DY = NX = NY = 0;
1960  ASX = ADX = ANX = 0;
1961  COL = ARG = CMD = 0;
1962 
1963  AbortCmd* abort = new AbortCmd();
1964  VDPCmd* dummy = new PsetCmd<Graphic7Mode, DummyOp>();
1965  for (unsigned cmd = 0x0; cmd < 0x40; ++cmd) {
1966  for (unsigned mode = 0; mode < 4; ++mode) {
1967  commands[cmd][mode] = abort;
1968  }
1969  }
1970  createHEngines<PointCmd>(0x40);
1971  createLEngines<PsetCmd >(0x50, dummy);
1972  createHEngines<SrchCmd >(0x60);
1973  createLEngines<LineCmd >(0x70, dummy);
1974  createLEngines<LmmvCmd >(0x80, dummy);
1975  createLEngines<LmmmCmd >(0x90, dummy);
1976  createHEngines<LmcmCmd >(0xA0);
1977  createLEngines<LmmcCmd >(0xB0, dummy);
1978  createHEngines<HmmvCmd >(0xC0);
1979  createHEngines<HmmmCmd >(0xD0);
1980  createHEngines<YmmmCmd >(0xE0);
1981  createHEngines<HmmcCmd >(0xF0);
1982  currentCommand = nullptr;
1983 
1984  //brokenTiming = renderSettings.getCmdTiming().getValue();
1985 
1986  renderSettings.getCmdTiming().attach(*this);
1987 }
1988 
1990 {
1991  renderSettings.getCmdTiming().detach(*this);
1992 
1993  delete commands[0x00][0]; // abort command
1994  delete commands[0x55][0]; // dummy command
1995  deleteHEngines(0x40);
1996  deleteLEngines(0x50);
1997  deleteHEngines(0x60);
1998  deleteLEngines(0x70);
1999  deleteLEngines(0x80);
2000  deleteLEngines(0x90);
2001  deleteHEngines(0xA0);
2002  deleteLEngines(0xB0);
2003  deleteHEngines(0xC0);
2004  deleteHEngines(0xD0);
2005  deleteHEngines(0xE0);
2006  deleteHEngines(0xF0);
2007 }
2008 
2010 {
2011  status = 0;
2012  scrMode = -1;
2013  for (unsigned i = 0; i < 15; ++i) {
2014  setCmdReg(i, 0, time);
2015  }
2016 
2017  updateDisplayMode(vdp.getDisplayMode(), time);
2018 }
2019 
2020 void VDPCmdEngine::update(const Setting& /*setting*/)
2021 {
2022  //brokenTiming = static_cast<const EnumSetting<bool>*>(&setting)->getValue();
2023 }
2024 
2026 {
2027  sync(time);
2028  if (currentCommand && (index != 12)) {
2029  cmdInProgressCallback->execute(index, value);
2030  }
2031  switch (index) {
2032  case 0x00: // source X low
2033  SX = (SX & 0x100) | value;
2034  break;
2035  case 0x01: // source X high
2036  SX = (SX & 0x0FF) | ((value & 0x01) << 8);
2037  break;
2038  case 0x02: // source Y low
2039  SY = (SY & 0x300) | value;
2040  break;
2041  case 0x03: // source Y high
2042  SY = (SY & 0x0FF) | ((value & 0x03) << 8);
2043  break;
2044 
2045  case 0x04: // destination X low
2046  DX = (DX & 0x100) | value;
2047  break;
2048  case 0x05: // destination X high
2049  DX = (DX & 0x0FF) | ((value & 0x01) << 8);
2050  break;
2051  case 0x06: // destination Y low
2052  DY = (DY & 0x300) | value;
2053  break;
2054  case 0x07: // destination Y high
2055  DY = (DY & 0x0FF) | ((value & 0x03) << 8);
2056  break;
2057 
2058  // TODO is DX 9 or 10 bits, at least current implementation needs
2059  // 10 bits (otherwise texts in UR are screwed)
2060  case 0x08: // number X low
2061  NX = (NX & 0x300) | value;
2062  break;
2063  case 0x09: // number X high
2064  NX = (NX & 0x0FF) | ((value & 0x03) << 8);
2065  break;
2066  case 0x0A: // number Y low
2067  NY = (NY & 0x300) | value;
2068  break;
2069  case 0x0B: // number Y high
2070  NY = (NY & 0x0FF) | ((value & 0x03) << 8);
2071  break;
2072 
2073  case 0x0C: // color
2074  COL = value;
2075  // Note: Real VDP always resets TR, but for such a short time
2076  // that the MSX won't notice it.
2077  // TODO: What happens on non-transfer commands?
2078  if (!currentCommand) status &= 0x7F;
2079  transfer = true;
2080  break;
2081  case 0x0D: // argument
2082  ARG = value;
2083  break;
2084  case 0x0E: // command
2085  CMD = value;
2086  executeCommand(time);
2087  break;
2088  default:
2089  UNREACHABLE;
2090  }
2091 }
2092 
2094 {
2095  switch (index) {
2096  case 0x00: return SX & 0xFF;
2097  case 0x01: return SX >> 8;
2098  case 0x02: return SY & 0xFF;
2099  case 0x03: return SY >> 8;
2100 
2101  case 0x04: return DX & 0xFF;
2102  case 0x05: return DX >> 8;
2103  case 0x06: return DY & 0xFF;
2104  case 0x07: return DY >> 8;
2105 
2106  case 0x08: return NX & 0xFF;
2107  case 0x09: return NX >> 8;
2108  case 0x0A: return NY & 0xFF;
2109  case 0x0B: return NY >> 8;
2110 
2111  case 0x0C: return COL;
2112  case 0x0D: return ARG;
2113  case 0x0E: return CMD;
2114  default: UNREACHABLE; return 0;
2115  }
2116 }
2117 
2119 {
2120  int newScrMode;
2121  switch (mode.getBase()) {
2122  case DisplayMode::GRAPHIC4:
2123  newScrMode = 0;
2124  break;
2125  case DisplayMode::GRAPHIC5:
2126  newScrMode = 1;
2127  break;
2128  case DisplayMode::GRAPHIC6:
2129  newScrMode = 2;
2130  break;
2131  case DisplayMode::GRAPHIC7:
2132  newScrMode = 3;
2133  break;
2134  default:
2135  if (vdp.getCmdBit()) {
2136  newScrMode = 3; // like GRAPHIC7
2137  // TODO timing might be different
2138  } else {
2139  newScrMode = -1; // no commands
2140  }
2141  break;
2142  }
2143 
2144  if (newScrMode != scrMode) {
2145  sync(time);
2146  if (currentCommand) {
2147  PRT_DEBUG("VDP mode switch while command in progress");
2148  if (newScrMode == -1) {
2149  // TODO: For now abort cmd in progress,
2150  // later find out what really happens.
2151  // At least CE remains high for a while,
2152  // but it is not yet clear what happens in VRAM.
2153  commandDone(time);
2154  } else {
2155  currentCommand = commands[CMD][newScrMode];
2156  }
2157  }
2158  scrMode = newScrMode;
2159  }
2160 }
2161 
2162 void VDPCmdEngine::executeCommand(EmuTime::param time)
2163 {
2164  // V9938 ops only work in SCREEN 5-8.
2165  // V9958 ops work in non SCREEN 5-8 when CMD bit is set
2166  if (scrMode < 0) {
2167  commandDone(time);
2168  return;
2169  }
2170 
2171  if (cmdTraceSetting->getValue()) {
2172  reportVdpCommand();
2173  }
2174 
2175  // Start command.
2176  status |= 0x01;
2177  currentCommand = commands[CMD][scrMode];
2178  currentCommand->start(time, *this);
2179 
2180  // Finish command now if instantaneous command timing is active.
2181  // Abort finishes on start, so currentCommand can be nullptr.
2182  //if (brokenTiming && currentCommand) {
2183  // currentCommand->execute(time, *this);
2184  //}
2185 }
2186 
2187 void VDPCmdEngine::reportVdpCommand()
2188 {
2189  const char* const COMMANDS[16] = {
2190  " ABRT"," ????"," ????"," ????","POINT"," PSET"," SRCH"," LINE",
2191  " LMMV"," LMMM"," LMCM"," LMMC"," HMMV"," HMMM"," YMMM"," HMMC"
2192  };
2193  const char* const OPS[16] = {
2194  "IMP ","AND ","OR ","XOR ","NOT ","NOP ","NOP ","NOP ",
2195  "TIMP","TAND","TOR ","TXOR","TNOT","NOP ","NOP ","NOP "
2196  };
2197 
2198  std::cerr << "VDPCmd " << COMMANDS[CMD >> 4] << '-' << OPS[CMD & 15]
2199  << '(' << int(SX) << ',' << int(SY) << ")->("
2200  << int(DX) << ',' << int(DY) << ")," << int(COL)
2201  << " [" << int((ARG & DIX) ? -int(NX) : int(NX))
2202  << ',' << int((ARG & DIY) ? -int(NY) : int(NY)) << ']' << std::endl;
2203 }
2204 
2205 void VDPCmdEngine::commandDone(EmuTime::param time)
2206 {
2207  // Note: TR is not reset yet; it is reset when S#2 is read next.
2208  status &= 0xFE; // reset CE
2209  CMD = 0;
2210  currentCommand = nullptr;
2211  statusChangeTime = EmuTime::infinity;
2212  vram.cmdReadWindow.disable(time);
2213  vram.cmdWriteWindow.disable(time);
2214 }
2215 
2216 
2217 // version 1: initial version
2218 // version 2: replaced member 'Clock<> clock' with 'Emutime time'
2219 // version 3: added 'phase', 'tmpSrc', 'tmpDst'
2220 template<typename Archive>
2221 void VDPCmdEngine::serialize(Archive& ar, unsigned version)
2222 {
2223  // In older (development) versions CMD was split in a CMD and a LOG
2224  // member, though it was combined for the savestate. Only the CMD part
2225  // was guaranteed to be zero when no command was executing. So when
2226  // loading an older savestate this can still be the case.
2227  if (!currentCommand) {
2228  assert((CMD & 0xF0) == 0); // assert(CMD == 0);
2229  }
2230 
2231  if (ar.versionAtLeast(version, 2)) {
2232  ar.serialize("time", time);
2233  } else {
2234  // in version 1, the 'time' member had type 'Clock<>'
2235  assert(ar.isLoader());
2236  VDP::VDPClock clock(EmuTime::dummy());
2237  ar.serialize("clock", clock);
2238  time = clock.getTime();
2239  }
2240  ar.serialize("statusChangeTime", statusChangeTime);
2241  ar.serialize("scrMode", scrMode);
2242  ar.serialize("status", status);
2243  ar.serialize("transfer", transfer);
2244  ar.serialize("SX", SX);
2245  ar.serialize("SY", SY);
2246  ar.serialize("DX", DX);
2247  ar.serialize("DY", DY);
2248  ar.serialize("NX", NX);
2249  ar.serialize("NY", NY);
2250  ar.serialize("ASX", ASX);
2251  ar.serialize("ADX", ADX);
2252  ar.serialize("ANX", ANX);
2253  ar.serialize("COL", COL);
2254  ar.serialize("ARG", ARG);
2255  ar.serialize("CMD", CMD);
2256 
2257  if (ar.versionAtLeast(version, 3)) {
2258  ar.serialize("phase", phase);
2259  ar.serialize("tmpSrc", tmpSrc);
2260  ar.serialize("tmpDst", tmpDst);
2261  } else {
2262  assert(ar.isLoader());
2263  phase = tmpSrc = tmpDst = 0;
2264  }
2265 
2266  if (ar.isLoader()) {
2267  if (CMD & 0xF0) {
2268  assert(scrMode >= 0);
2269  currentCommand = commands[CMD][scrMode];
2270  } else {
2271  currentCommand = nullptr;
2272  }
2273  }
2274 }
2276 
2277 } // namespace openmsx