diff --git a/border/shaders/average_fill/compose.slang b/border/shaders/average_fill/compose.slang index be2da33..ceddf3f 100644 --- a/border/shaders/average_fill/compose.slang +++ b/border/shaders/average_fill/compose.slang @@ -27,6 +27,7 @@ 3 = Smooth angle-based blending Changelog: + v1.2: Fix scaling bugs. v1.1: Add extension modes from blur fill; Add average gamma adjustment. v1.0: Initial release. */ @@ -116,7 +117,7 @@ vec3 blend_corner(vec3 a, // The first color to blend } void main() { - const vec2 pixel_coord = o2i(vTexCoord) * param.InputSize.xy; + const vec2 pixel_coord = o2i(vTexCoord); if (pixel_coord.x < param.OS_CROP_LEFT) { if (param.EXTEND_H < 0.5) { FragColor = vec4(0.0, 0.0, 0.0, 1.0); @@ -131,8 +132,7 @@ void main() { // Top left corner const vec3 top = textureLod(Top, vec2(0.5), BIG_NUMBER).rgb; const vec2 content_corner = - i2o(vec2(param.OS_CROP_LEFT, param.OS_CROP_TOP) * - param.InputSize.zw); + i2o(vec2(param.OS_CROP_LEFT, param.OS_CROP_TOP)); const vec2 viewport_corner = vec2(0.0, 0.0); FragColor = vec4(blend_corner(left, top, @@ -154,10 +154,8 @@ void main() { } // Bottom left corner const vec3 bottom = textureLod(Bottom, vec2(0.5), BIG_NUMBER).rgb; - const vec2 content_corner = - i2o(vec2(param.OS_CROP_LEFT, - param.InputSize.y - param.OS_CROP_BOTTOM) * - param.InputSize.zw); + const vec2 content_corner = i2o(vec2( + param.OS_CROP_LEFT, param.InputSize.y - param.OS_CROP_BOTTOM)); const vec2 viewport_corner = vec2(0.0, 1.0); FragColor = vec4(blend_corner(left, bottom, @@ -209,10 +207,8 @@ void main() { } // Top right corner const vec3 top = textureLod(Top, vec2(0.5), BIG_NUMBER).rgb; - const vec2 content_corner = - i2o(vec2(param.InputSize.x - param.OS_CROP_RIGHT, - param.OS_CROP_TOP) * - param.InputSize.zw); + const vec2 content_corner = i2o(vec2( + param.InputSize.x - param.OS_CROP_RIGHT, param.OS_CROP_TOP)); const vec2 viewport_corner = vec2(1.0, 0.0); FragColor = vec4(blend_corner(right, top, @@ -236,8 +232,7 @@ void main() { const vec3 bottom = textureLod(Bottom, vec2(0.5), BIG_NUMBER).rgb; const vec2 content_corner = i2o(vec2(param.InputSize.x - param.OS_CROP_RIGHT, - param.InputSize.y - param.OS_CROP_BOTTOM) * - param.InputSize.zw); + param.InputSize.y - param.OS_CROP_BOTTOM)); const vec2 viewport_corner = vec2(1.0, 1.0); FragColor = vec4(blend_corner(right, bottom, diff --git a/border/shaders/average_fill/parameters.slang b/border/shaders/average_fill/parameters.slang index 9dec098..79261e0 100644 --- a/border/shaders/average_fill/parameters.slang +++ b/border/shaders/average_fill/parameters.slang @@ -1,7 +1,7 @@ // See compose.slang for copyright and other information. // clang-format off -#pragma parameter AVERAGE_FILL_SETTINGS "=== Average fill v1.1 settings ===" 0.0 0.0 1.0 1.0 +#pragma parameter AVERAGE_FILL_SETTINGS "=== Average fill v1.2 settings ===" 0.0 0.0 1.0 1.0 #pragma parameter OS_CROP_TOP "Overscan crop top" 8.0 0.0 1024.0 1.0 #pragma parameter OS_CROP_BOTTOM "Overscan crop bottom" 8.0 0.0 1024.0 1.0 #pragma parameter OS_CROP_LEFT "Overscan crop left" 0.0 0.0 1024.0 1.0 diff --git a/border/shaders/blur_fill/compose.slang b/border/shaders/blur_fill/compose.slang index 2737f6c..494e38e 100644 --- a/border/shaders/blur_fill/compose.slang +++ b/border/shaders/blur_fill/compose.slang @@ -27,6 +27,7 @@ strength of the blur. Changelog: + v1.2: Fix scaling bugs. v1.1: Fix bug with glcore driver. v1.0: Initial release. */ @@ -77,7 +78,7 @@ layout(set = 0, binding = 3) uniform sampler2D Tiled; layout(set = 0, binding = 4) uniform sampler2D Blurred; void main() { - const vec2 pixel_coord = o2i(vTexCoord) * param.InputSize.xy; + const vec2 pixel_coord = o2i(vTexCoord); const vec4 input_extrema = vec4(param.OS_CROP_LEFT, param.OS_CROP_TOP, param.InputSize.x - param.OS_CROP_RIGHT, diff --git a/border/shaders/blur_fill/parameters.slang b/border/shaders/blur_fill/parameters.slang index c000d84..e6c9390 100644 --- a/border/shaders/blur_fill/parameters.slang +++ b/border/shaders/blur_fill/parameters.slang @@ -1,7 +1,7 @@ // See compose.slang for copyright and other information. // clang-format off -#pragma parameter BLUR_FILL_SETTINGS "=== Blur fill v1.1 settings ===" 0.0 0.0 1.0 1.0 +#pragma parameter BLUR_FILL_SETTINGS "=== Blur fill v1.2 settings ===" 0.0 0.0 1.0 1.0 #pragma parameter OS_CROP_TOP "Overscan crop top" 0.0 0.0 1024.0 1.0 #pragma parameter OS_CROP_BOTTOM "Overscan crop bottom" 0.0 0.0 1024.0 1.0 #pragma parameter OS_CROP_LEFT "Overscan crop left" 0.0 0.0 1024.0 1.0 diff --git a/border/shaders/blur_fill/render_sampling_areas.slang b/border/shaders/blur_fill/render_sampling_areas.slang index 72cc3d4..6717116 100644 --- a/border/shaders/blur_fill/render_sampling_areas.slang +++ b/border/shaders/blur_fill/render_sampling_areas.slang @@ -216,7 +216,7 @@ vec3 sample_mirrored_frame(sampler2D tex, vec2 coord, vec4 input_extrema) { } void main() { - const vec2 pixel_coord = o2i(vTexCoord) * param.InputSize.xy; + const vec2 pixel_coord = o2i(vTexCoord); FragColor = vec4( sample_mirrored_frame(Input, pixel_coord, vec4(param.OS_CROP_LEFT, param.OS_CROP_TOP, diff --git a/border/shaders/blur_fill/scaling.slang b/border/shaders/blur_fill/scaling.slang index f7d8f51..3f13db0 100644 --- a/border/shaders/blur_fill/scaling.slang +++ b/border/shaders/blur_fill/scaling.slang @@ -1,69 +1,73 @@ // See compose.slang for copyright and other information. -// Pixels in input coord. space -vec2 eff_input_res() { - if (param.CENTER_CROP > 0.5) { - return param.InputSize.xy - - vec2(param.OS_CROP_LEFT + param.OS_CROP_RIGHT, - param.OS_CROP_TOP + param.OS_CROP_BOTTOM); - } - return param.InputSize.xy - - 2.0 * vec2(min(param.OS_CROP_LEFT, param.OS_CROP_RIGHT), - min(param.OS_CROP_TOP, param.OS_CROP_BOTTOM)); -} - -// Output to input scaling, in unit coordinate systems. +// Scaling from unit output to pixel input space. vec2 scale_o2i() { - const vec2 eff_input_res = eff_input_res(); - const vec2 eff_aspect = - vec2(eff_input_res.x, param.FORCE_ASPECT_RATIO > 0.5 - ? eff_input_res.y / param.InputSize.y * - param.InputSize.x * param.ASPECT_V / - param.ASPECT_H - : eff_input_res.y); - if (param.FinalViewportSize.x / eff_aspect.x < - param.FinalViewportSize.y / eff_aspect.y) { + // Pixels in input coord. space, after cropping. + const vec2 eff_input_res = + param.InputSize.xy - + (param.CENTER_CROP > 0.5 + ? vec2(param.OS_CROP_LEFT + param.OS_CROP_RIGHT, + param.OS_CROP_TOP + param.OS_CROP_BOTTOM) + : 2 * vec2(min(param.OS_CROP_LEFT, param.OS_CROP_RIGHT), + min(param.OS_CROP_TOP, param.OS_CROP_BOTTOM))); + // Integer aspect ratio after cropping. + // lambda_1 * input_pixels.x, lambda_2 * input_pixels.y, + // possibly corrected for forced aspect ratio + const vec2 eff_aspect = param.FORCE_ASPECT_RATIO < 0.5 + ? eff_input_res + : vec2(eff_input_res.x * param.ASPECT_H, + eff_input_res.y * param.ASPECT_V); + float scale_x, scale_y; + if (param.FinalViewportSize.x * eff_aspect.y < + param.FinalViewportSize.y * eff_aspect.x) { // Scale will be limited by width. Calc x scale, then derive y scale // using aspect ratio. - const float scale_x = - param.FORCE_INTEGER_SCALING > 0.5 - ? floor(param.FinalViewportSize.x / eff_input_res.x) - : param.FinalViewportSize.x / eff_input_res.x; - const float scale_y = scale_x * eff_input_res.x * eff_aspect.y / - (eff_input_res.y * eff_aspect.x); - return param.FinalViewportSize.xy * param.InputSize.zw / - vec2(scale_x, scale_y); + scale_x = param.FinalViewportSize.x / eff_input_res.x; + if (param.FORCE_INTEGER_SCALING > 0.5) { + scale_x = max(1.0, floor(scale_x)); + } + if (param.FORCE_ASPECT_RATIO < 0.5) { + scale_y = scale_x; + } else { + scale_y = scale_x * param.ASPECT_V / param.ASPECT_H; + if (param.FORCE_INTEGER_SCALING > 0.5) { + scale_y = max(1.0, floor(scale_y)); + } + } } else { // Scale will be limited by height. - const float scale_y = - param.FORCE_INTEGER_SCALING > 0.5 - ? floor(param.FinalViewportSize.y / eff_input_res.y) - : param.FinalViewportSize.y / eff_input_res.y; - const float scale_x = scale_y * eff_input_res.y * eff_aspect.x / - (eff_input_res.x * eff_aspect.y); - return param.FinalViewportSize.xy * param.InputSize.zw / - vec2(scale_x, scale_y); + scale_y = param.FinalViewportSize.y / eff_input_res.y; + if (param.FORCE_INTEGER_SCALING > 0.5) { + scale_y = max(1.0, floor(scale_y)); + } + if (param.FORCE_ASPECT_RATIO < 0.5) { + scale_x = scale_y; + } else { + scale_x = scale_y * param.ASPECT_H / param.ASPECT_V; + if (param.FORCE_INTEGER_SCALING > 0.5) { + scale_x = max(1.0, floor(scale_x)); + } + } } + return param.FinalViewportSize.xy / vec2(scale_x, scale_y); } -// Input to output scaling, in unit coordinate systems. -vec2 scale_i2o() { return 1.0 / scale_o2i(); } - -// Get adjusted center in input unit coordinate system. +// Get adjusted center in input pixel coordinate system. +// Round to whole pixels to avoid issues on low-res output resolutions, e.g., +// when using a 10x by 1x horizontal superresolution. vec2 get_input_center() { - return param.CENTER_CROP > 0.5 - ? 0.5 * param.InputSize.zw * - vec2(param.OS_CROP_LEFT + param.InputSize.x - - param.OS_CROP_RIGHT, - param.OS_CROP_TOP + param.InputSize.y - - param.OS_CROP_BOTTOM) - : vec2(0.49999); + return floor(param.CENTER_CROP > 0.5 + ? 0.5 * vec2(param.OS_CROP_LEFT + param.InputSize.x - + param.OS_CROP_RIGHT, + param.OS_CROP_TOP + param.InputSize.y - + param.OS_CROP_BOTTOM) + : vec2(0.49999) * param.InputSize.xy); } -// In unit space (output to input). +// From unit output to pixel input space. // coord_in_input_space = o2i(coord_in_output_space) // This is used to sample from the input texture in the output pass. vec2 o2i(vec2 x) { return (x - 0.49999) * scale_o2i() + get_input_center(); } -// In unit coordinate systems. -vec2 i2o(vec2 x) { return (x - get_input_center()) * scale_i2o() + 0.49999; } +// From pixel input to unit output space. +vec2 i2o(vec2 x) { return (x - get_input_center()) / scale_o2i() + 0.49999; }