From f5cf818bcc02ce940e38cfd772ca1681f5b66054 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sat, 21 Feb 2026 17:47:46 +0300 Subject: [PATCH] rsx: Disable interpolation for SNORM emulated textures --- rpcs3/Emu/RSX/GL/GLDraw.cpp | 16 ++++++-- rpcs3/Emu/RSX/RSXThread.cpp | 77 ++----------------------------------- rpcs3/Emu/RSX/VK/VKDraw.cpp | 8 +++- 3 files changed, 22 insertions(+), 79 deletions(-) diff --git a/rpcs3/Emu/RSX/GL/GLDraw.cpp b/rpcs3/Emu/RSX/GL/GLDraw.cpp index e8ea4bc714..475507ee78 100644 --- a/rpcs3/Emu/RSX/GL/GLDraw.cpp +++ b/rpcs3/Emu/RSX/GL/GLDraw.cpp @@ -2,6 +2,7 @@ #include "GLGSRender.h" #include "../rsx_methods.h" #include "../Common/BufferUtils.h" +#include "../Program/GLSLCommon.h" #include "Emu/RSX/NV47/HW/context_accessors.define.h" @@ -315,6 +316,8 @@ void GLGSRender::load_texture_env() if (sampler_state->validate()) { + sampler_state->format_ex = tex.format_ex(); + if (m_textures_dirty[i]) { m_fs_sampler_states[i].apply(tex, fs_sampler_state[i].get()); @@ -324,12 +327,17 @@ void GLGSRender::load_texture_env() m_graphics_state |= rsx::fragment_program_state_dirty; } - if (const auto texture_format = tex.format() & ~(CELL_GCM_TEXTURE_UN | CELL_GCM_TEXTURE_LN); - sampler_state->format_class != rsx::classify_format(texture_format) && - (texture_format == CELL_GCM_TEXTURE_A8R8G8B8 || texture_format == CELL_GCM_TEXTURE_D8R8G8B8)) + const auto texture_format = sampler_state->format_ex.format(); + // Depth format redirected to BGRA8 resample stage. Do not filter to avoid bits leaking. + // If accurate graphics are desired, force a bitcast to COLOR as a workaround. + const bool is_depth_reconstructed = sampler_state->format_class != rsx::classify_format(texture_format) && + (texture_format == CELL_GCM_TEXTURE_A8R8G8B8 || texture_format == CELL_GCM_TEXTURE_D8R8G8B8); + // SNORM conversion required in shader. Do not interpolate to avoid introducing discontinuities due to how negative numbers work + const bool is_snorm = (sampler_state->format_ex.texel_remap_control & rsx::texture_control_bits::SEXT_MASK) != 0; + + if (is_depth_reconstructed || is_snorm) { // Depth format redirected to BGRA8 resample stage. Do not filter to avoid bits leaking. - // If accurate graphics are desired, force a bitcast to COLOR as a workaround. m_fs_sampler_states[i].set_parameteri(GL_TEXTURE_MIN_FILTER, GL_NEAREST); m_fs_sampler_states[i].set_parameteri(GL_TEXTURE_MAG_FILTER, GL_NEAREST); } diff --git a/rpcs3/Emu/RSX/RSXThread.cpp b/rpcs3/Emu/RSX/RSXThread.cpp index 5b4b732c4d..5bfc60ee2a 100644 --- a/rpcs3/Emu/RSX/RSXThread.cpp +++ b/rpcs3/Emu/RSX/RSXThread.cpp @@ -2321,83 +2321,14 @@ namespace rsx } } - if (const auto format_features = rsx::get_format_features(format); format_features != 0) + if (const auto& format_ex = sampler_descriptors[i]->format_ex; format_ex.features != 0) { - // NOTE: The unsigned_remap=bias flag being set flags the texture as being compressed normal (2n-1 / BX2) (UE3) - // NOTE: The ARGB8_signed flag means to reinterpret the raw bytes as signed. This is different than unsigned_remap=bias which does range decompression. - // This is a separate method of setting the format to signed mode without doing so per-channel - // Precedence = SNORM > GAMMA > UNSIGNED_REMAP/BX2 - // Games using mixed flags: (See Resistance 3 for GAMMA/BX2 relationship, UE3 for BX2 effect) - u32 argb8_signed = 0; - u32 unsigned_remap = 0; - u32 gamma = 0; - - auto remap_channel_bits = [](const rsx::texture_channel_remap_t& remap, u32 bits) -> u32 - { - if (!bits || remap.encoded == RSX_TEXTURE_REMAP_IDENTITY) [[ likely ]] - { - return bits; - } - - u32 result = 0; - for (u8 channel = 0; channel < 4; ++channel) - { - switch (remap.control_map[channel]) - { - case CELL_GCM_TEXTURE_REMAP_REMAP: - if (bits & (1u << remap.channel_map[channel])) - { - result |= (1u << channel); - } - break; - default: - break; - } - } - return result; - }; - - const auto texture_remap = tex.decoded_remap(); - if (format_features & RSX_FORMAT_FEATURE_SIGNED_COMPONENTS) - { - // Tests show this is applied pre-readout. It's just a property of the incoming bytes and is therefore subject to remap. - argb8_signed = remap_channel_bits(texture_remap, tex.argb_signed()); - } - - if (format_features & RSX_FORMAT_FEATURE_GAMMA_CORRECTION) - { - // Tests show this is applied post-readout. It's a property of the final value stored in the register and is not remapped. - // NOTE: GAMMA correction has no algorithmic effect on constants (0 and 1) so we need not mask it out for correctness. - gamma = tex.gamma() & ~(argb8_signed); - } - - if (format_features & RSX_FORMAT_FEATURE_BIASED_NORMALIZATION) - { - // The renormalization flag applies to all channels. It is weaker than the other flags. - // This applies on input and is subject to remap overrides - if (tex.unsigned_remap() == CELL_GCM_TEXTURE_UNSIGNED_REMAP_BIASED) - { - unsigned_remap = remap_channel_bits(texture_remap, 0xF) & ~(argb8_signed | gamma); - } - } - - u32 argb8_convert = gamma; - - // The options are mutually exclusive - ensure((argb8_signed & gamma) == 0); - ensure((argb8_signed & unsigned_remap) == 0); - ensure((gamma & unsigned_remap) == 0); - - // NOTE: Hardware tests show that remapping bypasses the channel swizzles completely - argb8_convert |= (argb8_signed << texture_control_bits::SEXT_OFFSET); - argb8_convert |= (unsigned_remap << texture_control_bits::EXPAND_OFFSET); - texture_control |= argb8_convert; - - texture_control |= format_features << texture_control_bits::FORMAT_FEATURES_OFFSET; + texture_control |= format_ex.texel_remap_control; + texture_control |= format_ex.features << texture_control_bits::FORMAT_FEATURES_OFFSET; if (current_fp_metadata.has_tex_bx2_conv) { - const u32 remap_hi = remap_channel_bits(texture_remap, 0xFu); + const u32 remap_hi = tex.decoded_remap().shuffle_mask_bits(0xFu); current_fragment_program.texture_params[i].remap &= ~(0xFu << 16u); current_fragment_program.texture_params[i].remap |= (remap_hi << 16u); } diff --git a/rpcs3/Emu/RSX/VK/VKDraw.cpp b/rpcs3/Emu/RSX/VK/VKDraw.cpp index 491078cc93..7a9d6aae6e 100644 --- a/rpcs3/Emu/RSX/VK/VKDraw.cpp +++ b/rpcs3/Emu/RSX/VK/VKDraw.cpp @@ -1,5 +1,6 @@ #include "stdafx.h" #include "../Common/BufferUtils.h" +#include "../Program/GLSLCommon.h" #include "../rsx_methods.h" #include "VKAsyncScheduler.h" @@ -307,6 +308,8 @@ void VKGSRender::load_texture_env() if (sampler_state->validate()) { + sampler_state->format_ex = tex.format_ex(); + if (sampler_state->is_cyclic_reference) { check_for_cyclic_refs |= true; @@ -324,7 +327,7 @@ void VKGSRender::load_texture_env() f32 min_lod = 0.f, max_lod = 0.f; f32 lod_bias = 0.f; - const u32 texture_format = tex.format() & ~(CELL_GCM_TEXTURE_UN | CELL_GCM_TEXTURE_LN); + const u32 texture_format = sampler_state->format_ex.format(); VkBool32 compare_enabled = VK_FALSE; VkCompareOp depth_compare_mode = VK_COMPARE_OP_NEVER; @@ -350,7 +353,8 @@ void VKGSRender::load_texture_env() if (sampler_state->format_class == RSX_FORMAT_CLASS_COLOR) [[likely]] { // Most PS3-like formats can be linearly filtered without problem - can_sample_linear = true; + // Exclude textures that require SNORM conversion however + can_sample_linear = (sampler_state->format_ex.texel_remap_control & rsx::texture_control_bits::SEXT_MASK) == 0; } else if (sampler_state->format_class != rsx::classify_format(texture_format) && (texture_format == CELL_GCM_TEXTURE_A8R8G8B8 || texture_format == CELL_GCM_TEXTURE_D8R8G8B8))