From 77b6ca6bed18c31e7f3eac5f329e1a4cf3d8f59e Mon Sep 17 00:00:00 2001 From: fishcu Date: Sun, 11 Jun 2023 22:57:38 +0200 Subject: [PATCH] Add average fill (#443) * Implement average fill * Implement corner blend modes * Add copyright; Change defaults; Add some polish * Add settings delimiter * Fix settings name --- border/average_fill.slangp | 59 ++++++ border/shaders/average_fill/compose.slang | 189 ++++++++++++++++++ .../average_fill/crop_and_sample_bottom.slang | 14 ++ .../average_fill/crop_and_sample_common.slang | 37 ++++ .../average_fill/crop_and_sample_left.slang | 14 ++ .../average_fill/crop_and_sample_right.slang | 14 ++ .../average_fill/crop_and_sample_top.slang | 14 ++ border/shaders/average_fill/parameters.slang | 11 + 8 files changed, 352 insertions(+) create mode 100644 border/average_fill.slangp create mode 100644 border/shaders/average_fill/compose.slang create mode 100644 border/shaders/average_fill/crop_and_sample_bottom.slang create mode 100644 border/shaders/average_fill/crop_and_sample_common.slang create mode 100644 border/shaders/average_fill/crop_and_sample_left.slang create mode 100644 border/shaders/average_fill/crop_and_sample_right.slang create mode 100644 border/shaders/average_fill/crop_and_sample_top.slang create mode 100644 border/shaders/average_fill/parameters.slang diff --git a/border/average_fill.slangp b/border/average_fill.slangp new file mode 100644 index 00000000..a88ac8bf --- /dev/null +++ b/border/average_fill.slangp @@ -0,0 +1,59 @@ +shaders = 7 + +shader0 = ../blurs/shaders/kawase/linearize.slang +scale_type0 = source +scale_x0 = 1.0 +scale_y0 = 1.0 +float_framebuffer0 = true +alias0 = "Input" + +shader1 = shaders/average_fill/crop_and_sample_top.slang +filter_linear1 = true +scale_type1 = absolute +scale_x1 = 512 +scale_y1 = 64 +float_framebuffer1 = true +wrap_mode1 = mirrored_repeat +alias1 = "Top" + +shader2 = shaders/average_fill/crop_and_sample_bottom.slang +filter_linear2 = true +scale_type2 = absolute +scale_x2 = 512 +scale_y2 = 64 +float_framebuffer2 = true +mipmap_input2 = true +alias2 = "Bottom" + +shader3 = shaders/average_fill/crop_and_sample_left.slang +filter_linear3 = true +scale_type3 = absolute +scale_x3 = 64 +scale_y3 = 512 +float_framebuffer3 = true +mipmap_input3 = true +alias3 = "Left" + +shader4 = shaders/average_fill/crop_and_sample_right.slang +filter_linear4 = true +scale_type4 = absolute +scale_x4 = 64 +scale_y4 = 512 +float_framebuffer4 = true +mipmap_input4 = true +alias4 = "Right" + +shader5 = shaders/average_fill/compose.slang +filter_linear5 = true +scale_type5 = viewport +scale_x5 = 1.0 +scale_y5 = 1.0 +float_framebuffer5 = true +mipmap_input5 = true + +shader6 = ../blurs/shaders/kawase/delinearize.slang +filter_linear6 = true +scale_type6 = viewport +scale_x6 = 1.0 +scale_y6 = 1.0 +float_framebuffer6 = true diff --git a/border/shaders/average_fill/compose.slang b/border/shaders/average_fill/compose.slang new file mode 100644 index 00000000..344bca7d --- /dev/null +++ b/border/shaders/average_fill/compose.slang @@ -0,0 +1,189 @@ +#version 450 + +/* + Average 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 the average color of an adjustable area next to it. + This is useful for certain games that do not render a full image to maintain + the overall aspect ratio and to avoid burn-in. + + In case the image is cropped on multiple sides, different blend modes for + the corner are available. Simply change the parameter for the "corner blend + mode". + The available corner blend modes are: + 0 = Draw horizontal bars on top + 1 = Draw vertical bars on top + 2 = Blend bars by weighted averaging + 3 = Smooth angle-based blending + + Changelog: + v1.0: Initial release. +*/ + +#include "parameters.slang" + +layout(push_constant) uniform Push { + vec4 InputSize; + vec4 OriginalSize; + vec4 OutputSize; + uint FrameCount; + float OS_CROP_TOP; + float OS_CROP_BOTTOM; + float OS_CROP_LEFT; + float OS_CROP_RIGHT; + float CORNER_BLEND_MODE; + float SAMPLE_SIZE; +} +param; + +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 Top; +layout(set = 0, binding = 4) uniform sampler2D Bottom; +layout(set = 0, binding = 5) uniform sampler2D Left; +layout(set = 0, binding = 6) uniform sampler2D Right; + +#define PI 3.1415926538 + +// For mipmap sampling, use a big offset to get the average of a PoT input. +#define BIG_NUMBER 9000.1 + +vec3 blend_corner(vec3 a, // The first color to blend + vec3 b, // The second color to blend + float wa, // The weight of the first color + float wb, // The weight of the second color + vec2 pixel_coord, // The coordinate to evaluate the blend for + vec2 corner_coord, // The coordinate of the corner of the + // content after cropping + vec2 gap_size // The component-wise distance from the corner + // of the content to the corner of the viewport +) { + switch (int(param.CORNER_BLEND_MODE)) { + case 0: + // Horizontal bars on top + return b; + case 1: + // Vertical bars on top + return a; + case 2: + // Weighted average of averages + return mix(a, b, wa / (wa + wb)); + case 3: + default: + // Angle blend + const vec2 delta = (pixel_coord - corner_coord) / gap_size; + // Use absolutes to always operate in 1st quadrant. + // This makes the angle work out to be correct in all cases when + // carefully choosing argument ordering. + const float angle = atan(abs(delta.y), abs(delta.x)) / (PI * 0.5); + // Smoothstep makes the transition perceptually smoother. + return mix(a, b, smoothstep(0.0, 1.0, angle)); + } +} + +void main() { + const vec2 pixel_coord = vTexCoord * param.InputSize.xy; + if (pixel_coord.x < param.OS_CROP_LEFT) { + const vec3 left = textureLod(Left, vec2(0.5), BIG_NUMBER).rgb; + if (pixel_coord.y < param.OS_CROP_TOP) { + // Top left corner + const vec3 top = textureLod(Top, vec2(0.5), BIG_NUMBER).rgb; + FragColor = + vec4(blend_corner(left, top, + param.InputSize.y - param.OS_CROP_TOP - + param.OS_CROP_BOTTOM, + param.InputSize.x - param.OS_CROP_LEFT - + param.OS_CROP_RIGHT, + pixel_coord, + vec2(param.OS_CROP_LEFT, param.OS_CROP_TOP), + vec2(param.OS_CROP_LEFT, param.OS_CROP_TOP)), + 1.0); + } else if (pixel_coord.y < param.InputSize.y - param.OS_CROP_BOTTOM) { + // Left bar + FragColor = vec4(left, 1.0); + } else { + // Bottom left corner + const vec3 bottom = textureLod(Bottom, vec2(0.5), BIG_NUMBER).rgb; + FragColor = vec4( + blend_corner(left, bottom, + param.InputSize.y - param.OS_CROP_TOP - + param.OS_CROP_BOTTOM, + param.InputSize.x - param.OS_CROP_LEFT - + param.OS_CROP_RIGHT, + pixel_coord, + vec2(param.OS_CROP_LEFT, + param.InputSize.y - param.OS_CROP_BOTTOM), + vec2(param.OS_CROP_LEFT, param.OS_CROP_BOTTOM)), + 1.0); + } + } else if (pixel_coord.x < param.InputSize.x - param.OS_CROP_RIGHT) { + if (pixel_coord.y < param.OS_CROP_TOP) { + // Top bar + FragColor = vec4(textureLod(Top, vec2(0.5), BIG_NUMBER).rgb, 1.0); + } else if (pixel_coord.y < param.InputSize.y - param.OS_CROP_BOTTOM) { + // Uncropped + // Do a sharp (nearest neighbor) resampling. + FragColor = vec4( + texture(Input, (floor(vTexCoord * param.InputSize.xy) + 0.5) * + param.InputSize.zw) + .rgb, + 1.0); + } else { + // Bottom bar + FragColor = + vec4(textureLod(Bottom, vec2(0.5), BIG_NUMBER).rgb, 1.0); + } + } else { + const vec3 right = textureLod(Right, vec2(0.5), BIG_NUMBER).rgb; + if (pixel_coord.y < param.OS_CROP_TOP) { + // Top right corner + const vec3 top = textureLod(Top, vec2(0.5), BIG_NUMBER).rgb; + FragColor = + vec4(blend_corner(right, top, + param.InputSize.y - param.OS_CROP_TOP - + param.OS_CROP_BOTTOM, + param.InputSize.x - param.OS_CROP_LEFT - + param.OS_CROP_RIGHT, + pixel_coord, + vec2(param.InputSize.x - param.OS_CROP_RIGHT, + param.OS_CROP_TOP), + vec2(param.OS_CROP_RIGHT, param.OS_CROP_TOP)), + 1.0); + } else if (pixel_coord.y < param.InputSize.y - param.OS_CROP_BOTTOM) { + // Right bar + FragColor = vec4(right, 1.0); + } else { + // Bottom right corner + const vec3 bottom = textureLod(Bottom, vec2(0.5), BIG_NUMBER).rgb; + FragColor = vec4( + blend_corner(right, bottom, + param.InputSize.y - param.OS_CROP_TOP - + param.OS_CROP_BOTTOM, + param.InputSize.x - param.OS_CROP_LEFT - + param.OS_CROP_RIGHT, + pixel_coord, + vec2(param.InputSize.x - param.OS_CROP_RIGHT, + param.InputSize.y - param.OS_CROP_BOTTOM), + vec2(param.OS_CROP_RIGHT, param.OS_CROP_BOTTOM)), + 1.0); + } + } +} diff --git a/border/shaders/average_fill/crop_and_sample_bottom.slang b/border/shaders/average_fill/crop_and_sample_bottom.slang new file mode 100644 index 00000000..dac49302 --- /dev/null +++ b/border/shaders/average_fill/crop_and_sample_bottom.slang @@ -0,0 +1,14 @@ +#version 450 + +// See compose.slang for copyright and other information. + +#include "parameters.slang" + +// clang-format off +#define EFF_CROP_TOP (param.InputSize.y - param.OS_CROP_BOTTOM - param.SAMPLE_SIZE) +#define EFF_CROP_BOTTOM (param.OS_CROP_BOTTOM) +#define EFF_CROP_LEFT (param.OS_CROP_LEFT) +#define EFF_CROP_RIGHT (param.OS_CROP_RIGHT) +// clang-format on + +#include "crop_and_sample_common.slang" diff --git a/border/shaders/average_fill/crop_and_sample_common.slang b/border/shaders/average_fill/crop_and_sample_common.slang new file mode 100644 index 00000000..59fc1548 --- /dev/null +++ b/border/shaders/average_fill/crop_and_sample_common.slang @@ -0,0 +1,37 @@ +// See compose.slang for copyright and other information. + +layout(push_constant) uniform Push { + vec4 InputSize; + vec4 OriginalSize; + vec4 OutputSize; + uint FrameCount; + float OS_CROP_TOP; + float OS_CROP_BOTTOM; + float OS_CROP_LEFT; + float OS_CROP_RIGHT; + float SAMPLE_SIZE; +} +param; + +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 = + mix(vec2(EFF_CROP_LEFT, EFF_CROP_TOP) * param.InputSize.zw, + 1.0 - vec2(EFF_CROP_RIGHT, EFF_CROP_BOTTOM) * param.InputSize.zw, + TexCoord); +} + +#pragma stage fragment +layout(location = 0) in vec2 vTexCoord; +layout(location = 0) out vec4 FragColor; +layout(set = 0, binding = 2) uniform sampler2D Input; + +void main() { FragColor = vec4(texture(Input, vTexCoord).rgb, 1.0); } diff --git a/border/shaders/average_fill/crop_and_sample_left.slang b/border/shaders/average_fill/crop_and_sample_left.slang new file mode 100644 index 00000000..f1edbeac --- /dev/null +++ b/border/shaders/average_fill/crop_and_sample_left.slang @@ -0,0 +1,14 @@ +#version 450 + +// See compose.slang for copyright and other information. + +#include "parameters.slang" + +// clang-format off +#define EFF_CROP_TOP (param.OS_CROP_TOP) +#define EFF_CROP_BOTTOM (param.OS_CROP_BOTTOM) +#define EFF_CROP_LEFT (param.OS_CROP_LEFT) +#define EFF_CROP_RIGHT (param.InputSize.x - param.OS_CROP_LEFT - param.SAMPLE_SIZE) +// clang-format on + +#include "crop_and_sample_common.slang" diff --git a/border/shaders/average_fill/crop_and_sample_right.slang b/border/shaders/average_fill/crop_and_sample_right.slang new file mode 100644 index 00000000..caa44436 --- /dev/null +++ b/border/shaders/average_fill/crop_and_sample_right.slang @@ -0,0 +1,14 @@ +#version 450 + +// See compose.slang for copyright and other information. + +#include "parameters.slang" + +// clang-format off +#define EFF_CROP_TOP (param.OS_CROP_TOP) +#define EFF_CROP_BOTTOM (param.OS_CROP_BOTTOM) +#define EFF_CROP_LEFT (param.InputSize.x - param.OS_CROP_RIGHT - param.SAMPLE_SIZE) +#define EFF_CROP_RIGHT (param.OS_CROP_RIGHT) +// clang-format on + +#include "crop_and_sample_common.slang" diff --git a/border/shaders/average_fill/crop_and_sample_top.slang b/border/shaders/average_fill/crop_and_sample_top.slang new file mode 100644 index 00000000..2ce33919 --- /dev/null +++ b/border/shaders/average_fill/crop_and_sample_top.slang @@ -0,0 +1,14 @@ +#version 450 + +// See compose.slang for copyright and other information. + +#include "parameters.slang" + +// clang-format off +#define EFF_CROP_TOP (param.OS_CROP_TOP) +#define EFF_CROP_BOTTOM (param.InputSize.y - param.OS_CROP_TOP - param.SAMPLE_SIZE) +#define EFF_CROP_LEFT (param.OS_CROP_LEFT) +#define EFF_CROP_RIGHT (param.OS_CROP_RIGHT) +// clang-format on + +#include "crop_and_sample_common.slang" diff --git a/border/shaders/average_fill/parameters.slang b/border/shaders/average_fill/parameters.slang new file mode 100644 index 00000000..e3ff7548 --- /dev/null +++ b/border/shaders/average_fill/parameters.slang @@ -0,0 +1,11 @@ +// See compose.slang for copyright and other information. + +// clang-format off +#pragma parameter AVERAGE_FILL_SETTINGS "=== Average fill v1.0 settings ===" 0.0 0.0 1.0 1.0 +#pragma parameter OS_CROP_TOP "Overscan crop top" 16.0 0.0 1024.0 1.0 +#pragma parameter OS_CROP_BOTTOM "Overscan crop bottom" 16.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 CORNER_BLEND_MODE "Cropped corner blend mode" 0.0 0.0 3.0 1.0 +#pragma parameter SAMPLE_SIZE "No. of lines for calculating the average" 4.0 1.0 64.0 1.0 +// clang-format on