diff --git a/Data/Sys/Shaders/integer_scaling.glsl b/Data/Sys/Shaders/integer_scaling.glsl new file mode 100644 index 0000000000..ff620ad359 --- /dev/null +++ b/Data/Sys/Shaders/integer_scaling.glsl @@ -0,0 +1,182 @@ +// Set aspect ratio to 'stretch' + +// Integer Scaling shader by One_More_Try / TryTwo +// Uses Sharp Bilinear from +// https://github.com/libretro/slang-shaders/blob/master/interpolation/shaders/sharp-bilinear.slang +// by Themaister, Public Domain license + +/* +[configuration] +[OptionBool] +GUIName = Please set aspect ratio to stretch +OptionName = ASPECT_MSG +DefaultValue = false + +[OptionBool] +GUIName = Use non-integer width +OptionName = WIDTH_UNLOCK +DefaultValue = false + +[OptionBool] +GUIName = Stretch width to window +OptionName = WIDTH_SKIP +DependentOption = WIDTH_UNLOCK +DefaultValue = false + +[OptionBool] +GUIName = Scale width to fit 4:3 +OptionName = WIDTH_43 +DependentOption = WIDTH_UNLOCK +DefaultValue = false + +[OptionBool] +GUIName = Scale width to fit 16:9 +OptionName = WIDTH_169 +DependentOption = WIDTH_UNLOCK +DefaultValue = false + +[OptionBool] +GUIName = Apply sharp bilinear for custom widths +OptionName = SHARP_BILINEAR +DefaultValue = false + +[OptionRangeFloat] +GUIName = Sharp bilinear factor (0 = auto) +OptionName = SHARP_PRESCALE +MinValue = 0.0 +MaxValue = 16.0 +StepAmount = 1.0 +DefaultValue = 0.0 + +[OptionBool] +GUIName = Manual scale - Set IR first +OptionName = MANUALSCALE +DefaultValue = false + +[OptionRangeFloat] +GUIName = Integer scale - No higher than IR +OptionName = INTEGER_SCALE +DependentOption = MANUALSCALE +MaxValue = 5.0 +MinValue = 1.0 +DefaultValue = 1.0 +StepAmount = 1.0 + +[OptionRangeFloat] +GUIName = Scale width +OptionName = WIDTH_SCALE +DependentOption = MANUALSCALE +MaxValue = 5.0 +MinValue = -2.0 +DefaultValue = 0.0 +StepAmount = 1.0 + +[OptionBool] +GUIName = Auto downscaling +OptionName = DOWNSCALE +DefaultValue = false + +[/configuration] +*/ + +void main() +{ + float4 c0 = float4(0.0, 0.0, 0.0, 0.0); + float2 scale = float2(1.0, 1.0); + float2 xfb_res = GetResolution(); + float2 win_res = GetWindowResolution(); + float2 coords = GetCoordinates(); + + // ratio is used to rescale the coords to the xfb size, which allows for integer scaling. + // ratio can then be multiplied by an integer to upscale/downscale, but upscale isn't supported by + // point-sampling. + float2 ratio = win_res / xfb_res; + + if (OptionEnabled(WIDTH_UNLOCK)) + { + if (OptionEnabled(WIDTH_SKIP)) + ratio.x = 1.0; + else if (OptionEnabled(WIDTH_43)) + ratio.x = win_res.x / (xfb_res.y * 4 / 3); + else if (OptionEnabled(WIDTH_169)) + ratio.x = win_res.x / (xfb_res.y * 16 / 9); + } + + if (OptionEnabled(MANUALSCALE)) + { + // There's no IR variable, so this guesses the IR, but may be off for some games. + float calc_ir = ceil(xfb_res.y / 500); + scale.y = calc_ir / GetOption(INTEGER_SCALE); + float manual_width = GetOption(WIDTH_SCALE); + + if (manual_width < 0.0) + scale.x = scale.y * (abs(manual_width) + 1); + else + scale.x = scale.y / (manual_width + 1); + + ratio = ratio * scale; + } + else if (OptionEnabled(DOWNSCALE) && (ratio.x < 1 || ratio.y < 1)) + { + scale.x = ceil(max(1.0 / ratio.y, 1.0 / ratio.x)); + scale.y = scale.x; + ratio = ratio * scale; + } + + // y and x are used to determine black bars vs drawable space. + float y = win_res.y - xfb_res.y / scale.y; + float y_top = (y / 2.0) * GetInvWindowResolution().y; + float y_bottom = (win_res.y - y / 2.0) * GetInvWindowResolution().y; + float yloc = (coords.y - y_top) * ratio.y; + + float x = win_res.x - xfb_res.x / scale.x; + + if (OptionEnabled(WIDTH_UNLOCK)) + { + if (OptionEnabled(WIDTH_SKIP)) + x = 0.0; + else if (OptionEnabled(WIDTH_43)) + x = win_res.x - xfb_res.y / scale.y * 4 / 3; + else if (OptionEnabled(WIDTH_169)) + x = win_res.x - xfb_res.y / scale.y * 16 / 9; + } + + float x_left = (x / 2.0) * GetInvWindowResolution().x; + float x_right = (win_res.x - x / 2.0) * GetInvWindowResolution().x; + float xloc = (coords.x - x_left) * ratio.x; + + if (OptionEnabled(SHARP_BILINEAR) && + (OptionEnabled(WIDTH_SKIP) || OptionEnabled(WIDTH_43) || OptionEnabled(WIDTH_169) || + (OptionEnabled(MANUALSCALE) && GetOption(WIDTH_SCALE) != 0.0))) + { + float texel = xloc * xfb_res.x; + float texel_floored = floor(texel); + float s = frac(texel); + float scale_sharp = GetOption(SHARP_PRESCALE); + + if (scale_sharp == 0) + { + if (OptionEnabled(WIDTH_43)) + scale_sharp = (4 / 3 * xfb_res.y / xfb_res.x); + else if (OptionEnabled(WIDTH_169)) + scale_sharp = (16 / 9 * xfb_res.y / xfb_res.x); + else + scale_sharp = ceil(win_res.x / xfb_res.x); + } + + float region_range = 0.5 - 0.5 / scale_sharp; + float center_dist = s - 0.5; + float f = (center_dist - clamp(center_dist, -region_range, region_range)) * scale_sharp + 0.5; + + float mod_texel = texel_floored + f; + xloc = mod_texel / xfb_res.x; + } + + if (coords.x >= x_left && x_right >= coords.x && coords.y >= y_top && y_bottom >= coords.y) + { + float2 sample_loc = float2(xloc, yloc); + c0 = SampleLocation(sample_loc); + } + + SetOutput(c0); +}