Merge pull request #22898 from hrydgard/depal-pixel-mapped

Depal: Skip the bilinear filter on exactly pixel-mapped draws
This commit is contained in:
Henrik Rydgård authored and GitHub committed 2026-11-03 00:22:21 +01:00
commit f416c318f4
8 files changed
+50 -20

No files matched your search

+7
View File
@@ -180,7 +180,14 @@ void BaseUpdateUniforms(UB_VS_FS_Base *ub, uint64_t dirtyUniforms, bool useBuffe
}
}
void DirtyDepalIfPixelMappedChanged(bool pixelMapped) {
if (gstate_c.shaderDepalMode != ShaderDepalMode::OFF && pixelMapped != gstate_c.depalPixelMapped) {
gstate_c.Dirty(DIRTY_DEPAL);
}
}
uint32_t PackDepalBits(bool pixelMapped) {
gstate_c.depalPixelMapped = pixelMapped;
const int indexMask = gstate.getClutIndexMask();
const int indexShift = gstate.getClutIndexShift();
const int indexOffset = gstate.getClutIndexStartPos() >> 4;
+2
View File
@@ -110,6 +110,8 @@ void LightUpdateUniforms(UB_VS_Lights *ub, uint64_t dirtyUniforms);
uint32_t PackLightControlBits();
uint32_t PackDepalBits(bool pixelMapped);
// Call before reading the dirty uniforms: the depal uniform depends on pixelMapped, which changes per draw.
void DirtyDepalIfPixelMappedChanged(bool pixelMapped);
void UpdateFogCoef(const GEState &state, float fogCoef[2]);
+35 -20
View File
@@ -1051,26 +1051,37 @@ static bool ExpandRectangles(int vertexCount, int &numDecodedVerts, int vertsSiz
vScale /= gstate_c.curTextureHeight;
}
// Look for pixel mapping, two ways at once:
// - pixelMapped (with smart 2D filtering): each rect is 1:1 in at least one direction. Turns the texture filter
// off and the sprite border fix with it. This one can change what's on screen.
// - exactlyPixelMapped: each rect is 1:1 in both directions at a whole-texel offset, so every pixel center lands
// on a texel center and bilinear gives the nearest texel anyway. Shader depal filters by hand, with four
// texel and four palette lookups per pixel, a lot for a slow GPU on a full-screen pass (DBZ Tenkaichi Tag
// Team has two per frame), so for it this turns the filter off without changing anything. Positions are only
// in pixels in through mode.
bool pixelMapped = g_Config.bSmart2DTexFiltering && !gstate_c.textureIsVideo;
if (pixelMapped) {
// Check for pixel mapping. If we find pixel mapping, abandon spriteBorderFix.
for (int i = 0; i < vertexCount; i += 2) {
const TransformedVertex &transVtxTL = transformed[indsIn[i + 0]];
const TransformedVertex &transVtxBR = transformed[indsIn[i + 1]];
if (pixelMapped) {
float dx = transVtxBR.x - transVtxTL.x;
float dy = transVtxBR.y - transVtxTL.y;
float du = transVtxBR.u - transVtxTL.u;
float dv = transVtxBR.v - transVtxTL.v;
// NOTE: We will accept it as pixel mapped if only one dimension is stretched. This fixes dialog frames in FFI.
// Though, there could be false positives in other games due to this. Let's see if it is a problem...
if (dx <= 0 || dy <= 0 || (dx != du && dy != dv)) {
pixelMapped = false;
break;
}
}
bool exactlyPixelMapped = throughmode && gstate_c.shaderDepalMode != ShaderDepalMode::OFF;
for (int i = 0; i < vertexCount && (pixelMapped || exactlyPixelMapped); i += 2) {
const TransformedVertex &transVtxTL = transformed[indsIn[i + 0]];
const TransformedVertex &transVtxBR = transformed[indsIn[i + 1]];
const float dx = transVtxBR.x - transVtxTL.x;
const float dy = transVtxBR.y - transVtxTL.y;
const float du = transVtxBR.u - transVtxTL.u;
const float dv = transVtxBR.v - transVtxTL.v;
if (dx <= 0 || dy <= 0) {
pixelMapped = false;
exactlyPixelMapped = false;
break;
}
// NOTE: We will accept it as pixel mapped if only one dimension is stretched. This fixes dialog frames in FFI.
// Though, there could be false positives in other games due to this. Let's see if it is a problem...
if (dx != du && dy != dv) {
pixelMapped = false;
}
const float offsetU = transVtxTL.u - transVtxTL.x;
const float offsetV = transVtxTL.v - transVtxTL.y;
if (dx != du || dy != dv || offsetU != floorf(offsetU) || offsetV != floorf(offsetV)) {
exactlyPixelMapped = false;
}
}
@@ -1095,6 +1106,10 @@ static bool ExpandRectangles(int vertexCount, int &numDecodedVerts, int vertsSiz
}
}
// The sprite border fix moves the UVs off the texel centers.
if (spriteBorderFixL != 0.0f || spriteBorderFixR != 0.0f || spriteBorderFixT != 0.0f || spriteBorderFixB != 0.0f) {
exactlyPixelMapped = false;
}
// Like the software renderer's ProcessRect: rectangles aren't clipped, one with a vertex behind the camera
// or outside the screen range is dropped whole. With depth clamp, a vertex past the near plane is exempt
@@ -1213,7 +1228,7 @@ static bool ExpandRectangles(int vertexCount, int &numDecodedVerts, int vertsSiz
}
numDecodedVerts = outVerts;
inds = newInds;
*pixelMappedExactly = pixelMapped;
*pixelMappedExactly = pixelMapped || exactlyPixelMapped;
*drawIndexCount = indsOut - newInds;
return true;
}
+1
View File
@@ -149,6 +149,7 @@ void ShaderManagerD3D11::ClearShaders() {
}
uint64_t ShaderManagerD3D11::UpdateUniforms(bool useBufferedRendering, bool pixelMapped) {
DirtyDepalIfPixelMappedChanged(pixelMapped);
uint64_t dirty = gstate_c.GetDirtyUniforms();
if (dirty != 0) {
D3D11_MAPPED_SUBRESOURCE map;
+1
View File
@@ -717,6 +717,7 @@ Shader *ShaderManagerGLES::ApplyVertexShader(bool useHWTransform, u32 vertexType
}
LinkedShader *ShaderManagerGLES::ApplyFragmentShader(VShaderID VSID, Shader *vs, const ComputedPipelineState &pipelineState, ClipInfoFlags clipInfoFlags, bool pixelMapped) {
DirtyDepalIfPixelMappedChanged(pixelMapped);
uint64_t dirty = gstate_c.GetDirtyUniforms();
if (dirty) {
if (lastShader_)
+2
View File
@@ -682,6 +682,8 @@ public:
ShaderDepalMode shaderDepalMode;
GEBufferFormat depalTextureFormat;
// The pixelMapped that the depal uniform was last packed with (it turns bilinear depal off).
bool depalPixelMapped;
void SetShaderDepal(ShaderDepalMode mode, GEBufferFormat texFormat = {}) {
if (mode != shaderDepalMode || (mode != ShaderDepalMode::OFF && texFormat != depalTextureFormat)) {
+1
View File
@@ -86,6 +86,7 @@ void ShaderManagerMetal::ClearShaders() {
}
uint64_t ShaderManagerMetal::UpdateUniforms(bool useBufferedRendering, bool pixelMapped) {
DirtyDepalIfPixelMappedChanged(pixelMapped);
uint64_t dirty = gstate_c.GetDirtyUniforms();
if (dirty != 0) {
if (dirty & DIRTY_BASE_UNIFORMS)
+1
View File
@@ -218,6 +218,7 @@ void ShaderManagerVulkan::ClearShaders() {
}
uint64_t ShaderManagerVulkan::UpdateUniforms(bool useBufferedRendering, bool pixelMapped) {
DirtyDepalIfPixelMappedChanged(pixelMapped);
uint64_t dirty = gstate_c.GetDirtyUniforms();
if (dirty != 0) {
if (dirty & DIRTY_BASE_UNIFORMS)