diff --git a/border/shaders/average_fill/compose.slang b/border/shaders/average_fill/compose.slang index 6dffe1a2..f73f0b04 100644 --- a/border/shaders/average_fill/compose.slang +++ b/border/shaders/average_fill/compose.slang @@ -1,8 +1,8 @@ #version 450 /* - Average fill v1.8 by fishku - Copyright (C) 2023 + Average fill v1.9 by fishku + Copyright (C) 2023-2024 Public domain license (CC0) This shader preset allows cropping the image on any side, and filling the @@ -27,6 +27,7 @@ 3 = Smooth angle-based blending Changelog: + v1.9: Update input transform library. v1.8: Add shift option from input transform library. v1.7: Add overscale option from crop and scale library. v1.6: Refactor for new scaling library. Add rotation support. @@ -85,32 +86,33 @@ global; #pragma stage vertex layout(location = 0) in vec4 Position; layout(location = 1) in vec2 TexCoord; -layout(location = 0) out vec2 vTexCoord; -layout(location = 1) out vec2 scale_o2i; -layout(location = 2) out vec4 crop; -layout(location = 3) out vec2 shift; +layout(location = 0) out vec4 input_corners; +layout(location = 1) out vec2 vTexCoord; +layout(location = 2) out vec2 scale_i2o; +layout(location = 3) out vec2 input_center; layout(location = 4) out vec2 tx_coord; layout(location = 5) out vec2 tx_per_px; layout(location = 6) out vec2 tx_to_uv; -layout(location = 7) out vec4 input_corners; -layout(location = 8) out vec2 cropped_input_size; +layout(location = 7) out vec2 cropped_input_size; void main() { gl_Position = global.MVP * Position; vTexCoord = TexCoord; - crop = vec4(param.OS_CROP_TOP, param.OS_CROP_LEFT, param.OS_CROP_BOTTOM, - param.OS_CROP_RIGHT); - scale_o2i = get_scale_o2i( + const vec4 crop = vec4(param.OS_CROP_TOP, param.OS_CROP_LEFT, + param.OS_CROP_BOTTOM, param.OS_CROP_RIGHT); + scale_i2o = get_scale_i2o( param.InputSize.xy, param.OutputSize.xy, crop, param.Rotation, param.CENTER_AFTER_CROPPING, param.FORCE_ASPECT_RATIO, vec2(param.ASPECT_H, param.ASPECT_V), vec2(param.FORCE_INTEGER_SCALING_H, param.FORCE_INTEGER_SCALING_V), - param.OVERSCALE, - /* output_size_is_final_viewport_size = */ false); - shift = vec2(param.SHIFT_H, param.SHIFT_V); - tx_coord = o2i(vTexCoord, param.InputSize.xy, crop, shift, param.Rotation, - param.CENTER_AFTER_CROPPING, scale_o2i); - tx_per_px = scale_o2i * param.OutputSize.zw; + param.OVERSCALE); + const vec2 shift = vec2(param.SHIFT_H, param.SHIFT_V); + input_center = get_input_center(param.InputSize.xy, param.OutputSize.xy, + scale_i2o, crop, shift, param.Rotation, + param.CENTER_AFTER_CROPPING); + tx_coord = transform(TexCoord, vec2(0.5), param.OutputSize.xy / scale_i2o, + input_center); + tx_per_px = 1.0 / scale_i2o; tx_to_uv = param.InputSize.zw; input_corners = get_input_corners(param.InputSize.xy, crop, param.Rotation); @@ -121,15 +123,14 @@ void main() { } #pragma stage fragment -layout(location = 0) in vec2 vTexCoord; -layout(location = 1) in vec2 scale_o2i; -layout(location = 2) in vec4 crop; -layout(location = 3) in vec2 shift; +layout(location = 0) in vec4 input_corners; +layout(location = 1) in vec2 vTexCoord; +layout(location = 2) in vec2 scale_i2o; +layout(location = 3) in vec2 input_center; layout(location = 4) in vec2 tx_coord; layout(location = 5) in vec2 tx_per_px; layout(location = 6) in vec2 tx_to_uv; -layout(location = 7) in vec4 input_corners; -layout(location = 8) in vec2 cropped_input_size; +layout(location = 7) in vec2 cropped_input_size; layout(location = 0) out vec4 FragColor; layout(set = 0, binding = 2) uniform sampler2D Input; layout(set = 0, binding = 3) uniform sampler2D Top; @@ -192,8 +193,8 @@ void main() { // Top left corner const vec3 top = textureLod(Top, vec2(0.5), BIG_NUMBER).rgb; const vec2 content_corner = - i2o(input_corners.xy, param.InputSize.xy, crop, shift, - param.Rotation, param.CENTER_AFTER_CROPPING, scale_o2i); + transform(input_corners.xy, input_center, + scale_i2o / param.OutputSize.xy, vec2(0.5)); const vec2 viewport_corner = vec2(0.0, 0.0); FragColor = vec4( blend_corner(left, top, cropped_input_size.y, @@ -212,8 +213,8 @@ void main() { // Bottom left corner const vec3 bottom = textureLod(Bottom, vec2(0.5), BIG_NUMBER).rgb; const vec2 content_corner = - i2o(input_corners.xw, param.InputSize.xy, crop, shift, - param.Rotation, param.CENTER_AFTER_CROPPING, scale_o2i); + transform(input_corners.xw, input_center, + scale_i2o / param.OutputSize.xy, vec2(0.5)); const vec2 viewport_corner = vec2(0.0, 1.0); FragColor = vec4( blend_corner(left, bottom, cropped_input_size.y, @@ -273,8 +274,8 @@ void main() { // Top right corner const vec3 top = textureLod(Top, vec2(0.5), BIG_NUMBER).rgb; const vec2 content_corner = - i2o(input_corners.zy, param.InputSize.xy, crop, shift, - param.Rotation, param.CENTER_AFTER_CROPPING, scale_o2i); + transform(input_corners.zy, input_center, + scale_i2o / param.OutputSize.xy, vec2(0.5)); const vec2 viewport_corner = vec2(1.0, 0.0); FragColor = vec4( blend_corner(right, top, cropped_input_size.y, @@ -293,8 +294,8 @@ void main() { // Bottom right corner const vec3 bottom = textureLod(Bottom, vec2(0.5), BIG_NUMBER).rgb; const vec2 content_corner = - i2o(input_corners.zw, param.InputSize.xy, crop, shift, - param.Rotation, param.CENTER_AFTER_CROPPING, scale_o2i); + transform(input_corners.zw, input_center, + scale_i2o / param.OutputSize.xy, vec2(0.5)); const vec2 viewport_corner = vec2(1.0, 1.0); FragColor = vec4( blend_corner(right, bottom, cropped_input_size.y, diff --git a/border/shaders/average_fill/parameters.inc b/border/shaders/average_fill/parameters.inc index cc1878ca..a6795b22 100644 --- a/border/shaders/average_fill/parameters.inc +++ b/border/shaders/average_fill/parameters.inc @@ -1,7 +1,7 @@ // See compose.slang for copyright and other information. // clang-format off -#pragma parameter AVERAGE_FILL_SETTINGS "=== Average fill v1.8 settings ===" 0.0 0.0 1.0 1.0 +#pragma parameter AVERAGE_FILL_SETTINGS "=== Average fill v1.9 settings ===" 0.0 0.0 1.0 1.0 #include "../../../misc/shaders/input_transform/parameters.inc" diff --git a/border/shaders/blur_fill/compose.slang b/border/shaders/blur_fill/compose.slang index e148f914..f70fbdae 100644 --- a/border/shaders/blur_fill/compose.slang +++ b/border/shaders/blur_fill/compose.slang @@ -2,7 +2,7 @@ /* Blur fill v1.9 by fishku - Copyright (C) 2023 + Copyright (C) 2023-2024 Public domain license (CC0) This shader preset allows cropping the image on any side, and filling the @@ -27,6 +27,7 @@ strength of the blur. Changelog: + v1.10: Update input transform library. v1.9: Add shift option from input transform library. v1.8: Add overscale option from crop and scale library. v1.7: Refactor for new scaling library. Add rotation support. @@ -98,17 +99,19 @@ void main() { vTexCoord = TexCoord; const vec4 crop = vec4(param.OS_CROP_TOP, param.OS_CROP_LEFT, param.OS_CROP_BOTTOM, param.OS_CROP_RIGHT); - const vec2 scale_o2i = get_scale_o2i( + const vec2 scale_i2o = get_scale_i2o( param.InputSize.xy, param.OutputSize.xy, crop, param.Rotation, param.CENTER_AFTER_CROPPING, param.FORCE_ASPECT_RATIO, vec2(param.ASPECT_H, param.ASPECT_V), vec2(param.FORCE_INTEGER_SCALING_H, param.FORCE_INTEGER_SCALING_V), - param.OVERSCALE, - /* output_size_is_final_viewport_size = */ false); + param.OVERSCALE); const vec2 shift = vec2(param.SHIFT_H, param.SHIFT_V); - tx_coord = o2i(vTexCoord, param.InputSize.xy, crop, shift, param.Rotation, - param.CENTER_AFTER_CROPPING, scale_o2i); - tx_per_px = scale_o2i * param.OutputSize.zw; + const vec2 input_center = get_input_center( + param.InputSize.xy, param.OutputSize.xy, scale_i2o, crop, shift, + param.Rotation, param.CENTER_AFTER_CROPPING); + tx_coord = transform(TexCoord, vec2(0.5), param.OutputSize.xy / scale_i2o, + input_center); + tx_per_px = 1.0 / scale_i2o; tx_to_uv = param.InputSize.zw; input_corners = get_input_corners(param.InputSize.xy, crop, param.Rotation); } diff --git a/border/shaders/blur_fill/parameters.inc b/border/shaders/blur_fill/parameters.inc index d5e3d438..a315a124 100644 --- a/border/shaders/blur_fill/parameters.inc +++ b/border/shaders/blur_fill/parameters.inc @@ -1,7 +1,7 @@ // See compose.slang for copyright and other information. // clang-format off -#pragma parameter BLUR_FILL_SETTINGS "=== Blur fill v1.9 settings ===" 0.0 0.0 1.0 1.0 +#pragma parameter BLUR_FILL_SETTINGS "=== Blur fill v1.10 settings ===" 0.0 0.0 1.0 1.0 #include "../../../misc/shaders/input_transform/parameters.inc" diff --git a/border/shaders/blur_fill/render_sampling_areas.slang b/border/shaders/blur_fill/render_sampling_areas.slang index 5653d2b7..c6537afc 100644 --- a/border/shaders/blur_fill/render_sampling_areas.slang +++ b/border/shaders/blur_fill/render_sampling_areas.slang @@ -48,15 +48,24 @@ void main() { vTexCoord = TexCoord; const vec4 crop = vec4(param.OS_CROP_TOP, param.OS_CROP_LEFT, param.OS_CROP_BOTTOM, param.OS_CROP_RIGHT); - const vec2 scale_o2i = get_scale_o2i( - param.InputSize.xy, param.FinalViewportSize.xy, crop, param.Rotation, + // Because RA "rotates" the final viewport size on rotated cores, we need to + // undo that rotation so that the math checks out. The 0 and 180 degree + // rotations and the 90 and 270 rotations are symmetric for 2D sizes, which + // is why we can use param.Rotation directly here. + const vec2 final_viewport_size_rotated = + get_rotated_size(param.FinalViewportSize.xy, param.Rotation); + const vec2 scale_i2o = get_scale_i2o( + param.InputSize.xy, final_viewport_size_rotated, crop, param.Rotation, param.CENTER_AFTER_CROPPING, param.FORCE_ASPECT_RATIO, vec2(param.ASPECT_H, param.ASPECT_V), - vec2(param.FORCE_INTEGER_SCALING_H, param.FORCE_INTEGER_SCALING_V), param.OVERSCALE, - /* output_size_is_final_viewport_size = */ true); + vec2(param.FORCE_INTEGER_SCALING_H, param.FORCE_INTEGER_SCALING_V), + param.OVERSCALE); const vec2 shift = vec2(param.SHIFT_H, param.SHIFT_V); - tx_coord = o2i(vTexCoord, param.InputSize.xy, crop, shift, param.Rotation, - param.CENTER_AFTER_CROPPING, scale_o2i); + const vec2 input_center = get_input_center( + param.InputSize.xy, final_viewport_size_rotated, scale_i2o, crop, shift, + param.Rotation, param.CENTER_AFTER_CROPPING); + tx_coord = transform(TexCoord, vec2(0.5), + final_viewport_size_rotated / scale_i2o, input_center); input_corners = get_input_corners(param.InputSize.xy, crop, param.Rotation); } diff --git a/handheld/shaders/authentic_gbc/shared.inc b/handheld/shaders/authentic_gbc/shared.inc index 7c912361..443587bc 100644 --- a/handheld/shaders/authentic_gbc/shared.inc +++ b/handheld/shaders/authentic_gbc/shared.inc @@ -1,16 +1,11 @@ // See the main shader file for copyright and other information. +#include "../../../misc/shaders/coverage/coverage.inc" + // As determined by counting pixels on a photo. const vec2 subpx_ratio = vec2(0.296, 0.910); const vec2 notch_ratio = vec2(0.115, 0.166); -float rect_coverage(vec4 px_rect, vec4 rect) { - const vec2 bl = max(rect.xy, px_rect.xy); - const vec2 tr = min(rect.zw, px_rect.zw); - const vec2 coverage = max(tr - bl, 0.0); - return coverage.x * coverage.y; -} - float subpx_coverage(vec4 px_rect, vec2 subpx_orig, vec2 subpx_size, vec2 notch_size) { return rect_coverage(px_rect, vec4(subpx_orig, subpx_orig + subpx_size)) - diff --git a/misc/shaders/coverage/coverage.inc b/misc/shaders/coverage/coverage.inc new file mode 100644 index 00000000..542f22c4 --- /dev/null +++ b/misc/shaders/coverage/coverage.inc @@ -0,0 +1,10 @@ +// Computes intersection area between pixels and geometric shapes for perfect +// (analytical) anti-aliasing. +// Assumes that the pixel rectangle has area 1 to avoid normalization. + +float rect_coverage(vec4 px_rect, vec4 rect) { + const vec2 bl = max(rect.xy, px_rect.xy); + const vec2 tr = min(rect.zw, px_rect.zw); + const vec2 coverage = max(tr - bl, 0.0); + return coverage.x * coverage.y; +} diff --git a/misc/shaders/input_transform/input_transform.inc b/misc/shaders/input_transform/input_transform.inc index eb98ee90..f67290ff 100644 --- a/misc/shaders/input_transform/input_transform.inc +++ b/misc/shaders/input_transform/input_transform.inc @@ -1,6 +1,6 @@ /* - Input transformation library v1.2 by fishku - Copyright (C) 2023 + Input transformation library v1.3 by fishku + Copyright (C) 2023-2024 Public domain license (CC0) Apply cropping, scaling, and transformation operations to input viewport and @@ -25,6 +25,7 @@ Refactored from the version that used to be in the blur_fill shader. Changelog: + v1.3: Bug fixes and cleanup. v1.2: Rename to "input transform". Add translation option. v1.1: Add overscaling option. Unify parameters. v1.0: Initial conversion from blur_fill release. Add rotation support. @@ -40,31 +41,30 @@ vec4 get_input_corners(vec2 input_size, vec4 crop, uint rotation) { return vec4(crop.y, crop.x, input_size.x - crop.w, input_size.y - crop.z); } -// Get adjusted center in input pixel coordinate system. -vec2 get_input_center(vec2 input_size, vec4 crop, vec2 shift, uint rotation, +// Get adjusted center in input pixel (texel) coordinate system. +// Crop is in input pixels (texels). +// Shift is in output pixels. +vec2 get_input_center(vec2 input_size, vec2 output_size, vec2 scale_i2o, + vec4 crop, vec2 shift, uint rotation, float center_after_cropping) { crop = get_rotated_crop(crop, rotation); shift = get_rotated_vector(shift, rotation); - return (center_after_cropping > 0.5 - ? 0.5 * vec2(crop.y + input_size.x - crop.w, - crop.x + input_size.y - crop.z) - : vec2(0.49999) * input_size) - - shift; + // If input and output sizes have different parity, shift by 1/2 of an + // output pixel to avoid having input pixel (texel) edges on output pixel + // centers, which leads to all sorts of issues. + return 0.5 * (input_size + center_after_cropping * + vec2(crop.y - crop.w, crop.x - crop.z)) + + (0.5 * mod(input_size + output_size, 2.0) - shift) / scale_i2o; } -// Scaling from unit output to pixel input space. -vec2 get_scale_o2i(vec2 input_size, vec2 output_size, vec4 crop, uint rotation, +// Scaling from input to output space. +vec2 get_scale_i2o(vec2 input_size, vec2 output_size, vec4 crop, uint rotation, float center_after_cropping, float force_aspect_ratio, - vec2 aspect, vec2 force_integer_scaling, float overscale, - bool output_size_is_final_viewport_size) { + vec2 aspect, vec2 force_integer_scaling, float overscale) { crop = get_rotated_crop(crop, rotation); - if (output_size_is_final_viewport_size) { - output_size = get_rotated_size(output_size, rotation); - } aspect = get_rotated_size(aspect, rotation); // Aspect ratio before cropping. - // lambda_1 * input_pixels.x, lambda_2 * input_pixels.y, - // possibly corrected for forced aspect ratio + // Corrected for forced aspect ratio. aspect = (force_aspect_ratio < 0.5 ? output_size * input_size.yx : (aspect.x < 0.5 || aspect.y < 0.5 @@ -77,78 +77,60 @@ vec2 get_scale_o2i(vec2 input_size, vec2 output_size, vec4 crop, uint rotation, : 2.0 * vec2(min(crop.y, crop.w), min(crop.x, crop.z))); force_integer_scaling = get_rotated_size(force_integer_scaling, rotation); - float scale_x, scale_y; + vec2 scale; if (output_size.x / (input_size.x * aspect.x) < output_size.y / (input_size.y * aspect.y)) { // Scale will be limited by width. Calc x scale, then derive y scale // using aspect ratio. - scale_x = mix(output_size.x / input_size.x, + scale.x = mix(output_size.x / input_size.x, output_size.y * aspect.x / (input_size.y * aspect.y), overscale); - if (force_integer_scaling.x > 0.5 && scale_x > 1.0) { - scale_x = floor(scale_x); + if (force_integer_scaling.x > 0.5 && scale.x > 1.0) { + scale.x = floor(scale.x); } - scale_y = scale_x * aspect.y / aspect.x; - if (force_integer_scaling.y > 0.5 && scale_y > 1.0) { - scale_y = floor(scale_y); + scale.y = scale.x * aspect.y / aspect.x; + if (force_integer_scaling.y > 0.5 && scale.y > 1.0) { + scale.y = floor(scale.y); } } else { // Scale will be limited by height. - scale_y = mix(output_size.y / input_size.y, + scale.y = mix(output_size.y / input_size.y, output_size.x * aspect.y / (input_size.x * aspect.x), overscale); - if (force_integer_scaling.y > 0.5 && scale_y > 1.0) { - scale_y = floor(scale_y); + if (force_integer_scaling.y > 0.5 && scale.y > 1.0) { + scale.y = floor(scale.y); } - scale_x = scale_y * aspect.x / aspect.y; - if (force_integer_scaling.x > 0.5 && scale_x > 1.0) { - scale_x = floor(scale_x); + scale.x = scale.y * aspect.x / aspect.y; + if (force_integer_scaling.x > 0.5 && scale.x > 1.0) { + scale.x = floor(scale.x); } } - return output_size / vec2(scale_x, scale_y); + return scale; } -// 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. -// Version where scale is passed in. -vec2 o2i(vec2 x, vec2 input_size, vec4 crop, vec2 shift, uint rotation, - float center_after_cropping, vec2 scale_o2i) { - return (x - 0.49999) * scale_o2i + get_input_center(input_size, crop, shift, - rotation, - center_after_cropping); +vec2 transform(vec2 x, vec2 input_center, vec2 scale, vec2 output_center) { + return (x - input_center) * scale + output_center; } -// Version that computes scale. vec2 o2i(vec2 x, vec2 input_size, vec2 output_size, vec4 crop, vec2 shift, uint rotation, float center_after_cropping, float force_aspect_ratio, - vec2 aspect, vec2 force_integer_scaling, float overscale, - bool output_size_is_final_viewport_size) { - return o2i(x, input_size, crop, shift, rotation, center_after_cropping, - get_scale_o2i(input_size, output_size, crop, rotation, - center_after_cropping, force_aspect_ratio, aspect, - force_integer_scaling, overscale, - output_size_is_final_viewport_size)); + vec2 aspect, vec2 force_integer_scaling, float overscale) { + const vec2 scale_i2o = get_scale_i2o( + input_size, output_size, crop, rotation, center_after_cropping, + force_aspect_ratio, aspect, force_integer_scaling, overscale); + return transform(x, vec2(0.5), output_size / scale_i2o, + get_input_center(input_size, output_size, scale_i2o, crop, + shift, rotation, center_after_cropping)); } -// From pixel input to unit output space. -// Version where scale is passed in. -vec2 i2o(vec2 x, vec2 input_size, vec4 crop, vec2 shift, uint rotation, - float center_after_cropping, vec2 scale_o2i) { - return (x - get_input_center(input_size, crop, shift, rotation, - center_after_cropping)) / - scale_o2i + - 0.49999; -} - -// Version that computes scale. vec2 i2o(vec2 x, vec2 input_size, vec2 output_size, vec4 crop, vec2 shift, uint rotation, float center_after_cropping, float force_aspect_ratio, - vec2 aspect, vec2 force_integer_scaling, float overscale, - bool output_size_is_final_viewport_size) { - return i2o(x, input_size, crop, shift, rotation, center_after_cropping, - get_scale_o2i(input_size, output_size, crop, rotation, - center_after_cropping, force_aspect_ratio, aspect, - force_integer_scaling, overscale, - output_size_is_final_viewport_size)); + vec2 aspect, vec2 force_integer_scaling, float overscale) { + const vec2 scale_i2o = get_scale_i2o( + input_size, output_size, crop, rotation, center_after_cropping, + force_aspect_ratio, aspect, force_integer_scaling, overscale); + return transform(x, + get_input_center(input_size, output_size, scale_i2o, crop, + shift, rotation, center_after_cropping), + scale_i2o / output_size, vec2(0.5)); } diff --git a/misc/shaders/input_transform/parameters.inc b/misc/shaders/input_transform/parameters.inc index b8f35654..0c58b629 100644 --- a/misc/shaders/input_transform/parameters.inc +++ b/misc/shaders/input_transform/parameters.inc @@ -15,7 +15,7 @@ #pragma parameter OS_CROP_RIGHT "Overscan crop right" 0.0 0.0 1024.0 1.0 #pragma parameter MOVING_SETTINGS "= Moving parameters =" 0.0 0.0 1.0 1.0 -#pragma parameter SHIFT_H "Horizontal shift" 0.0 -1024.0 1024.0 0.5 -#pragma parameter SHIFT_V "Vertical shift" 0.0 -1024.0 1024.0 0.5 +#pragma parameter SHIFT_H "Horizontal shift" 0.0 -2048.0 2048.0 1.0 +#pragma parameter SHIFT_V "Vertical shift" 0.0 -2048.0 2048.0 1.0 #pragma parameter CENTER_AFTER_CROPPING "Center cropped area" 1.0 0.0 1.0 1.0 // clang-format on diff --git a/pixel-art-scaling/box_filter_aa_xform.slangp b/pixel-art-scaling/box_filter_aa_xform.slangp new file mode 100644 index 00000000..73a007fe --- /dev/null +++ b/pixel-art-scaling/box_filter_aa_xform.slangp @@ -0,0 +1,5 @@ +shaders = 1 + +shader0 = shaders/box_filter_aa/box_filter_aa_xform.slang +filter_linear0 = false +scale_type0 = viewport diff --git a/pixel-art-scaling/shaders/box_filter_aa/box_filter_aa_xform.slang b/pixel-art-scaling/shaders/box_filter_aa/box_filter_aa_xform.slang new file mode 100644 index 00000000..426e0049 --- /dev/null +++ b/pixel-art-scaling/shaders/box_filter_aa/box_filter_aa_xform.slang @@ -0,0 +1,136 @@ +#version 450 + +/* + Box Filter AA v1.0 by fishku + Copyright (C) 2024 + Public domain license (CC0) + + Branching-free anti-aliasing using pixel coverage. + + Changelog: + v1.0: Initial release. +*/ + +// clang-format off +#include "../../../misc/shaders/input_transform/parameters.inc" + +#include "../../../misc/shaders/coverage/coverage.inc" +#include "../../../misc/shaders/input_transform/input_transform.inc" +// clang-format on + +layout(push_constant) uniform Push { + vec4 SourceSize; + vec4 OutputSize; + uint Rotation; + // From input transform library, scaling section + float FORCE_ASPECT_RATIO; + float ASPECT_H; + float ASPECT_V; + float FORCE_INTEGER_SCALING_H; + float FORCE_INTEGER_SCALING_V; + float OVERSCALE; + // From input transform library, cropping section + float OS_CROP_TOP; + float OS_CROP_BOTTOM; + float OS_CROP_LEFT; + float OS_CROP_RIGHT; + // From input transform library, moving section + float SHIFT_H; + float SHIFT_V; + float CENTER_AFTER_CROPPING; +} +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 vec4 px_rect; +layout(location = 1) out vec4 input_corners; +layout(location = 2) out vec2 scale_i2o; +layout(location = 3) out vec2 input_center; +layout(location = 4) out vec2 output_center; +layout(location = 5) out vec2 tx_coord; +void main() { + gl_Position = global.MVP * Position; + + px_rect = vec4(TexCoord * param.OutputSize.xy - 0.5, + TexCoord * param.OutputSize.xy + 0.5); + const vec4 crop = vec4(param.OS_CROP_TOP, param.OS_CROP_LEFT, + param.OS_CROP_BOTTOM, param.OS_CROP_RIGHT); + scale_i2o = get_scale_i2o( + param.SourceSize.xy, param.OutputSize.xy, crop, param.Rotation, + param.CENTER_AFTER_CROPPING, param.FORCE_ASPECT_RATIO, + vec2(param.ASPECT_H, param.ASPECT_V), + vec2(param.FORCE_INTEGER_SCALING_H, param.FORCE_INTEGER_SCALING_V), + param.OVERSCALE); + const vec2 shift = vec2(param.SHIFT_H, param.SHIFT_V); + input_center = get_input_center(param.SourceSize.xy, param.OutputSize.xy, + scale_i2o, crop, shift, param.Rotation, + param.CENTER_AFTER_CROPPING); + tx_coord = transform(TexCoord, vec2(0.5), param.OutputSize.xy / scale_i2o, + input_center); + output_center = 0.5 * param.OutputSize.xy; + input_corners = + get_input_corners(param.SourceSize.xy, crop, param.Rotation); +} + +#pragma stage fragment +layout(location = 0) in vec4 px_rect; +layout(location = 1) in vec4 input_corners; +layout(location = 2) in vec2 scale_i2o; +layout(location = 3) in vec2 input_center; +layout(location = 4) in vec2 output_center; +layout(location = 5) in vec2 tx_coord; +layout(location = 0) out vec4 FragColor; +layout(set = 0, binding = 2) uniform sampler2D Source; + +void main() { + // Figure out 4 nearest texels in source texture. + // Clamp tx_coord for proper cropping. + vec2 tx_coord_i; + const vec2 tx_coord_f = + modf(clamp(tx_coord, input_corners.xy, input_corners.zw), tx_coord_i); + const vec2 tx_coord_off = step(vec2(0.5), tx_coord_f) * 2.0 - 1.0; + vec2 tx_origins[] = {tx_coord_i, tx_coord_i + vec2(tx_coord_off.x, 0.0), + tx_coord_i + vec2(0.0, tx_coord_off.y), + tx_coord_i + tx_coord_off}; + + // Sample. + // Apply square for fast "gamma correction". + vec3 samples[] = { + texture(Source, (tx_origins[0] + 0.5) * param.SourceSize.zw).rgb, + texture(Source, (tx_origins[1] + 0.5) * param.SourceSize.zw).rgb, + texture(Source, (tx_origins[2] + 0.5) * param.SourceSize.zw).rgb, + texture(Source, (tx_origins[3] + 0.5) * param.SourceSize.zw).rgb}; + samples[0] *= samples[0]; + samples[1] *= samples[1]; + samples[2] *= samples[2]; + samples[3] *= samples[3]; + + // Apply shader. + // Transform tx_origins into pixel output space. + tx_origins[0] = + transform(tx_origins[0], input_center, scale_i2o, output_center); + tx_origins[1] = + transform(tx_origins[1], input_center, scale_i2o, output_center); + tx_origins[2] = + transform(tx_origins[2], input_center, scale_i2o, output_center); + tx_origins[3] = + transform(tx_origins[3], input_center, scale_i2o, output_center); + + const vec3 res = + samples[0] * rect_coverage(px_rect, vec4(tx_origins[0], + tx_origins[0] + scale_i2o)) + + samples[1] * rect_coverage(px_rect, vec4(tx_origins[1], + tx_origins[1] + scale_i2o)) + + samples[2] * rect_coverage(px_rect, vec4(tx_origins[2], + tx_origins[2] + scale_i2o)) + + samples[3] * rect_coverage(px_rect, vec4(tx_origins[3], + tx_origins[3] + scale_i2o)); + + // Apply sqrt for fast "gamma correction". + FragColor = vec4(sqrt(res), 1.0); +} diff --git a/pixel-art-scaling/shaders/pixel_aa/parameters.inc b/pixel-art-scaling/shaders/pixel_aa/parameters.inc index 70b7510a..8c752827 100644 --- a/pixel-art-scaling/shaders/pixel_aa/parameters.inc +++ b/pixel-art-scaling/shaders/pixel_aa/parameters.inc @@ -1,7 +1,7 @@ // See pixel_aa.slang for copyright and other information. // clang-format off -#pragma parameter PIX_AA_SETTINGS "=== Pixel AA v1.5 settings ===" 0.0 0.0 1.0 1.0 +#pragma parameter PIX_AA_SETTINGS "=== Pixel AA v1.6 settings ===" 0.0 0.0 1.0 1.0 #pragma parameter PIX_AA_SHARP "Pixel AA sharpening amount" 1.5 0.0 2.0 0.05 #pragma parameter PIX_AA_GAMMA "Enable gamma-correct blending" 1.0 0.0 1.0 1.0 #pragma parameter PIX_AA_SUBPX "Enable subpixel AA" 0.0 0.0 1.0 1.0 diff --git a/pixel-art-scaling/shaders/pixel_aa/pixel_aa.slang b/pixel-art-scaling/shaders/pixel_aa/pixel_aa.slang index 8d909ae0..134a7e9d 100644 --- a/pixel-art-scaling/shaders/pixel_aa/pixel_aa.slang +++ b/pixel-art-scaling/shaders/pixel_aa/pixel_aa.slang @@ -1,8 +1,8 @@ #version 450 /* - Pixel AA v1.5 by fishku - Copyright (C) 2023 + Pixel AA v1.6 by fishku + Copyright (C) 2023-2024 Public domain license (CC0) Features: @@ -24,6 +24,7 @@ subpixel anti-aliasing, results are identical to the "pixellate" shader. Changelog: + v1.6: Update input transform library. v1.5: Upstream optimizations from GLSL port. Add free transform preset. v1.4: Enable subpixel sampling for all four pixel layout orientations, including rotated screens. diff --git a/pixel-art-scaling/shaders/pixel_aa/pixel_aa_xform.slang b/pixel-art-scaling/shaders/pixel_aa/pixel_aa_xform.slang index a5e5580d..402de996 100644 --- a/pixel-art-scaling/shaders/pixel_aa/pixel_aa_xform.slang +++ b/pixel-art-scaling/shaders/pixel_aa/pixel_aa_xform.slang @@ -44,37 +44,39 @@ global; #pragma stage vertex layout(location = 0) in vec4 Position; layout(location = 1) in vec2 TexCoord; -layout(location = 0) out vec2 tx_coord; -layout(location = 1) out vec2 tx_per_px; -layout(location = 2) out vec2 tx_to_uv; -layout(location = 3) out vec4 input_corners; +layout(location = 0) out vec4 input_corners; +layout(location = 1) out vec2 tx_coord; +layout(location = 2) out vec2 tx_per_px; +layout(location = 3) out vec2 tx_to_uv; void main() { gl_Position = global.MVP * Position; const vec4 crop = vec4(param.OS_CROP_TOP, param.OS_CROP_LEFT, param.OS_CROP_BOTTOM, param.OS_CROP_RIGHT); - const vec2 scale_o2i = get_scale_o2i( + const vec2 scale_i2o = get_scale_i2o( param.SourceSize.xy, param.OutputSize.xy, crop, param.Rotation, param.CENTER_AFTER_CROPPING, param.FORCE_ASPECT_RATIO, vec2(param.ASPECT_H, param.ASPECT_V), vec2(param.FORCE_INTEGER_SCALING_H, param.FORCE_INTEGER_SCALING_V), - param.OVERSCALE, - /* output_size_is_final_viewport_size = */ false); + param.OVERSCALE); const vec2 shift = vec2(param.SHIFT_H, param.SHIFT_V); - tx_coord = o2i(TexCoord, param.SourceSize.xy, crop, shift, param.Rotation, - param.CENTER_AFTER_CROPPING, scale_o2i); - tx_per_px = scale_o2i * param.OutputSize.zw; + const vec2 input_center = get_input_center( + param.SourceSize.xy, param.OutputSize.xy, scale_i2o, crop, shift, + param.Rotation, param.CENTER_AFTER_CROPPING); + tx_coord = transform(TexCoord, vec2(0.5), param.OutputSize.xy / scale_i2o, + input_center); + tx_per_px = 1.0 / scale_i2o; tx_to_uv = param.SourceSize.zw; input_corners = get_input_corners(param.SourceSize.xy, crop, param.Rotation); } #pragma stage fragment -layout(location = 0) in vec2 tx_coord; -layout(location = 1) in vec2 tx_per_px; -layout(location = 2) in vec2 tx_to_uv; -layout(location = 3) in vec4 input_corners; +layout(location = 0) in vec4 input_corners; +layout(location = 1) in vec2 tx_coord; +layout(location = 2) in vec2 tx_per_px; +layout(location = 3) in vec2 tx_to_uv; layout(location = 0) out vec4 FragColor; layout(set = 0, binding = 2) uniform sampler2D Source;