From b9d89d63db30b6978f6ce3681ea113a9bcb8b4bd Mon Sep 17 00:00:00 2001 From: fishcu Date: Wed, 14 Jun 2023 21:21:36 +0200 Subject: [PATCH] Add blur fill (#446) * Initial commit, WIP * First working version; stretching the blur will require some refactoring * Add blur extension to borders * Implement 'trippy mode' (disable blur, show tiled) * Implement proper scaling, cropping, and centering independent of viewport res * Clean up; Fix bug with small blur pass resolutions * Add blur strength presets; Polish * Add background gamma adjustment --- blurs/shaders/dual_filter/parameters.slang | 2 +- border/blur_fill.slangp | 82 +++++++ border/blur_fill_stronger_blur.slangp | 82 +++++++ border/blur_fill_weaker_blur.slangp | 82 +++++++ border/shaders/blur_fill/compose.slang | 109 +++++++++ border/shaders/blur_fill/parameters.slang | 24 ++ .../blur_fill/render_sampling_areas.slang | 226 ++++++++++++++++++ border/shaders/blur_fill/scaling.slang | 64 +++++ 8 files changed, 670 insertions(+), 1 deletion(-) create mode 100644 border/blur_fill.slangp create mode 100644 border/blur_fill_stronger_blur.slangp create mode 100644 border/blur_fill_weaker_blur.slangp create mode 100644 border/shaders/blur_fill/compose.slang create mode 100644 border/shaders/blur_fill/parameters.slang create mode 100644 border/shaders/blur_fill/render_sampling_areas.slang create mode 100644 border/shaders/blur_fill/scaling.slang diff --git a/blurs/shaders/dual_filter/parameters.slang b/blurs/shaders/dual_filter/parameters.slang index ccc3b19..1f50b9f 100644 --- a/blurs/shaders/dual_filter/parameters.slang +++ b/blurs/shaders/dual_filter/parameters.slang @@ -2,5 +2,5 @@ // clang-format off #pragma parameter DUAL_FILTER_SETTINGS "=== Dual Filter Blur & Bloom v1.1 settings ===" 0.0 0.0 1.0 1.0 -#pragma parameter BLUR_RADIUS "Blur radius" 1.0 0.1 7.5 0.1 +#pragma parameter BLUR_RADIUS "Blur radius" 1.0 0.0 7.5 0.1 // clang-format on diff --git a/border/blur_fill.slangp b/border/blur_fill.slangp new file mode 100644 index 0000000..c484d7f --- /dev/null +++ b/border/blur_fill.slangp @@ -0,0 +1,82 @@ +shaders = 10 + +parameters = "BLUR_RADIUS" +BLUR_RADIUS = 2.0 + +shader0 = ../blurs/shaders/kawase/linearize.slang +scale_type0 = source +scale_x0 = 1.0 +scale_y0 = 1.0 +float_framebuffer0 = true +alias0 = "Input" + +shader1 = shaders/blur_fill/render_sampling_areas.slang +scale_type1 = viewport +scale_x1 = 0.2 +scale_y1 = 0.2 +float_framebuffer1 = true +alias1 = "Tiled" + +shader2 = ../blurs/shaders/dual_filter/downsample.slang +filter_linear2 = true +scale_type2 = source +scale_x2 = 0.5 +scale_y2 = 0.5 +float_framebuffer2 = true +wrap_mode2 = mirrored_repeat + +shader3 = ../blurs/shaders/dual_filter/downsample.slang +filter_linear3 = true +scale_type3 = source +scale_x3 = 0.5 +scale_y3 = 0.5 +float_framebuffer3 = true +wrap_mode3 = mirrored_repeat + +shader4 = ../blurs/shaders/dual_filter/downsample.slang +filter_linear4 = true +scale_type4 = source +scale_x4 = 0.5 +scale_y4 = 0.5 +float_framebuffer4 = true +wrap_mode4 = mirrored_repeat + +shader5 = ../blurs/shaders/dual_filter/upsample.slang +filter_linear5 = true +scale_type5 = source +scale_x5 = 2.0 +scale_y5 = 2.0 +float_framebuffer5 = true +wrap_mode5 = mirrored_repeat + +shader6 = ../blurs/shaders/dual_filter/upsample.slang +filter_linear6 = true +scale_type6 = source +scale_x6 = 2.0 +scale_y6 = 2.0 +float_framebuffer6 = true +wrap_mode6 = mirrored_repeat + +shader7 = ../blurs/shaders/dual_filter/upsample.slang +filter_linear7 = true +scale_type7 = source +scale_x7 = 2.0 +scale_y7 = 2.0 +float_framebuffer7 = true +wrap_mode7 = mirrored_repeat +alias7 = "Blurred" + +shader8 = shaders/blur_fill/compose.slang +filter_linear8 = true +scale_type8 = viewport +scale_x8 = 1.0 +scale_y8 = 1.0 +float_framebuffer8 = true +mipmap_input8 = true + +shader9 = ../blurs/shaders/kawase/delinearize.slang +filter_linear9 = true +scale_type9 = viewport +scale_x9 = 1.0 +scale_y9 = 1.0 +float_framebuffer9 = true diff --git a/border/blur_fill_stronger_blur.slangp b/border/blur_fill_stronger_blur.slangp new file mode 100644 index 0000000..c9eb2b8 --- /dev/null +++ b/border/blur_fill_stronger_blur.slangp @@ -0,0 +1,82 @@ +shaders = 10 + +parameters = "BLUR_RADIUS" +BLUR_RADIUS = 4.0 + +shader0 = ../blurs/shaders/kawase/linearize.slang +scale_type0 = source +scale_x0 = 1.0 +scale_y0 = 1.0 +float_framebuffer0 = true +alias0 = "Input" + +shader1 = shaders/blur_fill/render_sampling_areas.slang +scale_type1 = viewport +scale_x1 = 0.1 +scale_y1 = 0.1 +float_framebuffer1 = true +alias1 = "Tiled" + +shader2 = ../blurs/shaders/dual_filter/downsample.slang +filter_linear2 = true +scale_type2 = source +scale_x2 = 0.5 +scale_y2 = 0.5 +float_framebuffer2 = true +wrap_mode2 = mirrored_repeat + +shader3 = ../blurs/shaders/dual_filter/downsample.slang +filter_linear3 = true +scale_type3 = source +scale_x3 = 0.5 +scale_y3 = 0.5 +float_framebuffer3 = true +wrap_mode3 = mirrored_repeat + +shader4 = ../blurs/shaders/dual_filter/downsample.slang +filter_linear4 = true +scale_type4 = source +scale_x4 = 0.5 +scale_y4 = 0.5 +float_framebuffer4 = true +wrap_mode4 = mirrored_repeat + +shader5 = ../blurs/shaders/dual_filter/upsample.slang +filter_linear5 = true +scale_type5 = source +scale_x5 = 2.0 +scale_y5 = 2.0 +float_framebuffer5 = true +wrap_mode5 = mirrored_repeat + +shader6 = ../blurs/shaders/dual_filter/upsample.slang +filter_linear6 = true +scale_type6 = source +scale_x6 = 2.0 +scale_y6 = 2.0 +float_framebuffer6 = true +wrap_mode6 = mirrored_repeat + +shader7 = ../blurs/shaders/dual_filter/upsample.slang +filter_linear7 = true +scale_type7 = source +scale_x7 = 2.0 +scale_y7 = 2.0 +float_framebuffer7 = true +wrap_mode7 = mirrored_repeat +alias7 = "Blurred" + +shader8 = shaders/blur_fill/compose.slang +filter_linear8 = true +scale_type8 = viewport +scale_x8 = 1.0 +scale_y8 = 1.0 +float_framebuffer8 = true +mipmap_input8 = true + +shader9 = ../blurs/shaders/kawase/delinearize.slang +filter_linear9 = true +scale_type9 = viewport +scale_x9 = 1.0 +scale_y9 = 1.0 +float_framebuffer9 = true diff --git a/border/blur_fill_weaker_blur.slangp b/border/blur_fill_weaker_blur.slangp new file mode 100644 index 0000000..59eab3f --- /dev/null +++ b/border/blur_fill_weaker_blur.slangp @@ -0,0 +1,82 @@ +shaders = 10 + +parameters = "BLUR_RADIUS" +BLUR_RADIUS = 1.0 + +shader0 = ../blurs/shaders/kawase/linearize.slang +scale_type0 = source +scale_x0 = 1.0 +scale_y0 = 1.0 +float_framebuffer0 = true +alias0 = "Input" + +shader1 = shaders/blur_fill/render_sampling_areas.slang +scale_type1 = viewport +scale_x1 = 0.4 +scale_y1 = 0.4 +float_framebuffer1 = true +alias1 = "Tiled" + +shader2 = ../blurs/shaders/dual_filter/downsample.slang +filter_linear2 = true +scale_type2 = source +scale_x2 = 0.5 +scale_y2 = 0.5 +float_framebuffer2 = true +wrap_mode2 = mirrored_repeat + +shader3 = ../blurs/shaders/dual_filter/downsample.slang +filter_linear3 = true +scale_type3 = source +scale_x3 = 0.5 +scale_y3 = 0.5 +float_framebuffer3 = true +wrap_mode3 = mirrored_repeat + +shader4 = ../blurs/shaders/dual_filter/downsample.slang +filter_linear4 = true +scale_type4 = source +scale_x4 = 0.5 +scale_y4 = 0.5 +float_framebuffer4 = true +wrap_mode4 = mirrored_repeat + +shader5 = ../blurs/shaders/dual_filter/upsample.slang +filter_linear5 = true +scale_type5 = source +scale_x5 = 2.0 +scale_y5 = 2.0 +float_framebuffer5 = true +wrap_mode5 = mirrored_repeat + +shader6 = ../blurs/shaders/dual_filter/upsample.slang +filter_linear6 = true +scale_type6 = source +scale_x6 = 2.0 +scale_y6 = 2.0 +float_framebuffer6 = true +wrap_mode6 = mirrored_repeat + +shader7 = ../blurs/shaders/dual_filter/upsample.slang +filter_linear7 = true +scale_type7 = source +scale_x7 = 2.0 +scale_y7 = 2.0 +float_framebuffer7 = true +wrap_mode7 = mirrored_repeat +alias7 = "Blurred" + +shader8 = shaders/blur_fill/compose.slang +filter_linear8 = true +scale_type8 = viewport +scale_x8 = 1.0 +scale_y8 = 1.0 +float_framebuffer8 = true +mipmap_input8 = true + +shader9 = ../blurs/shaders/kawase/delinearize.slang +filter_linear9 = true +scale_type9 = viewport +scale_x9 = 1.0 +scale_y9 = 1.0 +float_framebuffer9 = true diff --git a/border/shaders/blur_fill/compose.slang b/border/shaders/blur_fill/compose.slang new file mode 100644 index 0000000..b9068e3 --- /dev/null +++ b/border/shaders/blur_fill/compose.slang @@ -0,0 +1,109 @@ +#version 450 + +/* + Blur fill v1.0 by fishku + Copyright (C) 2023 + Public domain license (CC0) + + This shader preset allows cropping the image on any side, and filling the + cropped area with a blurred version of the input image borders. + This is useful for certain games that do not render a full image to maintain + the overall aspect ratio and to avoid burn-in. + + The preset also allows you to extend the original content to a larger + screen. It's recommended to set the video scaling options as follows: + - Turn integer scaling OFF + - Set aspect ratio to FULL + The shader will then take over and handle the proper scaling and aspect + ratio of the input. + + The preset comes in three variants which differ only in the strength of the + blur. + Since the blur strength in the dual filter blur depends on the input + resolution, and because there is currently no mechanism to set resolution + based on user parameters, the three variants provide different sampling + resolutions which affect the strength of the blur. + Additionally to the resolution, a blur radius parameter controls the + strength of the blur. + + Changelog: + v1.0: Initial release. +*/ + +#include "../../../blurs/shaders/dual_filter/parameters.slang" +#include "parameters.slang" + +layout(push_constant) uniform Push { + vec4 InputSize; + vec4 TiledSize; + vec4 FinalViewportSize; + float OS_CROP_TOP; + float OS_CROP_BOTTOM; + float OS_CROP_LEFT; + float OS_CROP_RIGHT; + float CENTER_CROP; + float SAMPLE_SIZE; + float FORCE_ASPECT_RATIO; + float ASPECT_H; + float ASPECT_V; + float FORCE_INTEGER_SCALING; + float FILL_GAMMA; + // From dual filter blur + float BLUR_RADIUS; +} +param; + +#include "scaling.slang" + +layout(std140, set = 0, binding = 0) uniform UBO { mat4 MVP; } +global; + +#pragma stage vertex +layout(location = 0) in vec4 Position; +layout(location = 1) in vec2 TexCoord; +layout(location = 0) out vec2 vTexCoord; + +void main() { + gl_Position = global.MVP * Position; + vTexCoord = TexCoord; +} + +#pragma stage fragment +layout(location = 0) in vec2 vTexCoord; +layout(location = 0) out vec4 FragColor; +layout(set = 0, binding = 2) uniform sampler2D Input; +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 vec4 input_extrema = vec4(param.OS_CROP_LEFT, param.OS_CROP_TOP, + param.InputSize.x - param.OS_CROP_RIGHT, + param.InputSize.y - param.OS_CROP_BOTTOM); + if (any(lessThan(pixel_coord, input_extrema.xy)) || + any(greaterThanEqual(pixel_coord, input_extrema.zw))) { + if (param.BLUR_RADIUS > 0.0) { + // Sample blur. + FragColor = vec4( + pow(texture(Blurred, vTexCoord).rgb, vec3(param.FILL_GAMMA)), + 1.0); + } else { + // Sample tiled pattern. + // Do a sharp (nearest neighbor) resampling. + FragColor = + vec4(pow(texture(Tiled, + (floor(vTexCoord * param.TiledSize.xy) + 0.5) * + param.TiledSize.zw) + .rgb, + vec3(param.FILL_GAMMA)), + 1.0); + } + } else { + // Sample original. + // Do a sharp (nearest neighbor) resampling. + FragColor = vec4( + texture(Input, (floor(pixel_coord) + 0.5) * param.InputSize.zw).rgb, + 1.0); + } +} diff --git a/border/shaders/blur_fill/parameters.slang b/border/shaders/blur_fill/parameters.slang new file mode 100644 index 0000000..006839f --- /dev/null +++ b/border/shaders/blur_fill/parameters.slang @@ -0,0 +1,24 @@ +// See compose.slang for copyright and other information. + +// clang-format off +#pragma parameter BLUR_FILL_SETTINGS "=== Blur fill v1.0 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 +#pragma parameter OS_CROP_RIGHT "Overscan crop right" 0.0 0.0 1024.0 1.0 + +#pragma parameter CENTER_CROP "Center cropped area" 1.0 0.0 1.0 1.0 + +#pragma parameter SAMPLE_SIZE "No. of lines for rendering the blur" 16.0 1.0 1024.0 1.0 + +#pragma parameter BLUR_EXTEND_H "Extend the blur horizontally" 0.0 0.0 1.0 1.0 +#pragma parameter BLUR_EXTEND_V "Extend the blur vertically" 1.0 0.0 1.0 1.0 +#pragma parameter MIRROR_BLUR "Mirror the blur" 0.0 0.0 1.0 1.0 + +#pragma parameter FORCE_ASPECT_RATIO "Force aspect ratio" 0.0 0.0 1.0 1.0 +#pragma parameter ASPECT_H "Horizontal aspect ratio before crop" 4.0 1.0 100.0 1.0 +#pragma parameter ASPECT_V "Vertical aspect ratio before crop" 3.0 1.0 100.0 1.0 +#pragma parameter FORCE_INTEGER_SCALING "Force integer scaling" 1.0 0.0 1.0 1.0 + +#pragma parameter FILL_GAMMA "Background fill gamma adjustment" 1.4 0.5 2.0 0.1 +// clang-format on diff --git a/border/shaders/blur_fill/render_sampling_areas.slang b/border/shaders/blur_fill/render_sampling_areas.slang new file mode 100644 index 0000000..d01716d --- /dev/null +++ b/border/shaders/blur_fill/render_sampling_areas.slang @@ -0,0 +1,226 @@ +#version 450 + +// See compose.slang for copyright and other information. + +#include "parameters.slang" + +layout(push_constant) uniform Push { + vec4 InputSize; + vec4 FinalViewportSize; + float OS_CROP_TOP; + float OS_CROP_BOTTOM; + float OS_CROP_LEFT; + float OS_CROP_RIGHT; + float CENTER_CROP; + float SAMPLE_SIZE; + float BLUR_EXTEND_H; + float BLUR_EXTEND_V; + float MIRROR_BLUR; + float FORCE_ASPECT_RATIO; + float ASPECT_H; + float ASPECT_V; + float FORCE_INTEGER_SCALING; +} +param; + +#include "scaling.slang" + +layout(std140, set = 0, binding = 0) uniform UBO { mat4 MVP; } +global; + +#pragma stage vertex +layout(location = 0) in vec4 Position; +layout(location = 1) in vec2 TexCoord; +layout(location = 0) out vec2 vTexCoord; + +void main() { + gl_Position = global.MVP * Position; + vTexCoord = TexCoord; +} + +#pragma stage fragment +layout(location = 0) in vec2 vTexCoord; +layout(location = 0) out vec4 FragColor; +layout(set = 0, binding = 2) uniform sampler2D Input; + +float min(vec4 i) { return min(min(i.x, i.y), min(i.z, i.w)); } + +int argmin(vec4 i) { + const vec4 m = vec4(min(i)); + const vec4 ma = step(i, m) * vec4(0.0, -1.0, -2.0, -3.0); + return -int(min(ma)); +} + +// Returns a coordinate in [0, w) when repeating that interval (and optionally +// mirroring). +float mirrored_repeat(float w, float x) { + const float phase = mod(x, w); + if (param.MIRROR_BLUR < 0.5) { + return phase; + } + const int period = int(x / w); + return period % 2 == 1 != x > 0.0 ? phase : w - phase; +} + +float extend_left(vec2 coord, vec4 input_extrema) { + return input_extrema.x + + mirrored_repeat(param.SAMPLE_SIZE, coord.x - input_extrema.x); +} + +float extend_right(vec2 coord, vec4 input_extrema) { + return input_extrema.z - + mirrored_repeat(param.SAMPLE_SIZE, input_extrema.z - coord.x); +} + +float extend_top(vec2 coord, vec4 input_extrema) { + return input_extrema.y + + mirrored_repeat(param.SAMPLE_SIZE, coord.y - input_extrema.y); +} + +float extend_bottom(vec2 coord, vec4 input_extrema) { + return input_extrema.w - + mirrored_repeat(param.SAMPLE_SIZE, input_extrema.w - coord.y); +} + +// This function samples in a very specific way which is the foundation for +// blurring later. +// - If the sample coordinate is outside of the cropped input, Either black is +// returned if blur extension is turned off, or a repeated pattern from the +// sampling frame band is returned. +// - If the coordinate is inside the cropped input and within the frame band +// given by SAMPLE_SIZE, the original texture sample is returned. +// - If the coordinate is further inside than the frame band, a mirrored +// repeating sample is returned. The side of the frame that is sampled is given +// by the one that is closest to the sampled point. +vec3 sample_mirrored_frame(sampler2D tex, vec2 coord, vec4 input_extrema) { + if (coord.x < input_extrema.x) { + if (param.BLUR_EXTEND_H < 0.5) { + return vec3(0.0); + } + if (coord.y < input_extrema.y) { + // Top left corner extension + if (param.BLUR_EXTEND_V < 0.5) { + return vec3(0.0); + } + return texture(tex, vec2(extend_left(coord, input_extrema), + extend_top(coord, input_extrema)) * + param.InputSize.zw) + .rgb; + } else if (coord.y < input_extrema.w) { + // Left extension + return texture(tex, + vec2(extend_left(coord, input_extrema), coord.y) * + param.InputSize.zw) + .rgb; + } else { + // Bottom left corner extension + if (param.BLUR_EXTEND_V < 0.5) { + return vec3(0.0); + } + return texture(tex, vec2(extend_left(coord, input_extrema), + extend_bottom(coord, input_extrema)) * + param.InputSize.zw) + .rgb; + } + } else if (coord.x < input_extrema.z) { + if (coord.y < input_extrema.y) { + if (param.BLUR_EXTEND_V < 0.5) { + return vec3(0.0); + } + // Top extension + return texture(tex, + vec2(coord.x, extend_top(coord, input_extrema)) * + param.InputSize.zw) + .rgb; + } else if (coord.y < input_extrema.w) { + const vec4 inner_extrema = + input_extrema + vec4(param.SAMPLE_SIZE, param.SAMPLE_SIZE, + -param.SAMPLE_SIZE, -param.SAMPLE_SIZE); + if (any(lessThan(coord, inner_extrema.xy)) || + any(greaterThanEqual(coord, inner_extrema.zw))) { + // In frame band + return texture(tex, coord * param.InputSize.zw).rgb; + } + // Innermost -- mirrored repeat sampling from nearest side + const vec4 distances = + vec4(coord.x - inner_extrema.x, inner_extrema.z - coord.x, + coord.y - inner_extrema.y, inner_extrema.w - coord.y); + switch (argmin(distances)) { + case 0: + // left + return texture(tex, vec2(extend_left(coord, input_extrema), + coord.y) * + param.InputSize.zw) + .rgb; + case 1: + // right + return texture(tex, vec2(extend_right(coord, input_extrema), + coord.y) * + param.InputSize.zw) + .rgb; + case 2: + // top + return texture(tex, vec2(coord.x, + extend_top(coord, input_extrema)) * + param.InputSize.zw) + .rgb; + case 3: + default: + // bottom + return texture(tex, + vec2(coord.x, + extend_bottom(coord, input_extrema)) * + param.InputSize.zw) + .rgb; + } + } else { + if (param.BLUR_EXTEND_V < 0.5) { + return vec3(0.0); + } + // Bottom extension + return texture(tex, + vec2(coord.x, extend_bottom(coord, input_extrema)) * + param.InputSize.zw) + .rgb; + } + } else { + if (param.BLUR_EXTEND_H < 0.5) { + return vec3(0.0); + } + if (coord.y < input_extrema.y) { + // Top right corner extension + if (param.BLUR_EXTEND_V < 0.5) { + return vec3(0.0); + } + return texture(tex, vec2(extend_right(coord, input_extrema), + extend_top(coord, input_extrema)) * + param.InputSize.zw) + .rgb; + } else if (coord.y < input_extrema.w) { + // Right extension + return texture(tex, + vec2(extend_right(coord, input_extrema), coord.y) * + param.InputSize.zw) + .rgb; + } else { + // Bottom right corner extension + if (param.BLUR_EXTEND_V < 0.5) { + return vec3(0.0); + } + return texture(tex, vec2(extend_right(coord, input_extrema), + extend_bottom(coord, input_extrema)) * + param.InputSize.zw) + .rgb; + } + } +} + +void main() { + const vec2 pixel_coord = o2i(vTexCoord) * param.InputSize.xy; + FragColor = vec4( + sample_mirrored_frame(Input, pixel_coord, + vec4(param.OS_CROP_LEFT, param.OS_CROP_TOP, + param.InputSize.x - param.OS_CROP_RIGHT, + param.InputSize.y - param.OS_CROP_BOTTOM)), + 1.0); +} diff --git a/border/shaders/blur_fill/scaling.slang b/border/shaders/blur_fill/scaling.slang new file mode 100644 index 0000000..e3cf027 --- /dev/null +++ b/border/shaders/blur_fill/scaling.slang @@ -0,0 +1,64 @@ +#include "parameters.slang" + +// 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. +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) { + // 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); + } 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); + } +} + +// In unit space (output to input) +// 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) { + const vec2 center_in = + 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 (x - 0.49999) * scale_o2i() + center_in; +}