diff --git a/src/video_core/host_shaders/present_lanczos.frag b/src/video_core/host_shaders/present_lanczos.frag index b832af312..f5ce531f9 100644 --- a/src/video_core/host_shaders/present_lanczos.frag +++ b/src/video_core/host_shaders/present_lanczos.frag @@ -8,77 +8,93 @@ layout(location = 0) out vec4 color; layout(binding = 0) uniform sampler2D color_texture; -// This block defines the variable that will hold our setting value. -// It uses a preprocessor directive to switch between the OpenGL and Vulkan way of doing things. #ifdef VULKAN layout(push_constant) uniform LanczosPushConstant { - layout(offset = 128) int u_lanczos_a; + layout(offset = 128) int u_lanczos_a; // The 'a' parameter, recommend 2 or 3. } lanczos_pc; #else // OpenGL -layout(location = 0) uniform int u_lanczos_a; +layout(location = 0) uniform int u_lanczos_a; // The 'a' parameter, recommend 2 or 3. #endif -const float PI = 3.14159265359; +const float PI = 3.141592653589793; +// --- Helper Functions for Advanced Quality --- +vec3 to_linear(vec3 srgb_color) { return pow(srgb_color, vec3(2.2)); } +vec3 to_srgb(vec3 linear_color) { return pow(linear_color, vec3(1.0 / 2.2)); } + +// Sinc function float sinc(float x) { - if (x == 0.0) { - return 1.0; - } - return sin(PI * x) / (PI * x); + if (x == 0.0) return 1.0; + float pix = PI * x; + return sin(pix) / pix; } +// Lanczos windowed sinc function float lanczos_weight(float x, float a) { - if (abs(x) < a) { - return sinc(x) * sinc(x / a); - } + if (abs(x) < a) return sinc(x) * sinc(x / a); return 0.0; } vec4 textureLanczos(sampler2D ts, vec2 tc) { - // Get the 'a' value from the correct uniform based on the renderer #ifdef VULKAN const int a_val = lanczos_pc.u_lanczos_a; #else const int a_val = u_lanczos_a; #endif + + if (a_val < 1) return texture(ts, tc); + const float a = float(a_val); - - // If 'a' is 0 (which it will be by default since we aren't sending a value yet), - // just do a basic sample to avoid errors. - if (a_val == 0) { - return texture(ts, tc); - } - vec2 tex_size = vec2(textureSize(ts, 0)); vec2 inv_tex_size = 1.0 / tex_size; - vec2 p = tc * tex_size; + + vec2 p = tc * tex_size - 0.5; vec2 f = fract(p); vec2 p_int = p - f; vec4 sum = vec4(0.0); float weight_sum = 0.0; - const int MAX_A = 4; - for (int y = -MAX_A + 1; y <= MAX_A; ++y) { - if (abs(y) >= a_val) continue; - for (int x = -MAX_A + 1; x <= MAX_A; ++x) { - if (abs(x) >= a_val) continue; + vec3 min_color_linear = vec3(1.0); + vec3 max_color_linear = vec3(0.0); + // Get the center texel's color to help with the adaptive clamp. + vec3 center_color_linear = to_linear(texture(ts, (p_int + 0.5) * inv_tex_size).rgb); + + for (int y = -a_val + 1; y <= a_val; ++y) { + for (int x = -a_val + 1; x <= a_val; ++x) { vec2 offset = vec2(float(x), float(y)); + float w = lanczos_weight(f.x - offset.x, a) * lanczos_weight(f.y - offset.y, a); if (w != 0.0) { - sum += texture(ts, (p_int + offset) * inv_tex_size) * w; + vec2 sample_coord = (p_int + offset + 0.5) * inv_tex_size; + vec4 srgb_sample = texture(ts, sample_coord); + vec3 linear_sample_rgb = to_linear(srgb_sample.rgb); + + min_color_linear = min(min_color_linear, linear_sample_rgb); + max_color_linear = max(max_color_linear, linear_sample_rgb); + + sum.rgb += linear_sample_rgb * w; + sum.a += srgb_sample.a * w; weight_sum += w; } } } - if (weight_sum == 0.0) { - return texture(ts, tc); - } - return sum / weight_sum; + if (weight_sum == 0.0) return texture(ts, tc); + + vec4 final_color_linear = sum / weight_sum; + + // This is a more effective "Adaptive Anti-Ringing Clamp". + vec3 adaptive_min_bound = (min_color_linear + center_color_linear) * 0.5; + vec3 adaptive_max_bound = (max_color_linear + center_color_linear) * 0.5; + final_color_linear.rgb = clamp(final_color_linear.rgb, adaptive_min_bound, adaptive_max_bound); + + final_color_linear.rgb = to_srgb(final_color_linear.rgb); + + return final_color_linear; } void main() {