diff --git a/Core/EmulationSettings.h b/Core/EmulationSettings.h index 1aa3ea85..d9a6d2d5 100644 --- a/Core/EmulationSettings.h +++ b/Core/EmulationSettings.h @@ -9,6 +9,7 @@ enum EmulationFlags ShowFPS = 0x02, VerticalSync = 0x04, AllowInvalidInput = 0x08, + RemoveSpriteLimit = 0x10, Mmc3IrqAltBehavior = 0x8000, }; diff --git a/Core/PPU.cpp b/Core/PPU.cpp index 59cac2f2..fd4690b2 100644 --- a/Core/PPU.cpp +++ b/Core/PPU.cpp @@ -439,13 +439,8 @@ void PPU::LoadTileInfo() } } -void PPU::LoadSpriteTileInfo() +void PPU::LoadSprite(uint8_t spriteY, uint8_t tileIndex, uint8_t attributes, uint8_t spriteX, bool extraSprite) { - uint32_t spriteAddr = _spriteIndex * 4; - uint8_t spriteY = _secondarySpriteRAM[spriteAddr]; - uint8_t tileIndex = _secondarySpriteRAM[spriteAddr + 1]; - uint8_t attributes = _secondarySpriteRAM[spriteAddr + 2]; - uint8_t spriteX = _secondarySpriteRAM[spriteAddr + 3]; bool backgroundPriority = (attributes & 0x20) == 0x20; bool horizontalMirror = (attributes & 0x40) == 0x40; bool verticalMirror = (attributes & 0x80) == 0x80; @@ -464,17 +459,23 @@ void PPU::LoadSpriteTileInfo() tileAddr = ((tileIndex << 4) | _flags.SpritePatternAddr) + lineOffset; } - if(_spriteIndex < _spriteCount && spriteY < 240) { + if((_spriteIndex < _spriteCount || extraSprite) && spriteY < 240) { _spriteTiles[_spriteIndex].BackgroundPriority = backgroundPriority; _spriteTiles[_spriteIndex].HorizontalMirror = horizontalMirror; _spriteTiles[_spriteIndex].VerticalMirror = verticalMirror; _spriteTiles[_spriteIndex].PaletteOffset = ((attributes & 0x03) << 2) | 0x10; - _spriteTiles[_spriteIndex].LowByte = _memoryManager->ReadVRAM(tileAddr); - _spriteTiles[_spriteIndex].HighByte = _memoryManager->ReadVRAM(tileAddr + 8); + if(extraSprite) { + //Use DebugReadVRAM for extra sprites to prevent most side-effects. + _spriteTiles[_spriteIndex].LowByte = _memoryManager->DebugReadVRAM(tileAddr); + _spriteTiles[_spriteIndex].HighByte = _memoryManager->DebugReadVRAM(tileAddr + 8); + } else { + _spriteTiles[_spriteIndex].LowByte = _memoryManager->ReadVRAM(tileAddr); + _spriteTiles[_spriteIndex].HighByte = _memoryManager->ReadVRAM(tileAddr + 8); + } _spriteTiles[_spriteIndex].TileAddr = tileAddr; _spriteTiles[_spriteIndex].OffsetY = lineOffset; _spriteTiles[_spriteIndex].SpriteX = spriteX; - } else { + } else if(!extraSprite) { //Fetches to sprite 0xFF for remaining sprites/hidden - used by MMC3 IRQ counter lineOffset = 0; tileIndex = 0xFF; @@ -491,6 +492,25 @@ void PPU::LoadSpriteTileInfo() _spriteIndex++; } +void PPU::LoadExtraSprites() +{ + if(_spriteCount == 8 && EmulationSettings::CheckFlag(EmulationFlags::RemoveSpriteLimit)) { + for(uint32_t i = _overflowSpriteAddr; i < 0x100; i += 4) { + 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); + _spriteCount++; + } + } + } +} + +void PPU::LoadSpriteTileInfo() +{ + uint8_t *spriteAddr = _secondarySpriteRAM + _spriteIndex * 4; + LoadSprite(*spriteAddr, *(spriteAddr+1), *(spriteAddr+2), *(spriteAddr+3), false); +} + void PPU::LoadNextTile() { _state.LowBitShift |= _nextTile.LowByte; @@ -677,6 +697,7 @@ void PPU::CopyOAMData() _sprite0Added = false; _spriteInRange = false; _secondaryOAMAddr = 0; + _overflowSpriteAddr = 0; _oamCopyDone = false; _spriteAddrH = (_state.SpriteRamAddr >> 2) & 0x3F; @@ -684,6 +705,8 @@ void PPU::CopyOAMData() } else if(_cycle == 256) { _sprite0Visible = _sprite0Added; _spriteCount = (_secondaryOAMAddr >> 2); + + LoadExtraSprites(); } if(_cycle & 0x01) { @@ -727,6 +750,11 @@ void PPU::CopyOAMData() } } else { //8 sprites have been found, check next sprite for overflow + emulate PPU bug + if(_overflowSpriteAddr == 0) { + //Used to remove sprite limit + _overflowSpriteAddr = _spriteAddrH * 4; + } + if(_spriteInRange) { //Sprite is visible, consider this to be an overflow _statusFlags.SpriteOverflow = true; @@ -878,7 +906,7 @@ void PPU::StreamState(bool saving) Stream(_previousTile.HighByte); Stream(_previousTile.PaletteOffset); - for(int i = 0; i < 8; i++) { + for(int i = 0; i < 64; i++) { Stream(_spriteTiles[i].SpriteX); Stream(_spriteTiles[i].LowByte); Stream(_spriteTiles[i].HighByte); diff --git a/Core/PPU.h b/Core/PPU.h index a422512d..dc383035 100644 --- a/Core/PPU.h +++ b/Core/PPU.h @@ -123,11 +123,12 @@ class PPU : public IMemoryHandler, public Snapshotable TileInfo _nextTile; TileInfo _previousTile; - SpriteInfo _spriteTiles[8]; + SpriteInfo _spriteTiles[64]; uint32_t _spriteCount = 0; uint32_t _secondaryOAMAddr = 0; bool _sprite0Visible = false; + uint32_t _overflowSpriteAddr = 0; uint32_t _spriteIndex = 0; uint8_t _openBus = 0; @@ -178,7 +179,9 @@ class PPU : public IMemoryHandler, public Snapshotable void WritePaletteRAM(uint16_t addr, uint8_t value); void LoadTileInfo(); + void LoadSprite(uint8_t spriteY, uint8_t tileIndex, uint8_t attributes, uint8_t spriteX, bool extraSprite); void LoadSpriteTileInfo(); + void LoadExtraSprites(); void ShiftTileRegisters(); void InitializeShiftRegisters(); void LoadNextTile(); diff --git a/GUI.NET/Config/PreferenceInfo.cs b/GUI.NET/Config/PreferenceInfo.cs index aaf62b51..dff8f764 100644 --- a/GUI.NET/Config/PreferenceInfo.cs +++ b/GUI.NET/Config/PreferenceInfo.cs @@ -17,6 +17,7 @@ namespace Mesen.GUI.Config public bool AutoLoadIpsPatches = true; public bool AssociateNesFiles = false; public bool AllowInvalidInput = false; + public bool RemoveSpriteLimit = false; public bool UseAlternativeMmc3Irq = false; @@ -40,6 +41,7 @@ namespace Mesen.GUI.Config InteropEmu.SetFlag(EmulationFlags.Mmc3IrqAltBehavior, preferenceInfo.UseAlternativeMmc3Irq); InteropEmu.SetFlag(EmulationFlags.AllowInvalidInput, preferenceInfo.AllowInvalidInput); + InteropEmu.SetFlag(EmulationFlags.RemoveSpriteLimit, preferenceInfo.RemoveSpriteLimit); } } } diff --git a/GUI.NET/Forms/Config/frmPreferences.Designer.cs b/GUI.NET/Forms/Config/frmPreferences.Designer.cs index f2acd56f..de04b5e7 100644 --- a/GUI.NET/Forms/Config/frmPreferences.Designer.cs +++ b/GUI.NET/Forms/Config/frmPreferences.Designer.cs @@ -47,6 +47,7 @@ this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); this.chkUseAlternativeMmc3Irq = new System.Windows.Forms.CheckBox(); this.chkAllowInvalidInput = new System.Windows.Forms.CheckBox(); + this.chkRemoveSpriteLimit = new System.Windows.Forms.CheckBox(); this.tlpMain.SuspendLayout(); this.flowLayoutPanel6.SuspendLayout(); this.tabMain.SuspendLayout(); @@ -273,10 +274,13 @@ this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); this.tableLayoutPanel1.Controls.Add(this.chkUseAlternativeMmc3Irq, 0, 0); this.tableLayoutPanel1.Controls.Add(this.chkAllowInvalidInput, 0, 1); + this.tableLayoutPanel1.Controls.Add(this.chkRemoveSpriteLimit, 0, 2); this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill; this.tableLayoutPanel1.Location = new System.Drawing.Point(3, 3); this.tableLayoutPanel1.Name = "tableLayoutPanel1"; - this.tableLayoutPanel1.RowCount = 2; + this.tableLayoutPanel1.RowCount = 4; + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); this.tableLayoutPanel1.Size = new System.Drawing.Size(380, 207); @@ -302,6 +306,16 @@ this.chkAllowInvalidInput.Text = "Allow invalid input (e.g Down + Up or Left + Right at the same time)"; this.chkAllowInvalidInput.UseVisualStyleBackColor = true; // + // chkRemoveSpriteLimit + // + this.chkRemoveSpriteLimit.AutoSize = true; + this.chkRemoveSpriteLimit.Location = new System.Drawing.Point(3, 49); + this.chkRemoveSpriteLimit.Name = "chkRemoveSpriteLimit"; + this.chkRemoveSpriteLimit.Size = new System.Drawing.Size(205, 17); + this.chkRemoveSpriteLimit.TabIndex = 2; + this.chkRemoveSpriteLimit.Text = "Remove sprite limit (Reduces flashing)"; + this.chkRemoveSpriteLimit.UseVisualStyleBackColor = true; + // // frmPreferences // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); @@ -352,5 +366,6 @@ private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; private System.Windows.Forms.CheckBox chkUseAlternativeMmc3Irq; private System.Windows.Forms.CheckBox chkAllowInvalidInput; + private System.Windows.Forms.CheckBox chkRemoveSpriteLimit; } } \ No newline at end of file diff --git a/GUI.NET/Forms/Config/frmPreferences.cs b/GUI.NET/Forms/Config/frmPreferences.cs index d8c0390a..d088748a 100644 --- a/GUI.NET/Forms/Config/frmPreferences.cs +++ b/GUI.NET/Forms/Config/frmPreferences.cs @@ -25,6 +25,7 @@ namespace Mesen.GUI.Forms.Config AddBinding("UseAlternativeMmc3Irq", chkUseAlternativeMmc3Irq); AddBinding("AllowInvalidInput", chkAllowInvalidInput); + AddBinding("RemoveSpriteLimit", chkRemoveSpriteLimit); } protected override void OnFormClosed(FormClosedEventArgs e) diff --git a/GUI.NET/InteropEmu.cs b/GUI.NET/InteropEmu.cs index 19e1f193..27c43212 100644 --- a/GUI.NET/InteropEmu.cs +++ b/GUI.NET/InteropEmu.cs @@ -432,6 +432,7 @@ namespace Mesen.GUI ShowFPS = 0x02, VerticalSync = 0x04, AllowInvalidInput = 0x08, + RemoveSpriteLimit = 0x10, Mmc3IrqAltBehavior = 0x8000, }