mirror of
https://github.com/SourMesen/Mesen2.git
synced 2025-04-02 10:21:44 -04:00
Fixes freeze in TV Sports Football's "New Season" screen and is very close to new test rom's timings/results
1490 lines
42 KiB
C++
1490 lines
42 KiB
C++
#include "pch.h"
|
|
#include "PCE/PceVdc.h"
|
|
#include "PCE/PceVce.h"
|
|
#include "PCE/PceVpc.h"
|
|
#include "PCE/PceMemoryManager.h"
|
|
#include "PCE/PceConstants.h"
|
|
#include "PCE/PceConsole.h"
|
|
#include "Shared/EmuSettings.h"
|
|
#include "Shared/EventType.h"
|
|
#include "Shared/MessageManager.h"
|
|
#include "Utilities/Serializer.h"
|
|
|
|
PceVdc::PceVdc(Emulator* emu, PceConsole* console, PceVpc* vpc, PceVce* vce, bool isVdc2)
|
|
{
|
|
_emu = emu;
|
|
_console = console;
|
|
_vpc = vpc;
|
|
_vce = vce;
|
|
|
|
_vram = new uint16_t[0x8000];
|
|
_spriteRam = new uint16_t[0x100];
|
|
|
|
console->InitializeRam(_vram, 0x10000);
|
|
console->InitializeRam(_spriteRam, 0x200);
|
|
|
|
//These values can't ever be 0, init them to a possible value
|
|
_state.HvLatch.ColumnCount = 32;
|
|
_state.HvReg.ColumnCount = 32;
|
|
_state.HvLatch.RowCount = 32;
|
|
_state.HvReg.RowCount = 32;
|
|
_state.VramAddrIncrement = 1;
|
|
|
|
_state.HvReg.HorizDisplayWidth = 0x1F;
|
|
_state.HvLatch.HorizDisplayWidth = 0x1F;
|
|
_state.HvReg.VertDisplayWidth = 239;
|
|
_state.HvLatch.VertDisplayWidth = 239;
|
|
|
|
_isVdc2 = isVdc2;
|
|
_vramType = isVdc2 ? MemoryType::PceVideoRamVdc2 : MemoryType::PceVideoRam;
|
|
_spriteRamType = isVdc2 ? MemoryType::PceSpriteRamVdc2 : MemoryType::PceSpriteRam;
|
|
_emu->RegisterMemory(_vramType, _vram, 0x8000 * sizeof(uint16_t));
|
|
_emu->RegisterMemory(_spriteRamType, _spriteRam, 0x100 * sizeof(uint16_t));
|
|
}
|
|
|
|
PceVdc::~PceVdc()
|
|
{
|
|
delete[] _vram;
|
|
delete[] _spriteRam;
|
|
}
|
|
|
|
PceVdcState& PceVdc::GetState()
|
|
{
|
|
return _state;
|
|
}
|
|
|
|
uint8_t PceVdc::GetClockDivider()
|
|
{
|
|
return _vce->GetClockDivider();
|
|
}
|
|
|
|
uint16_t PceVdc::GetScanlineCount()
|
|
{
|
|
return _vce->GetScanlineCount();
|
|
}
|
|
|
|
uint16_t PceVdc::DotsToClocks(int dots)
|
|
{
|
|
return dots * GetClockDivider();
|
|
}
|
|
|
|
void PceVdc::Exec()
|
|
{
|
|
if(_state.SatbTransferRunning) {
|
|
ProcessSatbTransfer();
|
|
} else if(_vramDmaRunning) {
|
|
ProcessVramDmaTransfer();
|
|
}
|
|
|
|
if(_rowHasSprite0 || _pendingMemoryWrite || _pendingMemoryRead) {
|
|
DrawScanline();
|
|
|
|
if(_pendingMemoryRead || _pendingMemoryWrite) {
|
|
//TODO timing, this isn't quite right - should probably be aligned with VDC clocks?
|
|
ProcessVramAccesses();
|
|
}
|
|
}
|
|
|
|
if(_hModeCounter <= 3 || _nextEventCounter <= 3) {
|
|
ProcessVdcEvents();
|
|
} else {
|
|
_hModeCounter -= 3;
|
|
if(_nextEventCounter > 0) {
|
|
_nextEventCounter -= 3;
|
|
}
|
|
_state.HClock += 3;
|
|
}
|
|
|
|
if(_state.HClock == 1365) {
|
|
ProcessEndOfScanline();
|
|
}
|
|
|
|
if(!_isVdc2) {
|
|
_emu->ProcessPpuCycle<CpuType::Pce>();
|
|
}
|
|
}
|
|
|
|
void PceVdc::TriggerHdsIrqs()
|
|
{
|
|
if(_needVertBlankIrq) {
|
|
TriggerVerticalBlank();
|
|
}
|
|
if(_hasSpriteOverflow && _state.EnableOverflowIrq) {
|
|
_state.SpriteOverflow = true;
|
|
_vpc->SetIrq(this);
|
|
}
|
|
_hasSpriteOverflow = false;
|
|
}
|
|
|
|
void PceVdc::ProcessVdcEvents()
|
|
{
|
|
for(int i = 0; i < 3; i++) {
|
|
_state.HClock++;
|
|
|
|
if(--_hModeCounter == 0) {
|
|
DrawScanline();
|
|
SetHorizontalMode((PceVdcModeH)(((int)_hMode + 1) % 4));
|
|
}
|
|
|
|
if(_nextEventCounter && --_nextEventCounter == 0) {
|
|
ProcessEvent();
|
|
}
|
|
}
|
|
}
|
|
|
|
void PceVdc::ProcessEvent()
|
|
{
|
|
DrawScanline();
|
|
|
|
switch(_nextEvent) {
|
|
case PceVdcEvent::LatchScrollY:
|
|
_needVCounterClock = true;
|
|
_latchClockY = _state.HClock;
|
|
IncScrollY();
|
|
if(!_state.BurstModeEnabled) {
|
|
_state.BackgroundEnabled = _state.NextBackgroundEnabled;
|
|
_state.SpritesEnabled = _state.NextSpritesEnabled;
|
|
}
|
|
_nextEvent = PceVdcEvent::LatchScrollX;
|
|
_nextEventCounter = DotsToClocks(2);
|
|
break;
|
|
|
|
case PceVdcEvent::LatchScrollX:
|
|
_state.HvLatch.BgScrollX = _state.HvReg.BgScrollX;
|
|
_latchClockX = _state.HClock;
|
|
_nextEvent = PceVdcEvent::HdsIrqTrigger;
|
|
_nextEventCounter = DotsToClocks(6);
|
|
break;
|
|
|
|
case PceVdcEvent::HdsIrqTrigger:
|
|
if(_evalStartCycle >= 1365) {
|
|
//New row was about to start, but no time to start sprite eval, next row will have no sprites
|
|
_spriteCount = 0;
|
|
}
|
|
TriggerHdsIrqs();
|
|
_nextEvent = PceVdcEvent::None;
|
|
_nextEventCounter = UINT16_MAX;
|
|
break;
|
|
|
|
case PceVdcEvent::IncRcrCounter:
|
|
IncrementRcrCounter();
|
|
_nextEvent = PceVdcEvent::None;
|
|
_nextEventCounter = UINT16_MAX;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void PceVdc::SetHorizontalMode(PceVdcModeH hMode)
|
|
{
|
|
_hMode = hMode;
|
|
switch(_hMode) {
|
|
case PceVdcModeH::Hds:
|
|
_hModeCounter = DotsToClocks((_state.HvLatch.HorizDisplayStart + 1) * 8);
|
|
//LogDebug("H: " + std::to_string(_state.HClock) + " - HDS");
|
|
break;
|
|
|
|
case PceVdcModeH::Hdw:
|
|
_needVCounterClock = true;
|
|
_needRcrIncrement = true;
|
|
_nextEvent = PceVdcEvent::IncRcrCounter;
|
|
_nextEventCounter = DotsToClocks((_state.HvLatch.HorizDisplayWidth - 1) * 8) + 2;
|
|
_hModeCounter = DotsToClocks((_state.HvLatch.HorizDisplayWidth + 1) * 8);
|
|
//LogDebug("H: " + std::to_string(_state.HClock) + " - HDW start");
|
|
break;
|
|
|
|
case PceVdcModeH::Hde:
|
|
_loadBgStart = UINT16_MAX;
|
|
_evalStartCycle = UINT16_MAX;
|
|
_loadSpriteStart = _state.HClock;
|
|
|
|
_hModeCounter = DotsToClocks((_state.HvLatch.HorizDisplayEnd + 1) * 8);
|
|
//LogDebug("H: " + std::to_string(_state.HClock) + " - HDE");
|
|
break;
|
|
|
|
case PceVdcModeH::Hsw:
|
|
_loadBgStart = UINT16_MAX;
|
|
_evalStartCycle = UINT16_MAX;
|
|
_hModeCounter = DotsToClocks((_state.HvLatch.HorizSyncWidth + 1) * 8);
|
|
_hSyncStartClock = _console->GetMasterClock();
|
|
ProcessHorizontalSyncStart();
|
|
//LogDebug("H: " + std::to_string(_state.HClock) + " - HSW");
|
|
break;
|
|
}
|
|
}
|
|
|
|
void PceVdc::ProcessVerticalSyncStart()
|
|
{
|
|
//Latch VSW/VDS/VDW/VCR at the start of each vertical sync?
|
|
_state.HvLatch.VertSyncWidth = _state.HvReg.VertSyncWidth;
|
|
_state.HvLatch.VertDisplayStart = _state.HvReg.VertDisplayStart;
|
|
_state.HvLatch.VertDisplayWidth = _state.HvReg.VertDisplayWidth;
|
|
_state.HvLatch.VertEndPosVcr = _state.HvReg.VertEndPosVcr;
|
|
|
|
_state.HvLatch.VramAccessMode = _state.HvReg.VramAccessMode;
|
|
_state.HvLatch.SpriteAccessMode = _state.HvReg.SpriteAccessMode;
|
|
_state.HvLatch.RowCount = _state.HvReg.RowCount;
|
|
_state.HvLatch.ColumnCount = _state.HvReg.ColumnCount;
|
|
}
|
|
|
|
void PceVdc::ProcessHorizontalSyncStart()
|
|
{
|
|
//Latch HSW/HDS/HDW/HDE at the start of each horizontal sync?
|
|
_state.HvLatch.HorizSyncWidth = _state.HvReg.HorizSyncWidth;
|
|
_state.HvLatch.HorizDisplayStart = _state.HvReg.HorizDisplayStart;
|
|
_state.HvLatch.HorizDisplayWidth = _state.HvReg.HorizDisplayWidth;
|
|
_state.HvLatch.HorizDisplayEnd = _state.HvReg.HorizDisplayEnd;
|
|
_state.HvLatch.CgMode = _state.HvLatch.CgMode;
|
|
|
|
_nextEvent = PceVdcEvent::None;
|
|
_nextEventCounter = UINT16_MAX;
|
|
_tileCount = 0;
|
|
_screenOffsetX = 0;
|
|
|
|
uint16_t displayStart = _state.HClock + _hModeCounter + DotsToClocks((_state.HvLatch.HorizDisplayStart + 1) * 8);
|
|
|
|
if(displayStart - DotsToClocks(24) >= PceConstants::ClockPerScanline) {
|
|
return;
|
|
}
|
|
|
|
//Calculate when sprite evaluation, sprite fetching and bg fetching will occur on the scanline
|
|
if(_vMode == PceVdcModeV::Vdw || _state.RcrCounter == GetScanlineCount() - 1) {
|
|
uint16_t displayWidth = DotsToClocks((_state.HvLatch.HorizDisplayWidth + 1) * 8);
|
|
|
|
//Sprite evaluation runs on all visible scanlines + the scanline before the picture starts
|
|
uint16_t spriteEvalStart = displayStart - DotsToClocks(16);
|
|
_evalStartCycle = spriteEvalStart;
|
|
_evalLastCycle = 0;
|
|
_evalEndCycle = std::min<uint16_t>(PceConstants::ClockPerScanline, spriteEvalStart + displayWidth + DotsToClocks(8));
|
|
|
|
if(_vMode == PceVdcModeV::Vdw) {
|
|
//Turn on BG tile fetching
|
|
uint16_t bgFetchStart = displayStart - DotsToClocks(16);
|
|
_loadBgStart = bgFetchStart;
|
|
_loadBgLastCycle = 0;
|
|
_loadBgEnd = std::min<uint16_t>(PceConstants::ClockPerScanline, bgFetchStart + displayWidth + DotsToClocks(16));
|
|
}
|
|
}
|
|
|
|
uint16_t eventClocks;
|
|
if(_vMode == PceVdcModeV::Vdw) {
|
|
_nextEvent = PceVdcEvent::LatchScrollY;
|
|
|
|
//Less than 33 causes Asuka 120% to have a flickering line
|
|
//Either the RCR interrupt is too early, or the latching was too late
|
|
eventClocks = DotsToClocks(33);
|
|
} else {
|
|
_nextEvent = PceVdcEvent::HdsIrqTrigger;
|
|
eventClocks = DotsToClocks(24);
|
|
}
|
|
|
|
if((int16_t)displayStart - (int16_t)eventClocks <= (int16_t)_state.HClock) {
|
|
ProcessEvent();
|
|
} else {
|
|
_nextEventCounter = displayStart - eventClocks - _state.HClock;
|
|
}
|
|
}
|
|
|
|
void PceVdc::ProcessSpriteEvaluation()
|
|
{
|
|
if(_state.HClock < _evalStartCycle || _evalLastCycle >= 64 || _state.BurstModeEnabled) {
|
|
return;
|
|
}
|
|
|
|
uint16_t end = (std::min(_evalEndCycle, _state.HClock) - _evalStartCycle) / GetClockDivider() / 4;
|
|
if(_evalLastCycle >= end) {
|
|
return;
|
|
}
|
|
|
|
//LogDebug("SPR EVAL: " + std::to_string(_evalLastCycle) + " -> " + std::to_string(end - 1));
|
|
|
|
if(_evalLastCycle == 0) {
|
|
LoadSpriteTiles();
|
|
_spriteCount = 0;
|
|
_spriteRow = (_state.RcrCounter + 1) % GetScanlineCount();
|
|
}
|
|
|
|
bool removeSpriteLimit = _emu->GetSettings()->GetPcEngineConfig().RemoveSpriteLimit;
|
|
|
|
for(uint16_t i = _evalLastCycle; i < end; i++) {
|
|
//4 VDC clocks is taken for each sprite
|
|
if(i >= 64) {
|
|
break;
|
|
}
|
|
|
|
int16_t y = (int16_t)(_spriteRam[i * 4] & 0x3FF) - 64;
|
|
if(_spriteRow < y) {
|
|
//Sprite not visible on this line
|
|
continue;
|
|
}
|
|
|
|
uint8_t height;
|
|
uint16_t flags = _spriteRam[i * 4 + 3];
|
|
switch((flags >> 12) & 0x03) {
|
|
default:
|
|
case 0: height = 16; break;
|
|
case 1: height = 32; break;
|
|
case 2: case 3: height = 64; break;
|
|
}
|
|
|
|
if(_spriteRow >= y + height) {
|
|
//Sprite not visible on this line
|
|
continue;
|
|
}
|
|
|
|
uint16_t spriteRow = _spriteRow - y;
|
|
|
|
bool verticalMirror = (flags & 0x8000) != 0;
|
|
bool horizontalMirror = (flags & 0x800) != 0;
|
|
|
|
int yOffset = 0;
|
|
int rowOffset = 0;
|
|
if(verticalMirror) {
|
|
yOffset = (height - spriteRow - 1) & 0x0F;
|
|
rowOffset = (height - spriteRow - 1) >> 4;
|
|
} else {
|
|
yOffset = spriteRow & 0x0F;
|
|
rowOffset = spriteRow >> 4;
|
|
}
|
|
|
|
uint16_t tileIndex = (_spriteRam[i * 4 + 2] & 0x7FF) >> 1;
|
|
bool loadSp23 = (_spriteRam[i * 4 + 2] & 0x01) != 0;
|
|
uint8_t width = (flags & 0x100) ? 32 : 16;
|
|
if(width == 32) {
|
|
tileIndex &= ~0x01;
|
|
}
|
|
if(height == 32) {
|
|
tileIndex &= ~0x02;
|
|
} else if(height == 64) {
|
|
tileIndex &= ~0x06;
|
|
}
|
|
uint16_t spriteTileY = tileIndex | (rowOffset << 1);
|
|
|
|
for(int x = 0; x < width; x+=16) {
|
|
if(_spriteCount >= 16) {
|
|
_hasSpriteOverflow = true;
|
|
if(!removeSpriteLimit) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
int columnOffset;
|
|
if(horizontalMirror) {
|
|
columnOffset = (width - x - 1) >> 4;
|
|
} else {
|
|
columnOffset = x >> 4;
|
|
}
|
|
|
|
uint16_t spriteTile = spriteTileY | columnOffset;
|
|
_sprites[_spriteCount].Index = i;
|
|
_sprites[_spriteCount].X = (int16_t)(_spriteRam[i * 4 + 1] & 0x3FF) - 32 + x;
|
|
_sprites[_spriteCount].TileAddress = spriteTile * 64 + yOffset;
|
|
_sprites[_spriteCount].HorizontalMirroring = horizontalMirror;
|
|
_sprites[_spriteCount].ForegroundPriority = (flags & 0x80) != 0;
|
|
_sprites[_spriteCount].Palette = (flags & 0x0F);
|
|
_sprites[_spriteCount].LoadSp23 = loadSp23;
|
|
_spriteCount++;
|
|
}
|
|
}
|
|
|
|
_evalLastCycle = end;
|
|
}
|
|
|
|
template<bool skipRender>
|
|
void PceVdc::LoadBackgroundTiles()
|
|
{
|
|
if(_state.HClock < _loadBgStart || _state.BurstModeEnabled) {
|
|
return;
|
|
}
|
|
|
|
uint16_t end = (std::min(_loadBgEnd, _state.HClock) - _loadBgStart) / GetClockDivider();
|
|
|
|
if(_loadBgLastCycle >= end) {
|
|
return;
|
|
}
|
|
|
|
//LogDebug("BG: " + std::to_string(_loadBgLastCycle) + " -> " + std::to_string(end - 1));
|
|
|
|
uint16_t columnMask = _state.HvLatch.ColumnCount - 1;
|
|
uint16_t scrollOffset = _state.HvLatch.BgScrollX >> 3;
|
|
uint16_t row = (_state.HvLatch.BgScrollY) & ((_state.HvLatch.RowCount * 8) - 1);
|
|
|
|
if(_state.HvLatch.VramAccessMode == 0) {
|
|
for(uint16_t i = _loadBgLastCycle; i < end; i++) {
|
|
if constexpr(skipRender) {
|
|
_allowVramAccess = (i & 0x01) == 0;
|
|
} else {
|
|
switch(i & 0x07) {
|
|
//CPU can access VRAM
|
|
case 0: case 2: case 4: case 6: _allowVramAccess = true; break;
|
|
|
|
case 1: LoadBatEntry(scrollOffset, columnMask, row); break;
|
|
case 3: _allowVramAccess = false; break; //Unused BAT read?
|
|
case 5: LoadTileDataCg0(row); break;
|
|
case 7: LoadTileDataCg1(row); break;
|
|
}
|
|
}
|
|
}
|
|
} else if(_state.HvLatch.VramAccessMode == 3) {
|
|
//Mode 3 is 4 cycles per read, CPU has no VRAM access, only 2BPP
|
|
LoadBackgroundTilesWidth4(end, scrollOffset, columnMask, row);
|
|
} else {
|
|
//Mode 1/2 are 2 cycles per read
|
|
LoadBackgroundTilesWidth2(end, scrollOffset, columnMask, row);
|
|
}
|
|
|
|
_loadBgLastCycle = end;
|
|
}
|
|
|
|
void PceVdc::LoadBackgroundTilesWidth2(uint16_t end, uint16_t scrollOffset, uint16_t columnMask, uint16_t row)
|
|
{
|
|
for(uint16_t i = _loadBgLastCycle; i < end; i++) {
|
|
_allowVramAccess = false;
|
|
switch(i & 0x07) {
|
|
case 1: LoadBatEntry(scrollOffset, columnMask, row); break;
|
|
case 2: _allowVramAccess = true; break; //CPU
|
|
case 3: _allowVramAccess = true; break; //CPU
|
|
case 5: LoadTileDataCg0(row); break;
|
|
case 7: LoadTileDataCg1(row); break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void PceVdc::LoadBackgroundTilesWidth4(uint16_t end, uint16_t scrollOffset, uint16_t columnMask, uint16_t row)
|
|
{
|
|
_allowVramAccess = false;
|
|
for(uint16_t i = _loadBgLastCycle; i < end; i++) {
|
|
switch(i & 0x07) {
|
|
case 3: LoadBatEntry(scrollOffset, columnMask, row); break;
|
|
|
|
case 7:
|
|
//Load CG0 or CG1 based on CG mode flag
|
|
_tiles[_tileCount].TileData[0] = ReadVram(_tiles[_tileCount].TileAddr + (row & 0x07) + (_state.HvLatch.CgMode ? 8 : 0));
|
|
_tiles[_tileCount].TileData[1] = 0;
|
|
_tileCount++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void PceVdc::LoadBatEntry(uint16_t scrollOffset, uint16_t columnMask, uint16_t row)
|
|
{
|
|
uint16_t tileColumn = (scrollOffset + _tileCount) & columnMask;
|
|
uint16_t batEntry = ReadVram((row >> 3) * _state.HvLatch.ColumnCount + tileColumn);
|
|
_tiles[_tileCount].Palette = batEntry >> 12;
|
|
_tiles[_tileCount].TileAddr = ((batEntry & 0xFFF) * 16);
|
|
_allowVramAccess = false;
|
|
}
|
|
|
|
void PceVdc::LoadTileDataCg0(uint16_t row)
|
|
{
|
|
_tiles[_tileCount].TileData[0] = ReadVram(_tiles[_tileCount].TileAddr + (row & 0x07));
|
|
_allowVramAccess = false;
|
|
}
|
|
|
|
void PceVdc::LoadTileDataCg1(uint16_t row)
|
|
{
|
|
_tiles[_tileCount].TileData[1] = ReadVram(_tiles[_tileCount].TileAddr + (row & 0x07) + 8);
|
|
_allowVramAccess = false;
|
|
_tileCount++;
|
|
}
|
|
|
|
uint16_t PceVdc::ReadVram(uint16_t addr)
|
|
{
|
|
if(addr < 0x8000) {
|
|
return _vramOpenBus = _vram[addr];
|
|
}
|
|
|
|
//Camp California expects an empty sprite if tile index is out of bounds (>= $200) - this is probably caused by open bus behavior?
|
|
//Return last word of the previously loaded VRAM data
|
|
_emu->BreakIfDebugging(CpuType::Pce, BreakSource::PceBreakOnInvalidVramAddress);
|
|
return _vramOpenBus;
|
|
}
|
|
|
|
void PceVdc::LoadSpriteTiles()
|
|
{
|
|
_drawSpriteCount = 0;
|
|
_rowHasSprite0 = false;
|
|
|
|
if(_state.BurstModeEnabled || (_loadSpriteStart >= _loadBgStart && _loadSpriteStart < _loadBgEnd)) {
|
|
return;
|
|
}
|
|
|
|
bool removeSpriteLimit = _emu->GetSettings()->GetPcEngineConfig().RemoveSpriteLimit;
|
|
uint16_t clockCount = _loadSpriteStart > _loadBgStart ? (PceConstants::ClockPerScanline - _loadSpriteStart) + _loadBgStart : (_loadBgStart - _loadSpriteStart);
|
|
bool hasSprite0 = false;
|
|
memset(_xPosHasSprite, 0, sizeof(_xPosHasSprite));
|
|
if(_state.HvLatch.SpriteAccessMode != 1) {
|
|
//Modes 0/2/3 load 4 words over 4, 8 or 16 VDC clocks
|
|
uint16_t clocksPerSprite;
|
|
switch(_state.HvLatch.SpriteAccessMode) {
|
|
default: case 0: clocksPerSprite = 4; break;
|
|
case 2: clocksPerSprite = 8; break;
|
|
case 3: clocksPerSprite = 16; break;
|
|
}
|
|
|
|
_drawSpriteCount = std::min<uint16_t>(_spriteCount, clockCount / GetClockDivider() / clocksPerSprite);
|
|
_totalSpriteCount = removeSpriteLimit ? _spriteCount : _drawSpriteCount;
|
|
for(int i = 0; i < _totalSpriteCount; i++) {
|
|
PceSpriteInfo& spr = _drawSprites[i];
|
|
spr = _sprites[i];
|
|
memset(_xPosHasSprite + spr.X + 32, 1, 16);
|
|
uint16_t addr = spr.TileAddress;
|
|
spr.TileData[0] = ReadVram(addr);
|
|
spr.TileData[1] = ReadVram(addr + 16);
|
|
spr.TileData[2] = ReadVram(addr + 32);
|
|
spr.TileData[3] = ReadVram(addr + 48);
|
|
hasSprite0 |= spr.Index == 0;
|
|
}
|
|
} else {
|
|
//Mode 1 uses 2BPP sprites, 4 clocks per sprite
|
|
_drawSpriteCount = std::min<uint16_t>(_spriteCount, clockCount / GetClockDivider() / 4);
|
|
_totalSpriteCount = removeSpriteLimit ? _spriteCount : _drawSpriteCount;
|
|
for(int i = 0; i < _totalSpriteCount; i++) {
|
|
PceSpriteInfo& spr = _drawSprites[i];
|
|
spr = _sprites[i];
|
|
memset(_xPosHasSprite + spr.X + 32, 1, 16);
|
|
//Load SP0/SP1 or SP2/SP3 based on flag
|
|
uint16_t addr = spr.TileAddress + (spr.LoadSp23 ? 32 : 0);
|
|
spr.TileData[0] = ReadVram(addr);
|
|
spr.TileData[1] = ReadVram(addr + 16);
|
|
spr.TileData[2] = 0;
|
|
spr.TileData[3] = 0;
|
|
hasSprite0 |= spr.Index == 0;
|
|
}
|
|
}
|
|
|
|
if(hasSprite0 && _drawSpriteCount > 1) {
|
|
//Force VDC emulation to run on each CPU cycle, to ensure any sprite 0 hit IRQ is triggered at the correct time
|
|
_rowHasSprite0 = true;
|
|
}
|
|
}
|
|
|
|
bool PceVdc::IsDmaAllowed()
|
|
{
|
|
if(!_allowDma && !_state.BurstModeEnabled) {
|
|
//Can't DMA during rendering
|
|
return false;
|
|
}
|
|
|
|
if(_hMode == PceVdcModeH::Hsw && _console->GetMasterClock() - _hSyncStartClock <= DotsToClocks(8)) {
|
|
//VRAM accesses are blocked during the first 8 dots after horizontal sync,
|
|
//which prevents SATB/VRAM DMA from running during that time (based on test rom result)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void PceVdc::ProcessSatbTransfer()
|
|
{
|
|
if(!IsDmaAllowed()) {
|
|
return;
|
|
}
|
|
|
|
//This takes 1024 VDC cycles (so 2048/3072/4096 master clocks depending on VCE/VDC speed)
|
|
//1 word transfered every 4 dots (8 to 16 master clocks, depending on VCE clock divider)
|
|
_state.SatbTransferNextWordCounter += 3;
|
|
if(_state.SatbTransferNextWordCounter / GetClockDivider() >= 4) {
|
|
_state.SatbTransferNextWordCounter -= 4 * GetClockDivider();
|
|
|
|
int i = _state.SatbTransferOffset;
|
|
uint16_t value = ReadVram(_state.SatbBlockSrc + i);
|
|
_emu->ProcessPpuWrite<CpuType::Pce>(i << 1, value, _spriteRamType);
|
|
_emu->ProcessPpuWrite<CpuType::Pce>((i << 1) + 1, value, _spriteRamType);
|
|
_spriteRam[i] = value;
|
|
|
|
_state.SatbTransferOffset++;
|
|
|
|
if(_state.SatbTransferOffset == 0) {
|
|
_state.SatbTransferRunning = false;
|
|
|
|
if(_state.VramSatbIrqEnabled) {
|
|
_state.SatbTransferDone = true;
|
|
_vpc->SetIrq(this);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void PceVdc::ProcessVramDmaTransfer()
|
|
{
|
|
if(!IsDmaAllowed()) {
|
|
return;
|
|
}
|
|
|
|
_vramDmaPendingCycles += 3;
|
|
|
|
uint8_t hClocksPerDmaCycle = GetClockDivider() * 2;
|
|
|
|
while(_vramDmaPendingCycles >= hClocksPerDmaCycle) {
|
|
if(_vramDmaReadCycle) {
|
|
_vramDmaBuffer = ReadVram(_state.BlockSrc);
|
|
_vramDmaReadCycle = false;
|
|
} else {
|
|
_state.BlockLen--;
|
|
|
|
if(_state.BlockDst < 0x8000) {
|
|
//Ignore writes over $8000
|
|
_vram[_state.BlockDst] = _vramDmaBuffer;
|
|
}
|
|
|
|
_state.BlockSrc += (_state.DecrementSrc ? -1 : 1);
|
|
_state.BlockDst += (_state.DecrementDst ? -1 : 1);
|
|
_vramDmaReadCycle = true;
|
|
|
|
if(_state.BlockLen == 0xFFFF) {
|
|
_vramDmaRunning = false;
|
|
_vramDmaPendingCycles = 0;
|
|
if(_state.VramVramIrqEnabled) {
|
|
_state.VramTransferDone = true;
|
|
_vpc->SetIrq(this);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
_vramDmaPendingCycles -= hClocksPerDmaCycle;
|
|
}
|
|
}
|
|
|
|
void PceVdc::SetVertMode(PceVdcModeV vMode)
|
|
{
|
|
_vMode = vMode;
|
|
switch(_vMode) {
|
|
default:
|
|
case PceVdcModeV::Vds:
|
|
_vModeCounter = _state.HvLatch.VertDisplayStart + 2;
|
|
break;
|
|
|
|
case PceVdcModeV::Vdw:
|
|
_allowDma = false;
|
|
_vModeCounter = _state.HvLatch.VertDisplayWidth + 1;
|
|
_state.RcrCounter = 0;
|
|
break;
|
|
|
|
case PceVdcModeV::Vde:
|
|
_vModeCounter = _state.HvLatch.VertEndPosVcr;
|
|
break;
|
|
|
|
case PceVdcModeV::Vsw:
|
|
ProcessVerticalSyncStart();
|
|
_vModeCounter = _state.HvLatch.VertSyncWidth + 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void PceVdc::ClockVCounter()
|
|
{
|
|
_vModeCounter--;
|
|
if(_vModeCounter == 0) {
|
|
SetVertMode((PceVdcModeV)(((int)_vMode + 1) % 4));
|
|
}
|
|
_needVCounterClock = false;
|
|
}
|
|
|
|
void PceVdc::IncrementRcrCounter()
|
|
{
|
|
_state.RcrCounter++;
|
|
|
|
_needRcrIncrement = false;
|
|
ClockVCounter();
|
|
|
|
if(_vMode == PceVdcModeV::Vde && _state.RcrCounter == _state.HvLatch.VertDisplayWidth + 1) {
|
|
_needVertBlankIrq = true;
|
|
_verticalBlankDone = true;
|
|
}
|
|
|
|
//This triggers ~12 VDC cycles before the end of the visible part of the scanline
|
|
if(_state.EnableScanlineIrq && _state.RcrCounter == (int)_state.RasterCompareRegister - 0x40) {
|
|
_state.ScanlineDetected = true;
|
|
_vpc->SetIrq(this);
|
|
}
|
|
}
|
|
|
|
void PceVdc::IncScrollY()
|
|
{
|
|
if(_state.RcrCounter == 0) {
|
|
_state.HvLatch.BgScrollY = _state.HvReg.BgScrollY;
|
|
} else {
|
|
if(_state.BgScrollYUpdatePending) {
|
|
_state.HvLatch.BgScrollY = _state.HvReg.BgScrollY;
|
|
_state.BgScrollYUpdatePending = false;
|
|
}
|
|
|
|
_state.HvLatch.BgScrollY++;
|
|
}
|
|
}
|
|
|
|
void PceVdc::ProcessEndOfScanline()
|
|
{
|
|
DrawScanline();
|
|
|
|
_state.HClock = 0;
|
|
_state.Scanline++;
|
|
|
|
_latchClockX = UINT16_MAX;
|
|
_latchClockY = UINT16_MAX;
|
|
|
|
if(_state.Scanline == 256) {
|
|
_state.FrameCount++;
|
|
_vpc->SendFrame(this);
|
|
} else if(_state.Scanline >= GetScanlineCount()) {
|
|
//Update flags that were locked during burst mode
|
|
_state.Scanline = 0;
|
|
_verticalBlankDone = false;
|
|
_state.BurstModeEnabled = !_state.NextBackgroundEnabled && !_state.NextSpritesEnabled;
|
|
_state.BackgroundEnabled = _state.NextBackgroundEnabled;
|
|
_state.SpritesEnabled = _state.NextSpritesEnabled;
|
|
|
|
if(!_isVdc2) {
|
|
_vpc->ProcessStartFrame();
|
|
_emu->ProcessEvent(EventType::StartFrame);
|
|
}
|
|
}
|
|
|
|
_vpc->ProcessScanlineStart(this, _state.Scanline);
|
|
|
|
if(_needRcrIncrement) {
|
|
IncrementRcrCounter();
|
|
} else if(_needVCounterClock) {
|
|
ClockVCounter();
|
|
}
|
|
|
|
if(_hMode == PceVdcModeH::Hdw) {
|
|
//Display output was interrupted by hblank, start loading sprites in ~36 dots. (approximate, based on timing test)
|
|
//Could be incorrect in some scenarios, needs more testing
|
|
_loadSpriteStart = DotsToClocks(36);
|
|
}
|
|
|
|
//VCE sets HBLANK to low every 1365 clocks, interrupting what
|
|
//the VDC was doing and starting a HSW phase
|
|
if(_hMode != PceVdcModeH::Hsw) {
|
|
_hMode = PceVdcModeH::Hsw;
|
|
_hSyncStartClock = _console->GetMasterClock();
|
|
}
|
|
|
|
_loadBgStart = UINT16_MAX;
|
|
_evalStartCycle = UINT16_MAX;
|
|
|
|
//The HSW phase appears to be longer in 7mhz mode compared to 5/10mhz modes
|
|
//Less than 32 here breaks Camp California and Shapeshifter
|
|
_hModeCounter = DotsToClocks(GetClockDivider() == 3 ? 32 : 24);
|
|
_xStart = 0;
|
|
_lastDrawHClock = 0;
|
|
|
|
ProcessHorizontalSyncStart();
|
|
|
|
if(_state.Scanline == GetScanlineCount() - 3) {
|
|
//VCE sets VBLANK for 3 scanlines at the end of every frame
|
|
SetVertMode(PceVdcModeV::Vsw);
|
|
} else if(_state.Scanline == GetScanlineCount() - 2) {
|
|
if(!_verticalBlankDone) {
|
|
_needVertBlankIrq = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
void PceVdc::TriggerDmaStart()
|
|
{
|
|
_allowDma = true;
|
|
|
|
if(_state.SatbTransferPending || _state.RepeatSatbTransfer) {
|
|
_state.SatbTransferPending = false;
|
|
_state.SatbTransferRunning = true;
|
|
_state.SatbTransferNextWordCounter = 0;
|
|
_state.SatbTransferOffset = 0;
|
|
}
|
|
}
|
|
|
|
void PceVdc::TriggerVerticalBlank()
|
|
{
|
|
//End of display, trigger irq
|
|
if(_state.EnableVerticalBlankIrq) {
|
|
_state.VerticalBlank = true;
|
|
_vpc->SetIrq(this);
|
|
}
|
|
|
|
_needVertBlankIrq = false;
|
|
|
|
//Any pending SATB/VRAM DMA starts at the same time as the vblank irq is triggered
|
|
//This fixes the "new season" screen in "TV Sports Football"
|
|
TriggerDmaStart();
|
|
}
|
|
|
|
uint8_t PceVdc::GetTilePixelColor(const uint16_t chr0, const uint16_t chr1, const uint8_t shift)
|
|
{
|
|
uint16_t color = ((chr0 >> shift) & 0x101) | (((chr1 >> shift) & 0x101) << 2);
|
|
return (uint8_t)(color | ((color & 0x500) >> 7));
|
|
}
|
|
|
|
uint8_t PceVdc::GetSpritePixelColor(const uint16_t chrData[4], const uint8_t shift)
|
|
{
|
|
return (
|
|
((chrData[0] >> shift) & 0x01) |
|
|
(((chrData[1] >> shift) & 0x01) << 1) |
|
|
(((chrData[2] >> shift) & 0x01) << 2) |
|
|
(((chrData[3] >> shift) & 0x01) << 3)
|
|
);
|
|
}
|
|
|
|
void PceVdc::DrawScanline()
|
|
{
|
|
if(_state.Scanline < 14 || _state.Scanline >= 256) {
|
|
//Only 242 rows can be shown
|
|
return;
|
|
}
|
|
|
|
ProcessSpriteEvaluation();
|
|
|
|
bool skipRender = _vpc->IsSkipRenderEnabled();
|
|
if(skipRender) {
|
|
LoadBackgroundTiles<true>();
|
|
} else {
|
|
LoadBackgroundTiles<false>();
|
|
}
|
|
|
|
if(_totalSpriteCount > 0) {
|
|
if(_rowHasSprite0) {
|
|
if(skipRender) {
|
|
InternalDrawScanline<true, true, true>();
|
|
} else {
|
|
InternalDrawScanline<true, true, false>();
|
|
}
|
|
} else if(!skipRender) {
|
|
InternalDrawScanline<true, false, false>();
|
|
}
|
|
} else if(!skipRender) {
|
|
InternalDrawScanline<false, false, false>();
|
|
}
|
|
}
|
|
|
|
template<bool hasSprites, bool hasSprite0, bool skipRender>
|
|
void PceVdc::InternalDrawScanline()
|
|
{
|
|
uint16_t* out = _rowBuffer;
|
|
|
|
uint16_t pixelsToDraw = (_state.HClock - _lastDrawHClock) / GetClockDivider();
|
|
uint16_t xStart = _xStart;
|
|
uint16_t xMax = _xStart + pixelsToDraw;
|
|
|
|
bool inPicture = _hMode == PceVdcModeH::Hdw && _tileCount > 0;
|
|
|
|
if(inPicture && (_state.BackgroundEnabled || _state.SpritesEnabled)) {
|
|
PcEngineConfig& cfg = _emu->GetSettings()->GetPcEngineConfig();
|
|
bool bgEnabled = _state.BackgroundEnabled && !(_isVdc2 ? cfg.DisableBackgroundVdc2 : cfg.DisableBackground);
|
|
bool sprEnabled = _state.SpritesEnabled && !(_isVdc2 ? cfg.DisableSpritesVdc2 : cfg.DisableSprites);
|
|
uint16_t grayscaleBit = _vce->IsGrayscale() ? 0x200 : 0;
|
|
|
|
uint16_t outColor = 0;
|
|
uint8_t bgColor = 0;
|
|
for(; xStart < xMax; xStart++) {
|
|
if constexpr(!skipRender) {
|
|
outColor = PceVpc::TransparentPixelFlag | _vce->GetPalette(0);
|
|
bgColor = 0;
|
|
if(bgEnabled) {
|
|
uint16_t screenX = (_state.HvLatch.BgScrollX & 0x07) + _screenOffsetX;
|
|
uint16_t column = screenX >> 3;
|
|
bgColor = GetTilePixelColor(_tiles[column].TileData[0], _tiles[column].TileData[1], 7 - (screenX & 0x07));
|
|
if(bgColor != 0) {
|
|
outColor = _vce->GetPalette(_tiles[column].Palette * 16 + bgColor);
|
|
}
|
|
}
|
|
}
|
|
|
|
if constexpr(hasSprites) {
|
|
if(_state.SpritesEnabled && _xPosHasSprite[_screenOffsetX + 32]) {
|
|
uint8_t sprColor;
|
|
bool checkSprite0Hit = false;
|
|
for(uint16_t i = 0; i < _totalSpriteCount; i++) {
|
|
int16_t xOffset = _screenOffsetX - _drawSprites[i].X;
|
|
if(xOffset >= 0 && xOffset < 16) {
|
|
if(!_drawSprites[i].HorizontalMirroring) {
|
|
xOffset = 15 - xOffset;
|
|
}
|
|
|
|
sprColor = GetSpritePixelColor(_drawSprites[i].TileData, xOffset);
|
|
|
|
if(sprColor != 0) {
|
|
if constexpr(hasSprite0) {
|
|
if(checkSprite0Hit) {
|
|
//Note: don't trigger sprite 0 hit for sprites that are drawn because of the "remove sprite limit" option
|
|
if(_state.EnableCollisionIrq && i < _drawSpriteCount) {
|
|
_state.Sprite0Hit = true;
|
|
_vpc->SetIrq(this);
|
|
}
|
|
} else {
|
|
if(sprEnabled && (bgColor == 0 || _drawSprites[i].ForegroundPriority)) {
|
|
outColor = PceVpc::SpritePixelFlag | _vce->GetPalette(256 + _drawSprites[i].Palette * 16 + sprColor);
|
|
}
|
|
}
|
|
|
|
if(_drawSprites[i].Index == 0) {
|
|
checkSprite0Hit = true;
|
|
} else {
|
|
break;
|
|
}
|
|
} else {
|
|
if(sprEnabled && (bgColor == 0 || _drawSprites[i].ForegroundPriority)) {
|
|
outColor = PceVpc::SpritePixelFlag | _vce->GetPalette(256 + _drawSprites[i].Palette * 16 + sprColor);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if constexpr(!skipRender) {
|
|
out[xStart] = outColor | grayscaleBit;
|
|
}
|
|
_screenOffsetX++;
|
|
}
|
|
} else if(inPicture) {
|
|
if constexpr(!skipRender) {
|
|
uint16_t color = _vce->GetPalette(0);
|
|
for(; xStart < xMax; xStart++) {
|
|
//In picture, but BG is not enabled, draw bg color
|
|
out[xStart] = PceVpc::TransparentPixelFlag | color;
|
|
}
|
|
}
|
|
} else {
|
|
if constexpr(!skipRender) {
|
|
uint16_t color = _vce->GetPalette(16 * 16);
|
|
for(; xStart < xMax; xStart++) {
|
|
//Output hasn't started yet, display overscan color
|
|
out[xStart] = PceVpc::TransparentPixelFlag | color;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(_state.HClock == 1365) {
|
|
_vpc->ProcessScanlineEnd(this, _state.Scanline, _rowBuffer);
|
|
}
|
|
|
|
_xStart = xStart;
|
|
_lastDrawHClock = _state.HClock / GetClockDivider() * GetClockDivider();
|
|
}
|
|
|
|
void PceVdc::ProcessVramRead()
|
|
{
|
|
_state.ReadBuffer = ReadVram(_state.MemAddrRead);
|
|
if(_state.MemAddrRead < 0x8000) {
|
|
_emu->ProcessPpuRead<CpuType::Pce>((_state.MemAddrRead << 1), _state.ReadBuffer, _vramType);
|
|
_emu->ProcessPpuRead<CpuType::Pce>((_state.MemAddrRead << 1) + 1, _state.ReadBuffer, _vramType);
|
|
}
|
|
_state.MemAddrRead += _state.VramAddrIncrement;
|
|
_pendingMemoryRead = false;
|
|
}
|
|
|
|
void PceVdc::ProcessVramWrite()
|
|
{
|
|
if(_state.MemAddrWrite < 0x8000) {
|
|
//Ignore writes at $8000+
|
|
_emu->ProcessPpuWrite<CpuType::Pce>(_state.MemAddrWrite << 1, _state.VramData, _vramType);
|
|
_emu->ProcessPpuWrite<CpuType::Pce>((_state.MemAddrWrite << 1) + 1, _state.VramData, _vramType);
|
|
_vram[_state.MemAddrWrite] = _state.VramData;
|
|
} else {
|
|
_emu->BreakIfDebugging(CpuType::Pce, BreakSource::PceBreakOnInvalidVramAddress);
|
|
}
|
|
_state.MemAddrWrite += _state.VramAddrIncrement;
|
|
_pendingMemoryWrite = false;
|
|
}
|
|
|
|
void PceVdc::ProcessVramAccesses()
|
|
{
|
|
if(_transferDelay) {
|
|
_transferDelay -= 3;
|
|
if(_transferDelay > 0) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
_transferDelay = 0;
|
|
|
|
bool inBgFetch = !_state.BurstModeEnabled && _state.HClock >= _loadBgStart && _state.HClock < _loadBgEnd && _state.Scanline >= 14 && _state.Scanline < 256;
|
|
bool accessBlocked;
|
|
|
|
if(_vMode != PceVdcModeV::Vdw || _state.BurstModeEnabled || (((!_state.SpritesEnabled || _spriteCount == 0) && !inBgFetch && _vMode == PceVdcModeV::Vdw))) {
|
|
//-During SATB/VRAM DMA, prevent all transfers.
|
|
//-Allow a VRAM read/write every other dot during:
|
|
// -vblank
|
|
// -forced blank (burst mode)
|
|
// -sprite fetching when no sprites need to be fetched (sprites disabled or no sprites were found during sprite evaluation)
|
|
accessBlocked = (_state.SatbTransferRunning || _vramDmaRunning || ((_state.HClock / GetClockDivider()) & 0x01)) ? true : false;
|
|
} else {
|
|
//During tile/sprite fetching, only allow access on the CPU slots available during background tile fetches
|
|
accessBlocked = inBgFetch && !_allowVramAccess;
|
|
if(!accessBlocked && !inBgFetch && _state.SpritesEnabled) {
|
|
//Find how many clocks have elapsed since sprite fetching started
|
|
uint16_t clockCount = _state.HClock > _loadBgEnd ? (_state.HClock - _loadBgEnd) : (PceConstants::ClockPerScanline - _loadBgEnd + _state.HClock);
|
|
uint16_t dotCount = clockCount / GetClockDivider();
|
|
uint16_t clocksPerSprite;
|
|
switch(_state.HvLatch.SpriteAccessMode) {
|
|
default: case 0: case 1: clocksPerSprite = 4; break;
|
|
case 2: clocksPerSprite = 8; break;
|
|
case 3: clocksPerSprite = 16; break;
|
|
}
|
|
if(dotCount < _spriteCount * clocksPerSprite) {
|
|
//VDC is still fetching sprites, block access
|
|
accessBlocked = true;
|
|
} else {
|
|
//Sprite fetching is done, allow access every other dot
|
|
accessBlocked = ((_state.HClock / GetClockDivider()) & 0x01) ? true : false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!accessBlocked) {
|
|
if(_hMode == PceVdcModeH::Hsw && _console->GetMasterClock() - _hSyncStartClock < 8 * GetClockDivider()) {
|
|
//VRAM accesses appear to be blocked during the first 8 dots of horizontal sync
|
|
return;
|
|
}
|
|
|
|
if(_pendingMemoryRead) {
|
|
ProcessVramRead();
|
|
} else if(_pendingMemoryWrite) {
|
|
ProcessVramWrite();
|
|
}
|
|
}
|
|
}
|
|
|
|
void PceVdc::QueueMemoryRead()
|
|
{
|
|
//All of this is guesswork based on results from a test rom
|
|
//Read operations appear to be processed slightly slower than writes?
|
|
_pendingMemoryRead = true;
|
|
switch(GetClockDivider()) {
|
|
case 2: _transferDelay = 15; break; //5 exec ticks
|
|
case 3: _transferDelay = 24; break; //8 exec ticks
|
|
case 4: _transferDelay = 24; break; //8 exec ticks
|
|
}
|
|
}
|
|
|
|
void PceVdc::QueueMemoryWrite()
|
|
{
|
|
//All of this is guesswork based on results from a test rom
|
|
//Write operations appear to be processed slightly faster than reads?
|
|
_pendingMemoryWrite = true;
|
|
switch(GetClockDivider()) {
|
|
case 2: _transferDelay = 12; break; //4 exec ticks
|
|
case 3: _transferDelay = 18; break; //6 exec ticks
|
|
case 4: _transferDelay = 21; break; //7 exec ticks
|
|
}
|
|
}
|
|
|
|
void PceVdc::WaitForVramAccess()
|
|
{
|
|
//Stall the CPU when a read/write operation is pending
|
|
while(_pendingMemoryRead || _pendingMemoryWrite) {
|
|
//TODO timing, this is probably not quite right. CPU will be stalled until
|
|
//a VDC cycle that allows VRAM access is reached. This isn't always going to
|
|
//be a multiple of 3 master clocks like this currently assumes
|
|
_console->GetMemoryManager()->ExecFastCycle();
|
|
DrawScanline();
|
|
}
|
|
}
|
|
|
|
uint8_t PceVdc::ReadRegister(uint16_t addr)
|
|
{
|
|
DrawScanline();
|
|
|
|
switch(addr & 0x03) {
|
|
default:
|
|
case 0: {
|
|
uint8_t result = 0;
|
|
result |= (_pendingMemoryRead || _pendingMemoryWrite) ? 0x40 : 0x00;
|
|
result |= _state.VerticalBlank ? 0x20 : 0x00;
|
|
result |= _state.VramTransferDone ? 0x10 : 0x00;
|
|
result |= _state.SatbTransferDone ? 0x08 : 0x00;
|
|
result |= _state.ScanlineDetected ? 0x04 : 0x00;
|
|
result |= _state.SpriteOverflow ? 0x02 : 0x00;
|
|
result |= _state.Sprite0Hit ? 0x01 : 0x00;
|
|
|
|
_state.VerticalBlank = false;
|
|
_state.VramTransferDone = false;
|
|
_state.SatbTransferDone = false;
|
|
_state.ScanlineDetected = false;
|
|
_state.SpriteOverflow = false;
|
|
_state.Sprite0Hit = false;
|
|
|
|
_vpc->ClearIrq(this);
|
|
return result;
|
|
}
|
|
|
|
case 1: return 0; //Unused, reads return 0
|
|
|
|
//Reads to 2/3 will always return the read buffer, but the
|
|
//read address will only increment when register 2 is selected
|
|
case 2:
|
|
if(_pendingMemoryRead) {
|
|
//D&D Order of the Griffon breaks without this
|
|
WaitForVramAccess();
|
|
}
|
|
return (uint8_t)_state.ReadBuffer;
|
|
|
|
case 3:
|
|
if(_pendingMemoryRead) {
|
|
WaitForVramAccess();
|
|
}
|
|
uint8_t value = _state.ReadBuffer >> 8;
|
|
if(_state.CurrentReg == 0x02) {
|
|
QueueMemoryRead();
|
|
}
|
|
return value;
|
|
}
|
|
}
|
|
|
|
void PceVdc::WriteRegister(uint16_t addr, uint8_t value)
|
|
{
|
|
DrawScanline();
|
|
|
|
switch(addr & 0x03) {
|
|
case 0: _state.CurrentReg = value & 0x1F; break;
|
|
|
|
case 1: break; //Unused, writes do nothing
|
|
|
|
case 2:
|
|
case 3:
|
|
bool msb = (addr & 0x03) == 0x03;
|
|
|
|
switch(_state.CurrentReg) {
|
|
case 0x00:
|
|
WaitForVramAccess(); //Wonder Momo has graphical issues without this
|
|
UpdateReg(_state.MemAddrWrite, value, msb);
|
|
break;
|
|
|
|
case 0x01:
|
|
WaitForVramAccess();
|
|
UpdateReg(_state.MemAddrRead, value, msb);
|
|
if(msb) {
|
|
QueueMemoryRead();
|
|
}
|
|
break;
|
|
|
|
case 0x02:
|
|
WaitForVramAccess();
|
|
if(msb) {
|
|
UpdateReg(_state.VramData, value, true);
|
|
QueueMemoryWrite();
|
|
} else {
|
|
UpdateReg(_state.VramData, value, false);
|
|
}
|
|
break;
|
|
|
|
case 0x05:
|
|
if(msb) {
|
|
//TODO output select
|
|
//TODO dram refresh
|
|
switch((value >> 3) & 0x03) {
|
|
case 0: _state.VramAddrIncrement = 1; break;
|
|
case 1: _state.VramAddrIncrement = 0x20; break;
|
|
case 2: _state.VramAddrIncrement = 0x40; break;
|
|
case 3: _state.VramAddrIncrement = 0x80; break;
|
|
}
|
|
} else {
|
|
_state.EnableCollisionIrq = (value & 0x01) != 0;
|
|
_state.EnableOverflowIrq = (value & 0x02) != 0;
|
|
_state.EnableScanlineIrq = (value & 0x04) != 0;
|
|
_state.EnableVerticalBlankIrq = (value & 0x08) != 0;
|
|
|
|
_state.OutputVerticalSync = ((value & 0x30) >> 4) >= 2;
|
|
_state.OutputHorizontalSync = ((value & 0x30) >> 4) >= 1;
|
|
|
|
_state.NextSpritesEnabled = (value & 0x40) != 0;
|
|
_state.NextBackgroundEnabled = (value & 0x80) != 0;
|
|
if(_latchClockY == _state.HClock && !_state.BurstModeEnabled) {
|
|
//Write occurred at the same time as the CR latch, update latch too
|
|
_state.SpritesEnabled = _state.NextSpritesEnabled;
|
|
_state.BackgroundEnabled = _state.NextBackgroundEnabled;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 0x06: UpdateReg<0x3FF>(_state.RasterCompareRegister, value, msb); break;
|
|
case 0x07:
|
|
UpdateReg<0x3FF>(_state.HvReg.BgScrollX, value, msb);
|
|
if(_latchClockX == _state.HClock) {
|
|
//Write occurred at the same time as the BXR latch, update latch too
|
|
_state.HvLatch.BgScrollX = _state.HvReg.BgScrollX;
|
|
}
|
|
break;
|
|
case 0x08:
|
|
UpdateReg<0x1FF>(_state.HvReg.BgScrollY, value, msb);
|
|
_state.BgScrollYUpdatePending = true;
|
|
if(_latchClockY == _state.HClock) {
|
|
//Write occurred at the same time as the BYR latch, update latch too
|
|
IncScrollY();
|
|
}
|
|
break;
|
|
|
|
case 0x09:
|
|
if(!msb) {
|
|
switch((value >> 4) & 0x03) {
|
|
case 0: _state.HvReg.ColumnCount = 32; break;
|
|
case 1: _state.HvReg.ColumnCount = 64; break;
|
|
case 2: case 3: _state.HvReg.ColumnCount = 128; break;
|
|
}
|
|
|
|
_state.HvReg.RowCount = (value & 0x40) ? 64 : 32;
|
|
|
|
_state.HvReg.VramAccessMode = value & 0x03;
|
|
_state.HvReg.SpriteAccessMode = (value >> 2) & 0x03;
|
|
_state.HvReg.CgMode = (value & 0x80) != 0;
|
|
}
|
|
break;
|
|
|
|
case 0x0A:
|
|
if(msb) {
|
|
_state.HvReg.HorizDisplayStart = value & 0x7F;
|
|
} else {
|
|
_state.HvReg.HorizSyncWidth = value & 0x1F;
|
|
}
|
|
break;
|
|
|
|
case 0x0B:
|
|
if(msb) {
|
|
_state.HvReg.HorizDisplayEnd = value & 0x7F;
|
|
} else {
|
|
_state.HvReg.HorizDisplayWidth = value & 0x7F;
|
|
}
|
|
break;
|
|
|
|
case 0x0C:
|
|
if(msb) {
|
|
_state.HvReg.VertDisplayStart = value;
|
|
} else {
|
|
_state.HvReg.VertSyncWidth = value & 0x1F;
|
|
}
|
|
break;
|
|
|
|
case 0x0D:
|
|
UpdateReg<0x1FF>(_state.HvReg.VertDisplayWidth, value, msb);
|
|
break;
|
|
|
|
case 0x0E:
|
|
if(!msb) {
|
|
_state.HvReg.VertEndPosVcr = value;
|
|
}
|
|
break;
|
|
|
|
case 0x0F:
|
|
if(!msb) {
|
|
LogDebugIf(_vramDmaRunning, "[VRAM DMA] Write to register while running");
|
|
_state.VramSatbIrqEnabled = (value & 0x01) != 0;
|
|
_state.VramVramIrqEnabled = (value & 0x02) != 0;
|
|
_state.DecrementSrc = (value & 0x04) != 0;
|
|
_state.DecrementDst = (value & 0x08) != 0;
|
|
_state.RepeatSatbTransfer = (value & 0x10) != 0;
|
|
}
|
|
break;
|
|
|
|
case 0x10:
|
|
LogDebugIf(_vramDmaRunning, "[VRAM DMA] Write to register while running");
|
|
UpdateReg(_state.BlockSrc, value, msb);
|
|
break;
|
|
|
|
case 0x11:
|
|
LogDebugIf(_vramDmaRunning, "[VRAM DMA] Write to register while running");
|
|
UpdateReg(_state.BlockDst, value, msb);
|
|
break;
|
|
|
|
case 0x12:
|
|
LogDebugIf(_vramDmaRunning, "[VRAM DMA] Write to register while running");
|
|
UpdateReg(_state.BlockLen, value, msb);
|
|
if(msb) {
|
|
_vramDmaRunning = true;
|
|
_vramDmaReadCycle = true;
|
|
}
|
|
break;
|
|
|
|
case 0x13:
|
|
LogDebugIf(_state.SatbTransferRunning, "[Sprite DMA] Write to register while running");
|
|
UpdateReg(_state.SatbBlockSrc, value, msb);
|
|
if(msb) {
|
|
_state.SatbTransferPending = true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void PceVdc::Serialize(Serializer& s)
|
|
{
|
|
SVArray(_vram, 0x8000);
|
|
SVArray(_spriteRam, 0x100);
|
|
SVArray(_rowBuffer, PceConstants::MaxScreenWidth);
|
|
|
|
SV(_state.FrameCount);
|
|
SV(_state.HClock);
|
|
SV(_state.Scanline);
|
|
SV(_state.RcrCounter);
|
|
SV(_state.CurrentReg);
|
|
SV(_state.MemAddrWrite);
|
|
SV(_state.MemAddrRead);
|
|
SV(_state.ReadBuffer);
|
|
SV(_state.VramData);
|
|
SV(_state.EnableCollisionIrq);
|
|
SV(_state.EnableOverflowIrq);
|
|
SV(_state.EnableScanlineIrq);
|
|
SV(_state.EnableVerticalBlankIrq);
|
|
SV(_state.OutputVerticalSync);
|
|
SV(_state.OutputHorizontalSync);
|
|
SV(_state.SpritesEnabled);
|
|
SV(_state.BackgroundEnabled);
|
|
SV(_state.VramAddrIncrement);
|
|
SV(_state.RasterCompareRegister);
|
|
|
|
if(!s.IsSaving() && s.ContainsKey("ColumnCount")) {
|
|
//Backward-compatibility for older save states
|
|
s.Stream(_state.HvReg.ColumnCount, "ColumnCount");
|
|
s.Stream(_state.HvReg.RowCount, "RowCount");
|
|
s.Stream(_state.HvReg.SpriteAccessMode, "SpriteAccessMode");
|
|
s.Stream(_state.HvReg.VramAccessMode, "VramAccessMode");
|
|
s.Stream(_state.HvReg.CgMode, "CgMode");
|
|
|
|
_state.HvLatch.ColumnCount = _state.HvReg.ColumnCount;
|
|
_state.HvLatch.RowCount = _state.HvReg.RowCount;
|
|
_state.HvLatch.SpriteAccessMode = _state.HvReg.SpriteAccessMode;
|
|
_state.HvLatch.VramAccessMode = _state.HvReg.VramAccessMode;
|
|
_state.HvLatch.CgMode = _state.HvReg.CgMode;
|
|
} else {
|
|
SV(_state.HvLatch.ColumnCount);
|
|
SV(_state.HvLatch.RowCount);
|
|
SV(_state.HvLatch.SpriteAccessMode);
|
|
SV(_state.HvLatch.VramAccessMode);
|
|
SV(_state.HvLatch.CgMode);
|
|
SV(_state.HvReg.ColumnCount);
|
|
SV(_state.HvReg.RowCount);
|
|
SV(_state.HvReg.SpriteAccessMode);
|
|
SV(_state.HvReg.VramAccessMode);
|
|
SV(_state.HvReg.CgMode);
|
|
}
|
|
|
|
SV(_state.BgScrollYUpdatePending);
|
|
SV(_state.HvLatch.BgScrollX);
|
|
SV(_state.HvLatch.BgScrollY);
|
|
SV(_state.HvLatch.HorizDisplayEnd);
|
|
SV(_state.HvLatch.HorizDisplayStart);
|
|
SV(_state.HvLatch.HorizDisplayWidth);
|
|
SV(_state.HvLatch.HorizSyncWidth);
|
|
SV(_state.HvLatch.VertDisplayStart);
|
|
SV(_state.HvLatch.VertDisplayWidth);
|
|
SV(_state.HvLatch.VertEndPosVcr);
|
|
SV(_state.HvLatch.VertSyncWidth);
|
|
SV(_state.HvReg.BgScrollX);
|
|
SV(_state.HvReg.BgScrollY);
|
|
SV(_state.HvReg.HorizDisplayEnd);
|
|
SV(_state.HvReg.HorizDisplayStart);
|
|
SV(_state.HvReg.HorizDisplayWidth);
|
|
SV(_state.HvReg.HorizSyncWidth);
|
|
SV(_state.HvReg.VertDisplayStart);
|
|
SV(_state.HvReg.VertDisplayWidth);
|
|
SV(_state.HvReg.VertEndPosVcr);
|
|
SV(_state.HvReg.VertSyncWidth);
|
|
SV(_state.VramSatbIrqEnabled);
|
|
SV(_state.VramVramIrqEnabled);
|
|
SV(_state.DecrementSrc);
|
|
SV(_state.DecrementDst);
|
|
SV(_state.RepeatSatbTransfer);
|
|
SV(_state.BlockSrc);
|
|
SV(_state.BlockDst);
|
|
SV(_state.BlockLen);
|
|
SV(_state.SatbBlockSrc);
|
|
SV(_state.SatbTransferPending);
|
|
SV(_state.SatbTransferRunning);
|
|
SV(_state.SatbTransferNextWordCounter);
|
|
SV(_state.SatbTransferOffset);
|
|
SV(_state.VerticalBlank);
|
|
SV(_state.VramTransferDone);
|
|
SV(_state.SatbTransferDone);
|
|
SV(_state.ScanlineDetected);
|
|
SV(_state.SpriteOverflow);
|
|
SV(_state.Sprite0Hit);
|
|
SV(_state.BurstModeEnabled);
|
|
SV(_state.NextSpritesEnabled);
|
|
SV(_state.NextBackgroundEnabled);
|
|
|
|
if(s.GetFormat() != SerializeFormat::Map) {
|
|
//Hide these entries from the Lua API
|
|
SV(_vramOpenBus);
|
|
SV(_lastDrawHClock);
|
|
SV(_xStart);
|
|
SV(_hMode);
|
|
SV(_hModeCounter);
|
|
SV(_vMode);
|
|
SV(_vModeCounter);
|
|
|
|
SV(_screenOffsetX);
|
|
SV(_needRcrIncrement);
|
|
SV(_needVCounterClock);
|
|
SV(_needVertBlankIrq);
|
|
SV(_verticalBlankDone);
|
|
|
|
SV(_spriteCount);
|
|
SV(_spriteRow);
|
|
SV(_evalStartCycle);
|
|
SV(_evalEndCycle);
|
|
SV(_evalLastCycle);
|
|
SV(_hasSpriteOverflow);
|
|
|
|
SV(_loadBgStart);
|
|
SV(_loadBgEnd);
|
|
SV(_loadBgLastCycle);
|
|
SV(_tileCount);
|
|
SV(_allowVramAccess);
|
|
|
|
SV(_pendingMemoryRead);
|
|
SV(_pendingMemoryWrite);
|
|
SV(_transferDelay);
|
|
|
|
SV(_vramDmaRunning);
|
|
SV(_vramDmaReadCycle);
|
|
SV(_vramDmaBuffer);
|
|
SV(_vramDmaPendingCycles);
|
|
|
|
SV(_nextEvent);
|
|
SV(_nextEventCounter);
|
|
SV(_hSyncStartClock);
|
|
SV(_allowDma);
|
|
|
|
SV(_drawSpriteCount);
|
|
SV(_totalSpriteCount);
|
|
SV(_rowHasSprite0);
|
|
SV(_loadSpriteStart);
|
|
|
|
SV(_latchClockX);
|
|
SV(_latchClockY);
|
|
|
|
SVArray(_xPosHasSprite, sizeof(_xPosHasSprite));
|
|
|
|
for(int i = 0; i < _spriteCount; i++) {
|
|
SVI(_sprites[i].X);
|
|
SVI(_sprites[i].TileAddress);
|
|
SVI(_sprites[i].Index);
|
|
SVI(_sprites[i].Palette);
|
|
SVI(_sprites[i].HorizontalMirroring);
|
|
SVI(_sprites[i].ForegroundPriority);
|
|
SVI(_sprites[i].LoadSp23);
|
|
}
|
|
|
|
for(int i = 0; i < _totalSpriteCount; i++) {
|
|
SVI(_drawSprites[i].TileData[0]);
|
|
SVI(_drawSprites[i].TileData[1]);
|
|
SVI(_drawSprites[i].TileData[2]);
|
|
SVI(_drawSprites[i].TileData[3]);
|
|
SVI(_drawSprites[i].X);
|
|
SVI(_drawSprites[i].Index);
|
|
SVI(_drawSprites[i].Palette);
|
|
SVI(_drawSprites[i].HorizontalMirroring);
|
|
SVI(_drawSprites[i].ForegroundPriority);
|
|
}
|
|
|
|
for(int i = 0; i < _tileCount; i++) {
|
|
SVI(_tiles[i].TileData[0]);
|
|
SVI(_tiles[i].TileData[1]);
|
|
SVI(_tiles[i].Palette);
|
|
SVI(_tiles[i].TileAddr);
|
|
}
|
|
}
|
|
}
|