From 7971bb949b6c8d55cd40af1d6167c45116d4f5d1 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Tue, 8 Apr 2025 19:50:38 +1000 Subject: [PATCH] GPU/HW: Add Scale2x/Scale3x texture filters --- src/core/gpu_hw_shadergen.cpp | 94 ++++++++++++++++++++++++++++++++++- src/core/settings.cpp | 7 ++- src/core/types.h | 2 + 3 files changed, 101 insertions(+), 2 deletions(-) diff --git a/src/core/gpu_hw_shadergen.cpp b/src/core/gpu_hw_shadergen.cpp index 48f3cee22..2e1d54f51 100644 --- a/src/core/gpu_hw_shadergen.cpp +++ b/src/core/gpu_hw_shadergen.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin // SPDX-License-Identifier: CC-BY-NC-ND-4.0 // // NOTE: Some parts of this file have more permissive licenses. They are marked appropriately. @@ -831,6 +831,98 @@ void FilteredSampleFromVRAM(TEXPAGE_VALUE texpage, float2 coords, float4 uv_limi texcol = unpackUnorm4x8(res); } +#undef src +)"; + } + else if (texture_filter == GPUTextureFilter::Scale2x) + { + // Based on https://www.scale2x.it/algorithm + ss << R"( +#define src(xoffs, yoffs) packUnorm4x8(SampleFromVRAM(texpage, clamp(bcoords + float2((xoffs), (yoffs)), uv_limits.xy, uv_limits.zw))) + +void FilteredSampleFromVRAM(TEXPAGE_VALUE texpage, float2 coords, float4 uv_limits, out float4 texcol, out float ialpha) +{ + float2 bcoords = floor(coords); + + uint E = src(+0, +0); + uint B = src(+0, - 1); + uint D = src(-1, +0); + uint F = src(+1, +0); + uint H = src(+0, +1); + + uint J = (D == B && B != F && D != H) ? D : E; + uint K = (B == F && D != F && H != F) ? F : E; + uint L = (H == D && F != D && B != D) ? D : E; + uint M = (H == F && D != H && B != F) ? F : E; + + // select quadrant based on fractional part of texture coordinates + float2 fpart = frac(coords); + uint res = (fpart.x < 0.5f) ? ((fpart.y < 0.5f) ? J : L) : ((fpart.y < 0.5f) ? K : M); + + ialpha = float(res != 0u); + texcol = unpackUnorm4x8(res); +} + +#undef src +)"; + } + else if (texture_filter == GPUTextureFilter::Scale3x) + { + // Based on https://www.scale2x.it/algorithm + ss << R"( +#define src(xoffs, yoffs) packUnorm4x8(SampleFromVRAM(texpage, clamp(bcoords + float2((xoffs), (yoffs)), uv_limits.xy, uv_limits.zw))) + +void FilteredSampleFromVRAM(TEXPAGE_VALUE texpage, float2 coords, float4 uv_limits, out float4 texcol, out float ialpha) +{ + float2 bcoords = floor(coords); + + uint E = src(+0, +0); + uint B = src(+0, -1); + uint D = src(-1, +0); + uint F = src(+1, +0); + uint H = src(+0, +1); + + uint res = E; + if (B != H && D != F) { + uint A = src(-1, -1); + uint C = src(+1, -1); + uint G = src(-1, +1); + uint I = src(+1, +1); + + uint E0 = (D == B) ? D : E; + uint E1 = (D == B && E != C) || (B == F && E != A) ? B : E; + uint E2 = (B == F) ? F : E; + uint E3 = (D == B && E != G) || (D == H && E != A) ? D : E; + uint E4 = E; + uint E5 = (B == F && E != I) || (H == F && E != C) ? F : E; + uint E6 = (D == H) ? D : E; + uint E7 = (D == H && E != I) || (H == F && E != G) ? H : E; + uint E8 = (H == F) ? F : E; + + // select quadrant based on fractional part of texture coordinates + float2 fpart = frac(coords); + uint R0, R1, R2; + if (fpart.y < 0.34f) { + R0 = E0; + R1 = E1; + R2 = E2; + } else if (fpart.y < 0.67f) { + R0 = E3; + R1 = E4; + R2 = E5; + } else { + R0 = E6; + R1 = E7; + R2 = E8; + } + + res = (fpart.x < 0.34f) ? R0 : ((fpart.x < 0.67f) ? R1 : R2); + } + + ialpha = float(res != 0u); + texcol = unpackUnorm4x8(res); +} + #undef src )"; } diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 3b04be3f5..07c39a13f 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -1531,7 +1531,8 @@ GPURenderer Settings::GetAutomaticRenderer() } static constexpr const std::array s_texture_filter_names = { - "Nearest", "Bilinear", "BilinearBinAlpha", "JINC2", "JINC2BinAlpha", "xBR", "xBRBinAlpha", "MMPX", + "Nearest", "Bilinear", "BilinearBinAlpha", "JINC2", "JINC2BinAlpha", + "xBR", "xBRBinAlpha", "Scale2x", "Scale3x", "MMPX", }; static constexpr const std::array s_texture_filter_display_names = { TRANSLATE_DISAMBIG_NOOP("Settings", "Nearest-Neighbor", "GPUTextureFilter"), @@ -1541,8 +1542,12 @@ static constexpr const std::array s_texture_filter_display_names = { TRANSLATE_DISAMBIG_NOOP("Settings", "JINC2 (Slow, No Edge Blending)", "GPUTextureFilter"), TRANSLATE_DISAMBIG_NOOP("Settings", "xBR (Very Slow)", "GPUTextureFilter"), TRANSLATE_DISAMBIG_NOOP("Settings", "xBR (Very Slow, No Edge Blending)", "GPUTextureFilter"), + TRANSLATE_DISAMBIG_NOOP("Settings", "Scale2x (EPX)", "GPUTextureFilter"), + TRANSLATE_DISAMBIG_NOOP("Settings", "Scale3x (Slow)", "GPUTextureFilter"), TRANSLATE_DISAMBIG_NOOP("Settings", "MMPX (Slow)", "GPUTextureFilter"), }; +static_assert(s_texture_filter_names.size() == static_cast(GPUTextureFilter::Count)); +static_assert(s_texture_filter_display_names.size() == static_cast(GPUTextureFilter::Count)); std::optional Settings::ParseTextureFilterName(const char* str) { diff --git a/src/core/types.h b/src/core/types.h index d2f16fb86..cc346c798 100644 --- a/src/core/types.h +++ b/src/core/types.h @@ -97,6 +97,8 @@ enum class GPUTextureFilter : u8 JINC2BinAlpha, xBR, xBRBinAlpha, + Scale2x, + Scale3x, MMPX, Count };