diff --git a/border/autocrop-koko.slangp b/border/autocrop-koko.slangp new file mode 100644 index 00000000..0414032e --- /dev/null +++ b/border/autocrop-koko.slangp @@ -0,0 +1,22 @@ +shaders = 3 + + shader0 = shaders/autocrop-koko/autocrop0_precut.slang + alias0 = "autocrop_precut" + filter_linear0 = false + scale_type0 = source + scale0 = 1.0 + wrap_mode0 = "clamp_to_border" + + shader1 = shaders/autocrop-koko/autocrop1_compute.slang + alias1 = "autocrop_compute" +float_framebuffer1 = true + filter_linear1 = false + scale_type1 = source + scale1 = 1.0 + wrap_mode1 = "clamp_to_border" + mipmap_input1 = "true" + + shader2 = shaders/autocrop-koko/autocrop2_display.slang + alias2 = "autocrop_display" + filter_linear2 = true + scale_type2 = viewport diff --git a/border/autocrop-koko.txt b/border/autocrop-koko.txt new file mode 100644 index 00000000..8e86ad9d --- /dev/null +++ b/border/autocrop-koko.txt @@ -0,0 +1,30 @@ +**Autocrop**: + Clears solid bars around the frame. + + Autocrop maximum amount: + The higher, the more solid borders wil be cropped around the image. + 0.3 means 30% + + Number of mandatory lines to crop: + The minimum lines to always crop; this is useful because sometimes + games have one or two "spurious" lines at the very edge of the screen that + won't allow autocrop to work at all. + This can be used to ignore them. + + Samples per frame: + Higher values makes the shader search more in a single frame for solid areas. + This leads to more accurate result in less time, however it will also stress the gpu more. + Fortunately even low/lighter values like 10 will work good if you're ok + in waiting 2..3 seconds for the final crop value to be found. + + Sample size: + Search multiple pixels at once, this provide a big performance boost, but less accuracy. + It means that some solid bar could remain around the image. + + Scene change treshold + When autocrop finds a maximum crop value, it only tries to crop more when the scene changes. + By lowering this value, you tell the shader to try higher the crop more often. + Use 0.0 is probably useful only to trigger a new search. + + Transition speed + This modulates the smoothness of the animation between various crop values. \ No newline at end of file diff --git a/border/shaders/autocrop-koko/autocrop0_precut.slang b/border/shaders/autocrop-koko/autocrop0_precut.slang new file mode 100644 index 00000000..1be9d8a4 --- /dev/null +++ b/border/shaders/autocrop-koko/autocrop0_precut.slang @@ -0,0 +1,39 @@ +#version 450 + +///////////////////////////// GPL LICENSE NOTICE ///////////////////////////// +// +// autocrop-koko +// Copyright (C) 2024 Antonio Orefice +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or any later version. +// +//////////////////////////////////////////////////////////////////////////////// + +#include "autocrop_config.inc" + +#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 Source; + +void main() { + vec2 co = vTexCoord; + if (AUTOCROP_MAX > 0.0) + co = clamp(co, params.OriginalSize.zw * AUTOCROP_MIN, + 1-params.OriginalSize.zw * AUTOCROP_MIN ); + + FragColor = vec4(texture(Source, co).rgb, 1.0); +} \ No newline at end of file diff --git a/border/shaders/autocrop-koko/autocrop1_compute.slang b/border/shaders/autocrop-koko/autocrop1_compute.slang new file mode 100644 index 00000000..6fb222a1 --- /dev/null +++ b/border/shaders/autocrop-koko/autocrop1_compute.slang @@ -0,0 +1,199 @@ +#version 450 + +///////////////////////////// GPL LICENSE NOTICE ///////////////////////////// +// +// autocrop-koko +// Copyright (C) 2024 Antonio Orefice +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or any later version. +// +//////////////////////////////////////////////////////////////////////////////// + +#include "autocrop_config.inc" + +#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 autocrop_precut; +layout(set = 0, binding = 3) uniform sampler2D autocrop_precutFeedback; +layout(set = 0, binding = 4) uniform sampler2D autocrop_computeFeedback; + + +#define RGB2GRAY_VEC3 vec3(0.299, 0.587, 0.114) +float rgb_to_gray(vec3 rgb) { + return dot(rgb, RGB2GRAY_VEC3); +} + +bool scene_changed( sampler2D pre_smp, sampler2D cur_smp, float threshold) { + + vec3 pre_mip = textureLod( pre_smp, vec2(0.5), 20.0).rgb; + vec3 cur_mip = textureLod( cur_smp, vec2(0.5), 20.0).rgb; + + float pre_v = rgb_to_gray(pre_mip); + float cur_v = rgb_to_gray(cur_mip); + + float diff_v = cur_v - pre_v; + + // fadein: if the previous image is almost black and + // image is fading in, trigger a scene scange + if (pre_v < 2.0/255.0) + if (pre_v < cur_v) + return true; + + float diff_v_abs = abs(diff_v); + + // if lum is different enough trigger scene change + if (diff_v_abs >= threshold) + return true; + + /* + // from blank screen to something else, trigger SC + // This compares different lod mipmaps from the same samplig point + // to guess if the screen is solid or not. + vec3 pre_mip_l20 = pre_mip; + vec3 pre_mip_l1 = textureLod( pre_smp, vec2(0.5), vLods.x).rgb; + vec3 pre_mip_l2 = textureLod( pre_smp, vec2(0.5), vLods.y).rgb; + + if ( pre_mip_l1 == pre_mip_l2 && pre_mip_l2 == pre_mip_l20 ) + return true; + */ + + return false; +} + +float quasirandom(float n, float dmin, float dmax) { + // https://extremelearning.com.au/unreasonable-effectiveness-of-quasirandom-sequences/ + // Returns a number between dmin and dmax which would ideally + // fills the range by iterating through n values. + // n has to be integer for this to work. + float x = fract(n*0.754877626895905); + return ( (dmax-dmin) * x ) + dmin; +} + +float get_next_autocrop_amount(float max_autocrop_amount) { + + //Implementation requires the line_step to be the same for vertical and horizontal sampling. + //This could be a bit inefficient tho. + const float size_max = max(params.OriginalSize.x,params.OriginalSize.y) ; + const float next_line_step = 1/size_max; + + //This is needed to avoid sampling edges which may return out out of range values. + const vec2 d = params.OriginalSize.zw * 0.5 * (1+AUTOCROP_MIN*2); + //Reference color, topleft. + vec3 ref = textureLod(autocrop_precut, vec2(d), 0.0).rgb; // FIXME, ref sample needs to be fuzzy too? + float ref_lum = ref.r + ref.g + ref.b ; + + //convert zoom back to maximum cropped lines + const float max_crop_lines = ( size_max * (max_autocrop_amount -1) ) / ( 2*max_autocrop_amount) ; + + //Sample top,bottom,left,right lines; stop when a line sum contain a different pixel color than the reference + for (float croppedlines = AUTOCROP_MIN ; croppedlines < max_crop_lines ; croppedlines++) { + + vec3 smp_sum; + float line_lum; + float fuzzy = AUTOCROP_SAMPLE_SIZE; + + //Top + smp_sum = vec3(0.0); + for ( float x = params.FrameCount ; x < params.FrameCount+AUTOCROP_SAMPLES ; x ++ ) { + smp_sum += textureLod(autocrop_precut, vec2( quasirandom(x, d.x, 1-d.x ), 0.0+ next_line_step*croppedlines), fuzzy).rgb; + } + + line_lum = (smp_sum.r + smp_sum.g + smp_sum.b) / (AUTOCROP_SAMPLES); + + if ( abs(ref_lum - line_lum) > AUTOCROP_TOLERANCE) + return size_max / (size_max - (croppedlines) * 2) ; + + //BOTTOM + smp_sum = vec3(0.0); + for ( float x = params.FrameCount ; x < params.FrameCount+AUTOCROP_SAMPLES ; x ++ ) { + smp_sum += textureLod(autocrop_precut, vec2( quasirandom(x, d.x, 1-d.x) , (1.0-eps)-next_line_step*croppedlines), fuzzy).rgb; + } + + line_lum = (smp_sum.r + smp_sum.g + smp_sum.b) / (AUTOCROP_SAMPLES); + + if (abs( ref_lum - line_lum) > AUTOCROP_TOLERANCE) { + return size_max / (size_max - (croppedlines) * 2) ; + } + + //LEFT + smp_sum = vec3(0.0); + for ( float y = params.FrameCount; y < params.FrameCount+AUTOCROP_SAMPLES; y ++ ) { + smp_sum += textureLod(autocrop_precut, vec2(0.0+next_line_step*croppedlines, quasirandom(y, d.y, 1-d.y)), fuzzy).rgb; + } + + line_lum = (smp_sum.r + smp_sum.g + smp_sum.b) / (AUTOCROP_SAMPLES); + + if (abs( ref_lum - line_lum) > AUTOCROP_TOLERANCE) + return size_max / (size_max - (croppedlines) * 2) ; + + //RIGHT + smp_sum = vec3(0.0); + for ( float y = params.FrameCount; y < params.FrameCount+AUTOCROP_SAMPLES; y ++ ) { + smp_sum += textureLod(autocrop_precut, vec2((1.0-eps)-next_line_step*croppedlines, quasirandom(y, d.y, 1-d.y)), fuzzy).rgb; + } + + line_lum = (smp_sum.r + smp_sum.g + smp_sum.b) / (AUTOCROP_SAMPLES); + + if (abs( ref_lum - line_lum) > AUTOCROP_TOLERANCE) + return size_max / (size_max - (croppedlines) * 2); + + } + + return max_autocrop_amount; +} + +float get_autocrop() { + // This return a value that will be used in the final pass to zoom the picture. + float previous_autocrop_amount = AUTOCROP_MAX+1; + if (params.FrameCount < 30.0) { + previous_autocrop_amount = AUTOCROP_MAX+1; //Good start point. + } else { + previous_autocrop_amount = texture(autocrop_computeFeedback, AUTOCROP_SAMPLING_POINT).a; + } + + // Reset crop if scene has changed? + float next_autocrop_amount; + + if (scene_changed( autocrop_precutFeedback, autocrop_precut, AUTOCROP_STEADINESS) ) { + //When a scene changes, we must release the maximum crop amount and find a new one. + + //For entrance effect: + //return AUTOCROP_MAX+1; //"entrance effect? + + //No frills way: + next_autocrop_amount = get_next_autocrop_amount(AUTOCROP_MAX+1); + return next_autocrop_amount; + + } else { + + previous_autocrop_amount = texture(autocrop_computeFeedback, AUTOCROP_SAMPLING_POINT).a; + next_autocrop_amount = get_next_autocrop_amount(previous_autocrop_amount); + float r = mix( previous_autocrop_amount , next_autocrop_amount, AUTOCROP_TRANSITION_SPEED); + return clamp(r, 1.0, AUTOCROP_MAX+1); // needed to sanitize output when Feedback is unavailable (eg: just switched autocrop on) + } +} + +bool is_first_inside_rect(vec2 point, vec4 rect) { + vec2 bounded = clamp(point, rect.xy, rect.zw); + return point == bounded; +} + +void main() { + if (is_first_inside_rect(vTexCoord, vec4(AUTOCROP_SAMPLING_POINT-0.01, AUTOCROP_SAMPLING_POINT+0.01))) + if (AUTOCROP_MAX > 0.001) + FragColor.a = get_autocrop(); +} \ No newline at end of file diff --git a/border/shaders/autocrop-koko/autocrop2_display.slang b/border/shaders/autocrop-koko/autocrop2_display.slang new file mode 100644 index 00000000..2ffe5cef --- /dev/null +++ b/border/shaders/autocrop-koko/autocrop2_display.slang @@ -0,0 +1,45 @@ +#version 450 + +///////////////////////////// GPL LICENSE NOTICE ///////////////////////////// +// +// autocrop-koko +// Copyright (C) 2024 Antonio Orefice +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or any later version. +// +//////////////////////////////////////////////////////////////////////////////// + +#include "autocrop_config.inc" + +#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 autocrop_precut; +layout(set = 0, binding = 3) uniform sampler2D autocrop_compute; + +vec2 zoom(vec2 co, float zoom_factor) { + return 0.5 + (co - 0.5) / zoom_factor; +} + +void main() { + vec2 co = vTexCoord; + if (AUTOCROP_MAX > 0.0) { + float z = texture(autocrop_compute, AUTOCROP_SAMPLING_POINT).a; + co = zoom(co, z); + } + + FragColor = vec4(texture(autocrop_precut, co).rgb, 1.0); +} \ No newline at end of file diff --git a/border/shaders/autocrop-koko/autocrop_config.inc b/border/shaders/autocrop-koko/autocrop_config.inc new file mode 100644 index 00000000..0f33d82e --- /dev/null +++ b/border/shaders/autocrop-koko/autocrop_config.inc @@ -0,0 +1,52 @@ +///////////////////////////// GPL LICENSE NOTICE ///////////////////////////// +// +// autocrop-koko +// Copyright (C) 2024 Antonio Orefice +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or any later version. +// +//////////////////////////////////////////////////////////////////////////////// + +layout(push_constant) uniform Push { + vec4 SourceSize; + vec4 OriginalSize; + vec4 OutputSize; + uint FrameCount; +} params; + + +layout(std140, set = 0, binding = 0) uniform UBO { + mat4 MVP; + float AUTOCROP_MAX; + float AUTOCROP_MIN; + float AUTOCROP_SAMPLES; + float AUTOCROP_SAMPLE_SIZE; + float AUTOCROP_TRANSITION_SPEED; + float AUTOCROP_STEADINESS; +} global; + + +#pragma parameter AUTOCROP_MAX "★ Autocrop: maximum amount" 25.0 0.0 0.5 0.01 +#pragma parameter AUTOCROP_MIN " Number of mandatory lines to crop" 1.0 0.0 10.0 1.0 +#pragma parameter AUTOCROP_SAMPLES " Samples per frame (faster response, higher gpu use)" 20.0 0.0 300.0 1.0 +#pragma parameter AUTOCROP_SAMPLE_SIZE " Sample size (big speedup, less accurate)" 2.0 0.0 5.0 1.0 +#pragma parameter AUTOCROP_STEADINESS " Scene change treshold (0 = continuous cropping)" 0.2 0.0 0.5 0.01 +#pragma parameter AUTOCROP_TRANSITION_SPEED " Transition speed" 0.1 0.05 1.0 0.05 + + +#define AUTOCROP_MAX global.AUTOCROP_MAX +#define AUTOCROP_MIN global.AUTOCROP_MIN +#define AUTOCROP_SAMPLES global.AUTOCROP_SAMPLES +#define AUTOCROP_SAMPLE_SIZE global.AUTOCROP_SAMPLE_SIZE +#define AUTOCROP_TRANSITION_SPEED global.AUTOCROP_TRANSITION_SPEED +#define AUTOCROP_STEADINESS global.AUTOCROP_STEADINESS + +//Coords holding the autocrop zoom value +#define AUTOCROP_SAMPLING_POINT vec2(0.125) + +//How much colors can differ to to be considered part of a solid area +#define AUTOCROP_TOLERANCE (1.0/255.0) + +#define eps 1e-5 \ No newline at end of file