diff --git a/README.md b/README.md index 7f8a71277..16c23a2ac 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ A "BIOS" ROM image is required to to start the emulator and to play games. You c ## Latest News +- 2020/09/12: Additional texture filtering options added. - 2020/09/09: Basic cheat support added. Not all instructions/commands are supported yet. - 2020/09/01: Many additional user settings available, including memory cards and enhancements. Now you can set these per-game. - 2020/08/25: Automated builds for macOS now available. diff --git a/src/core/gpu_hw.cpp b/src/core/gpu_hw.cpp index 4741a9263..e1c0c60f9 100644 --- a/src/core/gpu_hw.cpp +++ b/src/core/gpu_hw.cpp @@ -26,7 +26,7 @@ ALWAYS_INLINE static constexpr std::tuple MinMax(T v1, T v2) ALWAYS_INLINE static bool ShouldUseUVLimits() { // We only need UV limits if PGXP is enabled, or texture filtering is enabled. - return g_settings.gpu_pgxp_enable || g_settings.gpu_texture_filtering; + return g_settings.gpu_pgxp_enable || g_settings.gpu_texture_filter != GPUTextureFilter::Nearest; } GPU_HW::GPU_HW() : GPU() @@ -50,7 +50,7 @@ bool GPU_HW::Initialize(HostDisplay* host_display) m_render_api = host_display->GetRenderAPI(); m_true_color = g_settings.gpu_true_color; m_scaled_dithering = g_settings.gpu_scaled_dithering; - m_texture_filtering = g_settings.gpu_texture_filtering; + m_texture_filtering = g_settings.gpu_texture_filter; m_using_uv_limits = ShouldUseUVLimits(); PrintSettingsToLog(); return true; @@ -96,7 +96,7 @@ void GPU_HW::UpdateHWSettings(bool* framebuffer_changed, bool* shaders_changed) *framebuffer_changed = (m_resolution_scale != resolution_scale); *shaders_changed = (m_resolution_scale != resolution_scale || m_true_color != g_settings.gpu_true_color || m_scaled_dithering != g_settings.gpu_scaled_dithering || - m_texture_filtering != g_settings.gpu_texture_filtering || m_using_uv_limits != use_uv_limits); + m_texture_filtering != g_settings.gpu_texture_filter || m_using_uv_limits != use_uv_limits); if (m_resolution_scale != resolution_scale) { @@ -109,7 +109,7 @@ void GPU_HW::UpdateHWSettings(bool* framebuffer_changed, bool* shaders_changed) m_resolution_scale = resolution_scale; m_true_color = g_settings.gpu_true_color; m_scaled_dithering = g_settings.gpu_scaled_dithering; - m_texture_filtering = g_settings.gpu_texture_filtering; + m_texture_filtering = g_settings.gpu_texture_filter; m_using_uv_limits = use_uv_limits; PrintSettingsToLog(); } @@ -148,7 +148,7 @@ void GPU_HW::PrintSettingsToLog() VRAM_HEIGHT * m_resolution_scale, m_max_resolution_scale); Log_InfoPrintf("Dithering: %s%s", m_true_color ? "Disabled" : "Enabled", (!m_true_color && m_scaled_dithering) ? " (Scaled)" : ""); - Log_InfoPrintf("Texture Filtering: %s", m_texture_filtering ? "Enabled" : "Disabled"); + Log_InfoPrintf("Texture Filtering: %s", Settings::GetTextureFilterDisplayName(m_texture_filtering)); Log_InfoPrintf("Dual-source blending: %s", m_supports_dual_source_blend ? "Supported" : "Not supported"); Log_InfoPrintf("Using UV limits: %s", m_using_uv_limits ? "YES" : "NO"); } @@ -1036,8 +1036,8 @@ void GPU_HW::DrawRendererStats(bool is_idle_frame) ImGui::TextUnformatted("Texture Filtering:"); ImGui::NextColumn(); - ImGui::TextColored(m_texture_filtering ? active_color : inactive_color, - m_texture_filtering ? "Enabled" : "Disabled"); + ImGui::TextColored((m_texture_filtering != GPUTextureFilter::Nearest) ? active_color : inactive_color, + Settings::GetTextureFilterDisplayName(m_texture_filtering)); ImGui::NextColumn(); ImGui::TextUnformatted("PGXP:"); diff --git a/src/core/gpu_hw.h b/src/core/gpu_hw.h index 2b6141db3..1d64de71d 100644 --- a/src/core/gpu_hw.h +++ b/src/core/gpu_hw.h @@ -270,7 +270,7 @@ protected: HostDisplay::RenderAPI m_render_api = HostDisplay::RenderAPI::None; bool m_true_color = true; bool m_scaled_dithering = false; - bool m_texture_filtering = false; + GPUTextureFilter m_texture_filtering = GPUTextureFilter::Nearest; bool m_supports_dual_source_blend = false; bool m_using_uv_limits = false; diff --git a/src/core/gpu_hw_d3d11.cpp b/src/core/gpu_hw_d3d11.cpp index 6a669a028..87c44e8df 100644 --- a/src/core/gpu_hw_d3d11.cpp +++ b/src/core/gpu_hw_d3d11.cpp @@ -332,7 +332,7 @@ bool GPU_HW_D3D11::CreateStateObjects() for (u8 transparency_mode = 0; transparency_mode < 5; transparency_mode++) { bl_desc = CD3D11_BLEND_DESC(CD3D11_DEFAULT()); - if (transparency_mode != static_cast(TransparencyMode::Disabled) || m_texture_filtering) + if (transparency_mode != static_cast(TransparencyMode::Disabled) || m_texture_filtering != GPUTextureFilter::Nearest) { bl_desc.RenderTarget[0].BlendEnable = TRUE; bl_desc.RenderTarget[0].SrcBlend = D3D11_BLEND_ONE; diff --git a/src/core/gpu_hw_shadergen.cpp b/src/core/gpu_hw_shadergen.cpp index ee1817367..893efc60e 100644 --- a/src/core/gpu_hw_shadergen.cpp +++ b/src/core/gpu_hw_shadergen.cpp @@ -6,10 +6,10 @@ Log_SetChannel(GPU_HW_ShaderGen); GPU_HW_ShaderGen::GPU_HW_ShaderGen(HostDisplay::RenderAPI render_api, u32 resolution_scale, bool true_color, - bool scaled_dithering, bool texture_filtering, bool uv_limits, + bool scaled_dithering, GPUTextureFilter texture_filtering, bool uv_limits, bool supports_dual_source_blend) : m_render_api(render_api), m_resolution_scale(resolution_scale), m_true_color(true_color), - m_scaled_dithering(scaled_dithering), m_texture_filering(texture_filtering), m_uv_limits(uv_limits), + m_scaled_dithering(scaled_dithering), m_texture_filter(texture_filtering), m_uv_limits(uv_limits), m_glsl(render_api != HostDisplay::RenderAPI::D3D11), m_supports_dual_source_blend(supports_dual_source_blend), m_use_glsl_interface_blocks(false) { @@ -148,6 +148,8 @@ void GPU_HW_ShaderGen::WriteHeader(std::stringstream& ss) ss << "#define CONSTANT const\n"; ss << "#define VECTOR_EQ(a, b) ((a) == (b))\n"; ss << "#define VECTOR_NEQ(a, b) ((a) != (b))\n"; + ss << "#define VECTOR_COMP_EQ(a, b) equal((a), (b))\n"; + ss << "#define VECTOR_COMP_NEQ(a, b) notEqual((a), (b))\n"; ss << "#define SAMPLE_TEXTURE(name, coords) texture(name, coords)\n"; ss << "#define LOAD_TEXTURE(name, coords, mip) texelFetch(name, coords, mip)\n"; ss << "#define LOAD_TEXTURE_OFFSET(name, coords, mip, offset) texelFetchOffset(name, coords, mip, offset)\n"; @@ -160,6 +162,8 @@ void GPU_HW_ShaderGen::WriteHeader(std::stringstream& ss) ss << "#define CONSTANT static const\n"; ss << "#define VECTOR_EQ(a, b) (all((a) == (b)))\n"; ss << "#define VECTOR_NEQ(a, b) (any((a) != (b)))\n"; + ss << "#define VECTOR_COMP_EQ(a, b) ((a) == (b))\n"; + ss << "#define VECTOR_COMP_NEQ(a, b) ((a) != (b))\n"; ss << "#define SAMPLE_TEXTURE(name, coords) name.Sample(name##_ss, coords)\n"; ss << "#define LOAD_TEXTURE(name, coords, mip) name.Load(int3(coords, mip))\n"; ss << "#define LOAD_TEXTURE_OFFSET(name, coords, mip, offset) name.Load(int3(coords, mip), offset)\n"; @@ -578,6 +582,476 @@ std::string GPU_HW_ShaderGen::GenerateBatchVertexShader(bool textured) return ss.str(); } +void GPU_HW_ShaderGen::WriteBatchTextureFilter(std::stringstream& ss, GPUTextureFilter texture_filter) +{ + // JINC2 and xBRZ shaders originally from beetle-psx, modified to support filtering mask channel. + if (texture_filter == GPUTextureFilter::Bilinear) + { + ss << R"( +void FilteredSampleFromVRAM(uint4 texpage, float2 coords, float4 uv_limits, + out float4 texcol, out float ialpha) +{ + // Compute the coordinates of the four texels we will be interpolating between. + // Clamp this to the triangle texture coordinates. + float2 texel_top_left = frac(coords) - float2(0.5, 0.5); + float2 texel_offset = sign(texel_top_left); + float4 fcoords = max(coords.xyxy + float4(0.0, 0.0, texel_offset.x, texel_offset.y), + float4(0.0, 0.0, 0.0, 0.0)); + + // Load four texels. + float4 s00 = SampleFromVRAM(texpage, clamp(fcoords.xy, uv_limits.xy, uv_limits.zw)); + float4 s10 = SampleFromVRAM(texpage, clamp(fcoords.zy, uv_limits.xy, uv_limits.zw)); + float4 s01 = SampleFromVRAM(texpage, clamp(fcoords.xw, uv_limits.xy, uv_limits.zw)); + float4 s11 = SampleFromVRAM(texpage, clamp(fcoords.zw, uv_limits.xy, uv_limits.zw)); + + // Compute alpha from how many texels aren't pixel color 0000h. + float a00 = float(VECTOR_NEQ(s00, TRANSPARENT_PIXEL_COLOR)); + float a10 = float(VECTOR_NEQ(s10, TRANSPARENT_PIXEL_COLOR)); + float a01 = float(VECTOR_NEQ(s01, TRANSPARENT_PIXEL_COLOR)); + float a11 = float(VECTOR_NEQ(s11, TRANSPARENT_PIXEL_COLOR)); + + // Bilinearly interpolate. + float2 weights = abs(texel_top_left); + texcol = lerp(lerp(s00, s10, weights.x), lerp(s01, s11, weights.x), weights.y); + ialpha = lerp(lerp(a00, a10, weights.x), lerp(a01, a11, weights.x), weights.y); + + // Compensate for partially transparent sampling. + if (ialpha > 0.0) + texcol.rgb /= float3(ialpha, ialpha, ialpha); +} +)"; + } + else if (texture_filter == GPUTextureFilter::JINC2) + { + ss << R"( +CONSTANT float JINC2_WINDOW_SINC = 0.44; +CONSTANT float JINC2_SINC = 0.82; +CONSTANT float JINC2_AR_STRENGTH = 0.8; + +CONSTANT float halfpi = 1.5707963267948966192313216916398; +CONSTANT float pi = 3.1415926535897932384626433832795; +CONSTANT float wa = 1.382300768; +CONSTANT float wb = 2.576105976; + +// Calculates the distance between two points +float d(float2 pt1, float2 pt2) +{ + float2 v = pt2 - pt1; + return sqrt(dot(v,v)); +} + +float min4(float a, float b, float c, float d) +{ + return min(a, min(b, min(c, d))); +} + +float4 min4(float4 a, float4 b, float4 c, float4 d) +{ + return min(a, min(b, min(c, d))); +} + +float max4(float a, float b, float c, float d) +{ + return max(a, max(b, max(c, d))); +} + +float4 max4(float4 a, float4 b, float4 c, float4 d) +{ + return max(a, max(b, max(c, d))); +} + +float4 resampler(float4 x) +{ + float4 res; + + // res = (x==float4(0.0, 0.0, 0.0, 0.0)) ? float4(wa*wb) : sin(x*wa)*sin(x*wb)/(x*x); + // Need to use mix(.., equal(..)) since we want zero check to be component wise + res = lerp(sin(x*wa)*sin(x*wb)/(x*x), float4(wa*wb, wa*wb, wa*wb, wa*wb), VECTOR_COMP_EQ(x,float4(0.0, 0.0, 0.0, 0.0))); + + return res; +} + +void FilteredSampleFromVRAM(uint4 texpage, float2 coords, float4 uv_limits, + out float4 texcol, out float ialpha) +{ + float4 weights[4]; + + float2 dx = float2(1.0, 0.0); + float2 dy = float2(0.0, 1.0); + + float2 pc = coords.xy; + + float2 tc = (floor(pc-float2(0.5,0.5))+float2(0.5,0.5)); + + weights[0] = resampler(float4(d(pc, tc -dx -dy), d(pc, tc -dy), d(pc, tc +dx -dy), d(pc, tc+2.0*dx -dy))); + weights[1] = resampler(float4(d(pc, tc -dx ), d(pc, tc ), d(pc, tc +dx ), d(pc, tc+2.0*dx ))); + weights[2] = resampler(float4(d(pc, tc -dx +dy), d(pc, tc +dy), d(pc, tc +dx +dy), d(pc, tc+2.0*dx +dy))); + weights[3] = resampler(float4(d(pc, tc -dx+2.0*dy), d(pc, tc +2.0*dy), d(pc, tc +dx+2.0*dy), d(pc, tc+2.0*dx+2.0*dy))); + + dx = dx; + dy = dy; + tc = tc; + +#define sample_texel(coords) SampleFromVRAM(texpage, clamp((coords), uv_limits.xy, uv_limits.zw)) + + float4 c00 = sample_texel(tc -dx -dy); + float a00 = float(VECTOR_NEQ(c00, TRANSPARENT_PIXEL_COLOR)); + float4 c10 = sample_texel(tc -dy); + float a10 = float(VECTOR_NEQ(c10, TRANSPARENT_PIXEL_COLOR)); + float4 c20 = sample_texel(tc +dx -dy); + float a20 = float(VECTOR_NEQ(c20, TRANSPARENT_PIXEL_COLOR)); + float4 c30 = sample_texel(tc+2.0*dx -dy); + float a30 = float(VECTOR_NEQ(c30, TRANSPARENT_PIXEL_COLOR)); + float4 c01 = sample_texel(tc -dx ); + float a01 = float(VECTOR_NEQ(c01, TRANSPARENT_PIXEL_COLOR)); + float4 c11 = sample_texel(tc ); + float a11 = float(VECTOR_NEQ(c11, TRANSPARENT_PIXEL_COLOR)); + float4 c21 = sample_texel(tc +dx ); + float a21 = float(VECTOR_NEQ(c21, TRANSPARENT_PIXEL_COLOR)); + float4 c31 = sample_texel(tc+2.0*dx ); + float a31 = float(VECTOR_NEQ(c31, TRANSPARENT_PIXEL_COLOR)); + float4 c02 = sample_texel(tc -dx +dy); + float a02 = float(VECTOR_NEQ(c02, TRANSPARENT_PIXEL_COLOR)); + float4 c12 = sample_texel(tc +dy); + float a12 = float(VECTOR_NEQ(c12, TRANSPARENT_PIXEL_COLOR)); + float4 c22 = sample_texel(tc +dx +dy); + float a22 = float(VECTOR_NEQ(c22, TRANSPARENT_PIXEL_COLOR)); + float4 c32 = sample_texel(tc+2.0*dx +dy); + float a32 = float(VECTOR_NEQ(c32, TRANSPARENT_PIXEL_COLOR)); + float4 c03 = sample_texel(tc -dx+2.0*dy); + float a03 = float(VECTOR_NEQ(c03, TRANSPARENT_PIXEL_COLOR)); + float4 c13 = sample_texel(tc +2.0*dy); + float a13 = float(VECTOR_NEQ(c13, TRANSPARENT_PIXEL_COLOR)); + float4 c23 = sample_texel(tc +dx+2.0*dy); + float a23 = float(VECTOR_NEQ(c23, TRANSPARENT_PIXEL_COLOR)); + float4 c33 = sample_texel(tc+2.0*dx+2.0*dy); + float a33 = float(VECTOR_NEQ(c33, TRANSPARENT_PIXEL_COLOR)); + +#undef sample_texel + + // Get min/max samples + float4 min_sample = min4(c11, c21, c12, c22); + float min_sample_alpha = min4(a11, a21, a12, a22); + float4 max_sample = max4(c11, c21, c12, c22); + float max_sample_alpha = max4(a11, a21, a12, a22); + + float4 color; + color = float4(dot(weights[0], float4(c00.x, c10.x, c20.x, c30.x)), dot(weights[0], float4(c00.y, c10.y, c20.y, c30.y)), dot(weights[0], float4(c00.z, c10.z, c20.z, c30.z)), dot(weights[0], float4(c00.w, c10.w, c20.w, c30.w))); + color+= float4(dot(weights[1], float4(c01.x, c11.x, c21.x, c31.x)), dot(weights[1], float4(c01.y, c11.y, c21.y, c31.y)), dot(weights[1], float4(c01.z, c11.z, c21.z, c31.z)), dot(weights[1], float4(c01.w, c11.w, c21.w, c31.w))); + color+= float4(dot(weights[2], float4(c02.x, c12.x, c22.x, c32.x)), dot(weights[2], float4(c02.y, c12.y, c22.y, c32.y)), dot(weights[2], float4(c02.z, c12.z, c22.z, c32.z)), dot(weights[2], float4(c02.w, c12.w, c22.w, c32.w))); + color+= float4(dot(weights[3], float4(c03.x, c13.x, c23.x, c33.x)), dot(weights[3], float4(c03.y, c13.y, c23.y, c33.y)), dot(weights[3], float4(c03.z, c13.z, c23.z, c33.z)), dot(weights[3], float4(c03.w, c13.w, c23.w, c33.w))); + color = color/(dot(weights[0], float4(1,1,1,1)) + dot(weights[1], float4(1,1,1,1)) + dot(weights[2], float4(1,1,1,1)) + dot(weights[3], float4(1,1,1,1))); + + float alpha; + alpha = dot(weights[0], float4(a00, a10, a20, a30)); + alpha+= dot(weights[1], float4(a01, a11, a21, a31)); + alpha+= dot(weights[2], float4(a02, a12, a22, a32)); + alpha+= dot(weights[3], float4(a03, a13, a23, a33)); + //alpha = alpha/(weights[0].w + weights[1].w + weights[2].w + weights[3].w); + alpha = alpha/(dot(weights[0], float4(1,1,1,1)) + dot(weights[1], float4(1,1,1,1)) + dot(weights[2], float4(1,1,1,1)) + dot(weights[3], float4(1,1,1,1))); + + // Anti-ringing + float4 aux = color; + float aux_alpha = alpha; + color = clamp(color, min_sample, max_sample); + alpha = clamp(alpha, min_sample_alpha, max_sample_alpha); + color = lerp(aux, color, JINC2_AR_STRENGTH); + alpha = lerp(aux_alpha, alpha, JINC2_AR_STRENGTH); + + // final sum and weight normalization + ialpha = alpha; + texcol = color; + + // Compensate for partially transparent sampling. + if (ialpha > 0.0) + texcol.rgb /= float3(ialpha, ialpha, ialpha); +} +)"; + } + else if (texture_filter == GPUTextureFilter::xBRZ) + { + ss << R"( +CONSTANT int BLEND_NONE = 0; +CONSTANT int BLEND_NORMAL = 1; +CONSTANT int BLEND_DOMINANT = 2; +CONSTANT float LUMINANCE_WEIGHT = 1.0; +CONSTANT float EQUAL_COLOR_TOLERANCE = 0.1176470588235294; +CONSTANT float STEEP_DIRECTION_THRESHOLD = 2.2; +CONSTANT float DOMINANT_DIRECTION_THRESHOLD = 3.6; +CONSTANT float4 w = float4(0.2627, 0.6780, 0.0593, 0.5); + +float DistYCbCr(float4 pixA, float4 pixB) +{ + const float scaleB = 0.5 / (1.0 - w.b); + const float scaleR = 0.5 / (1.0 - w.r); + float4 diff = pixA - pixB; + float Y = dot(diff, w); + float Cb = scaleB * (diff.b - Y); + float Cr = scaleR * (diff.r - Y); + + return sqrt(((LUMINANCE_WEIGHT * Y) * (LUMINANCE_WEIGHT * Y)) + (Cb * Cb) + (Cr * Cr)); +} + +bool IsPixEqual(const float4 pixA, const float4 pixB) +{ + return (DistYCbCr(pixA, pixB) < EQUAL_COLOR_TOLERANCE); +} + +float get_left_ratio(float2 center, float2 origin, float2 direction, float2 scale) +{ + float2 P0 = center - origin; + float2 proj = direction * (dot(P0, direction) / dot(direction, direction)); + float2 distv = P0 - proj; + float2 orth = float2(-direction.y, direction.x); + float side = sign(dot(P0, orth)); + float v = side * length(distv * scale); + +// return step(0, v); + return smoothstep(-sqrt(2.0)/2.0, sqrt(2.0)/2.0, v); +} + +#define P(coord, xoffs, yoffs) SampleFromVRAM(texpage, clamp(coords + float2((xoffs), (yoffs)), uv_limits.xy, uv_limits.zw)) + +void FilteredSampleFromVRAM(uint4 texpage, float2 coords, float4 uv_limits, + out float4 texcol, out float ialpha) +{ + //--------------------------------------- + // Input Pixel Mapping: -|x|x|x|- + // x|A|B|C|x + // x|D|E|F|x + // x|G|H|I|x + // -|x|x|x|- + + float2 scale = float2(8.0, 8.0); + float2 pos = frac(coords.xy) - float2(0.5, 0.5); + float2 coord = coords.xy - pos; + + float4 A = P(coord, -1,-1); + float Aw = A.w; + A.w = float(VECTOR_NEQ(A, TRANSPARENT_PIXEL_COLOR)); + float4 B = P(coord, 0,-1); + float Bw = B.w; + B.w = float(VECTOR_NEQ(B, TRANSPARENT_PIXEL_COLOR)); + float4 C = P(coord, 1,-1); + float Cw = C.w; + C.w = float(VECTOR_NEQ(C, TRANSPARENT_PIXEL_COLOR)); + float4 D = P(coord, -1, 0); + float Dw = D.w; + D.w = float(VECTOR_NEQ(D, TRANSPARENT_PIXEL_COLOR)); + float4 E = P(coord, 0, 0); + float Ew = E.w; + E.w = float(VECTOR_NEQ(E, TRANSPARENT_PIXEL_COLOR)); + float4 F = P(coord, 1, 0); + float Fw = F.w; + F.w = float(VECTOR_NEQ(F, TRANSPARENT_PIXEL_COLOR)); + float4 G = P(coord, -1, 1); + float Gw = G.w; + G.w = float(VECTOR_NEQ(G, TRANSPARENT_PIXEL_COLOR)); + float4 H = P(coord, 0, 1); + float Hw = H.w; + H.w = float(VECTOR_NEQ(H, TRANSPARENT_PIXEL_COLOR)); + float4 I = P(coord, 1, 1); + float Iw = I.w; + I.w = float(VECTOR_NEQ(H, TRANSPARENT_PIXEL_COLOR)); + + // blendResult Mapping: x|y| + // w|z| + int4 blendResult = int4(BLEND_NONE,BLEND_NONE,BLEND_NONE,BLEND_NONE); + + // Preprocess corners + // Pixel Tap Mapping: -|-|-|-|- + // -|-|B|C|- + // -|D|E|F|x + // -|G|H|I|x + // -|-|x|x|- + if (!((VECTOR_EQ(E,F) && VECTOR_EQ(H,I)) || (VECTOR_EQ(E,H) && VECTOR_EQ(F,I)))) + { + float dist_H_F = DistYCbCr(G, E) + DistYCbCr(E, C) + DistYCbCr(P(coord, 0,2), I) + DistYCbCr(I, P(coord, 2,0)) + (4.0 * DistYCbCr(H, F)); + float dist_E_I = DistYCbCr(D, H) + DistYCbCr(H, P(coord, 1,2)) + DistYCbCr(B, F) + DistYCbCr(F, P(coord, 2,1)) + (4.0 * DistYCbCr(E, I)); + bool dominantGradient = (DOMINANT_DIRECTION_THRESHOLD * dist_H_F) < dist_E_I; + blendResult.z = ((dist_H_F < dist_E_I) && VECTOR_NEQ(E,F) && VECTOR_NEQ(E,H)) ? ((dominantGradient) ? BLEND_DOMINANT : BLEND_NORMAL) : BLEND_NONE; + } + + + // Pixel Tap Mapping: -|-|-|-|- + // -|A|B|-|- + // x|D|E|F|- + // x|G|H|I|- + // -|x|x|-|- + if (!((VECTOR_EQ(D,E) && VECTOR_EQ(G,H)) || (VECTOR_EQ(D,G) && VECTOR_EQ(E,H)))) + { + float dist_G_E = DistYCbCr(P(coord, -2,1) , D) + DistYCbCr(D, B) + DistYCbCr(P(coord, -1,2), H) + DistYCbCr(H, F) + (4.0 * DistYCbCr(G, E)); + float dist_D_H = DistYCbCr(P(coord, -2,0) , G) + DistYCbCr(G, P(coord, 0,2)) + DistYCbCr(A, E) + DistYCbCr(E, I) + (4.0 * DistYCbCr(D, H)); + bool dominantGradient = (DOMINANT_DIRECTION_THRESHOLD * dist_D_H) < dist_G_E; + blendResult.w = ((dist_G_E > dist_D_H) && VECTOR_NEQ(E,D) && VECTOR_NEQ(E,H)) ? ((dominantGradient) ? BLEND_DOMINANT : BLEND_NORMAL) : BLEND_NONE; + } + + // Pixel Tap Mapping: -|-|x|x|- + // -|A|B|C|x + // -|D|E|F|x + // -|-|H|I|- + // -|-|-|-|- + if (!((VECTOR_EQ(B,C) && VECTOR_EQ(E,F)) || (VECTOR_EQ(B,E) && VECTOR_EQ(C,F)))) + { + float dist_E_C = DistYCbCr(D, B) + DistYCbCr(B, P(coord, 1,-2)) + DistYCbCr(H, F) + DistYCbCr(F, P(coord, 2,-1)) + (4.0 * DistYCbCr(E, C)); + float dist_B_F = DistYCbCr(A, E) + DistYCbCr(E, I) + DistYCbCr(P(coord, 0,-2), C) + DistYCbCr(C, P(coord, 2,0)) + (4.0 * DistYCbCr(B, F)); + bool dominantGradient = (DOMINANT_DIRECTION_THRESHOLD * dist_B_F) < dist_E_C; + blendResult.y = ((dist_E_C > dist_B_F) && VECTOR_NEQ(E,B) && VECTOR_NEQ(E,F)) ? ((dominantGradient) ? BLEND_DOMINANT : BLEND_NORMAL) : BLEND_NONE; + } + + // Pixel Tap Mapping: -|x|x|-|- + // x|A|B|C|- + // x|D|E|F|- + // -|G|H|-|- + // -|-|-|-|- + if (!((VECTOR_EQ(A,B) && VECTOR_EQ(D,E)) || (VECTOR_EQ(A,D) && VECTOR_EQ(B,E)))) + { + float dist_D_B = DistYCbCr(P(coord, -2,0), A) + DistYCbCr(A, P(coord, 0,-2)) + DistYCbCr(G, E) + DistYCbCr(E, C) + (4.0 * DistYCbCr(D, B)); + float dist_A_E = DistYCbCr(P(coord, -2,-1), D) + DistYCbCr(D, H) + DistYCbCr(P(coord, -1,-2), B) + DistYCbCr(B, F) + (4.0 * DistYCbCr(A, E)); + bool dominantGradient = (DOMINANT_DIRECTION_THRESHOLD * dist_D_B) < dist_A_E; + blendResult.x = ((dist_D_B < dist_A_E) && VECTOR_NEQ(E,D) && VECTOR_NEQ(E,B)) ? ((dominantGradient) ? BLEND_DOMINANT : BLEND_NORMAL) : BLEND_NONE; + } + + float4 res = E; + float resW = Ew; + + // Pixel Tap Mapping: -|-|-|-|- + // -|-|B|C|- + // -|D|E|F|x + // -|G|H|I|x + // -|-|x|x|- + if(blendResult.z != BLEND_NONE) + { + float dist_F_G = DistYCbCr(F, G); + float dist_H_C = DistYCbCr(H, C); + bool doLineBlend = (blendResult.z == BLEND_DOMINANT || + !((blendResult.y != BLEND_NONE && !IsPixEqual(E, G)) || (blendResult.w != BLEND_NONE && !IsPixEqual(E, C)) || + (IsPixEqual(G, H) && IsPixEqual(H, I) && IsPixEqual(I, F) && IsPixEqual(F, C) && !IsPixEqual(E, I)))); + + float2 origin = float2(0.0, 1.0 / sqrt(2.0)); + float2 direction = float2(1.0, -1.0); + if(doLineBlend) + { + bool haveShallowLine = (STEEP_DIRECTION_THRESHOLD * dist_F_G <= dist_H_C) && VECTOR_NEQ(E,G) && VECTOR_NEQ(D,G); + bool haveSteepLine = (STEEP_DIRECTION_THRESHOLD * dist_H_C <= dist_F_G) && VECTOR_NEQ(E,C) && VECTOR_NEQ(B,C); + origin = haveShallowLine? float2(0.0, 0.25) : float2(0.0, 0.5); + direction.x += haveShallowLine? 1.0: 0.0; + direction.y -= haveSteepLine? 1.0: 0.0; + } + + float4 blendPix = lerp(H,F, step(DistYCbCr(E, F), DistYCbCr(E, H))); + float blendW = lerp(Hw,Fw, step(DistYCbCr(E, F), DistYCbCr(E, H))); + res = lerp(res, blendPix, get_left_ratio(pos, origin, direction, scale)); + resW = lerp(resW, blendW, get_left_ratio(pos, origin, direction, scale)); + } + + // Pixel Tap Mapping: -|-|-|-|- + // -|A|B|-|- + // x|D|E|F|- + // x|G|H|I|- + // -|x|x|-|- + if(blendResult.w != BLEND_NONE) + { + float dist_H_A = DistYCbCr(H, A); + float dist_D_I = DistYCbCr(D, I); + bool doLineBlend = (blendResult.w == BLEND_DOMINANT || + !((blendResult.z != BLEND_NONE && !IsPixEqual(E, A)) || (blendResult.x != BLEND_NONE && !IsPixEqual(E, I)) || + (IsPixEqual(A, D) && IsPixEqual(D, G) && IsPixEqual(G, H) && IsPixEqual(H, I) && !IsPixEqual(E, G)))); + + float2 origin = float2(-1.0 / sqrt(2.0), 0.0); + float2 direction = float2(1.0, 1.0); + if(doLineBlend) + { + bool haveShallowLine = (STEEP_DIRECTION_THRESHOLD * dist_H_A <= dist_D_I) && VECTOR_NEQ(E,A) && VECTOR_NEQ(B,A); + bool haveSteepLine = (STEEP_DIRECTION_THRESHOLD * dist_D_I <= dist_H_A) && VECTOR_NEQ(E,I) && VECTOR_NEQ(F,I); + origin = haveShallowLine? float2(-0.25, 0.0) : float2(-0.5, 0.0); + direction.y += haveShallowLine? 1.0: 0.0; + direction.x += haveSteepLine? 1.0: 0.0; + } + origin = origin; + direction = direction; + + float4 blendPix = lerp(H,D, step(DistYCbCr(E, D), DistYCbCr(E, H))); + float blendW = lerp(Hw,Dw, step(DistYCbCr(E, D), DistYCbCr(E, H))); + res = lerp(res, blendPix, get_left_ratio(pos, origin, direction, scale)); + resW = lerp(resW, blendW, get_left_ratio(pos, origin, direction, scale)); + } + + // Pixel Tap Mapping: -|-|x|x|- + // -|A|B|C|x + // -|D|E|F|x + // -|-|H|I|- + // -|-|-|-|- + if(blendResult.y != BLEND_NONE) + { + float dist_B_I = DistYCbCr(B, I); + float dist_F_A = DistYCbCr(F, A); + bool doLineBlend = (blendResult.y == BLEND_DOMINANT || + !((blendResult.x != BLEND_NONE && !IsPixEqual(E, I)) || (blendResult.z != BLEND_NONE && !IsPixEqual(E, A)) || + (IsPixEqual(I, F) && IsPixEqual(F, C) && IsPixEqual(C, B) && IsPixEqual(B, A) && !IsPixEqual(E, C)))); + + float2 origin = float2(1.0 / sqrt(2.0), 0.0); + float2 direction = float2(-1.0, -1.0); + + if(doLineBlend) + { + bool haveShallowLine = (STEEP_DIRECTION_THRESHOLD * dist_B_I <= dist_F_A) && VECTOR_NEQ(E,I) && VECTOR_NEQ(H,I); + bool haveSteepLine = (STEEP_DIRECTION_THRESHOLD * dist_F_A <= dist_B_I) && VECTOR_NEQ(E,A) && VECTOR_NEQ(D,A); + origin = haveShallowLine? float2(0.25, 0.0) : float2(0.5, 0.0); + direction.y -= haveShallowLine? 1.0: 0.0; + direction.x -= haveSteepLine? 1.0: 0.0; + } + + float4 blendPix = lerp(F,B, step(DistYCbCr(E, B), DistYCbCr(E, F))); + float blendW = lerp(Fw,Bw, step(DistYCbCr(E, B), DistYCbCr(E, F))); + res = lerp(res, blendPix, get_left_ratio(pos, origin, direction, scale)); + resW = lerp(resW, blendW, get_left_ratio(pos, origin, direction, scale)); + } + + // Pixel Tap Mapping: -|x|x|-|- + // x|A|B|C|- + // x|D|E|F|- + // -|G|H|-|- + // -|-|-|-|- + if(blendResult.x != BLEND_NONE) + { + float dist_D_C = DistYCbCr(D, C); + float dist_B_G = DistYCbCr(B, G); + bool doLineBlend = (blendResult.x == BLEND_DOMINANT || + !((blendResult.w != BLEND_NONE && !IsPixEqual(E, C)) || (blendResult.y != BLEND_NONE && !IsPixEqual(E, G)) || + (IsPixEqual(C, B) && IsPixEqual(B, A) && IsPixEqual(A, D) && IsPixEqual(D, G) && !IsPixEqual(E, A)))); + + float2 origin = float2(0.0, -1.0 / sqrt(2.0)); + float2 direction = float2(-1.0, 1.0); + if(doLineBlend) + { + bool haveShallowLine = (STEEP_DIRECTION_THRESHOLD * dist_D_C <= dist_B_G) && VECTOR_NEQ(E,C) && VECTOR_NEQ(F,C); + bool haveSteepLine = (STEEP_DIRECTION_THRESHOLD * dist_B_G <= dist_D_C) && VECTOR_NEQ(E,G) && VECTOR_NEQ(H,G); + origin = haveShallowLine? float2(0.0, -0.25) : float2(0.0, -0.5); + direction.x -= haveShallowLine? 1.0: 0.0; + direction.y += haveSteepLine? 1.0: 0.0; + } + + float4 blendPix = lerp(D,B, step(DistYCbCr(E, B), DistYCbCr(E, D))); + float blendW = lerp(Dw,Bw, step(DistYCbCr(E, B), DistYCbCr(E, D))); + res = lerp(res, blendPix, get_left_ratio(pos, origin, direction, scale)); + resW = lerp(resW, blendW, get_left_ratio(pos, origin, direction, scale)); + } + + ialpha = res.w; + texcol = float4(res.xyz, resW); + + // Compensate for partially transparent sampling. + if (ialpha > 0.0) + texcol.rgb /= float3(ialpha, ialpha, ialpha); +} + +#undef P + +)"; + } +} + std::string GPU_HW_ShaderGen::GenerateBatchFragmentShader(GPU_HW::BatchRenderMode transparency, GPU::TextureMode texture_mode, bool dithering, bool interlacing) @@ -588,7 +1062,7 @@ std::string GPU_HW_ShaderGen::GenerateBatchFragmentShader(GPU_HW::BatchRenderMod const bool use_dual_source = m_supports_dual_source_blend && ((transparency != GPU_HW::BatchRenderMode::TransparencyDisabled && transparency != GPU_HW::BatchRenderMode::OnlyOpaque) || - m_texture_filering); + m_texture_filter != GPUTextureFilter::Nearest); std::stringstream ss; WriteHeader(ss); @@ -606,7 +1080,7 @@ std::string GPU_HW_ShaderGen::GenerateBatchFragmentShader(GPU_HW::BatchRenderMod DefineMacro(ss, "DITHERING_SCALED", m_scaled_dithering); DefineMacro(ss, "INTERLACING", interlacing); DefineMacro(ss, "TRUE_COLOR", m_true_color); - DefineMacro(ss, "TEXTURE_FILTERING", m_texture_filering); + DefineMacro(ss, "TEXTURE_FILTERING", m_texture_filter != GPUTextureFilter::Nearest); DefineMacro(ss, "UV_LIMITS", m_uv_limits); DefineMacro(ss, "USE_DUAL_SOURCE", use_dual_source); @@ -708,43 +1182,14 @@ float4 SampleFromVRAM(uint4 texpage, float2 coords) #endif } -void BilinearSampleFromVRAM(uint4 texpage, float2 coords, float4 uv_limits, - out float4 texcol, out float ialpha) -{ - // Compute the coordinates of the four texels we will be interpolating between. - // Clamp this to the triangle texture coordinates. - float2 texel_top_left = frac(coords) - float2(0.5, 0.5); - float2 texel_offset = sign(texel_top_left); - float4 fcoords = max(coords.xyxy + float4(0.0, 0.0, texel_offset.x, texel_offset.y), - float4(0.0, 0.0, 0.0, 0.0)); - - // Load four texels. - float4 s00 = SampleFromVRAM(texpage, clamp(fcoords.xy, uv_limits.xy, uv_limits.zw)); - float4 s10 = SampleFromVRAM(texpage, clamp(fcoords.zy, uv_limits.xy, uv_limits.zw)); - float4 s01 = SampleFromVRAM(texpage, clamp(fcoords.xw, uv_limits.xy, uv_limits.zw)); - float4 s11 = SampleFromVRAM(texpage, clamp(fcoords.zw, uv_limits.xy, uv_limits.zw)); - - // Compute alpha from how many texels aren't pixel color 0000h. - float a00 = float(VECTOR_NEQ(s00, TRANSPARENT_PIXEL_COLOR)); - float a10 = float(VECTOR_NEQ(s10, TRANSPARENT_PIXEL_COLOR)); - float a01 = float(VECTOR_NEQ(s01, TRANSPARENT_PIXEL_COLOR)); - float a11 = float(VECTOR_NEQ(s11, TRANSPARENT_PIXEL_COLOR)); - - // Bilinearly interpolate. - float2 weights = abs(texel_top_left); - texcol = lerp(lerp(s00, s10, weights.x), lerp(s01, s11, weights.x), weights.y); - ialpha = lerp(lerp(a00, a10, weights.x), lerp(a01, a11, weights.x), weights.y); - - // Compensate for partially transparent sampling. - if (ialpha > 0.0) - texcol.rgb /= float3(ialpha, ialpha, ialpha); -} - #endif )"; if (textured) { + if (m_texture_filter != GPUTextureFilter::Nearest) + WriteBatchTextureFilter(ss, m_texture_filter); + if (m_uv_limits) { DeclareFragmentEntryPoint(ss, 1, 1, @@ -794,7 +1239,7 @@ void BilinearSampleFromVRAM(uint4 texpage, float2 coords, float4 uv_limits, float4 texcol; #if TEXTURE_FILTERING - BilinearSampleFromVRAM(v_texpage, coords, uv_limits, texcol, ialpha); + FilteredSampleFromVRAM(v_texpage, coords, uv_limits, texcol, ialpha); if (ialpha < 0.5) discard; #else @@ -809,7 +1254,7 @@ void BilinearSampleFromVRAM(uint4 texpage, float2 coords, float4 uv_limits, ialpha = 1.0; #endif - semitransparent = (texcol.a != 0.0); + semitransparent = (texcol.a >= 0.5); // If not using true color, truncate the framebuffer colors to 5-bit. #if !TRUE_COLOR diff --git a/src/core/gpu_hw_shadergen.h b/src/core/gpu_hw_shadergen.h index caeafac9f..c2c399404 100644 --- a/src/core/gpu_hw_shadergen.h +++ b/src/core/gpu_hw_shadergen.h @@ -8,7 +8,7 @@ class GPU_HW_ShaderGen { public: GPU_HW_ShaderGen(HostDisplay::RenderAPI render_api, u32 resolution_scale, bool true_color, bool scaled_dithering, - bool texture_filtering, bool uv_limits, bool supports_dual_source_blend); + GPUTextureFilter texture_filtering, bool uv_limits, bool supports_dual_source_blend); ~GPU_HW_ShaderGen(); static bool UseGLSLBindingLayout(); @@ -45,12 +45,13 @@ private: void WriteCommonFunctions(std::stringstream& ss); void WriteBatchUniformBuffer(std::stringstream& ss); + void WriteBatchTextureFilter(std::stringstream& ss, GPUTextureFilter texture_filter); HostDisplay::RenderAPI m_render_api; u32 m_resolution_scale; bool m_true_color; bool m_scaled_dithering; - bool m_texture_filering; + GPUTextureFilter m_texture_filter; bool m_uv_limits; bool m_glsl; bool m_supports_dual_source_blend; diff --git a/src/core/gpu_hw_vulkan.cpp b/src/core/gpu_hw_vulkan.cpp index a9c44b697..d1c266e96 100644 --- a/src/core/gpu_hw_vulkan.cpp +++ b/src/core/gpu_hw_vulkan.cpp @@ -671,7 +671,7 @@ bool GPU_HW_Vulkan::CompilePipelines() if ((static_cast(transparency_mode) != TransparencyMode::Disabled && (static_cast(render_mode) != BatchRenderMode::TransparencyDisabled && static_cast(render_mode) != BatchRenderMode::OnlyOpaque)) || - m_texture_filtering) + m_texture_filtering != GPUTextureFilter::Nearest) { gpbuilder.SetBlendAttachment( 0, true, VK_BLEND_FACTOR_ONE, diff --git a/src/core/host_interface.cpp b/src/core/host_interface.cpp index e421ed42e..03cbc51aa 100644 --- a/src/core/host_interface.cpp +++ b/src/core/host_interface.cpp @@ -372,7 +372,7 @@ void HostInterface::SetDefaultSettings(SettingsInterface& si) si.SetBoolValue("GPU", "UseDebugDevice", false); si.SetBoolValue("GPU", "TrueColor", false); si.SetBoolValue("GPU", "ScaledDithering", true); - si.SetBoolValue("GPU", "TextureFiltering", false); + si.SetStringValue("GPU", "TextureFilter", Settings::GetTextureFilterName(Settings::DEFAULT_GPU_TEXTURE_FILTER)); si.SetBoolValue("GPU", "DisableInterlacing", false); si.SetBoolValue("GPU", "ForceNTSCTimings", false); si.SetBoolValue("GPU", "WidescreenHack", false); @@ -543,7 +543,7 @@ void HostInterface::CheckForSettingsChanges(const Settings& old_settings) g_settings.gpu_max_run_ahead != old_settings.gpu_max_run_ahead || g_settings.gpu_true_color != old_settings.gpu_true_color || g_settings.gpu_scaled_dithering != old_settings.gpu_scaled_dithering || - g_settings.gpu_texture_filtering != old_settings.gpu_texture_filtering || + g_settings.gpu_texture_filter != old_settings.gpu_texture_filter || g_settings.gpu_disable_interlacing != old_settings.gpu_disable_interlacing || g_settings.gpu_force_ntsc_timings != old_settings.gpu_force_ntsc_timings || g_settings.display_crop_mode != old_settings.display_crop_mode || diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 045a7a221..08f8517fb 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -102,7 +102,10 @@ void Settings::Load(SettingsInterface& si) gpu_use_debug_device = si.GetBoolValue("GPU", "UseDebugDevice", false); gpu_true_color = si.GetBoolValue("GPU", "TrueColor", true); gpu_scaled_dithering = si.GetBoolValue("GPU", "ScaledDithering", false); - gpu_texture_filtering = si.GetBoolValue("GPU", "TextureFiltering", false); + gpu_texture_filter = + ParseTextureFilterName( + si.GetStringValue("GPU", "TextureFilter", GetTextureFilterName(DEFAULT_GPU_TEXTURE_FILTER)).c_str()) + .value_or(DEFAULT_GPU_TEXTURE_FILTER); gpu_disable_interlacing = si.GetBoolValue("GPU", "DisableInterlacing", false); gpu_force_ntsc_timings = si.GetBoolValue("GPU", "ForceNTSCTimings", false); gpu_widescreen_hack = si.GetBoolValue("GPU", "WidescreenHack", false); @@ -217,7 +220,7 @@ void Settings::Save(SettingsInterface& si) const si.SetBoolValue("GPU", "UseDebugDevice", gpu_use_debug_device); si.SetBoolValue("GPU", "TrueColor", gpu_true_color); si.SetBoolValue("GPU", "ScaledDithering", gpu_scaled_dithering); - si.SetBoolValue("GPU", "TextureFiltering", gpu_texture_filtering); + si.SetStringValue("GPU", "TextureFilter", GetTextureFilterName(gpu_texture_filter)); si.SetBoolValue("GPU", "DisableInterlacing", gpu_disable_interlacing); si.SetBoolValue("GPU", "ForceNTSCTimings", gpu_force_ntsc_timings); si.SetBoolValue("GPU", "WidescreenHack", gpu_widescreen_hack); @@ -449,6 +452,35 @@ const char* Settings::GetRendererDisplayName(GPURenderer renderer) return s_gpu_renderer_display_names[static_cast(renderer)]; } +static constexpr auto s_texture_filter_names = make_array("Nearest", "Bilinear", "JINC2", "xBRZ"); +static constexpr auto s_texture_filter_display_names = + make_array(TRANSLATABLE("GPUTextureFilter", "Nearest-Neighbor"), TRANSLATABLE("GPUTextureFilter", "Bilinear"), + TRANSLATABLE("GPUTextureFilter", "JINC2"), TRANSLATABLE("GPUTextureFilter", "xBRZ")); + +std::optional Settings::ParseTextureFilterName(const char* str) +{ + int index = 0; + for (const char* name : s_texture_filter_names) + { + if (StringUtil::Strcasecmp(name, str) == 0) + return static_cast(index); + + index++; + } + + return std::nullopt; +} + +const char* Settings::GetTextureFilterName(GPUTextureFilter filter) +{ + return s_texture_filter_names[static_cast(filter)]; +} + +const char* Settings::GetTextureFilterDisplayName(GPUTextureFilter filter) +{ + return s_texture_filter_display_names[static_cast(filter)]; +} + static std::array s_display_crop_mode_names = {{"None", "Overscan", "Borders"}}; static std::array s_display_crop_mode_display_names = { {TRANSLATABLE("DisplayCropMode", "None"), TRANSLATABLE("DisplayCropMode", "Only Overscan Area"), diff --git a/src/core/settings.h b/src/core/settings.h index 26cc88491..aa5f63f9b 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -88,7 +88,7 @@ struct Settings bool gpu_use_debug_device = false; bool gpu_true_color = true; bool gpu_scaled_dithering = false; - bool gpu_texture_filtering = false; + GPUTextureFilter gpu_texture_filter = GPUTextureFilter::Nearest; bool gpu_disable_interlacing = false; bool gpu_force_ntsc_timings = false; bool gpu_widescreen_hack = false; @@ -201,6 +201,10 @@ struct Settings static const char* GetRendererName(GPURenderer renderer); static const char* GetRendererDisplayName(GPURenderer renderer); + static std::optional ParseTextureFilterName(const char* str); + static const char* GetTextureFilterName(GPUTextureFilter filter); + static const char* GetTextureFilterDisplayName(GPUTextureFilter filter); + static std::optional ParseDisplayCropMode(const char* str); static const char* GetDisplayCropModeName(DisplayCropMode crop_mode); static const char* GetDisplayCropModeDisplayName(DisplayCropMode crop_mode); @@ -227,6 +231,7 @@ struct Settings #else static constexpr GPURenderer DEFAULT_GPU_RENDERER = GPURenderer::HardwareOpenGL; #endif + static constexpr GPUTextureFilter DEFAULT_GPU_TEXTURE_FILTER = GPUTextureFilter::Nearest; static constexpr ConsoleRegion DEFAULT_CONSOLE_REGION = ConsoleRegion::Auto; static constexpr CPUExecutionMode DEFAULT_CPU_EXECUTION_MODE = CPUExecutionMode::Recompiler; static constexpr AudioBackend DEFAULT_AUDIO_BACKEND = AudioBackend::Cubeb; diff --git a/src/core/types.h b/src/core/types.h index 46482d951..5b1c3c9e8 100644 --- a/src/core/types.h +++ b/src/core/types.h @@ -66,6 +66,15 @@ enum class GPURenderer : u8 Count }; +enum class GPUTextureFilter : u8 +{ + Nearest, + Bilinear, + JINC2, + xBRZ, + Count +}; + enum class DisplayCropMode : u8 { None, diff --git a/src/duckstation-libretro/libretro_host_interface.cpp b/src/duckstation-libretro/libretro_host_interface.cpp index a5031841a..b8a0ae0ed 100644 --- a/src/duckstation-libretro/libretro_host_interface.cpp +++ b/src/duckstation-libretro/libretro_host_interface.cpp @@ -471,12 +471,12 @@ static std::array s_option_definitions = {{ "others will break.", {{"true", "Enabled"}, {"false", "Disabled"}}, "false"}, - {"duckstation_GPU.TextureFiltering", - "Bilinear Texture Filtering", + {"duckstation_GPU.TextureFilter", + "Texture Filtering", "Smooths out the blockyness of magnified textures on 3D object by using bilinear filtering. Will have a " "greater effect on higher resolution scales. Only applies to the hardware renderers.", - {{"true", "Enabled"}, {"false", "Disabled"}}, - "false"}, + {{"Nearest", "Nearest-Neighbor"}, {"Bilinear", "Bilinear"}, {"JINC2", "JINC2"}, {"xBRZ", "xBRZ"}}, + "Nearest"}, {"duckstation_GPU.WidescreenHack", "Widescreen Hack", "Increases the field of view from 4:3 to 16:9 in 3D games. For 2D games, or games which use pre-rendered " diff --git a/src/duckstation-qt/enhancementsettingswidget.cpp b/src/duckstation-qt/enhancementsettingswidget.cpp index c00cbf6aa..cdaefc717 100644 --- a/src/duckstation-qt/enhancementsettingswidget.cpp +++ b/src/duckstation-qt/enhancementsettingswidget.cpp @@ -5,7 +5,8 @@ #include "settingsdialog.h" #include "settingwidgetbinder.h" -EnhancementSettingsWidget::EnhancementSettingsWidget(QtHostInterface* host_interface, QWidget* parent, SettingsDialog* dialog) +EnhancementSettingsWidget::EnhancementSettingsWidget(QtHostInterface* host_interface, QWidget* parent, + SettingsDialog* dialog) : QWidget(parent), m_host_interface(host_interface) { m_ui.setupUi(this); @@ -16,8 +17,9 @@ EnhancementSettingsWidget::EnhancementSettingsWidget(QtHostInterface* host_inter SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.scaledDithering, "GPU", "ScaledDithering"); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.disableInterlacing, "GPU", "DisableInterlacing"); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.forceNTSCTimings, "GPU", "ForceNTSCTimings"); - SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.linearTextureFiltering, "GPU", - "TextureFiltering"); + SettingWidgetBinder::BindWidgetToEnumSetting( + m_host_interface, m_ui.textureFiltering, "GPU", "TextureFilter", &Settings::ParseTextureFilterName, + &Settings::GetTextureFilterDisplayName, Settings::DEFAULT_GPU_TEXTURE_FILTER); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.widescreenHack, "GPU", "WidescreenHack"); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.pgxpEnable, "GPU", "PGXPEnable", false); @@ -62,7 +64,7 @@ EnhancementSettingsWidget::EnhancementSettingsWidget(QtHostInterface* host_inter "approximately 17% faster.
For variable " "frame rate games, it may not affect the speed.")); dialog->registerWidgetHelp( - m_ui.linearTextureFiltering, tr("Bilinear Texture Filtering"), tr("Unchecked"), + m_ui.textureFiltering, tr("Texture Filtering"), tr("Unchecked"), tr("Smooths out the blockyness of magnified textures on 3D object by using bilinear filtering.
Will have a " "greater effect on higher resolution scales. Only applies to the hardware renderers.")); dialog->registerWidgetHelp( @@ -96,6 +98,12 @@ void EnhancementSettingsWidget::updateScaledDitheringEnabled() void EnhancementSettingsWidget::setupAdditionalUi() { QtUtils::FillComboBoxWithResolutionScales(m_ui.resolutionScale); + + for (u32 i = 0; i < static_cast(GPUTextureFilter::Count); i++) + { + m_ui.textureFiltering->addItem( + qApp->translate("GPUTextureFilter", Settings::GetTextureFilterDisplayName(static_cast(i)))); + } } void EnhancementSettingsWidget::updatePGXPSettingsEnabled() diff --git a/src/duckstation-qt/enhancementsettingswidget.ui b/src/duckstation-qt/enhancementsettingswidget.ui index c3d556433..02b04fa8f 100644 --- a/src/duckstation-qt/enhancementsettingswidget.ui +++ b/src/duckstation-qt/enhancementsettingswidget.ui @@ -42,24 +42,34 @@ - + + + + Texture Filtering: + + + + + + + True Color Rendering (24-bit, disables dithering) - + Scaled Dithering (scale dither pattern to resolution) - - + + - Bilinear Texture Filtering + Widescreen Hack (render 3D in 16:9) @@ -86,13 +96,6 @@ - - - - Widescreen Hack - - - diff --git a/src/duckstation-qt/gamepropertiesdialog.cpp b/src/duckstation-qt/gamepropertiesdialog.cpp index b595ac76a..7194d51e2 100644 --- a/src/duckstation-qt/gamepropertiesdialog.cpp +++ b/src/duckstation-qt/gamepropertiesdialog.cpp @@ -283,9 +283,19 @@ void GamePropertiesDialog::populateGameSettings() m_ui.userResolutionScale->setCurrentIndex(0); } + if (gs.gpu_texture_filter.has_value()) + { + QSignalBlocker sb(m_ui.userTextureFiltering); + m_ui.userTextureFiltering->setCurrentIndex(static_cast(gs.gpu_texture_filter.value()) + 1); + } + else + { + QSignalBlocker sb(m_ui.userResolutionScale); + m_ui.userTextureFiltering->setCurrentIndex(0); + } + populateBooleanUserSetting(m_ui.userTrueColor, gs.gpu_true_color); populateBooleanUserSetting(m_ui.userScaledDithering, gs.gpu_scaled_dithering); - populateBooleanUserSetting(m_ui.userBilinearTextureFiltering, gs.gpu_bilinear_texture_filtering); populateBooleanUserSetting(m_ui.userForceNTSCTimings, gs.gpu_force_ntsc_timings); populateBooleanUserSetting(m_ui.userWidescreenHack, gs.gpu_widescreen_hack); populateBooleanUserSetting(m_ui.userPGXP, gs.gpu_pgxp); @@ -388,10 +398,17 @@ void GamePropertiesDialog::connectUi() saveGameSettings(); }); + connect(m_ui.userTextureFiltering, QOverload::of(&QComboBox::currentIndexChanged), [this](int index) { + if (index <= 0) + m_game_settings.gpu_texture_filter.reset(); + else + m_game_settings.gpu_texture_filter = static_cast(index - 1); + saveGameSettings(); + }); + connectBooleanUserSetting(m_ui.userTrueColor, &m_game_settings.gpu_true_color); connectBooleanUserSetting(m_ui.userScaledDithering, &m_game_settings.gpu_scaled_dithering); connectBooleanUserSetting(m_ui.userForceNTSCTimings, &m_game_settings.gpu_force_ntsc_timings); - connectBooleanUserSetting(m_ui.userBilinearTextureFiltering, &m_game_settings.gpu_bilinear_texture_filtering); connectBooleanUserSetting(m_ui.userWidescreenHack, &m_game_settings.gpu_widescreen_hack); connectBooleanUserSetting(m_ui.userPGXP, &m_game_settings.gpu_pgxp); diff --git a/src/duckstation-qt/gamepropertiesdialog.ui b/src/duckstation-qt/gamepropertiesdialog.ui index e6af3e8bf..b98563e59 100644 --- a/src/duckstation-qt/gamepropertiesdialog.ui +++ b/src/duckstation-qt/gamepropertiesdialog.ui @@ -252,6 +252,16 @@ + + + + Texture Filtering: + + + + + + @@ -274,7 +284,7 @@ - + Widescreen Hack @@ -284,7 +294,7 @@ - + Force NTSC Timings (60hz-on-PAL) @@ -294,17 +304,7 @@ - - - - Bilinear Texture Filtering - - - true - - - - + PGXP Geometry Correction diff --git a/src/duckstation-sdl/sdl_host_interface.cpp b/src/duckstation-sdl/sdl_host_interface.cpp index c05269a55..2a05fe629 100644 --- a/src/duckstation-sdl/sdl_host_interface.cpp +++ b/src/duckstation-sdl/sdl_host_interface.cpp @@ -935,7 +935,23 @@ void SDLHostInterface::DrawQuickSettingsMenu() settings_changed |= ImGui::MenuItem("True (24-Bit) Color", nullptr, &m_settings_copy.gpu_true_color); settings_changed |= ImGui::MenuItem("Scaled Dithering", nullptr, &m_settings_copy.gpu_scaled_dithering); - settings_changed |= ImGui::MenuItem("Texture Filtering", nullptr, &m_settings_copy.gpu_texture_filtering); + + if (ImGui::BeginMenu("Texture Filtering")) + { + const GPUTextureFilter current = m_settings_copy.gpu_texture_filter; + for (u32 i = 0; i < static_cast(GPUTextureFilter::Count); i++) + { + if (ImGui::MenuItem(Settings::GetTextureFilterDisplayName(static_cast(i)), nullptr, + i == static_cast(current))) + { + m_settings_copy.gpu_texture_filter = static_cast(i); + settings_changed = true; + } + } + + ImGui::EndMenu(); + } + settings_changed |= ImGui::MenuItem("Disable Interlacing", nullptr, &m_settings_copy.gpu_disable_interlacing); settings_changed |= ImGui::MenuItem("Widescreen Hack", nullptr, &m_settings_copy.gpu_widescreen_hack); settings_changed |= ImGui::MenuItem("Display Linear Filtering", nullptr, &m_settings_copy.display_linear_filtering); @@ -1401,8 +1417,22 @@ void SDLHostInterface::DrawSettingsWindow() settings_changed = true; } + ImGui::Text("Texture Filtering:"); + ImGui::SameLine(indent); + int gpu_texture_filter = static_cast(m_settings_copy.gpu_texture_filter); + if (ImGui::Combo( + "##gpu_texture_filter", &gpu_texture_filter, + [](void*, int index, const char** out_text) { + *out_text = Settings::GetTextureFilterDisplayName(static_cast(index)); + return true; + }, + nullptr, static_cast(GPUTextureFilter::Count))) + { + m_settings_copy.gpu_texture_filter = static_cast(gpu_texture_filter); + settings_changed = true; + } + settings_changed |= ImGui::Checkbox("True 24-bit Color (disables dithering)", &m_settings_copy.gpu_true_color); - settings_changed |= ImGui::Checkbox("Texture Filtering", &m_settings_copy.gpu_texture_filtering); settings_changed |= ImGui::Checkbox("Disable Interlacing", &m_settings_copy.gpu_disable_interlacing); settings_changed |= ImGui::Checkbox("Force NTSC Timings", &m_settings_copy.gpu_force_ntsc_timings); settings_changed |= ImGui::Checkbox("Widescreen Hack", &m_settings_copy.gpu_widescreen_hack); diff --git a/src/frontend-common/game_settings.cpp b/src/frontend-common/game_settings.cpp index 4e655de20..46ced82ba 100644 --- a/src/frontend-common/game_settings.cpp +++ b/src/frontend-common/game_settings.cpp @@ -116,7 +116,7 @@ bool Entry::LoadFromStream(ByteStream* stream) !ReadOptionalFromStream(stream, &gpu_resolution_scale) || !ReadOptionalFromStream(stream, &gpu_true_color) || !ReadOptionalFromStream(stream, &gpu_scaled_dithering) || !ReadOptionalFromStream(stream, &gpu_force_ntsc_timings) || - !ReadOptionalFromStream(stream, &gpu_bilinear_texture_filtering) || + !ReadOptionalFromStream(stream, &gpu_texture_filter) || !ReadOptionalFromStream(stream, &gpu_widescreen_hack) || !ReadOptionalFromStream(stream, &gpu_pgxp) || !ReadOptionalFromStream(stream, &controller_1_type) || !ReadOptionalFromStream(stream, &controller_2_type) || !ReadOptionalFromStream(stream, &memory_card_1_type) || !ReadOptionalFromStream(stream, &memory_card_2_type) || @@ -154,7 +154,7 @@ bool Entry::SaveToStream(ByteStream* stream) const WriteOptionalToStream(stream, display_aspect_ratio) && WriteOptionalToStream(stream, gpu_resolution_scale) && WriteOptionalToStream(stream, gpu_true_color) && WriteOptionalToStream(stream, gpu_scaled_dithering) && WriteOptionalToStream(stream, gpu_force_ntsc_timings) && - WriteOptionalToStream(stream, gpu_bilinear_texture_filtering) && + WriteOptionalToStream(stream, gpu_texture_filter) && WriteOptionalToStream(stream, gpu_widescreen_hack) && WriteOptionalToStream(stream, gpu_pgxp) && WriteOptionalToStream(stream, controller_1_type) && WriteOptionalToStream(stream, controller_2_type) && WriteOptionalToStream(stream, memory_card_1_type) && WriteOptionalToStream(stream, memory_card_2_type) && @@ -201,7 +201,7 @@ static void ParseIniSection(Entry* entry, const char* section, const CSimpleIniA entry->gpu_scaled_dithering = StringUtil::FromChars(cvalue); cvalue = ini.GetValue(section, "GPUBilinearTextureFiltering", nullptr); if (cvalue) - entry->gpu_bilinear_texture_filtering = StringUtil::FromChars(cvalue); + entry->gpu_texture_filter = Settings::ParseTextureFilterName(cvalue); cvalue = ini.GetValue(section, "GPUForceNTSCTimings", nullptr); if (cvalue) entry->gpu_force_ntsc_timings = StringUtil::FromChars(cvalue); @@ -265,11 +265,8 @@ static void StoreIniSection(const Entry& entry, const char* section, CSimpleIniA ini.SetValue(section, "GPUTrueColor", entry.gpu_true_color.value() ? "true" : "false"); if (entry.gpu_scaled_dithering.has_value()) ini.SetValue(section, "GPUScaledDithering", entry.gpu_scaled_dithering.value() ? "true" : "false"); - if (entry.gpu_bilinear_texture_filtering.has_value()) - { - ini.SetValue(section, "GPUBilinearTextureFiltering", - entry.gpu_bilinear_texture_filtering.value() ? "true" : "false"); - } + if (entry.gpu_texture_filter.has_value()) + ini.SetValue(section, "GPUTextureFilter", Settings::GetTextureFilterName(entry.gpu_texture_filter.value())); if (entry.gpu_force_ntsc_timings.has_value()) ini.SetValue(section, "GPUForceNTSCTimings", entry.gpu_force_ntsc_timings.value() ? "true" : "false"); if (entry.gpu_widescreen_hack.has_value()) @@ -417,8 +414,8 @@ void Entry::ApplySettings(bool display_osd_messages) const g_settings.gpu_scaled_dithering = gpu_scaled_dithering.value(); if (gpu_force_ntsc_timings.has_value()) g_settings.gpu_force_ntsc_timings = gpu_force_ntsc_timings.value(); - if (gpu_bilinear_texture_filtering) - g_settings.gpu_texture_filtering = gpu_bilinear_texture_filtering.value(); + if (gpu_texture_filter.has_value()) + g_settings.gpu_texture_filter = gpu_texture_filter.value(); if (gpu_widescreen_hack.has_value()) g_settings.gpu_widescreen_hack = gpu_widescreen_hack.value(); if (gpu_pgxp.has_value()) diff --git a/src/frontend-common/game_settings.h b/src/frontend-common/game_settings.h index 3f67eb08c..9e466d743 100644 --- a/src/frontend-common/game_settings.h +++ b/src/frontend-common/game_settings.h @@ -47,7 +47,7 @@ struct Entry std::optional gpu_true_color; std::optional gpu_scaled_dithering; std::optional gpu_force_ntsc_timings; - std::optional gpu_bilinear_texture_filtering; + std::optional gpu_texture_filter; std::optional gpu_widescreen_hack; std::optional gpu_pgxp; std::optional controller_1_type;