diff --git a/Core/HdData.h b/Core/HdData.h index 68bd4ff5..afaea444 100644 --- a/Core/HdData.h +++ b/Core/HdData.h @@ -90,6 +90,8 @@ struct HdPpuTileInfo : public HdTileKey uint8_t BgColor; uint8_t SpriteColor; uint8_t PpuBackgroundColor; + + uint8_t OAMIndex; }; struct HdPpuPixelInfo @@ -111,11 +113,36 @@ struct HdPpuPixelInfo } }; +struct HdScreenTileInfo : public HdTileKey +{ + int16_t ScreenX; + int16_t ScreenY; + bool HorizontalMirroring; + bool VerticalMirroring; + bool BackgroundPriority; + bool IsNew; +}; + +struct HdSpriteFrameRangeInfo +{ + bool lastUpdated; + bool updated; + HdScreenTileInfo last; + HdScreenTileInfo current; + uint32_t lastStartFrameNumber; + uint32_t startFrameNumber; + + HdSpriteFrameRangeInfo() { + updated = false; + } +}; + struct HdScreenInfo { HdPpuPixelInfo* ScreenTiles; std::unordered_map WatchedAddressValues; uint32_t FrameNumber; + HdSpriteFrameRangeInfo* spriteFrameRanges; HdScreenInfo(const HdScreenInfo& that) = delete; @@ -390,12 +417,4 @@ enum class HdPackOptions DontRenderOriginalTiles = 16 }; -struct HdScreenTileInfo : public HdTileKey -{ - int16_t ScreenX; - int16_t ScreenY; - bool HorizontalMirroring; - bool VerticalMirroring; - bool BackgroundPriority; - bool IsNew; -}; + diff --git a/Core/HdPackConditions.h b/Core/HdPackConditions.h index 5ad3ae04..b35a7241 100644 --- a/Core/HdPackConditions.h +++ b/Core/HdPackConditions.h @@ -298,3 +298,32 @@ struct HdPackSpriteNearbyCondition : public HdPackBaseTileCondition return false; } }; + +struct HdPackSpriteFrameRangeCondition : public HdPackCondition +{ + uint32_t OperandA; + uint32_t OperandB; + + string GetConditionName() override { return "spriteFrameRange"; } + + void Initialize(uint32_t operandA, uint32_t operandB) + { + OperandA = operandA; + OperandB = operandB; + } + + string ToString() override + { + stringstream out; + out << "" << Name << "," << GetConditionName() << ","; + out << OperandA << ","; + out << OperandB; + + return out.str(); + } + + bool InternalCheckCondition(HdScreenInfo* screenInfo, int x, int y, HdPpuTileInfo* tile) override + { + return (screenInfo->FrameNumber - screenInfo->spriteFrameRanges[tile->OAMIndex].startFrameNumber) % OperandA >= OperandB; + } +}; \ No newline at end of file diff --git a/Core/HdPackLoader.cpp b/Core/HdPackLoader.cpp index 48555c26..cbc419f6 100644 --- a/Core/HdPackLoader.cpp +++ b/Core/HdPackLoader.cpp @@ -424,6 +424,8 @@ void HdPackLoader::ProcessConditionTag(vector &tokens, bool createInvert condition.reset(new HdPackMemoryCheckConstantCondition()); } else if(tokens[1] == "frameRange") { condition.reset(new HdPackFrameRangeCondition()); + } else if(tokens[1] == "spriteFrameRange") { + condition.reset(new HdPackSpriteFrameRangeCondition()); } else { MessageManager::Log("[HDPack] Invalid condition type: " + tokens[1]); return; @@ -530,6 +532,27 @@ void HdPackLoader::ProcessConditionTag(vector &tokens, bool createInvert ((HdPackFrameRangeCondition*)condition.get())->Initialize(operandA, operandB); } + else if (dynamic_cast(condition.get())) { + checkConstraint(_data->Version >= 101, "[HDPack] This feature requires version 101+ of HD Packs"); + checkConstraint(tokens.size() >= 4, "[HDPack] Condition tag should contain at least 4 parameters"); + + int32_t operandA; + int32_t operandB; + if (_data->Version == 101) { + operandA = HexUtilities::FromHex(tokens[index++]); + operandB = HexUtilities::FromHex(tokens[index++]); + } + else { + //Version 102+ + operandA = std::stoi(tokens[index++]); + operandB = std::stoi(tokens[index++]); + } + + checkConstraint(operandA >= 0 && operandA <= 0xFFFF, "[HDPack] Out of range spriteFrameRange operand"); + checkConstraint(operandB >= 0 && operandB <= 0xFFFF, "[HDPack] Out of range spriteFrameRange operand"); + + ((HdPackSpriteFrameRangeCondition*)condition.get())->Initialize(operandA, operandB); + } HdPackCondition *cond = condition.get(); condition.release(); diff --git a/Core/HdPpu.cpp b/Core/HdPpu.cpp index f158ffde..41bf45cb 100644 --- a/Core/HdPpu.cpp +++ b/Core/HdPpu.cpp @@ -16,6 +16,20 @@ void HdPpu::DrawPixel() uint16_t &pixel = _currentOutputBuffer[bufferOffset]; _lastSprite = nullptr; + //init sprite frame ranges at screen start + if (bufferOffset == 0) { + uint8_t spriteCnt = (_flags.LargeSprites ? 128 : 64); + for (int i = 0; i < spriteCnt; i++) { + _spriteFrameRanges[i].lastUpdated = _spriteFrameRanges[i].updated; + if (_spriteFrameRanges[i].updated) { + //copy last frame sprite data + memcpy(&(_spriteFrameRanges[i].last), &(_spriteFrameRanges[i].current), sizeof(_spriteFrameRanges[i].current)); + _spriteFrameRanges[i].updated = false; + _spriteFrameRanges[i].lastStartFrameNumber = _spriteFrameRanges[i].startFrameNumber; + } + } + } + if(IsRenderingEnabled() || ((_state.VideoRamAddr & 0x3F00) != 0x3F00)) { bool isChrRam = !_console->GetMapper()->HasChrRom(); BaseMapper *mapper = _console->GetMapper(); @@ -85,7 +99,25 @@ void HdPpu::DrawPixel() tileInfo.Sprite[j].PpuBackgroundColor = tileInfo.Tile.PpuBackgroundColor; tileInfo.Sprite[j].BgColorIndex = tileInfo.Tile.BgColorIndex; - + tileInfo.Sprite[j].OAMIndex = (sprite.OffsetY >= 8 ? sprite.OAMIndex + 64 : sprite.OAMIndex); + + if (!_spriteFrameRanges[tileInfo.Sprite[j].OAMIndex].updated) { + //fill the current frame sprite + if (isChrRam) { + memcpy(_spriteFrameRanges[tileInfo.Sprite[j].OAMIndex].current.TileData, tileInfo.Sprite[j].TileData, 16); + } + else { + _spriteFrameRanges[tileInfo.Sprite[j].OAMIndex].current.TileIndex = tileInfo.Sprite[j].TileIndex; + } + _spriteFrameRanges[tileInfo.Sprite[j].OAMIndex].current.PaletteColors = tileInfo.Sprite[j].PaletteColors; + _spriteFrameRanges[tileInfo.Sprite[j].OAMIndex].current.HorizontalMirroring = tileInfo.Sprite[j].HorizontalMirroring; + _spriteFrameRanges[tileInfo.Sprite[j].OAMIndex].current.VerticalMirroring = tileInfo.Sprite[j].VerticalMirroring; + _spriteFrameRanges[tileInfo.Sprite[j].OAMIndex].current.BackgroundPriority = tileInfo.Sprite[j].BackgroundPriority; + _spriteFrameRanges[tileInfo.Sprite[j].OAMIndex].current.ScreenX = sprite.SpriteX; + _spriteFrameRanges[tileInfo.Sprite[j].OAMIndex].current.ScreenY = _scanline - tileInfo.Sprite[j].OffsetY; + _spriteFrameRanges[tileInfo.Sprite[j].OAMIndex].updated = true; + } + j++; if(j >= 4) { break; @@ -112,6 +144,49 @@ void HdPpu::DrawPixel() } else { tileInfo.Tile.TileIndex = HdPpuTileInfo::NoTile; } + + //match sprite frame range at screen end + if (bufferOffset == PixelCount - 1) { + uint16_t distance; + uint16_t newDistance; + uint16_t newDistanceX; + uint16_t newDistanceY; + uint8_t spriteCnt = (_flags.LargeSprites ? 128 : 64); + + for (int i = 0; i < spriteCnt; i++) { + if (_spriteFrameRanges[i].updated) { + distance = 13; + _spriteFrameRanges[i].startFrameNumber = _frameCount; + for (int j = 0; j < spriteCnt; j++) { + if (_spriteFrameRanges[j].lastUpdated) { + newDistanceX = abs(_spriteFrameRanges[i].current.ScreenX - _spriteFrameRanges[j].last.ScreenX); + newDistanceY = abs(_spriteFrameRanges[i].current.ScreenY - _spriteFrameRanges[j].last.ScreenY); + newDistance = newDistanceX + newDistanceY; + if (newDistance < distance && newDistanceX <= 6 && newDistanceY <= 6) { + //check for matches + bool compareResult = false; + if (_spriteFrameRanges[i].current.BackgroundPriority == _spriteFrameRanges[j].last.BackgroundPriority + && _spriteFrameRanges[i].current.HorizontalMirroring == _spriteFrameRanges[j].last.HorizontalMirroring + && _spriteFrameRanges[i].current.VerticalMirroring == _spriteFrameRanges[j].last.VerticalMirroring + && _spriteFrameRanges[i].current.PaletteColors == _spriteFrameRanges[j].last.PaletteColors + ) { + if (isChrRam) { + compareResult = (memcmp(_spriteFrameRanges[i].current.TileData, _spriteFrameRanges[j].last.TileData, 16) == 0); + } + else { + compareResult = (_spriteFrameRanges[i].current.TileIndex == _spriteFrameRanges[j].last.TileIndex); + } + } + if (compareResult) { + _spriteFrameRanges[i].startFrameNumber = _spriteFrameRanges[j].lastStartFrameNumber; + } + distance = newDistance; + } + } + } + } + } + } } else { //"If the current VRAM address points in the range $3F00-$3FFF during forced blanking, the color indicated by this palette location will be shown on screen instead of the backdrop color." pixel = ReadPaletteRAM(_state.VideoRamAddr) | _intensifyColorBits; @@ -147,6 +222,7 @@ void HdPpu::SendFrame() _console->GetNotificationManager()->SendNotification(ConsoleNotificationType::PpuFrameDone, _currentOutputBuffer); _info->FrameNumber = _frameCount; + _info->spriteFrameRanges = _spriteFrameRanges; _info->WatchedAddressValues.clear(); for(uint32_t address : _hdData->WatchedMemoryAddresses) { if(address & HdPackBaseMemoryCondition::PpuMemoryMarker) { diff --git a/Core/HdPpu.h b/Core/HdPpu.h index ceff9523..67c4853e 100644 --- a/Core/HdPpu.h +++ b/Core/HdPpu.h @@ -1,6 +1,7 @@ #pragma once #include "stdafx.h" #include "PPU.h" +#include "HdData.h" struct HdScreenInfo; struct HdPackData; @@ -13,6 +14,7 @@ private: HdScreenInfo *_screenInfo[2]; HdScreenInfo *_info; uint32_t _version; + HdSpriteFrameRangeInfo _spriteFrameRanges[128]; protected: HdPackData *_hdData = nullptr; diff --git a/Core/PPU.cpp b/Core/PPU.cpp index 075c0aa5..c3c86db0 100644 --- a/Core/PPU.cpp +++ b/Core/PPU.cpp @@ -337,6 +337,7 @@ uint8_t PPU::ReadRAM(uint16_t addr) uint8_t step = ((_cycle - 257) % 8) > 3 ? 3 : ((_cycle - 257) % 8); _secondaryOAMAddr = (_cycle - 257) / 8 * 4 + step; _oamCopybuffer = _secondarySpriteRAM[_secondaryOAMAddr]; + _oamID = _secondarySpriteRAMLink[_secondaryOAMAddr >> 2]; } //Return the value that PPU is currently using for sprite evaluation/rendering returnValue = _oamCopybuffer; @@ -690,7 +691,7 @@ void PPU::LoadTileInfo() } } -void PPU::LoadSprite(uint8_t spriteY, uint8_t tileIndex, uint8_t attributes, uint8_t spriteX, bool extraSprite) +void PPU::LoadSprite(uint8_t spriteY, uint8_t tileIndex, uint8_t attributes, uint8_t spriteX, bool extraSprite, uint8_t oamID) { bool backgroundPriority = (attributes & 0x20) == 0x20; bool horizontalMirror = (attributes & 0x40) == 0x40; @@ -730,6 +731,7 @@ void PPU::LoadSprite(uint8_t spriteY, uint8_t tileIndex, uint8_t attributes, uin info.AbsoluteTileAddr = _console->GetMapper()->ToAbsoluteChrAddress(tileAddr); info.OffsetY = lineOffset; info.SpriteX = spriteX; + info.OAMIndex = oamID; if(_scanline >= 0) { //Sprites read on prerender scanline are not shown on scanline 0 @@ -788,7 +790,7 @@ void PPU::LoadExtraSprites() for(uint32_t i = (_lastVisibleSpriteAddr + 4) & 0xFF; i != _firstVisibleSpriteAddr; i = (i + 4) & 0xFF) { uint8_t spriteY = _spriteRAM[i]; if(_scanline >= spriteY && _scanline < spriteY + (_flags.LargeSprites ? 16 : 8)) { - LoadSprite(spriteY, _spriteRAM[i + 1], _spriteRAM[i + 2], _spriteRAM[i + 3], true); + LoadSprite(spriteY, _spriteRAM[i + 1], _spriteRAM[i + 2], _spriteRAM[i + 3], true, i >> 2); _spriteCount++; } } @@ -799,7 +801,7 @@ void PPU::LoadExtraSprites() void PPU::LoadSpriteTileInfo() { uint8_t *spriteAddr = _secondarySpriteRAM + _spriteIndex * 4; - LoadSprite(*spriteAddr, *(spriteAddr+1), *(spriteAddr+2), *(spriteAddr+3), false); + LoadSprite(*spriteAddr, *(spriteAddr+1), *(spriteAddr+2), *(spriteAddr+3), false, _secondarySpriteRAMLink[_spriteIndex]); } void PPU::ShiftTileRegisters() @@ -1036,12 +1038,14 @@ void PPU::ProcessSpriteEvaluation() if(_cycle & 0x01) { //Read a byte from the primary OAM on odd cycles _oamCopybuffer = ReadSpriteRam(_state.SpriteRamAddr); + _oamID = _state.SpriteRamAddr >> 2; } else { if(_oamCopyDone) { _spriteAddrH = (_spriteAddrH + 1) & 0x3F; if(_secondaryOAMAddr >= 0x20) { //"As seen above, a side effect of the OAM write disable signal is to turn writes to the secondary OAM into reads from it." _oamCopybuffer = _secondarySpriteRAM[_secondaryOAMAddr & 0x1F]; + _oamID = _secondarySpriteRAMLink[(_secondaryOAMAddr >> 2) & 0x08]; } } else { if(!_spriteInRange && _scanline >= _oamCopybuffer && _scanline < _oamCopybuffer + (_flags.LargeSprites ? 16 : 8)) { @@ -1051,7 +1055,7 @@ void PPU::ProcessSpriteEvaluation() if(_secondaryOAMAddr < 0x20) { //Copy 1 byte to secondary OAM _secondarySpriteRAM[_secondaryOAMAddr] = _oamCopybuffer; - + _secondarySpriteRAMLink[_secondaryOAMAddr >> 2] =_oamID; if(_spriteInRange) { _spriteAddrL++; _secondaryOAMAddr++; @@ -1083,6 +1087,7 @@ void PPU::ProcessSpriteEvaluation() } else { //"As seen above, a side effect of the OAM write disable signal is to turn writes to the secondary OAM into reads from it." _oamCopybuffer = _secondarySpriteRAM[_secondaryOAMAddr & 0x1F]; + _oamID = _secondarySpriteRAMLink[(_secondaryOAMAddr >> 2) & 0x08]; //8 sprites have been found, check next sprite for overflow + emulate PPU bug if(_spriteInRange) { diff --git a/Core/PPU.h b/Core/PPU.h index 72062246..75504186 100644 --- a/Core/PPU.h +++ b/Core/PPU.h @@ -44,6 +44,7 @@ class PPU : public IMemoryHandler, public Snapshotable uint8_t _spriteRAM[0x100]; uint8_t _secondarySpriteRAM[0x20]; + uint8_t _secondarySpriteRAMLink[8]; bool _hasSprite[257]; uint16_t *_currentOutputBuffer; @@ -90,6 +91,7 @@ class PPU : public IMemoryHandler, public Snapshotable uint8_t _spriteAddrL; bool _oamCopyDone; uint8_t _overflowBugCounter; + uint8_t _oamID; bool _needStateUpdate; bool _renderingEnabled; @@ -134,7 +136,7 @@ class PPU : public IMemoryHandler, public Snapshotable void TriggerNmi(); void LoadTileInfo(); - void LoadSprite(uint8_t spriteY, uint8_t tileIndex, uint8_t attributes, uint8_t spriteX, bool extraSprite); + void LoadSprite(uint8_t spriteY, uint8_t tileIndex, uint8_t attributes, uint8_t spriteX, bool extraSprite, uint8_t oamID); void LoadSpriteTileInfo(); void LoadExtraSprites(); __forceinline void ShiftTileRegisters(); diff --git a/Core/Types.h b/Core/Types.h index 999b3714..0d3d0c9a 100644 --- a/Core/Types.h +++ b/Core/Types.h @@ -185,6 +185,7 @@ struct SpriteInfo : TileInfo uint8_t SpriteX; bool VerticalMirror; //used by HD ppu + uint8_t OAMIndex; //used by HD ppu }; struct ApuLengthCounterState