diff --git a/Utilities/geometry.h b/Utilities/geometry.h index faace6c77e..3ffbc04dd3 100644 --- a/Utilities/geometry.h +++ b/Utilities/geometry.h @@ -821,6 +821,14 @@ struct color4_base a *= rhs; } + void operator += (const color4_base& rhs) + { + r += rhs.r; + g += rhs.g; + b += rhs.b; + a += rhs.a; + } + constexpr color4_base operator * (const color4_base& rhs) const { return { r * rhs.r, g * rhs.g, b * rhs.b, a * rhs.a }; diff --git a/rpcs3/Emu/RSX/GL/glutils/sampler.cpp b/rpcs3/Emu/RSX/GL/glutils/sampler.cpp index 580caf0dc2..0d37dc3524 100644 --- a/rpcs3/Emu/RSX/GL/glutils/sampler.cpp +++ b/rpcs3/Emu/RSX/GL/glutils/sampler.cpp @@ -82,11 +82,32 @@ namespace gl { // NOTE: In OpenGL, the border texels are processed by the pipeline and will be swizzled by the texture view. // Therefore, we pass the raw value here, and the texture view will handle the rest for us. - const auto encoded_color = tex.border_color(); + const bool sext_conv_required = (sampled_image->format_ex.texel_remap_control & rsx::SEXT_MASK) != 0; + const auto encoded_color = tex.border_color(sext_conv_required); if (get_parameteri(GL_TEXTURE_BORDER_COLOR) != encoded_color) { m_propertiesi[GL_TEXTURE_BORDER_COLOR] = encoded_color; - const auto border_color = rsx::decode_border_color(encoded_color); + auto border_color = rsx::decode_border_color(encoded_color); + + if (const auto snorm_mask = tex.argb_signed(); + !sext_conv_required && tex.argb_signed()) [[ unlikely ]] + { + // Hardware SNORM is active + ensure(sampled_image->format_ex.hw_SNORM_possible()); + + // Convert the border color in host space (2N - 1) + // HW does the conversion in integer space as (x - 128) / 127 which introduces a biasing error. + const float bias_v = 128.f / 255.f; + const float scale_v = 255.f / 127.f; + + color4f scale{ 1.f }, bias{ 0.f }; + if (snorm_mask & 1) { scale.a = scale_v; bias.a = -bias_v; } + if (snorm_mask & 2) { scale.r = scale_v; bias.r = -bias_v; } + if (snorm_mask & 4) { scale.g = scale_v; bias.g = -bias_v; } + if (snorm_mask & 8) { scale.b = scale_v; bias.b = -bias_v; } + border_color = (border_color + bias) * scale; + } + glSamplerParameterfv(sampler_handle, GL_TEXTURE_BORDER_COLOR, border_color.rgba); } } diff --git a/rpcs3/Emu/RSX/RSXTexture.cpp b/rpcs3/Emu/RSX/RSXTexture.cpp index 6f8eec0e3b..071a1c8a78 100644 --- a/rpcs3/Emu/RSX/RSXTexture.cpp +++ b/rpcs3/Emu/RSX/RSXTexture.cpp @@ -364,11 +364,15 @@ namespace rsx return dimension() != rsx::texture_dimension::dimension1d ? ((registers[NV4097_SET_TEXTURE_IMAGE_RECT + (m_index * 8)]) & 0xffff) : 1; } - u32 fragment_texture::border_color() const + u32 fragment_texture::border_color(bool apply_colorspace_remapping) const { const u32 raw = registers[NV4097_SET_TEXTURE_BORDER_COLOR + (m_index * 8)]; - const u32 sext = argb_signed(); + if (!apply_colorspace_remapping) [[ likely ]] + { + return raw; + } + const u32 sext = argb_signed(); if (!sext) [[ likely ]] { return raw; @@ -430,9 +434,9 @@ namespace rsx return (conv & mask) | (raw & ~mask); } - color4f fragment_texture::remapped_border_color() const + color4f fragment_texture::remapped_border_color(bool apply_colorspace_remapping) const { - color4f base_color = rsx::decode_border_color(border_color()); + color4f base_color = rsx::decode_border_color(border_color(apply_colorspace_remapping)); if (remap() == RSX_TEXTURE_REMAP_IDENTITY) { return base_color; @@ -571,14 +575,14 @@ namespace rsx return dimension() != rsx::texture_dimension::dimension1d ? ((registers[NV4097_SET_VERTEX_TEXTURE_IMAGE_RECT + (m_index * 8)]) & 0xffff) : 1; } - u32 vertex_texture::border_color() const + u32 vertex_texture::border_color(bool) const { return registers[NV4097_SET_VERTEX_TEXTURE_BORDER_COLOR + (m_index * 8)]; } - color4f vertex_texture::remapped_border_color() const + color4f vertex_texture::remapped_border_color(bool) const { - return rsx::decode_border_color(border_color()); + return rsx::decode_border_color(border_color(false)); } u16 vertex_texture::depth() const diff --git a/rpcs3/Emu/RSX/RSXTexture.h b/rpcs3/Emu/RSX/RSXTexture.h index 3b0ccdebce..3acb532c0f 100644 --- a/rpcs3/Emu/RSX/RSXTexture.h +++ b/rpcs3/Emu/RSX/RSXTexture.h @@ -80,8 +80,8 @@ namespace rsx u16 height() const; // Border Color - u32 border_color() const; - color4f remapped_border_color() const; + u32 border_color(bool apply_colorspace_remapping = false) const; + color4f remapped_border_color(bool apply_colorspace_remapping = false) const; u16 depth() const; u32 pitch() const; @@ -136,8 +136,8 @@ namespace rsx u16 height() const; // Border Color - u32 border_color() const; - color4f remapped_border_color() const; + u32 border_color(bool = false) const; + color4f remapped_border_color(bool = false) const; u16 depth() const; u32 pitch() const; diff --git a/rpcs3/Emu/RSX/VK/VKDraw.cpp b/rpcs3/Emu/RSX/VK/VKDraw.cpp index 344bde8b64..e13cc9aa94 100644 --- a/rpcs3/Emu/RSX/VK/VKDraw.cpp +++ b/rpcs3/Emu/RSX/VK/VKDraw.cpp @@ -274,11 +274,11 @@ void VKGSRender::load_texture_env() return false; }; - auto get_border_color = [&](const rsx::Texture auto& tex) + auto get_border_color = [&](const rsx::Texture auto& tex, bool remap_colorspace) { return m_device->get_custom_border_color_support().require_border_color_remap - ? tex.remapped_border_color() - : rsx::decode_border_color(tex.border_color()); + ? tex.remapped_border_color(remap_colorspace) + : rsx::decode_border_color(tex.border_color(remap_colorspace)); }; std::lock_guard lock(m_sampler_mutex); @@ -394,9 +394,30 @@ void VKGSRender::load_texture_env() // NOTE: In vulkan, the border color can bypass the sample swizzle stage. // Check the device properties to determine whether to pre-swizzle the colors or not. - const auto border_color = rsx::is_border_clamped_texture(tex) - ? vk::border_color_t(get_border_color(tex)) - : vk::border_color_t(VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK); + const bool sext_conv_required = (sampler_state->format_ex.texel_remap_control & rsx::texture_control_bits::SEXT_MASK) != 0; + vk::border_color_t border_color(VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK); + + if (rsx::is_border_clamped_texture(tex)) + { + auto color_value = get_border_color(tex, sext_conv_required); + if (const auto snorm_mask = tex.argb_signed(); + !sext_conv_required && snorm_mask) + { + // Convert the border color in host space (2N - 1) + // HW does the conversion in integer space as (x - 128) / 127 which introduces a biasing error. + const float bias_v = 128.f / 255.f; + const float scale_v = 255.f / 127.f; + + color4f scale{ 1.f }, bias{ 0.f }; + if (snorm_mask & 1) { scale.a = scale_v; bias.a = -bias_v; } + if (snorm_mask & 2) { scale.r = scale_v; bias.r = -bias_v; } + if (snorm_mask & 4) { scale.g = scale_v; bias.g = -bias_v; } + if (snorm_mask & 8) { scale.b = scale_v; bias.b = -bias_v; } + color_value = (color_value + bias) * scale; + } + + border_color = color_value; + } // Check if non-point filtering can even be used on this format bool can_sample_linear; @@ -404,7 +425,7 @@ void VKGSRender::load_texture_env() { // Most PS3-like formats can be linearly filtered without problem // Exclude textures that require SNORM conversion however - can_sample_linear = (sampler_state->format_ex.texel_remap_control & rsx::texture_control_bits::SEXT_MASK) == 0; + can_sample_linear = !sext_conv_required; } else if (sampler_state->format_class != rsx::classify_format(texture_format) && (texture_format == CELL_GCM_TEXTURE_A8R8G8B8 || texture_format == CELL_GCM_TEXTURE_D8R8G8B8)) @@ -554,7 +575,7 @@ void VKGSRender::load_texture_env() // NOTE: In vulkan, the border color can bypass the sample swizzle stage. // Check the device properties to determine whether to pre-swizzle the colors or not. const auto border_color = is_border_clamped_texture(tex) - ? vk::border_color_t(get_border_color(tex)) + ? vk::border_color_t(get_border_color(tex, false)) : vk::border_color_t(VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK); if (vs_sampler_handles[i] &&