diff --git a/GPU/Common/ShaderUniforms.cpp b/GPU/Common/ShaderUniforms.cpp index d2525a5f5e..7bb4f6b92a 100644 --- a/GPU/Common/ShaderUniforms.cpp +++ b/GPU/Common/ShaderUniforms.cpp @@ -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; diff --git a/GPU/Common/ShaderUniforms.h b/GPU/Common/ShaderUniforms.h index f387978d0d..e85fc16b83 100644 --- a/GPU/Common/ShaderUniforms.h +++ b/GPU/Common/ShaderUniforms.h @@ -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]); diff --git a/GPU/Common/SoftwareTransformCommon.cpp b/GPU/Common/SoftwareTransformCommon.cpp index 745c03c250..792be7833d 100644 --- a/GPU/Common/SoftwareTransformCommon.cpp +++ b/GPU/Common/SoftwareTransformCommon.cpp @@ -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; } diff --git a/GPU/D3D11/ShaderManagerD3D11.cpp b/GPU/D3D11/ShaderManagerD3D11.cpp index ef0bfc08f3..b7db0c056c 100644 --- a/GPU/D3D11/ShaderManagerD3D11.cpp +++ b/GPU/D3D11/ShaderManagerD3D11.cpp @@ -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; diff --git a/GPU/GLES/ShaderManagerGLES.cpp b/GPU/GLES/ShaderManagerGLES.cpp index e64ab6d1dd..5d1191af4f 100644 --- a/GPU/GLES/ShaderManagerGLES.cpp +++ b/GPU/GLES/ShaderManagerGLES.cpp @@ -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_) diff --git a/GPU/GPUState.h b/GPU/GPUState.h index 3cdf60b567..b22c2fec11 100644 --- a/GPU/GPUState.h +++ b/GPU/GPUState.h @@ -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)) { diff --git a/GPU/Metal/ShaderManagerMetal.mm b/GPU/Metal/ShaderManagerMetal.mm index 9cecf3bf9c..068b495d69 100644 --- a/GPU/Metal/ShaderManagerMetal.mm +++ b/GPU/Metal/ShaderManagerMetal.mm @@ -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) diff --git a/GPU/Vulkan/ShaderManagerVulkan.cpp b/GPU/Vulkan/ShaderManagerVulkan.cpp index 6bcc8b4c00..7aec3872c7 100644 --- a/GPU/Vulkan/ShaderManagerVulkan.cpp +++ b/GPU/Vulkan/ShaderManagerVulkan.cpp @@ -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)