// $Id: VDPCmdEngine.cc 5972 2006-12-29 18:46:11Z m9710797 $ /* TODO: - How is 64K VRAM handled? VRAM size is never inspected by the command engine. How does a real MSX handle it? Mirroring of first 64K or empty memory space? - How is extended VRAM handled? The current VDP implementation does not support it. Since it is not accessed by the renderer, it is possible allocate it here. But maybe it makes more sense to have all RAM managed by the VDP? - Currently all VRAM access is done at the start time of a series of updates: currentTime is not increased until the very end of the sync method. It should ofcourse be updated after every read and write. An acceptable approximation would be an update after every pixel/byte operation. */ /* About NX, NY - for block commands NX = 0 is equivalent to NX = 512 (TODO recheck this) and NY = 0 is equivalent to NY = 1024 - when NX or NY is too large and the VDP command hits the border, the following happens: - when the left or right border is hit, the line terminates - when the top border is hit (line 0) the command terminates - when the bottom border (line 511 or 1023) the command continues (wraps to the top) - in 512 lines modes (e.g. screen 7) NY is NOT limited to 512, so when NY > 512, part of the screen is overdrawn twice - in 256 columns modes (e.g. screen 5) when "SX/DX >= 256", only 1 element (pixel or byte) is processed per horizontal line. The real x-ccordinate is "SX/DX & 255". */ #include "VDPCmdEngine.hh" #include "EmuTime.hh" #include "VDPVRAM.hh" #include "BooleanSetting.hh" #include "RenderSettings.hh" #include #include #include using std::min; using std::max; using std::auto_ptr; namespace openmsx { // Constants: const byte MXD = 0x20; const byte MXS = 0x10; const byte DIY = 0x08; const byte DIX = 0x04; const byte EQ = 0x02; const byte MAJ = 0x01; // Timing tables: // Sprites: On On Off Off // Screen: Off On Off On const unsigned SRCH_TIMING[5] = { 92, 125, 92, 92, 0 }; const unsigned LINE_TIMING[5] = { 120, 147, 120, 132, 0 }; const unsigned HMMV_TIMING[5] = { 49, 65, 49, 62, 0 }; const unsigned LMMV_TIMING[5] = { 98, 137, 98, 124, 0 }; const unsigned YMMM_TIMING[5] = { 65, 125, 65, 68, 0 }; const unsigned HMMM_TIMING[5] = { 92, 136, 92, 97, 0 }; const unsigned LMMM_TIMING[5] = { 129, 197, 129, 132, 0 }; // Inline methods first, to make sure they are actually inlined: template static inline unsigned clipNX_1_pixel(unsigned DX, unsigned NX, byte ARG) { if (unlikely(DX >= Mode::PIXELS_PER_LINE)) { return 1; } NX = NX ? NX : Mode::PIXELS_PER_LINE; return (ARG & DIX) ? min(NX, DX + 1) : min(NX, Mode::PIXELS_PER_LINE - DX); } template static inline unsigned clipNX_1_byte(unsigned DX, unsigned NX, byte ARG) { static const unsigned BYTES_PER_LINE = Mode::PIXELS_PER_LINE >> Mode::PIXELS_PER_BYTE_SHIFT; DX >>= Mode::PIXELS_PER_BYTE_SHIFT; if (unlikely(BYTES_PER_LINE <= DX)) { return 1; } NX >>= Mode::PIXELS_PER_BYTE_SHIFT; NX = NX ? NX : BYTES_PER_LINE; return (ARG & DIX) ? min(NX, DX + 1) : min(NX, BYTES_PER_LINE - DX); } template static inline unsigned clipNX_2_pixel(unsigned SX, unsigned DX, unsigned NX, byte ARG) { if (unlikely(SX >= Mode::PIXELS_PER_LINE) || unlikely(DX >= Mode::PIXELS_PER_LINE)) { return 1; } NX = NX ? NX : Mode::PIXELS_PER_LINE; return (ARG & DIX) ? min(NX, min(SX, DX) + 1) : min(NX, Mode::PIXELS_PER_LINE - max(SX, DX)); } template static inline unsigned clipNX_2_byte(unsigned SX, unsigned DX, unsigned NX, byte ARG) { static const unsigned BYTES_PER_LINE = Mode::PIXELS_PER_LINE >> Mode::PIXELS_PER_BYTE_SHIFT; SX >>= Mode::PIXELS_PER_BYTE_SHIFT; DX >>= Mode::PIXELS_PER_BYTE_SHIFT; if (unlikely(BYTES_PER_LINE <= SX) || unlikely(BYTES_PER_LINE <= DX)) { return 1; } NX >>= Mode::PIXELS_PER_BYTE_SHIFT; NX = NX ? NX : BYTES_PER_LINE; return (ARG & DIX) ? min(NX, min(SX, DX) + 1) : min(NX, BYTES_PER_LINE - max(SX, DX)); } static inline unsigned clipNY_1(unsigned DY, unsigned NY, byte ARG) { NY = NY ? NY : 1024; return (ARG & DIY) ? min(NY, DY + 1) : NY; } static inline unsigned clipNY_2(unsigned SY, unsigned DY, unsigned NY, byte ARG) { NY = NY ? NY : 1024; return (ARG & DIY) ? min(NY, min(SY, DY) + 1) : NY; } // Graphic4Mode: inline unsigned VDPCmdEngine::Graphic4Mode::addressOf(unsigned x, unsigned y, bool extVRAM) { return likely(!extVRAM) ? (((y & 1023) << 7) | ((x & 255) >> 1)) : (((y & 511) << 7) | ((x & 255) >> 1) | 0x20000); } inline byte VDPCmdEngine::Graphic4Mode::point( VDPVRAM& vram, unsigned x, unsigned y, bool extVRAM) { return ( vram.cmdReadWindow.readNP(addressOf(x, y, extVRAM)) >> (((~x) & 1) << 2) ) & 15; } inline void VDPCmdEngine::Graphic4Mode::pset( const EmuTime& time, VDPVRAM& vram, unsigned x, unsigned y, bool extVRAM, byte colour, LogOp& op) { byte sh = ((~x) & 1) << 2; op.pset(time, vram, addressOf(x, y, extVRAM), colour << sh, ~(15 << sh)); } // Graphic5Mode: inline unsigned VDPCmdEngine::Graphic5Mode::addressOf(unsigned x, unsigned y, bool extVRAM) { return likely(!extVRAM) ? (((y & 1023) << 7) | ((x & 511) >> 2)) : (((y & 511) << 7) | ((x & 511) >> 2) | 0x20000); } inline byte VDPCmdEngine::Graphic5Mode::point(VDPVRAM& vram, unsigned x, unsigned y, bool extVRAM) { return ( vram.cmdReadWindow.readNP(addressOf(x, y, extVRAM)) >> (((~x) & 3) << 1) ) & 3; } inline void VDPCmdEngine::Graphic5Mode::pset( const EmuTime& time, VDPVRAM& vram, unsigned x, unsigned y, bool extVRAM, byte colour, LogOp& op) { byte sh = ((~x) & 3) << 1; op.pset(time, vram, addressOf(x, y, extVRAM), colour << sh, ~(3 << sh)); } // Graphic6Mode: inline unsigned VDPCmdEngine::Graphic6Mode::addressOf(unsigned x, unsigned y, bool extVRAM) { return likely(!extVRAM) ? (((x & 2) << 15) | ((y & 511) << 7) | ((x & 511) >> 2)) : (0x20000 | ((y & 511) << 7) | ((x & 511) >> 2)); } inline byte VDPCmdEngine::Graphic6Mode::point(VDPVRAM& vram, unsigned x, unsigned y, bool extVRAM) { return ( vram.cmdReadWindow.readNP(addressOf(x, y, extVRAM)) >> (((~x) & 1) << 2) ) & 15; } inline void VDPCmdEngine::Graphic6Mode::pset( const EmuTime& time, VDPVRAM& vram, unsigned x, unsigned y, bool extVRAM, byte colour, LogOp& op) { byte sh = ((~x) & 1) << 2; op.pset(time, vram, addressOf(x, y, extVRAM), colour << sh, ~(15 << sh)); } // Graphic7Mode: inline unsigned VDPCmdEngine::Graphic7Mode::addressOf(unsigned x, unsigned y, bool extVRAM) { return likely(!extVRAM) ? (((x & 1) << 16) | ((y & 511) << 7) | ((x & 255) >> 1)) : (0x20000 | ((y & 511) << 7) | ((x & 255) >> 1)); } inline byte VDPCmdEngine::Graphic7Mode::point(VDPVRAM& vram, unsigned x, unsigned y, bool extVRAM) { return vram.cmdReadWindow.readNP(addressOf(x, y, extVRAM)); } inline void VDPCmdEngine::Graphic7Mode::pset( const EmuTime& time, VDPVRAM& vram, unsigned x, unsigned y, bool extVRAM, byte colour, LogOp& op) { op.pset(time, vram, addressOf(x, y, extVRAM), colour, 0); } // Logical operations: typedef VDPCmdEngine::LogOp LogOp; class DummyOp: public LogOp { public: virtual void pset( const EmuTime& /*time*/, VDPVRAM& /*vram*/, unsigned /*addr*/, byte /*colour*/, byte /*mask*/) { // Undefined logical operations do nothing. } }; class ImpOp: public LogOp { public: virtual void pset( const EmuTime& time, VDPVRAM& vram, unsigned addr, byte colour, byte mask) { vram.cmdWrite(addr, (vram.cmdWriteWindow.readNP(addr) & mask) | colour, time); } }; class AndOp: public LogOp { public: virtual void pset( const EmuTime& time, VDPVRAM& vram, unsigned addr, byte colour, byte mask) { vram.cmdWrite(addr, vram.cmdWriteWindow.readNP(addr) & (colour | mask), time); } }; class OrOp: public LogOp { public: virtual void pset( const EmuTime& time, VDPVRAM& vram, unsigned addr, byte colour, byte /*mask*/) { vram.cmdWrite(addr, vram.cmdWriteWindow.readNP(addr) | colour, time); } }; class XorOp: public LogOp { public: virtual void pset( const EmuTime& time, VDPVRAM& vram, unsigned addr, byte colour, byte /*mask*/) { vram.cmdWrite(addr, vram.cmdWriteWindow.readNP(addr) ^ colour, time); } }; class NotOp: public LogOp { public: virtual void pset( const EmuTime& time, VDPVRAM& vram, unsigned addr, byte colour, byte mask) { vram.cmdWrite(addr, (vram.cmdWriteWindow.readNP(addr) & mask) | ~(colour | mask), time); } }; template class TransparentOp: public Op { public: virtual void pset( const EmuTime& time, VDPVRAM& vram, unsigned addr, byte colour, byte mask) { if (colour) Op::pset(time, vram, addr, colour, mask); } }; typedef TransparentOp TImpOp; typedef TransparentOp TAndOp; typedef TransparentOp TOrOp; typedef TransparentOp TXorOp; typedef TransparentOp TNotOp; static auto_ptr operations[16] = { auto_ptr(new ImpOp()), auto_ptr(new AndOp()), auto_ptr(new OrOp()), auto_ptr(new XorOp()), auto_ptr(new NotOp()), auto_ptr(new DummyOp()), auto_ptr(new DummyOp()), auto_ptr(new DummyOp()), auto_ptr(new TImpOp()), auto_ptr(new TAndOp()), auto_ptr(new TOrOp()), auto_ptr(new TXorOp()), auto_ptr(new TNotOp()), auto_ptr(new DummyOp()), auto_ptr(new DummyOp()), auto_ptr(new DummyOp()), }; // Construction and destruction: template