// Execute TIA cycles public void Execute(int cycles) { // Still ignoring cycles... // delay vblank latch if (_vblankDelay > 0) { _vblankDelay++; if (_vblankDelay == 3) { _vblankEnabled = (_vblankValue & 0x02) != 0; _vblankDelay = 0; } } // delay latch to new playfield register if (_pf0Updater) { _pf0DelayClock++; if (_pf0DelayClock > _pf0MaxDelay) { _playField.Grp = (uint)((_playField.Grp & 0x0FFFF) + ((ReverseBits(_pf0Update, 8) & 0x0F) << 16)); _pf0Updater = false; } } if (_pf1Updater) { _pf1DelayClock++; if (_pf1DelayClock > _pf1MaxDelay) { _playField.Grp = (uint)((_playField.Grp & 0xF00FF) + (_pf1Update << 8)); _pf1Updater = false; } } if (_pf2Updater) { _pf2DelayClock++; if (_pf2DelayClock > _pf2MaxDelay) { _playField.Grp = (uint)((_playField.Grp & 0xFFF00) + ReverseBits(_pf2Update, 8)); _pf2Updater = false; } } // delay latch to missile enable if (_enam0Delay > 0) { _enam0Delay++; if (_enam0Delay == 3) { _enam0Delay = 0; _player0.Missile.Enabled = _enam0Val; } } if (_enam1Delay > 0) { _enam1Delay++; if (_enam1Delay == 3) { _enam1Delay = 0; _player1.Missile.Enabled = _enam1Val; } } // delay latch to ball enable if (_enambDelay > 0) { _enambDelay++; if (_enambDelay == 3) { _enambDelay = 0; _ball.Enabled = _enambVal; } } // delay latch to player graphics registers if (_prg0Delay > 0) { _prg0Delay++; if (_prg0Delay == 3) { _prg0Delay = 0; _player0.Grp = _prg0Val; _player1.Dgrp = _player1.Grp; } } if (_prg1Delay > 0) { _prg1Delay++; if (_prg1Delay == 3) { _prg1Delay = 0; _player1.Grp = _prg1Val; _player0.Dgrp = _player0.Grp; // TODO: Find a game that uses this functionality and test it _ball.Denabled = _ball.Enabled; } } // HMP write delay if (_hmp0Delay > 0) { _hmp0Delay++; if (_hmp0Delay == 4) { _hmp0Delay = 0; _player0.HM = _hmp0Val; } } if (_hmp1Delay > 0) { _hmp1Delay++; if (_hmp1Delay == 3) { _hmp1Delay = 0; _player1.HM = _hmp1Val; } } // Reset the RDY flag when we reach hblank if (_hsyncCnt <= 0) { _core.Cpu.RDY = true; } // Assume we're on the left side of the screen for now var rightSide = false; // ---- Things that happen only in the drawing section ---- // TODO: Remove this magic number (17). It depends on the HMOVE if (_hsyncCnt >= (_hmove.LateHBlankReset ? 76 : 68)) { _doTicks = false; // TODO: Remove this magic number if ((_hsyncCnt / 4) >= 37) { rightSide = true; } // The bit number of the PF data which we want int pfBit = ((_hsyncCnt / 4) - 17) % 20; // Create the mask for the bit we want // Note that bits are arranged 0 1 2 3 4 .. 19 int pfMask = 1 << (20 - 1 - pfBit); // Reverse the mask if on the right and playfield is reflected if (rightSide && _playField.Reflect) { pfMask = ReverseBits(pfMask, 20); } // Calculate collisions byte collisions = 0x00; if ((_playField.Grp & pfMask) != 0) { collisions |= CXPF; } // ---- Player 0 ---- collisions |= _player0.Tick() ? CXP0 : (byte)0x00; // ---- Missile 0 ---- collisions |= _player0.Missile.Tick() ? CXM0 : (byte)0x00; // ---- Player 1 ---- collisions |= _player1.Tick() ? CXP1 : (byte)0x00; // ---- Missile 0 ---- collisions |= _player1.Missile.Tick() ? CXM1 : (byte)0x00; // ---- Ball ---- collisions |= _ball.Tick() ? CXBL : (byte)0x00; // Pick the pixel color from collisions int pixelColor = BackColor; if (_core.Settings.ShowBG) { pixelColor = _palette[_playField.BkColor]; } if ((collisions & CXPF) != 0 && _core.Settings.ShowPlayfield) { if (_playField.Score) { pixelColor = !rightSide ? _palette[_player0.Color] : _palette[_player1.Color]; } else { pixelColor = _palette[_playField.PfColor]; } } if ((collisions & CXBL) != 0) { _ball.Collisions |= collisions; if (_core.Settings.ShowBall) { pixelColor = _palette[_playField.PfColor]; } } if ((collisions & CXM1) != 0) { _player1.Missile.Collisions |= collisions; if (_core.Settings.ShowMissle2) { pixelColor = _palette[_player1.Color]; } } if ((collisions & CXP1) != 0) { _player1.Collisions |= collisions; if (_core.Settings.ShowPlayer2) { pixelColor = _palette[_player1.Color]; } } if ((collisions & CXM0) != 0) { _player0.Missile.Collisions |= collisions; if (_core.Settings.ShowMissle1) { pixelColor = _palette[_player0.Color]; } } if ((collisions & CXP0) != 0) { _player0.Collisions |= collisions; if (_core.Settings.ShowPlayer1) { pixelColor = _palette[_player0.Color]; } } if (_playField.Score && !_playField.Priority && ((collisions & CXPF) != 0) && _core.Settings.ShowPlayfield) { pixelColor = !rightSide ? _palette[_player0.Color] : _palette[_player1.Color]; } if (_playField.Priority && (collisions & CXPF) != 0 && _core.Settings.ShowPlayfield) { pixelColor = _palette[_playField.PfColor]; } // Handle vblank if (_vblankEnabled) { pixelColor = BackColor; } // Add the pixel to the scanline // TODO: Remove this magic number (68) int y = _currentScanLine; // y >= max screen height means lag frame or game crashed, but is a legal situation. // either way, there's nothing to display if (y < MaxScreenHeight) { int x = _hsyncCnt - 68; if (x < 0 || x > 159) // this can't happen, right? { throw new Exception(); // TODO } _scanlinebuffer[(_currentScanLine * ScreenWidth) + x] = pixelColor; } } else { _doTicks = true; } // if extended HBLank is active, the screen area still needs a color if (_hsyncCnt >= 68 && _hsyncCnt < 76 && _hmove.LateHBlankReset) { int pixelColor = 0; // Add the pixel to the scanline // TODO: Remove this magic number (68) int y = _currentScanLine; // y >= max screen height means lag frame or game crashed, but is a legal situation. // either way, there's nothing to display if (y < MaxScreenHeight) { int x = _hsyncCnt - 68; if (x < 0 || x > 159) // this can't happen, right? { throw new Exception(); // TODO } _scanlinebuffer[(_currentScanLine * ScreenWidth) + x] = pixelColor; } } // Handle HMOVE if (_hmove.HMoveEnabled) { if (_hmove.DecCntEnabled) { // Actually do stuff only evey 4 pulses if (_hmove.HMoveCnt == 0) { // If the latch is still set if (_hmove.Player0Latch) { // If the move counter still has a bit in common with the HM register if (((15 - _hmove.Player0Cnt) ^ ((_player0.HM & 0x07) | ((~(_player0.HM & 0x08)) & 0x08))) != 0x0F) { _p0Stuff = true; } else { _hmove.Player0Latch = false; } } if (_hmove.Missile0Latch) { // If the move counter still has a bit in common with the HM register if (((15 - _hmove.Missile0Cnt) ^ ((_player0.Missile.Hm & 0x07) | ((~(_player0.Missile.Hm & 0x08)) & 0x08))) != 0x0F) { _m0Stuff = true; } else { _hmove.Missile0Latch = false; } } if (_hmove.Player1Latch) { // If the move counter still has a bit in common with the HM register if (((15 - _hmove.Player1Cnt) ^ ((_player1.HM & 0x07) | ((~(_player1.HM & 0x08)) & 0x08))) != 0x0F) { _p1Stuff = true; } else { _hmove.Player1Latch = false; } } if (_hmove.Missile1Latch) { // If the move counter still has a bit in common with the HM register if (((15 - _hmove.Missile1Cnt) ^ ((_player1.Missile.Hm & 0x07) | ((~(_player1.Missile.Hm & 0x08)) & 0x08))) != 0x0F) { _m1Stuff = true; } else { _hmove.Missile1Latch = false; } } if (_hmove.BallLatch) { // If the move counter still has a bit in common with the HM register if (((15 - _hmove.BallCnt) ^ ((_ball.HM & 0x07) | ((~(_ball.HM & 0x08)) & 0x08))) != 0x0F) { _bStuff = true; } else { _hmove.BallLatch = false; } } if (!_hmove.Player0Latch && !_hmove.Player1Latch && !_hmove.BallLatch && !_hmove.Missile0Latch && !_hmove.Missile1Latch) { _hmove.HMoveEnabled = false; _hmove.DecCntEnabled = false; _hmove.HMoveDelayCnt = 0; } } _hmove.HMoveCnt++; _hmove.HMoveCnt %= 4; if (_p0Stuff && _hsyncCnt % 4 == 0) { _p0Stuff = false; // "Clock-Stuffing" if (_doTicks) { _player0.Tick(); } // Increase by 1, max of 15 _hmove.test_count_p0++; if (_hmove.test_count_p0 < 16) { _hmove.Player0Cnt++; } else { _hmove.Player0Cnt = 0; } } if (_p1Stuff && _hsyncCnt % 4 == 0) { _p1Stuff = false; // "Clock-Stuffing" if (_doTicks) { _player1.Tick(); } // Increase by 1, max of 15 _hmove.test_count_p1++; if (_hmove.test_count_p1 < 16) { _hmove.Player1Cnt++; } else { _hmove.Player1Cnt = 0; } } if (_m0Stuff && _hsyncCnt % 4 == 0) { _m0Stuff = false; // "Clock-Stuffing" if (_doTicks) { _player0.Missile.Tick(); } // Increase by 1, max of 15 _hmove.test_count_m0++; if (_hmove.test_count_m0 < 16) { _hmove.Missile0Cnt++; } else { _hmove.Missile0Cnt = 0; } } if (_m1Stuff && _hsyncCnt % 4 == 0) { _m1Stuff = false; // "Clock-Stuffing" if (_doTicks) { _player1.Missile.Tick(); } // Increase by 1, max of 15 _hmove.test_count_m1++; if (_hmove.test_count_m1 < 16) { _hmove.Missile1Cnt++; } else { _hmove.Missile1Cnt = 0; } } if (_bStuff && _hsyncCnt % 4 == 0) { _bStuff = false; // "Clock-Stuffing" if (_doTicks) { _ball.Tick(); } // Increase by 1, max of 15 _hmove.test_count_b++; if (_hmove.test_count_b < 16) { _hmove.BallCnt++; } else { _hmove.BallCnt = 0; } } } if (_hmove.HMoveDelayCnt < 5) { _hmove.HMoveDelayCnt++; } if (_hmove.HMoveDelayCnt == 5) { _hmove.HMoveDelayCnt++; _hmove.HMoveCnt = 0; _hmove.DecCntEnabled = true; _hmove.test_count_p0 = 0; _hmove.test_count_p1 = 0; _hmove.test_count_m0 = 0; _hmove.test_count_m1 = 0; _hmove.test_count_b = 0; _hmove.Player0Latch = true; _hmove.Player0Cnt = 0; _hmove.Missile0Latch = true; _hmove.Missile0Cnt = 0; _hmove.Player1Latch = true; _hmove.Player1Cnt = 0; _hmove.Missile1Latch = true; _hmove.Missile1Cnt = 0; _hmove.BallLatch = true; _hmove.BallCnt = 0; _hmove.LateHBlankReset = true; } } // do the audio sampling if (_hsyncCnt == 36 || _hsyncCnt == 148) { LocalAudioCycles[AudioClocks] += (short)(AUD[0].Cycle() / 2); LocalAudioCycles[AudioClocks] += (short)(AUD[1].Cycle() / 2); AudioClocks++; } // Increment the hsync counter _hsyncCnt++; _hsyncCnt %= 228; // End of the line? Add it to the buffer! if (_hsyncCnt == 0) { _hmove.LateHBlankReset = false; _currentScanLine++; LineCount++; } }
// Execute TIA cycles public void Execute(int cycles) { // Still ignoring cycles... // Assume we're on the left side of the screen for now var rightSide = false; // ---- Things that happen only in the drawing section ---- // TODO: Remove this magic number (17). It depends on the HMOVE if ((_hsyncCnt / 4) >= (_hmove.LateHBlankReset ? 19 : 17)) { // TODO: Remove this magic number if ((_hsyncCnt / 4) >= 37) { rightSide = true; } // The bit number of the PF data which we want int pfBit = ((_hsyncCnt / 4) - 17) % 20; // Create the mask for the bit we want // Note that bits are arranged 0 1 2 3 4 .. 19 int pfMask = 1 << (20 - 1 - pfBit); // Reverse the mask if on the right and playfield is reflected if (rightSide && _playField.Reflect) { pfMask = ReverseBits(pfMask, 20); } // Calculate collisions byte collisions = 0x00; if ((_playField.Grp & pfMask) != 0) { collisions |= CXPF; } // ---- Player 0 ---- collisions |= _player0.Tick() ? CXP0 : (byte)0x00; // ---- Missile 0 ---- collisions |= _player0.Missile.Tick() ? CXM0 : (byte)0x00; // ---- Player 1 ---- collisions |= _player1.Tick() ? CXP1 : (byte)0x00; // ---- Missile 0 ---- collisions |= _player1.Missile.Tick() ? CXM1 : (byte)0x00; // ---- Ball ---- collisions |= _ball.Tick() ? CXBL : (byte)0x00; // Pick the pixel color from collisions int pixelColor = BackColor; if (_core.Settings.ShowBG) { pixelColor = _palette[_playField.BkColor]; } if ((collisions & CXPF) != 0 && _core.Settings.ShowPlayfield) { if (_playField.Score) { if (!rightSide) { pixelColor = _palette[_player0.Color]; } else { pixelColor = _palette[_player1.Color]; } } else { pixelColor = _palette[_playField.PfColor]; } } if ((collisions & CXBL) != 0) { _ball.Collisions |= collisions; if (_core.Settings.ShowBall) { pixelColor = _palette[_playField.PfColor]; } } if ((collisions & CXM1) != 0) { _player1.Missile.Collisions |= collisions; if (_core.Settings.ShowMissle2) { pixelColor = _palette[_player1.Color]; } } if ((collisions & CXP1) != 0) { _player1.Collisions |= collisions; if (_core.Settings.ShowPlayer2) { pixelColor = _palette[_player1.Color]; } } if ((collisions & CXM0) != 0) { _player0.Missile.Collisions |= collisions; if (_core.Settings.ShowMissle1) { pixelColor = _palette[_player0.Color]; } } if ((collisions & CXP0) != 0) { _player0.Collisions |= collisions; if (_core.Settings.ShowPlayer1) { pixelColor = _palette[_player0.Color]; } } if (_playField.Priority && (collisions & CXPF) != 0 && _core.Settings.ShowPlayfield) { if (_playField.Score) { pixelColor = !rightSide ? _palette[_player0.Color] : _palette[_player1.Color]; } else { pixelColor = _palette[_playField.PfColor]; } } // Handle vblank if (_vblankEnabled) { pixelColor = BackColor; } // Add the pixel to the scanline // TODO: Remove this magic number (68) int y = _CurrentScanLine; // y >= max screen height means lag frame or game crashed, but is a legal situation. // either way, there's nothing to display if (y < MaxScreenHeight) { int x = _hsyncCnt - 68; if (x < 0 || x > 159) // this can't happen, right? { throw new Exception(); // TODO } _scanlinebuffer[_CurrentScanLine * ScreenWidth + x] = pixelColor; } } // ---- Things that happen every time ---- // Handle HMOVE if (_hmove.HMoveEnabled) { // On the first time, set the latches and counters if (_hmove.HMoveJustStarted) { _hmove.Player0Latch = true; _hmove.Player0Cnt = 0; _hmove.Missile0Latch = true; _hmove.Missile0Cnt = 0; _hmove.Player1Latch = true; _hmove.Player1Cnt = 0; _hmove.Missile1Latch = true; _hmove.Missile1Cnt = 0; _hmove.BallLatch = true; _hmove.BallCnt = 0; _hmove.HMoveCnt = 0; _hmove.HMoveJustStarted = false; _hmove.LateHBlankReset = true; _hmove.DecCntEnabled = false; } if (_hmove.DecCntEnabled) { // Actually do stuff only evey 4 pulses if (_hmove.HMoveCnt == 0) { // If the latch is still set if (_hmove.Player0Latch) { // If the move counter still has a bit in common with the HM register if (((15 - _hmove.Player0Cnt) ^ ((_player0.HM & 0x07) | ((~(_player0.HM & 0x08)) & 0x08))) != 0x0F) { // "Clock-Stuffing" _player0.Tick(); // Increase by 1, max of 15 _hmove.Player0Cnt++; _hmove.Player0Cnt %= 16; } else { _hmove.Player0Latch = false; } } if (_hmove.Missile0Latch) { if (_hmove.Missile0Cnt == 15) { } // If the move counter still has a bit in common with the HM register if (((15 - _hmove.Missile0Cnt) ^ ((_player0.Missile.Hm & 0x07) | ((~(_player0.Missile.Hm & 0x08)) & 0x08))) != 0x0F) { // "Clock-Stuffing" _player0.Missile.Tick(); // Increase by 1, max of 15 _hmove.Missile0Cnt++; _hmove.Missile0Cnt %= 16; } else { _hmove.Missile0Latch = false; _hmove.Missile0Cnt = 0; } } if (_hmove.Player1Latch) { // If the move counter still has a bit in common with the HM register if (((15 - _hmove.Player1Cnt) ^ ((_player1.HM & 0x07) | ((~(_player1.HM & 0x08)) & 0x08))) != 0x0F) { // "Clock-Stuffing" _player1.Tick(); // Increase by 1, max of 15 _hmove.Player1Cnt++; _hmove.Player1Cnt %= 16; } else { _hmove.Player1Latch = false; } } if (_hmove.Missile1Latch) { // If the move counter still has a bit in common with the HM register if (((15 - _hmove.Missile1Cnt) ^ ((_player1.Missile.Hm & 0x07) | ((~(_player1.Missile.Hm & 0x08)) & 0x08))) != 0x0F) { // "Clock-Stuffing" _player1.Missile.Tick(); // Increase by 1, max of 15 _hmove.Missile1Cnt++; _hmove.Missile1Cnt %= 16; } else { _hmove.Missile1Latch = false; } } if (_hmove.BallLatch) { // If the move counter still has a bit in common with the HM register if (((15 - _hmove.BallCnt) ^ ((_ball.HM & 0x07) | ((~(_ball.HM & 0x08)) & 0x08))) != 0x0F) { // "Clock-Stuffing" _ball.Tick(); // Increase by 1, max of 15 _hmove.BallCnt++; _hmove.BallCnt %= 16; } else { _hmove.BallLatch = false; } } if (!_hmove.Player0Latch && !_hmove.Player1Latch && !_hmove.BallLatch && !_hmove.Missile0Latch && !_hmove.Missile1Latch) { _hmove.HMoveEnabled = false; _hmove.DecCntEnabled = false; _hmove.HMoveDelayCnt = 0; } } _hmove.HMoveCnt++; _hmove.HMoveCnt %= 4; } if (_hmove.HMoveDelayCnt < 6) { _hmove.HMoveDelayCnt++; } if (_hmove.HMoveDelayCnt == 6) { _hmove.HMoveDelayCnt++; _hmove.HMoveCnt = 0; _hmove.DecCntEnabled = true; } } // Increment the hsync counter _hsyncCnt++; _hsyncCnt %= 228; // End of the line? Add it to the buffer! if (_hsyncCnt == 0) { _hmove.LateHBlankReset = false; _CurrentScanLine++; LineCount++; _audioClocks += 2; // TODO: increment this at the appropriate places twice per line } }