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:

<condition>myCondition,spriteFrameRange,301,60
[myCondition]<tile>0,1001,FF0F3600,8,0,1,N
This commit is contained in:
mkwong98 2021-04-15 19:54:01 +08:00
parent 0f35fdc97c
commit 8e0816807f
8 changed files with 172 additions and 15 deletions

View file

@ -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<uint32_t, uint8_t> 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;
};

View file

@ -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 << "<condition>" << 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;
}
};

View file

@ -424,6 +424,8 @@ void HdPackLoader::ProcessConditionTag(vector<string> &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<string> &tokens, bool createInvert
((HdPackFrameRangeCondition*)condition.get())->Initialize(operandA, operandB);
}
else if (dynamic_cast<HdPackSpriteFrameRangeCondition*>(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();

View file

@ -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) {

View file

@ -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;

View file

@ -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) {

View file

@ -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();

View file

@ -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