Add autocrop-koko shader

This commit is contained in:
Antonio Orefice 2024-04-22 10:44:25 +02:00
parent ca2c0223c5
commit d49bab8d0c
6 changed files with 387 additions and 0 deletions

View file

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

30
border/autocrop-koko.txt Normal file
View file

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

View file

@ -0,0 +1,39 @@
#version 450
///////////////////////////// GPL LICENSE NOTICE /////////////////////////////
//
// autocrop-koko
// Copyright (C) 2024 Antonio Orefice <kokoko3k@gmail.com>
//
// 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);
}

View file

@ -0,0 +1,199 @@
#version 450
///////////////////////////// GPL LICENSE NOTICE /////////////////////////////
//
// autocrop-koko
// Copyright (C) 2024 Antonio Orefice <kokoko3k@gmail.com>
//
// 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();
}

View file

@ -0,0 +1,45 @@
#version 450
///////////////////////////// GPL LICENSE NOTICE /////////////////////////////
//
// autocrop-koko
// Copyright (C) 2024 Antonio Orefice <kokoko3k@gmail.com>
//
// 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);
}

View file

@ -0,0 +1,52 @@
///////////////////////////// GPL LICENSE NOTICE /////////////////////////////
//
// autocrop-koko
// Copyright (C) 2024 Antonio Orefice <kokoko3k@gmail.com>
//
// 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