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
This commit is contained in:
fishcu 2023-06-14 21:21:36 +02:00 committed by GitHub
parent 362420d8db
commit b9d89d63db
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 670 additions and 1 deletions

View file

@ -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

82
border/blur_fill.slangp Normal file
View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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);
}
}

View file

@ -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

View file

@ -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);
}

View file

@ -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;
}