Files
FiskbitandSour 1000562edd NES: Implement accurate sprite shifters & allow sprites on scanline 0 (#141)
* NES: Improve sprite fetch accuracy.

For sprite fetch, this improves in-range handling (which also helps with calculating the address to fetch) and stops treating sprites differently depending on whether we think they were valid based on sprite eval. The PPU uses just OAM2 during sprite fetch and not any other state from eval about whether sprites were good or not.

This change does not affect the behavior of any automated NES tests or AccuracyCoin. It improves performance slightly.

* NES: Allow sprites on scanline 0.

During pre-render, the PPU doesn't do OAM2 init or sprite eval, but it does do sprite fetch using the current contents of OAM2, which will usually be what's left over after sprite evaluation on scanline 239. This change allows these sprites to be drawn.

In AccuracyCoin, this change makes Mesen pass the Sprites on Scanline 0 test. However, it is detected as an RGB PPU because the skipped dot doesn't currently impact the position of the first sprite out of the shifters, which is a much more invasive change.

* NES: Improve sprite shifter accuracy.

* Fix hd pack player+builder logic (Sour)

* Improve dotskip accuracy without hurting performance.

* Change rendering check in pixel output (improves accuracy, sometimes performance).

---------

Co-authored-by: Sour <[email protected]>
2026-05-27 22:42:41 -07:00

68 lines
1.6 KiB
C++

#pragma once
#include "pch.h"
class BitUtilities
{
public:
template<uint8_t bitNumber, typename T>
__forceinline static void SetBits(T& dst, uint8_t src)
{
dst = (dst & ~(0xFF << bitNumber)) | (src << bitNumber);
}
template<uint8_t bitNumber, typename T>
__forceinline static uint8_t GetBits(T value)
{
return (uint8_t)(value >> bitNumber);
}
template<typename T>
__forceinline static void SetBitInArray(T array[], size_t byteCount, size_t position, bool value)
{
position %= byteCount * 8;
array[position / 8] &= ~(1 << (position % 8));
if(value) {
array[position / 8] |= 1 << (position % 8);
}
}
template<typename T>
__forceinline static uint8_t GetBitInArray(T array[], size_t byteCount, size_t position)
{
return (uint8_t)((array[(position / 8) % byteCount] >> (position % 8)) & 0x01);
}
//mask must not equal 0.
__forceinline static uint32_t GetLowestBitIndex(uint32_t mask)
{
#ifdef _MSC_VER
unsigned long index;
_BitScanForward(&index, mask);
return (uint32_t)index;
#else
return (uint32_t)__builtin_ctz(mask);
#endif
}
//mask must not equal 0.
__forceinline static uint32_t GetHighestBitIndex(uint32_t mask)
{
#ifdef _MSC_VER
unsigned long index;
_BitScanReverse(&index, mask);
return (uint32_t)index;
#else
// __builtin_clz returns leading zeros from the top (bit 31).
// To get the index (0-31), we subtract from 31.
return 31 - (uint32_t)__builtin_clz(mask);
#endif
}
__forceinline static uint8_t ReverseByte(uint8_t b)
{
b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; // Swap nybbles
b = (b & 0xCC) >> 2 | (b & 0x33) << 2; // Swap quarters
b = (b & 0xAA) >> 1 | (b & 0x55) << 1; // Swap bits
return b;
}
};