From 8e0816807f03217e4c9481e35c856281e7ede16e Mon Sep 17 00:00:00 2001 From: mkwong98 Date: Thu, 15 Apr 2021 19:54:01 +0800 Subject: [PATCH] Add sprite frame range condition A new condition named "spriteFrameRange" is added. It works like frameRange except it follows frame counters assigned to each sprite. Frame counter picks up the nearest frame counter within 6x6 pixels from the last frame and resets if graphics, palette, bg priority or orientation has changed. The condition can by used by sprite tiles only. For example: myCondition,spriteFrameRange,301,60 [myCondition]0,1001,FF0F3600,8,0,1,N --- Core/HdData.h | 37 ++++++++++++++----- Core/HdPackConditions.h | 29 +++++++++++++++ Core/HdPackLoader.cpp | 23 ++++++++++++ Core/HdPpu.cpp | 78 ++++++++++++++++++++++++++++++++++++++++- Core/HdPpu.h | 2 ++ Core/PPU.cpp | 13 ++++--- Core/PPU.h | 4 ++- Core/Types.h | 1 + 8 files changed, 172 insertions(+), 15 deletions(-) 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