From 8524a42da5de1ffc9788e1ff53053b039c3ffd05 Mon Sep 17 00:00:00 2001 From: hunterk Date: Fri, 30 Jul 2021 08:24:20 -0500 Subject: [PATCH] Add crt-guest-advanced, update other crt-guest, update ntsc-adaptive (#183) * update crt-guest, add advanced * re-add some prematurely deleted GDV passes * fix ntsc res behavior * disable field-merging on 2-phase to allow MD rainbow artifacting * remove unused function --- crt/crt-guest-advanced-fast.slangp | 72 ++ crt/crt-guest-advanced-fastest.slangp | 39 + crt/crt-guest-advanced-hires.slangp | 78 ++ crt/crt-guest-advanced.slangp | 95 ++ crt/crt-guest-dr-venom2.slangp | 85 -- crt/shaders/guest/advanced/afterglow0.slang | 76 ++ .../guest/advanced/avg-lum-hires.slang | 87 ++ crt/shaders/guest/advanced/avg-lum-ntsc.slang | 110 +++ crt/shaders/guest/advanced/avg-lum.slang | 110 +++ .../guest/advanced/bloom_horizontal.slang | 101 ++ .../advanced/bloom_horizontal_ntsc.slang | 101 ++ .../guest/advanced/bloom_vertical.slang | 101 ++ .../guest/advanced/bloom_vertical_ntsc.slang | 101 ++ crt/shaders/guest/advanced/convert-ntsc.slang | 69 ++ .../advanced/crt-guest-advanced-hires.slang | 775 +++++++++++++++ .../advanced/crt-guest-advanced-ntsc.slang | 904 ++++++++++++++++++ .../guest/advanced/crt-guest-advanced.slang | 822 ++++++++++++++++ .../guest/advanced/custom-fast-sharpen.slang | 85 ++ .../guest/advanced/deconvergence.slang | 198 ++++ .../guest/advanced/gaussian_horizontal.slang | 99 ++ .../gaussian_vertical.slang} | 66 +- .../guest/advanced/linearize-hires.slang | 172 ++++ .../guest/advanced/linearize-ntsc.slang | 176 ++++ crt/shaders/guest/advanced/linearize.slang | 176 ++++ .../guest/advanced/lut/inv-trinitron-lut.png | Bin 0 -> 67306 bytes crt/shaders/guest/advanced/lut/nec-lut.png | Bin 0 -> 82816 bytes crt/shaders/guest/advanced/lut/ntsc-lut.png | Bin 0 -> 84693 bytes .../guest/advanced/lut/trinitron-lut.png | Bin 0 -> 90136 bytes .../advanced/pre-shaders-afterglow.slang | 356 +++++++ crt/shaders/guest/avg-lum0.slang | 63 -- crt/shaders/guest/crt-sm/blur_horiz-sm.slang | 4 +- crt/shaders/guest/crt-sm/blur_vert-sm.slang | 4 +- crt/shaders/guest/crt-sm/crt-guest-sm.slang | 44 +- crt/shaders/guest/fast/bloom_horizontal.slang | 103 ++ crt/shaders/guest/fast/bloom_vertical.slang | 102 ++ .../guest/fast/crt-guest-advanced-pass1.slang | 166 ++++ .../fast/crt-guest-advanced-pass1f.slang | 159 +++ .../guest/fast/crt-guest-advanced-pass2.slang | 601 ++++++++++++ .../fast/crt-guest-advanced-pass2f.slang | 633 ++++++++++++ crt/shaders/guest/fast/deconvergence-f.slang | 176 ++++ crt/shaders/guest/fast/linearize.slang | 176 ++++ crt/shaders/guest/fast/linearizef.slang | 129 +++ crt/shaders/guest/fast/pre-shaders.slang | 349 +++++++ crt/shaders/guest/fast/pre-shadersf.slang | 265 +++++ ntsc/shaders/ntsc-adaptive/ntsc-pass1.slang | 55 +- ntsc/shaders/ntsc-adaptive/ntsc-pass2.slang | 16 +- presets/crt-guest-advanced-ntsc.slangp | 134 +++ presets/crt-guest-dr-venom2-hires.slangp | 64 -- presets/crt-guest-dr-venom2-ntsc.slangp | 84 -- 49 files changed, 8026 insertions(+), 355 deletions(-) create mode 100644 crt/crt-guest-advanced-fast.slangp create mode 100644 crt/crt-guest-advanced-fastest.slangp create mode 100644 crt/crt-guest-advanced-hires.slangp create mode 100644 crt/crt-guest-advanced.slangp delete mode 100644 crt/crt-guest-dr-venom2.slangp create mode 100644 crt/shaders/guest/advanced/afterglow0.slang create mode 100644 crt/shaders/guest/advanced/avg-lum-hires.slang create mode 100644 crt/shaders/guest/advanced/avg-lum-ntsc.slang create mode 100644 crt/shaders/guest/advanced/avg-lum.slang create mode 100644 crt/shaders/guest/advanced/bloom_horizontal.slang create mode 100644 crt/shaders/guest/advanced/bloom_horizontal_ntsc.slang create mode 100644 crt/shaders/guest/advanced/bloom_vertical.slang create mode 100644 crt/shaders/guest/advanced/bloom_vertical_ntsc.slang create mode 100644 crt/shaders/guest/advanced/convert-ntsc.slang create mode 100644 crt/shaders/guest/advanced/crt-guest-advanced-hires.slang create mode 100644 crt/shaders/guest/advanced/crt-guest-advanced-ntsc.slang create mode 100644 crt/shaders/guest/advanced/crt-guest-advanced.slang create mode 100644 crt/shaders/guest/advanced/custom-fast-sharpen.slang create mode 100644 crt/shaders/guest/advanced/deconvergence.slang create mode 100644 crt/shaders/guest/advanced/gaussian_horizontal.slang rename crt/shaders/guest/{fast/smoothing - kopija.slang => advanced/gaussian_vertical.slang} (56%) create mode 100644 crt/shaders/guest/advanced/linearize-hires.slang create mode 100644 crt/shaders/guest/advanced/linearize-ntsc.slang create mode 100644 crt/shaders/guest/advanced/linearize.slang create mode 100644 crt/shaders/guest/advanced/lut/inv-trinitron-lut.png create mode 100644 crt/shaders/guest/advanced/lut/nec-lut.png create mode 100644 crt/shaders/guest/advanced/lut/ntsc-lut.png create mode 100644 crt/shaders/guest/advanced/lut/trinitron-lut.png create mode 100644 crt/shaders/guest/advanced/pre-shaders-afterglow.slang delete mode 100644 crt/shaders/guest/avg-lum0.slang create mode 100644 crt/shaders/guest/fast/bloom_horizontal.slang create mode 100644 crt/shaders/guest/fast/bloom_vertical.slang create mode 100644 crt/shaders/guest/fast/crt-guest-advanced-pass1.slang create mode 100644 crt/shaders/guest/fast/crt-guest-advanced-pass1f.slang create mode 100644 crt/shaders/guest/fast/crt-guest-advanced-pass2.slang create mode 100644 crt/shaders/guest/fast/crt-guest-advanced-pass2f.slang create mode 100644 crt/shaders/guest/fast/deconvergence-f.slang create mode 100644 crt/shaders/guest/fast/linearize.slang create mode 100644 crt/shaders/guest/fast/linearizef.slang create mode 100644 crt/shaders/guest/fast/pre-shaders.slang create mode 100644 crt/shaders/guest/fast/pre-shadersf.slang create mode 100644 presets/crt-guest-advanced-ntsc.slangp delete mode 100644 presets/crt-guest-dr-venom2-hires.slangp delete mode 100644 presets/crt-guest-dr-venom2-ntsc.slangp diff --git a/crt/crt-guest-advanced-fast.slangp b/crt/crt-guest-advanced-fast.slangp new file mode 100644 index 0000000..f705d6c --- /dev/null +++ b/crt/crt-guest-advanced-fast.slangp @@ -0,0 +1,72 @@ +shaders = 9 + +textures = "SamplerLUT1;SamplerLUT2;SamplerLUT3;SamplerLUT4" +SamplerLUT1 = shaders/guest/advanced/lut/trinitron-lut.png +SamplerLUT1_linear = true +SamplerLUT2 = shaders/guest/advanced/lut/inv-trinitron-lut.png +SamplerLUT2_linear = true +SamplerLUT3 = shaders/guest/advanced/lut/nec-lut.png +SamplerLUT3_linear = true +SamplerLUT4 = shaders/guest/advanced/lut/ntsc-lut.png +SamplerLUT4_linear = true + +shader0 = ../stock.slang +filter_linear0 = false +scale_type0 = source +scale0 = 1.0 + +shader1 = ../stock.slang +filter_linear1 = false +scale_type1 = source +scale1 = 1.0 +alias1 = StockPass + +shader2 = shaders/guest/fast/pre-shaders.slang +filter_linear2 = true +scale_type2 = source +scale2 = 1.0 +alias2 = PrePass + +shader3 = shaders/guest/fast/linearize.slang +filter_linear3 = true +scale_type3 = source +scale3 = 1.0 +float_framebuffer3 = true +alias3 = LinearizePass + +shader4 = shaders/guest/fast/bloom_horizontal.slang +filter_linear4 = true +scale_type_x4 = absolute +scale_x4 = 800.0 +scale_type_y4 = source +scale_y4 = 1.0 +float_framebuffer4 = true + +shader5 = shaders/guest/fast/bloom_vertical.slang +filter_linear5 = true +scale_type_x5 = source +scale_x5 = 1.0 +scale_type_y5 = source +scale_y5 = 2.0 +float_framebuffer5 = true +alias5 = BloomPass + +shader6 = shaders/guest/fast/crt-guest-advanced-pass1.slang +filter_linear6 = true +scale_type_x6 = viewport +scale_x6 = 1.0 +scale_type_y6 = source +scale_y6 = 0.5 +float_framebuffer6 = true + +shader7 = shaders/guest/fast/crt-guest-advanced-pass2.slang +filter_linear7 = true +scale_type7 = viewport +scale_x7 = 1.0 +scale_y7 = 1.0 + +shader8 = shaders/guest/fast/deconvergence-f.slang +filter_linear8 = true +scale_type8 = viewport +scale_x8 = 1.0 +scale_y8 = 1.0 diff --git a/crt/crt-guest-advanced-fastest.slangp b/crt/crt-guest-advanced-fastest.slangp new file mode 100644 index 0000000..f5f124b --- /dev/null +++ b/crt/crt-guest-advanced-fastest.slangp @@ -0,0 +1,39 @@ +shaders = 4 + +textures = "SamplerLUT1;SamplerLUT2;SamplerLUT3;SamplerLUT4" +SamplerLUT1 = shaders/guest/advanced/lut/trinitron-lut.png +SamplerLUT1_linear = true +SamplerLUT2 = shaders/guest/advanced/lut/inv-trinitron-lut.png +SamplerLUT2_linear = true +SamplerLUT3 = shaders/guest/advanced/lut/nec-lut.png +SamplerLUT3_linear = true +SamplerLUT4 = shaders/guest/advanced/lut/ntsc-lut.png +SamplerLUT4_linear = true + +shader0 = shaders/guest/fast/pre-shadersf.slang +filter_linear0 = true +scale_type0 = source +scale0 = 1.0 +alias0 = PrePassDontChange + +shader1 = shaders/guest/fast/linearizef.slang +filter_linear1 = true +scale_type1 = source +scale1 = 1.0 +float_framebuffer1 = true +alias1 = LinearizePass + +shader2 = shaders/guest/fast/crt-guest-advanced-pass1f.slang +filter_linear2 = true +scale_type_x2 = viewport +scale_x2 = 1.0 +scale_type_y2 = source +scale_y2 = 1.0 +float_framebuffer2 = true + +shader3 = shaders/guest/fast/crt-guest-advanced-pass2f.slang +filter_linear3 = true +scale_type3 = viewport +scale_x3 = 1.0 +scale_y3 = 1.0 +alias3 = Pass2 diff --git a/crt/crt-guest-advanced-hires.slangp b/crt/crt-guest-advanced-hires.slangp new file mode 100644 index 0000000..d92ee91 --- /dev/null +++ b/crt/crt-guest-advanced-hires.slangp @@ -0,0 +1,78 @@ +shaders = 10 + +shader0 = ../stock.slang +filter_linear0 = false +scale_type0 = source +scale0 = 1.0 + +shader1 = ../stock.slang +filter_linear1 = false +scale_type1 = source +scale1 = 1.0 +alias1 = StockPass + +shader2 = shaders/guest/advanced/afterglow0.slang +filter_linear2 = false +scale_type2 = source +scale2 = 1.0 +alias2 = AfterglowPass + +shader3 = shaders/guest/advanced/pre-shaders-afterglow.slang +filter_linear3 = false +scale_type3 = source +scale3 = 1.0 +mipmap_input3 = true +alias3 = PrePass + +textures = "SamplerLUT1;SamplerLUT2;SamplerLUT3;SamplerLUT4" +SamplerLUT1 = shaders/guest/advanced/lut/trinitron-lut.png +SamplerLUT1_linear = true +SamplerLUT2 = shaders/guest/advanced/lut/inv-trinitron-lut.png +SamplerLUT2_linear = true +SamplerLUT3 = shaders/guest/advanced/lut/nec-lut.png +SamplerLUT3_linear = true +SamplerLUT4 = shaders/guest/advanced/lut/ntsc-lut.png +SamplerLUT4_linear = true + +shader4 = shaders/guest/advanced/avg-lum-hires.slang +filter_linear4 = true +scale_type4 = source +scale4 = 1.0 +mipmap_input4 = true +alias4 = AvgLumPass + +shader5 = shaders/guest/advanced/linearize-hires.slang +filter_linear5 = true +scale_type5 = source +scale5 = 1.0 +alias5 = LinearizePass +float_framebuffer5 = true + +shader6 = shaders/guest/advanced/bloom_horizontal_ntsc.slang +filter_linear6 = true +scale_type_x6 = absolute +scale_x6 = 800.0 +scale_type_y6 = source +scale_y6 = 1.0 +float_framebuffer6 = true + +shader7 = shaders/guest/advanced/bloom_vertical_ntsc.slang +filter_linear7 = true +scale_type_x7 = absolute +scale_x7 = 800.0 +scale_type_y7 = absolute +scale_y7 = 600.0 +float_framebuffer7 = true +alias7 = GlowPass + +shader8 = shaders/guest/advanced/crt-guest-advanced-hires.slang +filter_linear8 = true +scale_type8 = viewport +scale_x8 = 1.0 +scale_y8 = 1.0 + +shader9 = shaders/guest/advanced/deconvergence.slang +filter_linear9 = true +scale_type9 = viewport +scale_x9 = 1.0 +scale_y9 = 1.0 diff --git a/crt/crt-guest-advanced.slangp b/crt/crt-guest-advanced.slangp new file mode 100644 index 0000000..49f4a92 --- /dev/null +++ b/crt/crt-guest-advanced.slangp @@ -0,0 +1,95 @@ +shaders = 12 + +shader0 = ../stock.slang +filter_linear0 = false +scale_type0 = source +scale0 = 1.0 + +shader1 = ../stock.slang +filter_linear1 = false +scale_type1 = source +scale1 = 1.0 +alias1 = StockPass + +shader2 = shaders/guest/advanced/afterglow0.slang +filter_linear2 = false +scale_type2 = source +scale2 = 1.0 +alias2 = AfterglowPass + +shader3 = shaders/guest/advanced/pre-shaders-afterglow.slang +filter_linear3 = false +scale_type3 = source +mipmap_input3 = true +scale3 = 1.0 +alias3 = PrePass + +textures = "SamplerLUT1;SamplerLUT2;SamplerLUT3;SamplerLUT4" +SamplerLUT1 = shaders/guest/advanced/lut/trinitron-lut.png +SamplerLUT1_linear = true +SamplerLUT2 = shaders/guest/advanced/lut/inv-trinitron-lut.png +SamplerLUT2_linear = true +SamplerLUT3 = shaders/guest/advanced/lut/nec-lut.png +SamplerLUT3_linear = true +SamplerLUT4 = shaders/guest/advanced/lut/ntsc-lut.png +SamplerLUT4_linear = true + +shader4 = shaders/guest/advanced/avg-lum.slang +filter_linear4 = true +scale_type4 = source +scale4 = 1.0 +mipmap_input4 = true +alias4 = AvgLumPass + +shader5 = shaders/guest/advanced/linearize.slang +filter_linear5 = true +scale_type5 = source +scale5 = 1.0 +alias5 = LinearizePass +float_framebuffer5 = true + +shader6 = shaders/guest/advanced/gaussian_horizontal.slang +filter_linear6 = true +scale_type_x6 = absolute +scale_x6 = 800.0 +scale_type_y6 = source +scale_y6 = 1.0 +float_framebuffer6 = true + +shader7 = shaders/guest/advanced/gaussian_vertical.slang +filter_linear7 = true +scale_type_x7 = absolute +scale_x7 = 800.0 +scale_type_y7 = absolute +scale_y7 = 600.0 +float_framebuffer7 = true +alias7 = GlowPass + +shader8 = shaders/guest/advanced/bloom_horizontal.slang +filter_linear8 = true +scale_type_x8 = absolute +scale_x8 = 800.0 +scale_type_y8 = absolute +scale_y8 = 600.0 +float_framebuffer8 = true + +shader9 = shaders/guest/advanced/bloom_vertical.slang +filter_linear9 = true +scale_type_x9 = source +scale_x9 = 1.0 +scale_type_y9 = source +scale_y9 = 1.0 +float_framebuffer9 = true +alias9 = BloomPass + +shader10 = shaders/guest/advanced/crt-guest-advanced.slang +filter_linear10 = true +scale_type10 = viewport +scale_x10 = 1.0 +scale_y10 = 1.0 + +shader11 = shaders/guest/advanced/deconvergence.slang +filter_linear11 = true +scale_type11 = viewport +scale_x11 = 1.0 +scale_y11 = 1.0 diff --git a/crt/crt-guest-dr-venom2.slangp b/crt/crt-guest-dr-venom2.slangp deleted file mode 100644 index cc82eaa..0000000 --- a/crt/crt-guest-dr-venom2.slangp +++ /dev/null @@ -1,85 +0,0 @@ -shaders = 11 - -shader0 = ../stock.slang -filter_linear0 = false -scale_type0 = source -scale0 = 1.0 -alias0 = StockPass - -shader1 = shaders/guest/crt-gdv-new/afterglow0.slang -filter_linear1 = false -scale_type1 = source -scale1 = 1.0 -alias1 = AfterglowPass - -shader2 = shaders/guest/crt-gdv-new/pre-shaders-afterglow.slang -filter_linear2 = false -scale_type2 = source -scale2 = 1.0 -alias2 = PrePass - -textures = "SamplerLUT1;SamplerLUT2;SamplerLUT3;SamplerLUT4" -SamplerLUT1 = shaders/guest/lut/sony_trinitron1.png -SamplerLUT1_linear = true -SamplerLUT2 = shaders/guest/lut/sony_trinitron2.png -SamplerLUT2_linear = true -SamplerLUT3 = shaders/guest/lut/other1.png -SamplerLUT3_linear = true -SamplerLUT4 = shaders/guest/lut/custom_lut.png -SamplerLUT4_linear = true - -shader3 = shaders/guest/crt-gdv-new/avg-lum.slang -filter_linear3 = true -scale_type3 = source -scale3 = 1.0 -mipmap_input3 = true -alias3 = AvgLumPass - -shader4 = shaders/guest/crt-gdv-new/linearize.slang -filter_linear4 = true -scale_type4 = source -scale4 = 1.0 -alias4 = LinearizePass -float_framebuffer4 = true # comment this line for max precision - -shader5 = shaders/guest/crt-gdv-new/gaussian_horizontal.slang -filter_linear5 = true -scale_type_x5 = viewport -scale_x5 = 0.5 -scale_type_y5 = source -scale_y5 = 1.0 - -shader6 = shaders/guest/crt-gdv-new/gaussian_vertical.slang -filter_linear6 = true -scale_type_x6 = viewport -scale_x6 = 0.5 -scale_type_y6 = viewport -scale_y6 = 0.5 -alias6 = GlowPass - -shader7 = shaders/guest/crt-gdv-new/bloom_horizontal.slang -filter_linear7 = true -scale_type_x7 = source -scale_x7 = 1.0 -scale_type_y7 = source -scale_y7 = 1.0 - -shader8 = shaders/guest/crt-gdv-new/bloom_vertical.slang -filter_linear8 = true -scale_type_x8 = source -scale_x8 = 1.0 -scale_type_y8 = source -scale_y8 = 1.0 -alias8 = BloomPass - -shader9 = shaders/guest/crt-gdv-new/crt-guest-dr-venom2.slang -filter_linear9 = true -scale_type9 = viewport -scale_x9 = 1.0 -scale_y9 = 1.0 - -shader10 = shaders/guest/crt-gdv-new/deconvergence.slang -filter_linear10 = true -scale_type10 = viewport -scale_x10 = 1.0 -scale_y10 = 1.0 diff --git a/crt/shaders/guest/advanced/afterglow0.slang b/crt/shaders/guest/advanced/afterglow0.slang new file mode 100644 index 0000000..ad482a2 --- /dev/null +++ b/crt/shaders/guest/advanced/afterglow0.slang @@ -0,0 +1,76 @@ +#version 450 + +/* + Phosphor Afterglow Shader pass 0 + + Copyright (C) 2020 guest(r) - guest.r@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 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +layout(push_constant) uniform Push +{ + float PR, PG, PB; +} params; + +#pragma parameter bogus_afterglow "[ AFTERGLOW SETTINGS ]:" 0.0 0.0 1.0 1.0 +#pragma parameter PR " Persistence Red" 0.25 0.0 0.50 0.01 +#pragma parameter PG " Persistence Green" 0.25 0.0 0.50 0.01 +#pragma parameter PB " Persistence Blue" 0.25 0.0 0.50 0.01 + +#define PR params.PR +#define PG params.PG +#define PB params.PB + +#define COMPAT_TEXTURE(c,d) texture(c,d) + +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 OriginalHistory0; +layout(set = 0, binding = 3) uniform sampler2D AfterglowPassFeedback; + +#define TEX0 vTexCoord + + +void main() +{ + vec3 color = COMPAT_TEXTURE(OriginalHistory0, TEX0.xy).rgb; + vec3 accumulate = COMPAT_TEXTURE(AfterglowPassFeedback, TEX0.xy).rgb; + + float w = 1.0; + if ((color.r + color.g + color.b < 1.5/255.0)) { w = 0.0; } + + vec3 result = mix( max(mix(color, accumulate, 0.49 + vec3(PR, PG, PB))- 3.0/255.0, 0.0), color, w); + + FragColor = vec4(result, w); +} \ No newline at end of file diff --git a/crt/shaders/guest/advanced/avg-lum-hires.slang b/crt/shaders/guest/advanced/avg-lum-hires.slang new file mode 100644 index 0000000..e446989 --- /dev/null +++ b/crt/shaders/guest/advanced/avg-lum-hires.slang @@ -0,0 +1,87 @@ +#version 450 + +/* + Average Luminance Shader + + Copyright (C) 2018-2020 guest(r) - guest.r@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 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + Thanks to HunterK for the mipmap hint. :D +*/ + +layout(push_constant) uniform Push +{ + uint FrameCount; + vec4 SourceSize; + float lsmooth; +} params; + +#pragma parameter lsmooth "Raster Bloom Effect Smoothing" 0.70 0.50 0.99 0.01 + +#define lsmooth params.lsmooth + +#define COMPAT_TEXTURE(c,d) texture(c,d) +#define SourceSize params.SourceSize +#define InputSize SourceSize +#define TEX0 vTexCoord + +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 * 1.0001; +} + +#pragma stage fragment +layout(location = 0) in vec2 vTexCoord; +layout(location = 0) out vec4 FragColor; +layout(set = 0, binding = 2) uniform sampler2D PrePass; +layout(set = 0, binding = 3) uniform sampler2D AvgLumPassFeedback; + + +void main() +{ + if (vTexCoord.x > 0.2 || vTexCoord.y > 0.2) discard; + + float m = max(log2(SourceSize.x), log2(SourceSize.y)); + m = floor(max(m, 1.0)); + + float ltotal = 0.0; + + ltotal+= length(textureLod(PrePass, vec2(0.25, 0.25), m).rgb); + ltotal+= length(textureLod(PrePass, vec2(0.25, 0.75), m).rgb); + ltotal+= length(textureLod(PrePass, vec2(0.75, 0.25), m).rgb); + ltotal+= length(textureLod(PrePass, vec2(0.75, 0.75), m).rgb); + + ltotal*=0.25; + + ltotal = pow(0.577350269 * ltotal, 0.65); + + float lhistory = texture(AvgLumPassFeedback, vec2(0.1,0.1)).a; + + ltotal = mix(ltotal, lhistory, lsmooth); + + FragColor = vec4(ltotal); +} \ No newline at end of file diff --git a/crt/shaders/guest/advanced/avg-lum-ntsc.slang b/crt/shaders/guest/advanced/avg-lum-ntsc.slang new file mode 100644 index 0000000..86da9b9 --- /dev/null +++ b/crt/shaders/guest/advanced/avg-lum-ntsc.slang @@ -0,0 +1,110 @@ +#version 450 + +/* + Average Luminance Shader, Smart Edge Interpolation Coefficients Calculation + + Copyright (C) 2018-2020 guest(r) - guest.r@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 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + Thanks to HunterK for the mipmap hint. :D +*/ + +layout(push_constant) uniform Push +{ + uint FrameCount; + vec4 SourceSize; + float lsmooth; + float sth; +} params; + +#pragma parameter lsmooth "Raster Bloom Effect Smoothing" 0.70 0.50 0.99 0.01 + +#define lsmooth params.lsmooth + +#define COMPAT_TEXTURE(c,d) texture(c,d) +#define SourceSize params.SourceSize +#define TEX0 vTexCoord + +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 * 1.0001; +} + +#pragma stage fragment +layout(location = 0) in vec2 vTexCoord; +layout(location = 0) out vec4 FragColor; +layout(set = 0, binding = 2) uniform sampler2D Source; +layout(set = 0, binding = 3) uniform sampler2D AvgLumPassFeedback; + +// Reference: http://www.compuphase.com/cmetric.htm +// Reference: ScaleFX, author Sp00kyFox + +float dist(vec3 A, vec3 B) +{ + float r = 0.5 * (A.r + B.r); + vec3 d = A - B; + vec3 c = vec3(2. + r, 4., 3. - r); + + return sqrt(dot(c*d, d)) / 3.; +} + +void main() +{ + float m = max(log2(SourceSize.x), log2(SourceSize.y)); + m = floor(max(m, 1.0)); + + vec2 dx = vec2(1.0/SourceSize.x, 0.0); + vec2 dy = vec2(0.0, 1.0/SourceSize.y); + vec2 y2 = 2.0*dy; + vec2 x2 = 2.0*dx; + + float ltotal = 0.0; + + ltotal+= length(textureLod(Source, vec2(0.25, 0.25), m).rgb); + ltotal+= length(textureLod(Source, vec2(0.25, 0.75), m).rgb); + ltotal+= length(textureLod(Source, vec2(0.75, 0.25), m).rgb); + ltotal+= length(textureLod(Source, vec2(0.75, 0.75), m).rgb); + + ltotal*=0.25; + + ltotal = pow(0.577350269 * ltotal, 0.65); + + float lhistory = texture(AvgLumPassFeedback, vec2(0.5,0.5)).a; + + ltotal = mix(ltotal, lhistory, lsmooth); + + vec3 l1 = COMPAT_TEXTURE(Source, TEX0.xy ).rgb; + vec3 r1 = COMPAT_TEXTURE(Source, TEX0.xy +dx ).rgb; + vec3 l2 = COMPAT_TEXTURE(Source, TEX0.xy -dx ).rgb; + vec3 r2 = COMPAT_TEXTURE(Source, TEX0.xy +x2 ).rgb; + + float c1 = dist(l2,l1); + float c2 = dist(l1,r1); + float c3 = dist(r2,r1); + + FragColor = vec4(c1,c2,c3,ltotal); +} \ No newline at end of file diff --git a/crt/shaders/guest/advanced/avg-lum.slang b/crt/shaders/guest/advanced/avg-lum.slang new file mode 100644 index 0000000..86da9b9 --- /dev/null +++ b/crt/shaders/guest/advanced/avg-lum.slang @@ -0,0 +1,110 @@ +#version 450 + +/* + Average Luminance Shader, Smart Edge Interpolation Coefficients Calculation + + Copyright (C) 2018-2020 guest(r) - guest.r@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 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + Thanks to HunterK for the mipmap hint. :D +*/ + +layout(push_constant) uniform Push +{ + uint FrameCount; + vec4 SourceSize; + float lsmooth; + float sth; +} params; + +#pragma parameter lsmooth "Raster Bloom Effect Smoothing" 0.70 0.50 0.99 0.01 + +#define lsmooth params.lsmooth + +#define COMPAT_TEXTURE(c,d) texture(c,d) +#define SourceSize params.SourceSize +#define TEX0 vTexCoord + +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 * 1.0001; +} + +#pragma stage fragment +layout(location = 0) in vec2 vTexCoord; +layout(location = 0) out vec4 FragColor; +layout(set = 0, binding = 2) uniform sampler2D Source; +layout(set = 0, binding = 3) uniform sampler2D AvgLumPassFeedback; + +// Reference: http://www.compuphase.com/cmetric.htm +// Reference: ScaleFX, author Sp00kyFox + +float dist(vec3 A, vec3 B) +{ + float r = 0.5 * (A.r + B.r); + vec3 d = A - B; + vec3 c = vec3(2. + r, 4., 3. - r); + + return sqrt(dot(c*d, d)) / 3.; +} + +void main() +{ + float m = max(log2(SourceSize.x), log2(SourceSize.y)); + m = floor(max(m, 1.0)); + + vec2 dx = vec2(1.0/SourceSize.x, 0.0); + vec2 dy = vec2(0.0, 1.0/SourceSize.y); + vec2 y2 = 2.0*dy; + vec2 x2 = 2.0*dx; + + float ltotal = 0.0; + + ltotal+= length(textureLod(Source, vec2(0.25, 0.25), m).rgb); + ltotal+= length(textureLod(Source, vec2(0.25, 0.75), m).rgb); + ltotal+= length(textureLod(Source, vec2(0.75, 0.25), m).rgb); + ltotal+= length(textureLod(Source, vec2(0.75, 0.75), m).rgb); + + ltotal*=0.25; + + ltotal = pow(0.577350269 * ltotal, 0.65); + + float lhistory = texture(AvgLumPassFeedback, vec2(0.5,0.5)).a; + + ltotal = mix(ltotal, lhistory, lsmooth); + + vec3 l1 = COMPAT_TEXTURE(Source, TEX0.xy ).rgb; + vec3 r1 = COMPAT_TEXTURE(Source, TEX0.xy +dx ).rgb; + vec3 l2 = COMPAT_TEXTURE(Source, TEX0.xy -dx ).rgb; + vec3 r2 = COMPAT_TEXTURE(Source, TEX0.xy +x2 ).rgb; + + float c1 = dist(l2,l1); + float c2 = dist(l1,r1); + float c3 = dist(r2,r1); + + FragColor = vec4(c1,c2,c3,ltotal); +} \ No newline at end of file diff --git a/crt/shaders/guest/advanced/bloom_horizontal.slang b/crt/shaders/guest/advanced/bloom_horizontal.slang new file mode 100644 index 0000000..72af8f4 --- /dev/null +++ b/crt/shaders/guest/advanced/bloom_horizontal.slang @@ -0,0 +1,101 @@ +#version 450 + +/* + Gaussian blur - horizontal pass, dynamic range, resizable + + Copyright (C) 2020 guest(r) - guest.r@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 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +layout(push_constant) uniform Push +{ + vec4 LinearizePassSize; + vec4 OriginalSize; + vec4 OutputSize; + uint FrameCount; + float SIZEHB; + float SIGMA_HB; +} params; + +#pragma parameter bogus_bloom "[ BLOOM/HALATION/(GLOW) PASS SETTINGS ]:" 0.0 0.0 1.0 1.0 + +#pragma parameter SIZEHB " Horizontal Bloom/Halation/(Glow) Radius" 4.0 1.0 50.0 1.0 +#define SIZEHB params.SIZEHB + +#pragma parameter SIGMA_HB " Horizontal Bloom/Halation/(Glow) Sigma" 0.70 0.25 15.0 0.05 +#define SIGMA_HB params.SIGMA_HB + +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 LinearizePass; + +#define COMPAT_TEXTURE(c,d) texture(c,d) + +float invsqrsigma = 1.0/(2.0*SIGMA_HB*SIGMA_HB); + +float gaussian(float x) +{ + return exp(-x*x*invsqrsigma); +} + +void main() +{ + vec4 SourceSize1 = params.OriginalSize; + float f = fract(SourceSize1.x * vTexCoord.x); + f = 0.5 - f; + vec2 tex = floor(SourceSize1.xy * vTexCoord)*SourceSize1.zw + 0.5*SourceSize1.zw; + vec4 color = vec4(0.0); + vec2 dx = vec2(SourceSize1.z, 0.0); + + float w; + float wsum = 0.0; + vec4 pixel; + float n = -SIZEHB; + + do + { + pixel = COMPAT_TEXTURE(LinearizePass, tex + n*dx); + w = gaussian(n+f); + pixel.a = max(max(pixel.r, pixel.g),pixel.b); + pixel.a*=pixel.a*pixel.a; + color = color + w * pixel; + wsum = wsum + w; + n = n + 1.0; + + } while (n <= SIZEHB); + + color = color / wsum; + + FragColor = vec4(color.rgb, pow(color.a, 0.333333)); +} \ No newline at end of file diff --git a/crt/shaders/guest/advanced/bloom_horizontal_ntsc.slang b/crt/shaders/guest/advanced/bloom_horizontal_ntsc.slang new file mode 100644 index 0000000..687427f --- /dev/null +++ b/crt/shaders/guest/advanced/bloom_horizontal_ntsc.slang @@ -0,0 +1,101 @@ +#version 450 + +/* + Gaussian blur - horizontal pass, dynamic range, resizable + + Copyright (C) 2020 guest(r) - guest.r@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 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +layout(push_constant) uniform Push +{ + vec4 LinearizePassSize; + vec4 OriginalSize; + vec4 OutputSize; + uint FrameCount; + float SIZEHB; + float SIGMA_HB; +} params; + +#pragma parameter bogus_bloom "[ BLOOM/HALATION/(GLOW) PASS SETTINGS ]:" 0.0 0.0 1.0 1.0 + +#pragma parameter SIZEHB " Horizontal Bloom/Halation/(Glow) Radius" 6.0 1.0 50.0 1.0 +#define SIZEHB params.SIZEHB + +#pragma parameter SIGMA_HB " Horizontal Bloom/Halation/(Glow) Sigma" 1.0 0.20 15.0 0.10 +#define SIGMA_HB params.SIGMA_HB + +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 LinearizePass; + +#define COMPAT_TEXTURE(c,d) texture(c,d) + +float invsqrsigma = 1.0/(2.0*SIGMA_HB*SIGMA_HB); + +float gaussian(float x) +{ + return exp(-x*x*invsqrsigma); +} + +void main() +{ + vec4 SourceSize1 = params.OriginalSize; + float f = fract(SourceSize1.x * vTexCoord.x); + f = 0.5 - f; + vec2 tex = floor(SourceSize1.xy * vTexCoord)*SourceSize1.zw + 0.5*SourceSize1.zw; + vec4 color = vec4(0.0); + vec2 dx = vec2(SourceSize1.z, 0.0); + + float w; + float wsum = 0.0; + vec4 pixel; + float n = -SIZEHB; + + do + { + pixel = COMPAT_TEXTURE(LinearizePass, tex + n*dx); + w = gaussian(n+f); + pixel.a = max(max(pixel.r, pixel.g),pixel.b); + pixel.a*=pixel.a*pixel.a; + color = color + w * pixel; + wsum = wsum + w; + n = n + 1.0; + + } while (n <= SIZEHB); + + color = color / wsum; + + FragColor = vec4(color.rgb, pow(color.a, 0.333333)); +} \ No newline at end of file diff --git a/crt/shaders/guest/advanced/bloom_vertical.slang b/crt/shaders/guest/advanced/bloom_vertical.slang new file mode 100644 index 0000000..5dd8a3c --- /dev/null +++ b/crt/shaders/guest/advanced/bloom_vertical.slang @@ -0,0 +1,101 @@ +#version 450 + +/* + Gaussian blur - vertical pass, dynamic range, resizable + + Copyright (C) 2020 guest(r) - guest.r@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 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +layout(push_constant) uniform Push +{ + vec4 SourceSize; + vec4 OriginalSize; + vec4 OutputSize; + uint FrameCount; + float SIZEVB; + float SIGMA_VB; +} params; + + +#pragma parameter SIZEVB " Vertical Bloom/Halation/(Glow) Radius" 4.0 1.0 50.0 1.0 +#define SIZEVB params.SIZEVB + +#pragma parameter SIGMA_VB " Vertical Bloom/Halation/(Glow) Sigma" 0.70 0.25 15.0 0.05 +#define SIGMA_VB params.SIGMA_VB + +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 Source; + +#define COMPAT_TEXTURE(c,d) texture(c,d) + +float invsqrsigma = 1.0/(2.0*SIGMA_VB*SIGMA_VB); + +float gaussian(float x) +{ + return exp(-x*x*invsqrsigma); +} + +void main() +{ + vec4 SourceSize1 = params.SourceSize; + SourceSize1.yw = params.OriginalSize.yw; + + float f = fract(SourceSize1.y * vTexCoord.y); + f = 0.5 - f; + vec2 tex = floor(SourceSize1.xy * vTexCoord)*SourceSize1.zw + 0.5*SourceSize1.zw; + vec4 color = vec4(0.0); + vec2 dy = vec2(0.0, SourceSize1.w); + + float w; + float wsum = 0.0; + vec4 pixel; + float n = -SIZEVB; + + do + { + pixel = COMPAT_TEXTURE(Source, tex + n*dy); + w = gaussian(n+f); + pixel.a*=pixel.a*pixel.a; + color = color + w * pixel; + wsum = wsum + w; + n = n + 1.0; + + } while (n <= SIZEVB); + + color = color / wsum; + + FragColor = vec4(color.rgb, pow(color.a, 0.175)); +} \ No newline at end of file diff --git a/crt/shaders/guest/advanced/bloom_vertical_ntsc.slang b/crt/shaders/guest/advanced/bloom_vertical_ntsc.slang new file mode 100644 index 0000000..91c6c75 --- /dev/null +++ b/crt/shaders/guest/advanced/bloom_vertical_ntsc.slang @@ -0,0 +1,101 @@ +#version 450 + +/* + Gaussian blur - vertical pass, dynamic range, resizable + + Copyright (C) 2020 guest(r) - guest.r@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 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +layout(push_constant) uniform Push +{ + vec4 SourceSize; + vec4 OriginalSize; + vec4 OutputSize; + uint FrameCount; + float SIZEVB; + float SIGMA_VB; +} params; + + +#pragma parameter SIZEVB " Vertical Bloom/Halation/(Glow) Radius" 6.0 1.0 50.0 1.0 +#define SIZEVB params.SIZEVB + +#pragma parameter SIGMA_VB " Vertical Bloom/Halation/(Glow) Sigma" 1.0 0.20 15.0 0.10 +#define SIGMA_VB params.SIGMA_VB + +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 Source; + +#define COMPAT_TEXTURE(c,d) texture(c,d) + +float invsqrsigma = 1.0/(2.0*SIGMA_VB*SIGMA_VB); + +float gaussian(float x) +{ + return exp(-x*x*invsqrsigma); +} + +void main() +{ + vec4 SourceSize1 = params.SourceSize; + SourceSize1.yw = params.OriginalSize.yw; + + float f = fract(SourceSize1.y * vTexCoord.y); + f = 0.5 - f; + vec2 tex = floor(SourceSize1.xy * vTexCoord)*SourceSize1.zw + 0.5*SourceSize1.zw; + vec4 color = vec4(0.0); + vec2 dy = vec2(0.0, SourceSize1.w); + + float w; + float wsum = 0.0; + vec4 pixel; + float n = -SIZEVB; + + do + { + pixel = COMPAT_TEXTURE(Source, tex + n*dy); + w = gaussian(n+f); + pixel.a*=pixel.a*pixel.a; + color = color + w * pixel; + wsum = wsum + w; + n = n + 1.0; + + } while (n <= SIZEVB); + + color = color / wsum; + + FragColor = vec4(color.rgb, pow(color.a, 0.175)); +} \ No newline at end of file diff --git a/crt/shaders/guest/advanced/convert-ntsc.slang b/crt/shaders/guest/advanced/convert-ntsc.slang new file mode 100644 index 0000000..2778803 --- /dev/null +++ b/crt/shaders/guest/advanced/convert-ntsc.slang @@ -0,0 +1,69 @@ +#version 450 + +layout(push_constant) uniform Push +{ + vec4 SourceSize; + vec4 OriginalSize; + vec4 OutputSize; + uint FrameCount; + float blendMode; +} params; + +#pragma parameter blendMode "NTSC Blend Mode (Main Mode Control)" 1.0 0.0 2.0 1.0 + +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 Source; +layout(set = 0, binding = 3) uniform sampler2D PrePass0; + +vec3 plant (vec3 tar, float r) +{ + float t = max(max(tar.r,tar.g),tar.b) + 0.00001; + return tar * r / t; +} + +vec3 declip(vec3 c, float b) +{ + float m = max(max(c.r,c.g),c.b); + if (m > b) c = c*b/m; + return c; +} + +void main() +{ + vec2 dx = vec2(params.SourceSize.z * 0.5, 0.0); + vec3 col1 = texture(Source, vTexCoord -dx).rgb; + vec3 col2 = texture(Source, vTexCoord +dx).rgb; + vec3 colc = max(col1, col2); + vec3 col = plant(sqrt(col1*col2), max(max(colc.r, colc.g),colc.b)); + + vec3 orig = texture(PrePass0, vTexCoord).rgb; + vec3 res = normalize(col + 0.00001) * min(length(col), length(orig)); + + float k2 = 1.0/(dot(col1 - res, col1 - res) + 0.0001); + float k3 = 1.0/(dot(col2 - res, col2 - res) + 0.0001); + + vec3 res1 = (k2 * col1 + k3 * col2) / (k2 + k3); + res1 = clamp(res1, min(col1,col2), max(col1, col2)); + + if ( params.blendMode == 1.0) res = res1; + + FragColor = vec4(res, 1.0); +} \ No newline at end of file diff --git a/crt/shaders/guest/advanced/crt-guest-advanced-hires.slang b/crt/shaders/guest/advanced/crt-guest-advanced-hires.slang new file mode 100644 index 0000000..7a3dda4 --- /dev/null +++ b/crt/shaders/guest/advanced/crt-guest-advanced-hires.slang @@ -0,0 +1,775 @@ +#version 450 + +/* + CRT - Guest - Advanced (Hi-Res version) + + Copyright (C) 2018-2021 guest(r) - guest.r@gmail.com + + Incorporates many good ideas and suggestions from Dr. Venom. + I would also like give thanks to many Libretro forums members for continuous feedback, suggestions and caring about the shader. + + 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 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +layout(push_constant) uniform Push +{ + float TATE, IOS, OS, BLOOM, brightboost, brightboost1, gsl, scanline1, scanline2, beam_min, beam_max, beam_size, + h_sharp, s_sharp, csize, bsize, warpX, warpY, glow, shadowMask, masksize, vertmask, + slotmask, slotmask1, slotwidth, double_slot, mcut, maskDark, maskLight, maskstr, spike, intres; +} params; + +layout(std140, set = 0, binding = 0) uniform UBO +{ + mat4 MVP; + vec4 SourceSize; + vec4 OriginalSize; + vec4 OutputSize; + uint FrameCount; + float bloom; + float halation; + float mclip; + float scans; + float scansub; + float slotms; + float gamma_c; + float mask_gamma; + float gamma_out; + float overscanX; + float overscanY; + float c_shape; + float barspeed; + float barintensity; + float bardir; +} global; + +#pragma parameter bogus_brightness "[ BRIGHTNESS SETTINGS ]:" 0.0 0.0 1.0 1.0 + +#pragma parameter glow " Glow Strength" 0.08 -2.0 2.0 0.01 +#define glow params.glow // Glow Strength + +#pragma parameter bloom " Bloom Strength" 0.0 0.0 2.0 0.05 +#define bloom global.bloom // bloom effect + +#pragma parameter halation " Halation Strength" 0.0 0.0 2.0 0.025 +#define halation global.halation // halation effect + +#pragma parameter gamma_c " Gamma correct" 1.0 0.50 2.0 0.02 +#define gamma_c global.gamma_c // adjust brightness + +#pragma parameter brightboost " Bright Boost Dark Pixels" 1.40 0.25 10.0 0.05 +#define brightboost params.brightboost // adjust brightness + +#pragma parameter brightboost1 " Bright Boost Bright Pixels" 1.10 0.25 3.00 0.025 +#define brightboost1 params.brightboost1 // adjust brightness + +#pragma parameter bogus_scanline "[ SCANLINE OPTIONS ]: " 0.0 0.0 1.0 1.0 + +#pragma parameter gsl " Scanline Type" 0.0 -1.0 2.0 1.0 +#define gsl params.gsl // Alternate scanlines + +#pragma parameter scanline1 " Scanline Beam Shape Center" 6.0 0.0 20.0 0.5 +#define scanline1 params.scanline1 // scanline param, vertical sharpness + +#pragma parameter scanline2 " Scanline Beam Shape Edges" 8.0 3.0 40.0 1.0 +#define scanline2 params.scanline2 // scanline param, vertical sharpness + +#pragma parameter beam_min " Scanline Shape Dark Pixels" 1.30 0.25 3.5 0.05 +#define beam_min params.beam_min // dark area beam min - narrow + +#pragma parameter beam_max " Scanline Shape Bright Pixels" 1.00 0.4 2.5 0.05 +#define beam_max params.beam_max // bright area beam max - wide + +#pragma parameter beam_size " Increased Bright Scanline Beam" 0.60 0.0 1.0 0.05 +#define beam_size params.beam_size // increased max. beam size + +#pragma parameter vertmask " Scanline Color Deconvergence" 0.0 -1.0 1.0 0.1 +#define vertmask params.vertmask // Scanline deconvergence colors + +#pragma parameter scans " Scanline Saturation" 0.60 0.0 1.0 0.05 +#define scans global.scans // scanline saturation + + +// Scanline darken 'edges' effect - need to uncomment it. + +// #pragma parameter scansub " Scanline darken 'edges'" 0.0 0.0 0.30 0.005 +// #define scansub global.scansub // scanline substraction + +#pragma parameter spike " Scanline Spike Removal" 1.0 0.0 2.0 0.10 +#define spike params.spike + +#pragma parameter bogus_filtering "[ FILTERING OPTIONS ]: " 0.0 0.0 1.0 1.0 + +#pragma parameter h_sharp " Horizontal sharpness" 5.20 0.20 15.0 0.10 +#define h_sharp params.h_sharp // pixel sharpness + +#pragma parameter s_sharp " Substractive sharpness (1.0 recommended)" 0.50 0.0 1.5 0.10 +#define s_sharp params.s_sharp // substractive sharpness + +#pragma parameter bogus_screen "[ SCREEN OPTIONS ]: " 0.0 0.0 1.0 1.0 + +#pragma parameter intres " Internal Resolution Y: 224p/240p, 1.5...y-dowsample" 0.0 0.0 6.0 0.5 // Joint parameter with linearize pass, values must match +#define intres params.intres // interlace resolution + +#pragma parameter TATE " TATE Mode" 0.0 0.0 1.0 1.0 +#define TATE params.TATE // Screen orientation + +#pragma parameter IOS " Integer Scaling: Odd:Y, Even:'X'+Y" 0.0 0.0 4.0 1.0 +#define IOS params.IOS // Smart Integer Scaling + +#pragma parameter OS " R. Bloom Overscan Mode" 1.0 0.0 2.0 1.0 +#define OS params.OS // Do overscan + +#pragma parameter BLOOM " Raster bloom %" 0.0 0.0 20.0 1.0 +#define BLOOM params.BLOOM // Bloom overscan percentage + +#pragma parameter csize " Corner size" 0.0 0.0 0.25 0.01 +#define csize params.csize // corner size + +#pragma parameter bsize " Border smoothness" 600.0 100.0 700.0 10.0 +#define bsize params.bsize // border smoothness + +#pragma parameter barspeed " Hum Bar Speed" 50.0 5.0 200.0 1.0 + +#pragma parameter barintensity " Hum Bar Intensity" 0.0 -1.0 1.0 0.01 + +#pragma parameter bardir " Hum Bar Direction" 0.0 0.0 1.0 1.0 + +#pragma parameter warpX " CurvatureX (default 0.03)" 0.0 0.0 0.25 0.01 +#define warpX params.warpX // Curvature X + +#pragma parameter warpY " CurvatureY (default 0.04)" 0.0 0.0 0.25 0.01 +#define warpY params.warpY // Curvature Y + +#pragma parameter c_shape " Curvature Shape" 0.25 0.05 0.60 0.05 +#define c_shape global.c_shape // curvature shape + +#pragma parameter overscanX " Overscan X original pixels" 0.0 -50.0 50.0 1.0 +#define overscanX global.overscanX // OverscanX pixels + +#pragma parameter overscanY " Overscan Y original pixels" 0.0 -50.0 50.0 1.0 +#define overscanY global.overscanY // OverscanY pixels + +#pragma parameter bogus_masks "[ CRT MASK OPTIONS ]: " 0.0 0.0 1.0 1.0 + +#pragma parameter shadowMask " CRT Mask: 0:CGWG, 1-4:Lottes, 5-7:'Trinitron'" 0.0 -1.0 8.0 1.0 +#define shadowMask params.shadowMask // Mask Style + +#pragma parameter maskstr " Mask Strength (0, 5-8)" 0.3 -0.5 1.0 0.05 +#define maskstr params.maskstr // maskstr Mask Strength + +#pragma parameter mcut " Mask 5-7 Low Strength" 1.10 0.0 2.0 0.05 +#define mcut params.mcut // Mask 5-7 dark color strength + +#pragma parameter masksize " CRT Mask Size (2.0 is nice in 4k)" 1.0 1.0 4.0 1.0 +#define masksize params.masksize // Mask Size + +#pragma parameter maskDark " Lottes maskDark" 0.5 0.0 2.0 0.05 +#define maskDark params.maskDark // Dark "Phosphor" + +#pragma parameter maskLight " Lottes maskLight" 1.5 0.0 2.0 0.05 +#define maskLight params.maskLight // Light "Phosphor" + +#pragma parameter mask_gamma " Mask gamma" 2.40 1.0 5.0 0.05 +#define mask_gamma global.mask_gamma // Mask application gamma + +#pragma parameter slotmask " Slot Mask Strength Bright Pixels" 0.0 0.0 1.0 0.05 +#define slotmask params.slotmask + +#pragma parameter slotmask1 " Slot Mask Strength Dark Pixels" 0.0 0.0 1.0 0.05 +#define slotmask1 params.slotmask1 + +#pragma parameter slotwidth " Slot Mask Width" 2.0 1.0 6.0 0.5 +#define slotwidth params.slotwidth // Slot Mask Width + +#pragma parameter double_slot " Slot Mask Height: 2x1 or 4x1" 1.0 1.0 2.0 1.0 +#define double_slot params.double_slot // Slot Mask Height + +#pragma parameter slotms " Slot Mask Size" 1.0 1.0 4.0 1.0 +#define slotms global.slotms // Slot Mask Size + +#pragma parameter mclip " Keep Mask effect with clipping" 0.5 0.0 1.0 0.05 +#define mclip global.mclip // Slot Mask Size + +#pragma parameter gamma_out "Gamma out" 2.4 1.0 5.0 0.05 +#define gamma_out global.gamma_out // output gamma + + +#define COMPAT_TEXTURE(c,d) texture(c,d) +#define TEX0 vTexCoord + +#define OutputSize global.OutputSize +#define gl_FragCoord (vTexCoord * OutputSize.xy) + +#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 * 1.00001; +} + +#pragma stage fragment +layout(location = 0) in vec2 vTexCoord; +layout(location = 0) out vec4 FragColor; +layout(set = 0, binding = 2) uniform sampler2D LinearizePass; +layout(set = 0, binding = 3) uniform sampler2D AvgLumPass; +layout(set = 0, binding = 4) uniform sampler2D GlowPass; +layout(set = 0, binding = 5) uniform sampler2D PrePass; + +#define eps 1e-10 + +float st(float x) +{ + return exp2(-10.0*x*x); +} + +float sw0(float x, float color, float scanline) +{ + float tmp = mix(beam_min, beam_max, color); + float ex = x*tmp; + ex = (gsl > -0.5) ? ex*ex : mix(ex*ex, ex*ex*ex, 0.4); + return exp2(-scanline*ex); +} + +float sw1(float x, float color, float scanline) +{ + x = mix (x, beam_min*x, max(x-0.4*color,0.0)); + float tmp = mix(1.2*beam_min, beam_max, color); + float ex = x*tmp; + return exp2(-scanline*ex*ex); +} + +float sw2(float x, float color, float scanline) +{ + float tmp = mix((2.5-0.5*color)*beam_min, beam_max, color); + tmp = mix(beam_max, tmp, pow(x, color+0.3)); + float ex = x*tmp; + return exp2(-scanline*ex*ex); +} + +// Shadow mask (1-4 from PD CRT Lottes shader). + +vec3 Mask(vec2 pos, float mx) +{ + pos = floor(pos/masksize); + vec3 mask = vec3(maskDark, maskDark, maskDark); + vec3 one = vec3(1.0); + float dark_compensate = mix(max( clamp( mix (mcut, maskstr, mx),0.0, 1.0) - 0.3, 0.0) + 1.0, 1.0, mx); + float mc = 1.0 - max(maskstr, 0.0); + + // No mask + if (shadowMask == -1.0) + { + mask = vec3(1.0); + } + + // Phosphor. + else if (shadowMask == 0.0) + { + pos.x = fract(pos.x*0.5); + if (pos.x < 0.5) { mask.r = 1.0; mask.g = mc; mask.b = 1.0; } + else { mask.r = mc; mask.g = 1.0; mask.b = mc; } + } + + // Very compressed TV style shadow mask. + else if (shadowMask == 1.0) + { + float line = maskLight; + float odd = 0.0; + + if (fract(pos.x/6.0) < 0.5) + odd = 1.0; + if (fract((pos.y + odd)/2.0) < 0.5) + line = maskDark; + + pos.x = fract(pos.x/3.0); + + if (pos.x < 0.333) mask.r = maskLight; + else if (pos.x < 0.666) mask.g = maskLight; + else mask.b = maskLight; + + mask*=line; + } + + // Aperture-grille. + else if (shadowMask == 2.0) + { + pos.x = fract(pos.x/3.0); + + if (pos.x < 0.333) mask.r = maskLight; + else if (pos.x < 0.666) mask.g = maskLight; + else mask.b = maskLight; + } + + // Stretched VGA style shadow mask (same as prior shaders). + else if (shadowMask == 3.0) + { + pos.x += pos.y*3.0; + pos.x = fract(pos.x/6.0); + + if (pos.x < 0.333) mask.r = maskLight; + else if (pos.x < 0.666) mask.g = maskLight; + else mask.b = maskLight; + } + + // VGA style shadow mask. + else if (shadowMask == 4.0) + { + pos.xy = floor(pos.xy*vec2(1.0, 0.5)); + pos.x += pos.y*3.0; + pos.x = fract(pos.x/6.0); + + if (pos.x < 0.333) mask.r = maskLight; + else if (pos.x < 0.666) mask.g = maskLight; + else mask.b = maskLight; + } + + // Trinitron mask 5 + else if (shadowMask == 5.0) + { + mask = vec3(0.0); + pos.x = fract(pos.x/2.0); + if (pos.x < 0.5) + { mask.r = 1.0; + mask.b = 1.0; + } + else mask.g = 1.0; + mask = clamp(mix( mix(one, mask, mcut), mix(one, mask, maskstr), mx), 0.0, 1.0) * dark_compensate; + } + + // Trinitron mask 6 + else if (shadowMask == 6.0) + { + mask = vec3(0.0); + pos.x = fract(pos.x/3.0); + if (pos.x < 0.333) mask.r = 1.0; + else if (pos.x < 0.666) mask.g = 1.0; + else mask.b = 1.0; + mask = clamp(mix( mix(one, mask, mcut), mix(one, mask, maskstr), mx), 0.0, 1.0) * dark_compensate; + } + + // BW Trinitron mask 7 + else if (shadowMask == 7.0) + { + float maskTmp = clamp(mix( mix(1.0, 0.0, mcut), mix(1.0, 0.0, maskstr), mx), 0.0, 1.0) * dark_compensate; + mask = vec3(maskTmp); + pos.x = fract(pos.x/2.0); + if (pos.x < 0.5) mask = vec3(1.0); + } + + // 4k mask + else + { + mask = vec3(mc); + pos.x = fract(pos.x * 0.25); + if (pos.x < 0.2) mask.r = 1.0; + else if (pos.x < 0.4) mask.rg = 1.0.xx; + else if (pos.x < 0.7) mask.gb = 1.0.xx; + else mask.b = 1.0; + } + + return mask; +} + +float SlotMask(vec2 pos, float m) +{ + if ((slotmask + slotmask1) == 0.0) return 1.0; + else + { + pos = floor(pos/slotms); + float mlen = slotwidth*2.0; + float px = fract(pos.x/mlen); + float py = floor(fract(pos.y/(2.0*double_slot))*2.0*double_slot); + float slot_dark = mix(1.0-slotmask1, 1.0-slotmask, m); + float slot = 1.0; + if (py == 0.0 && px < 0.5) slot = slot_dark; else + if (py == double_slot && px >= 0.5) slot = slot_dark; + + return slot; + } +} + +vec2 Warp(vec2 pos) +{ + pos = pos*2.0-1.0; + pos = mix(pos, vec2(pos.x*inversesqrt(1.0-c_shape*pos.y*pos.y), pos.y*inversesqrt(1.0-c_shape*pos.x*pos.x)), vec2(warpX, warpY)/c_shape); + return pos*0.5 + 0.5; +} + +vec2 Overscan(vec2 pos, float dx, float dy){ + pos=pos*2.0-1.0; + pos*=vec2(dx,dy); + return pos*0.5+0.5; +} + +float humbar(float pos) +{ + if (global.barintensity == 0.0) return 1.0; else + { + pos = (global.barintensity >= 0.0) ? pos : (1.0-pos); + pos = fract(pos + mod(float(global.FrameCount),global.barspeed)/(global.barspeed-1.0)); + pos = (global.barintensity < 0.0) ? pos : (1.0-pos); + return (1.0-global.barintensity) + global.barintensity*pos; + } +} + +// Borrowed from maskstr's crt-geom, under GPL + +float corner(vec2 coord) +{ + coord = min(coord, vec2(1.0)-coord) * vec2(1.0, OutputSize.y/OutputSize.x); + vec2 cdist = vec2(max(csize/3.0, max((1.0-smoothstep(100.0,600.0,bsize))*0.01,0.002))); + coord = (cdist - min(coord,cdist)); + float dist = sqrt(dot(coord,coord)); + return clamp((cdist.x-dist)*bsize,0.0, 1.0); +} + +vec3 declip(vec3 c, float b) +{ + float m = max(max(c.r,c.g),c.b); + if (m > b) c = c*b/m; + return c; +} + +vec3 gc(vec3 c) +{ + float mc = max(max(c.r,c.g),c.b); + float mg = pow(mc, 1.0/gamma_c); + return c * mg/(mc + eps); +} + +void main() +{ + vec4 SourceSize = global.OriginalSize; + + float lum = COMPAT_TEXTURE(AvgLumPass, vec2(0.1,0.1)).a; + float gamma_in = 1.0/COMPAT_TEXTURE(LinearizePass, vec2(0.25,0.25)).a; + float intera = COMPAT_TEXTURE(LinearizePass, vec2(0.75,0.25)).a; + bool interb = (intera < 0.75); + + bool notate = (TATE < 0.5); + + float SourceY = mix(SourceSize.y, SourceSize.x, TATE); + float sy = 1.0; + if (intres == 0.5) sy = SourceY/224.0; else + if (intres == 1.0) sy = SourceY/240.0; else + if (intres > 1.25) sy = intres; + if (notate) SourceSize.yw*=vec2(1.0/sy, sy); else SourceSize.xz*=vec2(1.0/sy, sy); + + // Calculating texel coordinates + + vec2 texcoord = TEX0.xy; + if (IOS > 0.0){ + vec2 ofactor = OutputSize.xy/SourceSize.xy; + vec2 intfactor = (IOS < 2.5) ? floor(ofactor) : ceil(ofactor); + vec2 diff = ofactor/intfactor; + float scan = mix(diff.y, diff.x, TATE); + texcoord = Overscan(texcoord, scan, scan); + if (IOS == 1.0 || IOS == 3.0) texcoord = mix(vec2(TEX0.x, texcoord.y), vec2(texcoord.x, TEX0.y), TATE); + } + + float factor = 1.00 + (1.0-0.5*OS)*BLOOM/100.0 - lum*BLOOM/100.0; + texcoord = Overscan(texcoord, factor, factor); + texcoord = Overscan(texcoord, (SourceSize.x - overscanX)/SourceSize.x, (SourceSize.y - overscanY)/SourceSize.y); + vec2 pos = Warp(texcoord); + vec2 pos0 = Warp(TEX0.xy); + + vec2 coffset = vec2(0.5, 0.5); + + vec2 ps = SourceSize.zw; + vec2 OGL2Pos = pos * SourceSize.xy - coffset; + vec2 fp = fract(OGL2Pos); + + vec2 dx = vec2(ps.x,0.0); + vec2 dy = vec2(0.0, ps.y); + + // Reading the texels + vec2 x2 = 2.0*dx; + vec2 y2 = 2.0*dy; + + vec2 offx = dx; + vec2 off2 = x2; + vec2 offy = dy; + float fpx = fp.x; + if(!notate) + { + offx = dy; + off2 = y2; + offy = dx; + fpx = fp.y; + } + float f = (notate) ? fp.y : fp.x; + + vec2 pC4 = floor(OGL2Pos) * ps + 0.5*ps; + + if (interb) pC4.y = pos.y; + + float h_sharp1 = pow(h_sharp, 1.4); + + float zero = exp2(-h_sharp1); + + float sharp1 = s_sharp * zero; + + float wl5 = 4.0 + fpx; wl5*=0.5; + float wl4 = 3.0 + fpx; wl4*=0.5; + float wl3 = 2.0 + fpx; wl3*=0.5; + float wl2 = 1.0 + fpx; wl2*=0.5; + float wl1 = fpx; wl1*=0.5; + float wr1 = 1.0 - fpx; wr1*=0.5; + float wr2 = 2.0 - fpx; wr2*=0.5; + float wr3 = 3.0 - fpx; wr3*=0.5; + float wr4 = 4.0 - fpx; wr4*=0.5; + float wr5 = 5.0 - fpx; wr5*=0.5; + + wl5*=wl5; wl5 = exp2(-h_sharp1*wl5); + wl4*=wl4; wl4 = exp2(-h_sharp1*wl4); + wl3*=wl3; wl3 = exp2(-h_sharp1*wl3); + wl2*=wl2; wl2 = exp2(-h_sharp1*wl2); + wl1*=wl1; wl1 = exp2(-h_sharp1*wl1); + wr1*=wr1; wr1 = exp2(-h_sharp1*wr1); + wr2*=wr2; wr2 = exp2(-h_sharp1*wr2); + wr3*=wr3; wr3 = exp2(-h_sharp1*wr3); + wr4*=wr4; wr4 = exp2(-h_sharp1*wr4); + wr5*=wr5; wr5 = exp2(-h_sharp1*wr5); + + float fp1 = 1.-fpx; + + float twl5 = max(wl5 - sharp1, 0.0); + float twl4 = max(wl4 - sharp1, mix(0.0,mix(-0.03, 0.00, fpx),float(s_sharp > 0.05))); float swl4 = max(wl4 - sharp1, 0.0); + float twl3 = max(wl3 - sharp1, mix(0.0,mix(-0.10, -0.03, fpx),float(s_sharp > 0.05))); float swl3 = max(wl3 - sharp1, 0.0); + float twl2 = max(wl2 - sharp1, 0.0); + float twl1 = max(wl1 - sharp1, 0.0); + float twr1 = max(wr1 - sharp1, 0.0); + float twr2 = max(wr2 - sharp1, 0.0); + float twr3 = max(wr3 - sharp1, mix(0.0,mix(-0.10, -0.03, fp1),float(s_sharp > 0.05))); float swr3 = max(wr3 - sharp1, 0.0); + float twr4 = max(wr4 - sharp1, mix(0.0,mix(-0.03, 0.00, fp1),float(s_sharp > 0.05))); float swr4 = max(wr4 - sharp1, 0.0); + float twr5 = max(wr5 - sharp1, 0.0); + + float wtt = 1.0/(twl5+twl4+twl3+twl2+twl1+twr1+twr2+twr3+twr4+twr5); + float wt = 1.0/(swl3+twl2+twl1+twr1+twr2+swr3); + bool sharp = (s_sharp > 0.05); + + vec3 l5 = COMPAT_TEXTURE(LinearizePass, pC4 -2.0*off2).xyz; + vec3 l4 = COMPAT_TEXTURE(LinearizePass, pC4 -3.0*offx).xyz; + vec3 l3 = COMPAT_TEXTURE(LinearizePass, pC4 -off2).xyz; + vec3 l2 = COMPAT_TEXTURE(LinearizePass, pC4 -offx).xyz; + vec3 l1 = COMPAT_TEXTURE(LinearizePass, pC4 ).xyz; + vec3 r1 = COMPAT_TEXTURE(LinearizePass, pC4 +offx).xyz; + vec3 r2 = COMPAT_TEXTURE(LinearizePass, pC4 +off2).xyz; + vec3 r3 = COMPAT_TEXTURE(LinearizePass, pC4 +3.0*offx).xyz; + vec3 r4 = COMPAT_TEXTURE(LinearizePass, pC4 +4.0*offx).xyz; + vec3 r5 = COMPAT_TEXTURE(LinearizePass, pC4 +5.0*offx).xyz; + + vec3 sl3 = l3*l3*l3; sl3*=sl3; + vec3 sl2 = l2*l2*l2; sl2*=sl2; + vec3 sl1 = l1*l1*l1; sl1*=sl1; + vec3 sr1 = r1*r1*r1; sr1*=sr1; + vec3 sr2 = r2*r2*r2; sr2*=sr2; + vec3 sr3 = r3*r3*r3; sr3*=sr3; + + vec3 color1 = (l5*twl5+l4*twl4+l3*twl3+l2*twl2+l1*twl1+r1*twr1+r2*twr2+r3*twr3+r4*twr4+r5*twr5)*wtt; + + vec3 colmin1 = min(min(l1,r1), min(l2,r2)); + vec3 colmax1 = max(max(l1,r1), max(l2,r2)); + vec3 colmin2 = min(min(l3,r3), min(l4,r4)); + vec3 colmax2 = max(max(l3,r3), max(l4,r4)); + vec3 colmin = min(colmin1, colmin2); + vec3 colmax = max(colmax1, colmax2); + + if (sharp) color1 = clamp(color1, colmin, colmax); + + vec3 gtmp = vec3(1.0/6.0); + + vec3 scolor1 = (sl3*swl3 + sl2*twl2 + sl1*twl1 + sr1*twr1 + sr2*twr2 + sr3*swr3)*wt; + scolor1 = pow(scolor1, gtmp); vec3 mcolor1 = scolor1; + scolor1 = min(mix(color1, scolor1, spike),1.0); + + vec3 color2, scolor2, mcolor2; + + if (!interb) +{ + pC4+=offy; + + l5 = COMPAT_TEXTURE(LinearizePass, pC4 -2.0*off2).xyz; + l4 = COMPAT_TEXTURE(LinearizePass, pC4 -3.0*offx).xyz; + l3 = COMPAT_TEXTURE(LinearizePass, pC4 -off2).xyz; + l2 = COMPAT_TEXTURE(LinearizePass, pC4 -offx).xyz; + l1 = COMPAT_TEXTURE(LinearizePass, pC4 ).xyz; + r1 = COMPAT_TEXTURE(LinearizePass, pC4 +offx).xyz; + r2 = COMPAT_TEXTURE(LinearizePass, pC4 +off2).xyz; + r3 = COMPAT_TEXTURE(LinearizePass, pC4 +3.0*offx).xyz; + r4 = COMPAT_TEXTURE(LinearizePass, pC4 +4.0*offx).xyz; + r5 = COMPAT_TEXTURE(LinearizePass, pC4 +5.0*offx).xyz; + + sl3 = l3*l3*l3; sl3*=sl3; + sl2 = l2*l2*l2; sl2*=sl2; + sl1 = l1*l1*l1; sl1*=sl1; + sr1 = r1*r1*r1; sr1*=sr1; + sr2 = r2*r2*r2; sr2*=sr2; + sr3 = r3*r3*r3; sr3*=sr3; + + color2 = (l5*twl5+l4*twl4+l3*twl3+l2*twl2+l1*twl1+r1*twr1+r2*twr2+r3*twr3+r4*twr4+r5*twr5)*wtt; + + colmin1 = min(min(l1,r1), min(l2,r2)); + colmax1 = max(max(l1,r1), max(l2,r2)); + colmin2 = min(min(l3,r3), min(l3,r3)); + colmax2 = max(max(l4,r4), max(l4,r4)); + colmin = min(colmin1, colmin2); + colmax = max(colmax1, colmax2); + + if (sharp) color2 = clamp(color2, colmin, colmax); + + scolor2 = (sl3*swl3 + sl2*twl2 + sl1*twl1 + sr1*twr1 + sr2*twr2 + sr3*swr3)*wt; + scolor2 = pow(scolor2, gtmp); mcolor2 = scolor2; + scolor2 = min(mix(color2, scolor2, spike),1.0); +} + // calculating scanlines + + vec3 ctmp; vec3 mcolor; float w3; vec3 color; + vec3 one = vec3(1.0); + +if (!interb) +{ + float shape1 = mix(scanline1, scanline2, f); + float shape2 = mix(scanline1, scanline2, 1.0-f); + + float wt1 = st(f); + float wt2 = st(1.0-f); + + vec3 color00 = color1*wt1 + color2*wt2; + vec3 scolor0 = scolor1*wt1 + scolor2*wt2; + mcolor = (mcolor1*wt1 + mcolor2*wt2)/(wt1+wt2); + + ctmp = color00/(wt1+wt2); + vec3 sctmp = scolor0/(wt1+wt2); + + float wf1, wf2; + + vec3 cref1 = mix(sctmp, scolor1, beam_size); float creff1 = max(max(cref1.r,cref1.g),cref1.b); + vec3 cref2 = mix(sctmp, scolor2, beam_size); float creff2 = max(max(cref2.r,cref2.g),cref2.b); + + float f1 = f; + float f2 = 1.0-f; + + if (gsl < 0.5) { wf1 = sw0(f1,creff1,shape1); wf2 = sw0(f2,creff2,shape2);} else + if (gsl == 1.0) { wf1 = sw1(f1,creff1,shape1); wf2 = sw1(f2,creff2,shape2);} else + { wf1 = sw2(f1,creff1,shape1); wf2 = sw2(f2,creff2,shape2);} + + if ((wf1 + wf2) > 1.0) { float wtmp = 1.0/(wf1+wf2); wf1*=wtmp; wf2*=wtmp; } + + // Scanline darken 'edges' effect - need to uncomment it. + + // float ws1 = max(wf1 - scansub, 0.2*wf1*wf2); wf1 = ws1/(1.0 - wf1 + ws1); + // float ws2 = max(wf2 - scansub, 0.2*wf2*wf1); wf2 = ws2/(1.0 - wf2 + ws2); + + // Scanline saturation application + + vec3 w1 = vec3(wf1); vec3 w2 = vec3(wf2); + w3 = wf1+wf2; + + cref1 = color1 / (max(max(color1.r,color1.g),color1.b) + 0.00001); + cref2 = color2 / (max(max(color2.r,color2.g),color2.b) + 0.00001); + + w1 = mix(w1*mix(one, cref1*cref1*cref1, scans), w1, wf1); + w2 = mix(w2*mix(one, cref2*cref2*cref2, scans), w2, wf2); + + vec3 cd1 = one; vec3 cd2 = one; float vm = sqrt(abs(vertmask)); + + float v_high1 = 1.0 + 0.3*vm; + float v_high2 = 1.0 + 0.6*vm; + float v_low = 1.0 - vm; + + float ds1 = min(max(1.0-w3*w3, 2.5*f1), 1.0); + float ds2 = min(max(1.0-w3*w3, 2.5*f2), 1.0); + + if (vertmask < 0.0) + { + cd1 = mix(one, vec3(v_high2, v_low, v_low), ds1); + cd2 = mix(one, vec3(v_low, v_high1, v_high1), ds2); + } + else + { + cd1 = mix(one, vec3(v_high1, v_low, v_high1), ds1); + cd2 = mix(one, vec3(v_low, v_high2, v_low), ds2); + } + + color = gc(color1)*w1*cd1 + gc(color2)*w2*cd2; + color = min(color, 1.0); +} + + if (interb) + { + color = gc(color1); + mcolor = mcolor1; + } + + float mx = max(max(mcolor.r,mcolor.g),mcolor.b); + mx = pow(mx, 1.20/gamma_in); + + // Apply Mask + + vec3 orig1 = color; + vec3 cmask = one; + + vec2 maskcoord = gl_FragCoord.yx * 1.000001; + if (notate) maskcoord = maskcoord.yx; + + float smask = SlotMask(maskcoord, mx); + cmask*= Mask(maskcoord, mx); + + color = pow(color, vec3(mask_gamma/gamma_in)); + color = color*cmask; + color = min(color,1.0); + color = color*smask; + color = pow(color, vec3(gamma_in/mask_gamma)); + + cmask = min(cmask*smask, 1.0); + + if (interb) ctmp = color; + float colmx = pow( max( max(ctmp.r, ctmp.g), ctmp.b), 1.40/gamma_out); + float bb = mix(brightboost, brightboost1, colmx); + if (interb) bb = (abs(intera-0.5)<0.1) ? pow(0.80*bb, 0.65) : pow(bb, 0.70); + color*=bb; + + vec3 Glow = COMPAT_TEXTURE(GlowPass, pos ).rgb; + vec3 Bloom = Glow; + float maxb = COMPAT_TEXTURE(GlowPass, pos ).a; + float vig = COMPAT_TEXTURE(PrePass, clamp(pos, 0.0+0.5*global.OriginalSize.zw, 1.0-0.5*global.OriginalSize.zw)).a; + + vec3 Bloom1 = min(Glow*(orig1+color), max(0.5*(colmx + orig1 - color),0.0)); + color = color + bloom*Bloom1; + + color = min(color, mix(one, cmask, mclip)); + if (!interb) color = declip(color, pow(w3,0.6)); + + if (halation > 0.025) { + Bloom = mix(0.5*(Bloom + Bloom*Bloom), Bloom*Bloom, colmx); + color = color + (1.1-0.25*colmx)*(0.75+maxb)*Bloom*(0.75 + 0.70*pow(colmx,0.33333))*mix(1.0,w3,0.5*colmx)*mix(one,cmask,0.35 + 0.4*maxb)*halation; } + + Glow = mix(Glow, 0.25*color, 0.7*colmx); + if (glow >= 0.0) color = color + 0.5*Glow*glow; else { cmask*=cmask; cmask*=cmask; color = color + (-glow)*cmask*Glow; } + + color = min(color, 1.0); + + color = pow(color, vec3(1.0/gamma_out)); + + float corner0 = corner(pos0); + + FragColor = vec4(color*vig*humbar(mix(pos.y, pos.x, global.bardir)), corner0); +} diff --git a/crt/shaders/guest/advanced/crt-guest-advanced-ntsc.slang b/crt/shaders/guest/advanced/crt-guest-advanced-ntsc.slang new file mode 100644 index 0000000..d9c8086 --- /dev/null +++ b/crt/shaders/guest/advanced/crt-guest-advanced-ntsc.slang @@ -0,0 +1,904 @@ +#version 450 + +/* + CRT - Guest - Advanced + + Copyright (C) 2018-2021 guest(r) - guest.r@gmail.com + + Incorporates many good ideas and suggestions from Dr. Venom. + I would also like give thanks to many Libretro forums members for continuous feedback, suggestions and caring about the shader. + + 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 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +layout(push_constant) uniform Push +{ + float TATE, IOS, OS, BLOOM, brightboost, brightboost1, gsl, scanline1, scanline2, beam_min, beam_max, beam_size, + h_sharp, s_sharp, csize, bsize, warpX, warpY, glow, shadowMask, masksize, vertmask, + slotmask, slotmask1, slotwidth, double_slot, mcut, maskDark, maskLight, maskstr, spike; +} params; + +layout(std140, set = 0, binding = 0) uniform UBO +{ + mat4 MVP; + vec4 SourceSize; + vec4 OriginalSize; + vec4 OutputSize; + uint FrameCount; + float bloom; + float halation; + float scans; + float scansub; + float slotms; + float mclip; + float gamma_c; + float mask_gamma; + float smart_ei; + float ei_limit; + float sth; + float gamma_out; + float overscanY; + float intres; + float prescalex; + float c_shape; + float barspeed; + float barintensity; + float bardir; + float blendMode; + float scangamma; +} global; + + +#pragma parameter bogus_brightness "[ BRIGHTNESS SETTINGS ]:" 0.0 0.0 1.0 1.0 + +#pragma parameter blendMode "NTSC Blend Mode (Main Mode Control)" 1.0 0.0 2.0 1.0 + +#pragma parameter glow " Glow Strength" 0.08 -2.0 2.0 0.01 +#define glow params.glow // Glow Strength + +#pragma parameter bloom " Bloom Strength" 0.0 0.0 2.0 0.05 +#define bloom global.bloom // bloom effect + +#pragma parameter halation " Halation Strength" 0.0 0.0 2.0 0.025 +#define halation global.halation // halation effect + +#pragma parameter gamma_c " Gamma correct" 1.0 0.50 2.0 0.02 +#define gamma_c global.gamma_c // adjust brightness + +#pragma parameter brightboost " Bright Boost Dark Pixels" 1.25 0.25 10.0 0.05 +#define brightboost params.brightboost // adjust brightness + +#pragma parameter brightboost1 " Bright Boost Bright Pixels" 1.10 0.25 3.00 0.025 +#define brightboost1 params.brightboost1 // adjust brightness + +#pragma parameter bogus_scanline "[ SCANLINE OPTIONS ]: " 0.0 0.0 1.0 1.0 + +#pragma parameter gsl " Scanline Type" 0.0 -1.0 2.0 1.0 +#define gsl params.gsl // Alternate scanlines + +#pragma parameter scanline1 " Scanline Beam Shape Center" 6.0 0.0 20.0 0.5 +#define scanline1 params.scanline1 // scanline param, vertical sharpness + +#pragma parameter scanline2 " Scanline Beam Shape Edges" 8.0 0.0 40.0 1.0 +#define scanline2 params.scanline2 // scanline param, vertical sharpness + +#pragma parameter beam_min " Scanline Shape Dark Pixels" 1.30 0.25 3.5 0.05 +#define beam_min params.beam_min // dark area beam min - narrow + +#pragma parameter beam_max " Scanline Shape Bright Pixels" 1.00 0.4 2.5 0.05 +#define beam_max params.beam_max // bright area beam max - wide + +#pragma parameter beam_size " Increased Bright Scanline Beam" 0.60 0.0 1.0 0.05 +#define beam_size params.beam_size // increased max. beam size + +#pragma parameter vertmask " Scanline Color Deconvergence" 0.0 -1.0 1.0 0.1 +#define vertmask params.vertmask // Scanline deconvergence colors + +#pragma parameter scans " Scanline Saturation" 0.65 0.0 1.0 0.05 +#define scans global.scans // scanline saturation + +// Scanline darken 'edges' effect - need to uncomment it. + +// #pragma parameter scansub " Scanline darken 'edges'" 0.0 0.0 0.30 0.005 +// #define scansub global.scansub // scanline substraction + +#pragma parameter spike " Scanline Spike Removal" 0.7 0.0 2.0 0.10 +#define spike params.spike + +#pragma parameter scangamma " Scanline Gamma" 2.40 0.5 5.0 0.05 +#define scangamma global.scangamma + +#pragma parameter bogus_filtering "[ FILTERING OPTIONS ]: " 0.0 0.0 1.0 1.0 + +#pragma parameter h_sharp " Horizontal sharpness" 3.70 0.10 15.0 0.10 +#define h_sharp params.h_sharp // pixel sharpness + +#pragma parameter s_sharp " Substractive sharpness (1.0 recommended)" 0.20 0.0 1.5 0.10 +#define s_sharp params.s_sharp // substractive sharpness + +#pragma parameter smart_ei " Smart Edges Effect Strength" 0.0 0.0 20.0 0.25 +#define smart_ei global.smart_ei // smart edge handling + +#pragma parameter ei_limit " Smart Edges Effect Strength Limit" 2.0 1.0 12.0 0.1 +#define ei_limit global.ei_limit // smart edge handling + +#pragma parameter sth " Smart Edges Smoothing Threshold" 0.20 0.0 1.0 0.01 +#define sth global.sth // corner size + +#pragma parameter bogus_screen "[ SCREEN OPTIONS ]: " 0.0 0.0 1.0 1.0 + +#pragma parameter intres " Internal Resolution Y: 224p/240p, 1.5...y-dowsample" 0.0 0.0 6.0 0.5 // Joint parameter with linearize pass, values must match + +#pragma parameter TATE " TATE Mode" 0.0 0.0 1.0 1.0 +#define TATE params.TATE // Screen orientation + +#pragma parameter IOS " Integer Scaling: Odd:Y, Even:'X'+Y" 0.0 0.0 4.0 1.0 +#define IOS params.IOS // Smart Integer Scaling + +#pragma parameter OS " R. Bloom Overscan Mode" 1.0 0.0 2.0 1.0 +#define OS params.OS // Do overscan + +#pragma parameter BLOOM " Raster bloom %" 0.0 0.0 20.0 1.0 +#define BLOOM params.BLOOM // Bloom overscan percentage + +#pragma parameter csize " Corner size" 0.0 0.0 0.25 0.01 +#define csize params.csize // corner size + +#pragma parameter bsize " Border smoothness" 600.0 100.0 700.0 10.0 +#define bsize params.bsize // border smoothness + +#pragma parameter barspeed " Hum Bar Speed" 50.0 5.0 200.0 1.0 + +#pragma parameter barintensity " Hum Bar Intensity" 0.0 -1.0 1.0 0.01 + +#pragma parameter bardir " Hum Bar Direction" 0.0 0.0 1.0 1.0 + +#pragma parameter warpX " CurvatureX (default 0.03)" 0.0 0.0 0.25 0.01 +#define warpX params.warpX // Curvature X + +#pragma parameter warpY " CurvatureY (default 0.04)" 0.0 0.0 0.25 0.01 +#define warpY params.warpY // Curvature Y + +#pragma parameter c_shape " Curvature Shape" 0.25 0.05 0.60 0.05 +#define c_shape global.c_shape // curvature shape + +#pragma parameter overscanY " Overscan Y original pixels" 0.0 -50.0 50.0 1.0 +#define overscanY global.overscanY // OverscanY pixels + +#pragma parameter prescalex " Prescale-X Factor (for xBR...pre-shader)" 1.0 1.0 4.0 1.0 +#define prescalex global.prescalex // prescale-x factor + +#pragma parameter bogus_masks "[ CRT MASK OPTIONS ]: " 0.0 0.0 1.0 1.0 + +#pragma parameter shadowMask " CRT Mask: 0:CGWG, 1-4:Lottes, 5-7:'Trinitron'" 0.0 -1.0 8.0 1.0 +#define shadowMask params.shadowMask // Mask Style + +#pragma parameter maskstr " Mask Strength (0, 5-8)" 0.3 -0.5 1.0 0.05 +#define maskstr params.maskstr // CGWG Mask Strength + +#pragma parameter mcut " Mask 5-7 Low Strength" 1.10 0.0 2.0 0.05 +#define mcut params.mcut // Mask 5-7 dark color strength + +#pragma parameter masksize " CRT Mask Size (2.0 is nice in 4k)" 1.0 1.0 4.0 1.0 +#define masksize params.masksize // Mask Size + +#pragma parameter maskDark " Lottes maskDark" 0.5 0.0 2.0 0.05 +#define maskDark params.maskDark // Dark "Phosphor" + +#pragma parameter maskLight " Lottes maskLight" 1.5 0.0 2.0 0.05 +#define maskLight params.maskLight // Light "Phosphor" + +#pragma parameter mask_gamma " Mask gamma" 2.40 1.0 5.0 0.025 +#define mask_gamma global.mask_gamma // Mask application gamma + +#pragma parameter slotmask " Slot Mask Strength Bright Pixels" 0.0 0.0 1.0 0.05 +#define slotmask params.slotmask + +#pragma parameter slotmask1 " Slot Mask Strength Dark Pixels" 0.0 0.0 1.0 0.05 +#define slotmask1 params.slotmask1 + +#pragma parameter slotwidth " Slot Mask Width" 2.0 1.0 6.0 0.5 +#define slotwidth params.slotwidth // Slot Mask Width + +#pragma parameter double_slot " Slot Mask Height: 2x1 or 4x1" 1.0 1.0 2.0 1.0 +#define double_slot params.double_slot // Slot Mask Height + +#pragma parameter slotms " Slot Mask Size" 1.0 1.0 4.0 1.0 +#define slotms global.slotms // Slot Mask Size + +#pragma parameter mclip " Keep Mask effect with clipping" 0.50 0.0 1.0 0.05 +#define mclip global.mclip // Slot Mask Size + +#pragma parameter gamma_out "Gamma out" 2.0 1.0 5.0 0.05 +#define gamma_out global.gamma_out // output gamma + +#define COMPAT_TEXTURE(c,d) texture(c,d) +#define TEX0 vTexCoord + +#define OutputSize global.OutputSize +#define gl_FragCoord (vTexCoord * OutputSize.xy) + +#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 * 1.000001; +} + +#pragma stage fragment +layout(location = 0) in vec2 vTexCoord; +layout(location = 0) out vec4 FragColor; +layout(set = 0, binding = 2) uniform sampler2D LinearizePass; +layout(set = 0, binding = 3) uniform sampler2D AvgLumPass; +layout(set = 0, binding = 4) uniform sampler2D GlowPass; +layout(set = 0, binding = 5) uniform sampler2D BloomPass; +layout(set = 0, binding = 6) uniform sampler2D PrePass0; +layout(set = 0, binding = 7) uniform sampler2D NtscPass; + +#define eps 1e-8 + +float st(float x) +{ + return exp2(-10.0*x*x); +} + +float sw0(float x, float color, float scanline) +{ + float tmp = mix(beam_min, beam_max, color); + float ex = x*tmp; + ex = (gsl > -0.5) ? ex*ex : mix(ex*ex, ex*ex*ex, 0.4); + return exp2(-scanline*ex); +} + +float sw1(float x, float color, float scanline) +{ + x = mix (x, beam_min*x, max(x-0.4*color,0.0)); + float tmp = mix(1.2*beam_min, beam_max, color); + float ex = x*tmp; + return exp2(-scanline*ex*ex); +} + +float sw2(float x, float color, float scanline) +{ + float tmp = mix((2.5-0.5*color)*beam_min, beam_max, color); + tmp = mix(beam_max, tmp, pow(x, color+0.3)); + float ex = x*tmp; + return exp2(-scanline*ex*ex); +} + +// Shadow mask (1-4 from PD CRT Lottes shader). + +vec3 Mask(vec2 pos, float mx) +{ + pos = floor(pos/masksize); + vec3 mask = vec3(maskDark, maskDark, maskDark); + vec3 one = vec3(1.0); + float dark_compensate = mix(max( clamp( mix (mcut, maskstr, mx),0.0, 1.0) - 0.3, 0.0) + 1.0, 1.0, mx); + float mc = 1.0 - max(maskstr, 0.0); + + // No mask + if (shadowMask == -1.0) + { + mask = vec3(1.0); + } + + // Phosphor. + else if (shadowMask == 0.0) + { + pos.x = fract(pos.x*0.5); + if (pos.x < 0.5) { mask.r = 1.0; mask.g = mc; mask.b = 1.0; } + else { mask.r = mc; mask.g = 1.0; mask.b = mc; } + } + + // Very compressed TV style shadow mask. + else if (shadowMask == 1.0) + { + float line = maskLight; + float odd = 0.0; + + if (fract(pos.x/6.0) < 0.5) + odd = 1.0; + if (fract((pos.y + odd)/2.0) < 0.5) + line = maskDark; + + pos.x = fract(pos.x/3.0); + + if (pos.x < 0.333) mask.r = maskLight; + else if (pos.x < 0.666) mask.g = maskLight; + else mask.b = maskLight; + + mask*=line; + } + + // Aperture-grille. + else if (shadowMask == 2.0) + { + pos.x = fract(pos.x/3.0); + + if (pos.x < 0.333) mask.r = maskLight; + else if (pos.x < 0.666) mask.g = maskLight; + else mask.b = maskLight; + } + + // Stretched VGA style shadow mask (same as prior shaders). + else if (shadowMask == 3.0) + { + pos.x += pos.y*3.0; + pos.x = fract(pos.x/6.0); + + if (pos.x < 0.333) mask.r = maskLight; + else if (pos.x < 0.666) mask.g = maskLight; + else mask.b = maskLight; + } + + // VGA style shadow mask. + else if (shadowMask == 4.0) + { + pos.xy = floor(pos.xy*vec2(1.0, 0.5)); + pos.x += pos.y*3.0; + pos.x = fract(pos.x/6.0); + + if (pos.x < 0.333) mask.r = maskLight; + else if (pos.x < 0.666) mask.g = maskLight; + else mask.b = maskLight; + } + + // Trinitron mask 5 + else if (shadowMask == 5.0) + { + mask = vec3(0.0); + pos.x = fract(pos.x/2.0); + if (pos.x < 0.5) + { mask.r = 1.0; + mask.b = 1.0; + } + else mask.g = 1.0; + mask = clamp(mix( mix(one, mask, mcut), mix(one, mask, maskstr), mx), 0.0, 1.0) * dark_compensate; + } + + // Trinitron mask 6 + else if (shadowMask == 6.0) + { + mask = vec3(0.0); + pos.x = fract(pos.x/3.0); + if (pos.x < 0.333) mask.r = 1.0; + else if (pos.x < 0.666) mask.g = 1.0; + else mask.b = 1.0; + mask = clamp(mix( mix(one, mask, mcut), mix(one, mask, maskstr), mx), 0.0, 1.0) * dark_compensate; + } + + // BW Trinitron mask 7 + else if (shadowMask == 7.0) + { + float maskTmp = clamp(mix( mix(1.0, 0.0, mcut), mix(1.0, 0.0, maskstr), mx), 0.0, 1.0) * dark_compensate; + mask = vec3(maskTmp); + pos.x = fract(pos.x/2.0); + if (pos.x < 0.5) mask = vec3(1.0); + } + + // 4k mask + else + { + mask = vec3(mc); + pos.x = fract(pos.x * 0.25); + if (pos.x < 0.2) mask.r = 1.0; + else if (pos.x < 0.4) mask.rg = 1.0.xx; + else if (pos.x < 0.7) mask.gb = 1.0.xx; + else mask.b = 1.0; + } + + return mask; +} + +float SlotMask(vec2 pos, float m) +{ + if (slotmask == 0.0) return 1.0; + else + { + pos = floor(pos/slotms); + float mlen = slotwidth*2.0; + float px = fract(pos.x/mlen); + float py = floor(fract(pos.y/(2.0*double_slot))*2.0*double_slot); + float slot_dark = mix(1.0-slotmask1, 1.0-slotmask, m); + float slot = 1.0; + if (py == 0.0 && px < 0.5) slot = slot_dark; else + if (py == double_slot && px >= 0.5) slot = slot_dark; + + return slot; + } +} + +vec2 Warp(vec2 pos) +{ + pos = pos*2.0-1.0; + pos = mix(pos, vec2(pos.x*inversesqrt(1.0-c_shape*pos.y*pos.y), pos.y*inversesqrt(1.0-c_shape*pos.x*pos.x)), vec2(warpX, warpY)/c_shape); + return pos*0.5 + 0.5; +} + +vec2 Overscan(vec2 pos, float dx, float dy){ + pos=pos*2.0-1.0; + pos*=vec2(dx,dy); + return pos*0.5+0.5; +} + +float humbar(float pos) +{ + if (global.barintensity == 0.0) return 1.0; else + { + pos = (global.barintensity >= 0.0) ? pos : (1.0-pos); + pos = fract(pos + mod(float(global.FrameCount),global.barspeed)/(global.barspeed-1.0)); + pos = (global.barintensity < 0.0) ? pos : (1.0-pos); + return (1.0-global.barintensity) + global.barintensity*pos; + } +} + +// Borrowed from cgwg's crt-geom, under GPL + +float corner(vec2 coord) +{ + coord = min(coord, vec2(1.0)-coord) * vec2(1.0, OutputSize.y/OutputSize.x); + vec2 cdist = vec2(max(csize/3.0, max((1.0-smoothstep(100.0,600.0,bsize))*0.01,0.002))); + coord = (cdist - min(coord,cdist)); + float dist = sqrt(dot(coord,coord)); + return clamp((cdist.x-dist)*bsize,0.0, 1.0); +} + +vec3 declip(vec3 c, float b) +{ + float m = max(max(c.r,c.g),c.b); + if (m > b) c = c*b/m; + return c; +} + +vec3 gc(vec3 c) +{ + float mc = max(max(c.r,c.g),c.b); + float mg = pow(mc, 1.0/gamma_c); + return c * mg/(mc + eps); +} + +vec3 plant (vec3 tar, float r) +{ + float t = max(max(tar.r,tar.g),tar.b) + 0.00001; + return tar * r / t; +} + +void main() +{ + vec4 SourceSize = global.OriginalSize * vec4(prescalex, 1.0, 1.0/prescalex, 1.0); + + bool nmode1 = (global.blendMode == 1.0); + bool nmode2 = (global.blendMode > 1.5); + + if (nmode2) SourceSize*= vec4(2.0, 1.0, 0.5, 1.0); + + float lum = COMPAT_TEXTURE(AvgLumPass, vec2(0.5,0.5)).a; + + float gamma_in = 1.0/COMPAT_TEXTURE(LinearizePass, vec2(0.25,0.25)).a; + float intera = COMPAT_TEXTURE(LinearizePass, vec2(0.75,0.25)).a; + bool interb = (intera < 0.75); + bool notate = (TATE < 0.5); + + float SourceY = mix(SourceSize.y, SourceSize.x, TATE); + float sy = 1.0; + if (global.intres == 0.5) sy = SourceY/224.0; else + if (global.intres == 1.0) sy = SourceY/240.0; else + if (global.intres > 1.25) sy = global.intres; + if (notate) SourceSize*=vec4(1.0, 1.0/sy, 1.0, sy); else SourceSize*=vec4(1.0/sy, 1.0, sy, 1.0); + + // Calculating texel coordinates + + vec2 texcoord = TEX0.xy; + if (IOS > 0.0){ + vec2 ofactor = OutputSize.xy/SourceSize.xy; + vec2 intfactor = (IOS < 2.5) ? floor(ofactor) : ceil(ofactor); + vec2 diff = ofactor/intfactor; + float scan = mix(diff.y, diff.x, TATE); + texcoord = Overscan(texcoord, scan, scan); + if (IOS == 1.0 || IOS == 3.0) texcoord = mix(vec2(TEX0.x, texcoord.y), vec2(texcoord.x, TEX0.y), TATE); + } + + float factor = 1.00 + (1.0-0.5*OS)*BLOOM/100.0 - lum*BLOOM/100.0; + texcoord = Overscan(texcoord, factor, factor); + + texcoord = Overscan(texcoord, 1.0, (SourceSize.y - overscanY)/SourceSize.y); + + vec2 pos = Warp(texcoord); + vec2 pos0 = Warp(TEX0.xy); + + bool smarte = (smart_ei > 0.0 && notate); // smart edge interpolation on / off + + vec2 coffset = vec2(0.5, 0.5); + + vec2 ps = SourceSize.zw; + vec2 OGL2Pos = pos * SourceSize.xy - coffset; + vec2 fp = fract(OGL2Pos); + + vec2 dx = vec2(ps.x,0.0); + vec2 dy = vec2(0.0, ps.y); + + // Reading the texels + vec2 x2 = 2.0*dx; + vec2 y2 = 2.0*dy; + + vec2 offx = dx; + vec2 off2 = x2; + vec2 offy = dy; + float fpx = fp.x; + if(!notate) + { + offx = dy; + off2 = y2; + offy = dx; + fpx = fp.y; + } + float f = (notate) ? fp.y : fp.x; + + vec2 pC4 = floor(OGL2Pos) * ps + 0.5*ps; + + vec2 pC5 = mix(vec2(pos.x, pC4.y), vec2(pC4.x, pos.y), float(!notate)); + + if (interb) pC4.y = pos.y; + + float zero = exp2(-h_sharp); + float sharp1 = s_sharp * zero; + + float fdivider = min(prescalex + 0.325*float(nmode2), 2.0); + + float wl3 = (2.0 + fpx)/fdivider; + float wl2 = (1.0 + fpx)/fdivider; + float wl1 = ( fpx)/fdivider; + float wr1 = (1.0 - fpx)/fdivider; + float wr2 = (2.0 - fpx)/fdivider; + float wr3 = (3.0 - fpx)/fdivider; + + wl3*=wl3; wl3 = exp2(-h_sharp*wl3); + wl2*=wl2; wl2 = exp2(-h_sharp*wl2); + wl1*=wl1; wl1 = exp2(-h_sharp*wl1); + wr1*=wr1; wr1 = exp2(-h_sharp*wr1); + wr2*=wr2; wr2 = exp2(-h_sharp*wr2); + wr3*=wr3; wr3 = exp2(-h_sharp*wr3); + + float fp1 = 1.-fpx; + + float twl3 = max(wl3 - sharp1, 0.0); + float twl2 = max(wl2 - sharp1, mix(-0.12, 0.0, 1.0-fp1*fp1)); + float twl1 = max(wl1 - sharp1, 0.0); + float twr1 = max(wr1 - sharp1, 0.0); + float twr2 = max(wr2 - sharp1, mix(-0.12, 0.0, 1.0-fpx*fpx)); + float twr3 = max(wr3 - sharp1, 0.0); + + bool sharp = (sharp1 > 0.0); + + float rwl3, rwl2, rwr2; + + float rwl1 = twl1; + float rwr1 = twr1; + vec3 c1, c2; + + if (smarte) + { + rwl3 = wl3; rwl2 = wl2; + rwl1 = wl1; rwr1 = wr1; + rwr2 = wr2; + twl3 = 0.0; twr3 = 0.0; + vec3 t = COMPAT_TEXTURE(AvgLumPass, pC4 - offy).xyz; + vec3 a = COMPAT_TEXTURE(AvgLumPass, pC4 ).xyz; + vec3 b = COMPAT_TEXTURE(AvgLumPass, pC4 + offy).xyz; + vec3 d = COMPAT_TEXTURE(AvgLumPass, pC4 +dy+dy).xyz; + c1 = (h_sharp > 2.6) ? a : min(a,(t + a + b)/3.0); c1 = max(c1 - sth, 0.0); + c2 = (h_sharp > 2.6) ? b : min(b,(a + b + d)/3.0); c2 = max(c2 - sth, 0.0); + } + + vec3 cc, l3, l2, l1, r1, r2, r3, sl2, sl1, sr1, sr2, color1, color2, colmin, colmax; + +if (nmode2) +{ + l3 = pow(COMPAT_TEXTURE(NtscPass, pC4 -off2).rgb, gamma_in.xxx); + l2 = pow(COMPAT_TEXTURE(NtscPass, pC4 -offx).rgb, gamma_in.xxx); + l1 = pow(COMPAT_TEXTURE(NtscPass, pC4 ).rgb, gamma_in.xxx); + r1 = pow(COMPAT_TEXTURE(NtscPass, pC4 +offx).rgb, gamma_in.xxx); + r2 = pow(COMPAT_TEXTURE(NtscPass, pC4 +off2).rgb, gamma_in.xxx); + r3 = pow(COMPAT_TEXTURE(NtscPass, pC4 +offx+off2).rgb, gamma_in.xxx); +} +else +{ + l3 = COMPAT_TEXTURE(LinearizePass, pC4 -off2).rgb; + l2 = COMPAT_TEXTURE(LinearizePass, pC4 -offx).rgb; + l1 = COMPAT_TEXTURE(LinearizePass, pC4 ).rgb; + r1 = COMPAT_TEXTURE(LinearizePass, pC4 +offx).rgb; + r2 = COMPAT_TEXTURE(LinearizePass, pC4 +off2).rgb; + r3 = COMPAT_TEXTURE(LinearizePass, pC4 +offx+off2).rgb; + cc = pow(COMPAT_TEXTURE(NtscPass, pC5).rgb, gamma_in.xxx); +} + colmin = min(min(l1,r1), min(l2,r2)); + colmax = max(max(l1,r1), max(l2,r2)); + + if (smarte) + { + float pc = min(1.0 + smart_ei*c1.y, ei_limit); + float pl = min(1.0 + smart_ei*max(c1.y,c1.x), ei_limit); + float pr = min(1.0 + smart_ei*max(c1.y,c1.z), ei_limit); + twl1 = pow(abs(rwl1), pc); twr1 = pow(abs(rwr1), pc); + twl2 = pow(abs(rwl2), pl); twr2 = pow(abs(rwr2), pr); + float wmax = max(twl1, twr1); + float sharp_ei = s_sharp*pow(zero, pc)/wmax; + twl2 = max(twl2/wmax - sharp_ei, mix(-0.12, 0.0, 1.0-fp1*fp1)); + twl1 = max(twl1/wmax - sharp_ei, 0.0); + twr1 = max(twr1/wmax - sharp_ei, 0.0); + twr2 = max(twr2/wmax - sharp_ei, mix(-0.12, 0.0, 1.0-fpx*fpx)); + } + color1 = (l3*twl3 + l2*twl2 + l1*twl1 + r1*twr1 + r2*twr2 + r3*twr3)/(twl3+twl2+twl1+twr1+twr2+twr3); + if (sharp) color1 = clamp(color1, colmin, colmax); + if (nmode1 && !interb) color1 = min(normalize (color1+eps) * length(sqrt(color1 * cc)) ,1.0); + + color1 = pow(color1, vec3(scangamma/gamma_in)); + + float ts = 0.033; + + bool ntscbm = (global.blendMode == 0.0); + +if (ntscbm) +{ + l2 = COMPAT_TEXTURE(PrePass0, pC4 -offx).rgb; + l1 = COMPAT_TEXTURE(PrePass0, pC4 ).rgb; + r1 = COMPAT_TEXTURE(PrePass0, pC4 +offx).rgb; + r2 = COMPAT_TEXTURE(PrePass0, pC4 +off2).rgb; +} + + float lm2 = max(max(l2.r,l2.g),l2.b); + float lm1 = max(max(l1.r,l1.g),l1.b); + float rm1 = max(max(r1.r,r1.g),r1.b); + float rm2 = max(max(r2.r,r2.g),r2.b); + +if (ntscbm) +{ + lm2 = pow(lm2, gamma_in); + lm1 = pow(lm1, gamma_in); + rm1 = pow(rm1, gamma_in); + rm2 = pow(rm2, gamma_in); +} + + float swl2 = max(twl2, eps) * (lm2+ts); + float swl1 = twl1 * (lm1+ts); + float swr1 = twr1 * (rm1+ts); + float swr2 = max(twr2, eps) * (rm2+ts); + + float fscolor1 = (lm2*swl2 + lm1*swl1 + rm1*swr1 + rm2*swr2)/(swl2+swl1+swr1+swr2); + vec3 mcolor1 = vec3(fscolor1); + vec3 scolor1 = vec3(clamp(mix(max(max(color1.r,color1.g),color1.b), fscolor1, spike), 0.0, 1.0)); + + vec3 scolor2, mcolor2; + + if (!interb) +{ + pC4+=offy; + pC5+=offy; + +if (nmode2) +{ + l3 = pow(COMPAT_TEXTURE(NtscPass, pC4 -off2).rgb, gamma_in.xxx); + l2 = pow(COMPAT_TEXTURE(NtscPass, pC4 -offx).rgb, gamma_in.xxx); + l1 = pow(COMPAT_TEXTURE(NtscPass, pC4 ).rgb, gamma_in.xxx); + r1 = pow(COMPAT_TEXTURE(NtscPass, pC4 +offx).rgb, gamma_in.xxx); + r2 = pow(COMPAT_TEXTURE(NtscPass, pC4 +off2).rgb, gamma_in.xxx); + r3 = pow(COMPAT_TEXTURE(NtscPass, pC4 +offx+off2).rgb, gamma_in.xxx); +} +else +{ + l3 = COMPAT_TEXTURE(LinearizePass, pC4 -off2).rgb; + l2 = COMPAT_TEXTURE(LinearizePass, pC4 -offx).rgb; + l1 = COMPAT_TEXTURE(LinearizePass, pC4 ).rgb; + r1 = COMPAT_TEXTURE(LinearizePass, pC4 +offx).rgb; + r2 = COMPAT_TEXTURE(LinearizePass, pC4 +off2).rgb; + r3 = COMPAT_TEXTURE(LinearizePass, pC4 +offx+off2).rgb; + cc = pow(COMPAT_TEXTURE(NtscPass, pC5).rgb, gamma_in.xxx); +} + + colmin = min(min(l1,r1), min(l2,r2)); + colmax = max(max(l1,r1), max(l2,r2)); + + if (smarte) + { + float pc = min(1.0 + smart_ei*c2.y, ei_limit); + float pl = min(1.0 + smart_ei*max(c2.y,c2.x), ei_limit); + float pr = min(1.0 + smart_ei*max(c2.y,c2.z), ei_limit); + twl1 = pow(abs(rwl1), pc); twr1 = pow(abs(rwr1), pc); + twl2 = pow(abs(rwl2), pl); twr2 = pow(abs(rwr2), pr); + float wmax = max(twl1, twr1); + float sharp_ei = s_sharp*pow(zero, pc)/wmax; + twl2 = max(twl2/wmax - sharp_ei, mix(-0.12, 0.0, 1.0-fp1*fp1)); + twl1 = max(twl1/wmax - sharp_ei, 0.0); + twr1 = max(twr1/wmax - sharp_ei, 0.0); + twr2 = max(twr2/wmax - sharp_ei, mix(-0.12, 0.0, 1.0-fpx*fpx)); + } + color2 = (l3*twl3 + l2*twl2 + l1*twl1 + r1*twr1 + r2*twr2 + r3*twr3)/(twl3+twl2+twl1+twr1+twr2+twr3); + if (sharp) color2 = clamp(color2, colmin, colmax); + if (nmode1 && !interb) color2 = min(normalize (color2+eps) * length(sqrt(color2 * cc)) ,1.0); + + color2 = pow(color2, vec3(scangamma/gamma_in)); + +if (ntscbm) +{ + l2 = COMPAT_TEXTURE(PrePass0, pC4 -offx).rgb; + l1 = COMPAT_TEXTURE(PrePass0, pC4 ).rgb; + r1 = COMPAT_TEXTURE(PrePass0, pC4 +offx).rgb; + r2 = COMPAT_TEXTURE(PrePass0, pC4 +off2).rgb; +} + + lm2 = max(max(l2.r,l2.g),l2.b); + lm1 = max(max(l1.r,l1.g),l1.b); + rm1 = max(max(r1.r,r1.g),r1.b); + rm2 = max(max(r2.r,r2.g),r2.b); + +if (ntscbm) +{ + lm2 = pow(lm2, gamma_in); + lm1 = pow(lm1, gamma_in); + rm1 = pow(rm1, gamma_in); + rm2 = pow(rm2, gamma_in); +} + + swl2 = max(twl2, 0.0) * (lm2+ts); + swl1 = twl1 * (lm1+ts); + swr1 = twr1 * (rm1+ts); + swr2 = max(twr2, 0.0) * (rm2+ts); + + float fscolor2 = (lm2*swl2 + lm1*swl1 + rm1*swr1 + rm2*swr2)/(swl2+swl1+swr1+swr2); + mcolor2 = vec3(fscolor2); + scolor2 = vec3(clamp(mix(max(max(color2.r,color2.g),color2.b), fscolor2, spike), 0.0, 1.0)); +} + + vec3 ctmp; vec3 mcolor; float w3; vec3 color; + vec3 one = vec3(1.0); + +if (!interb) +{ + // calculating scanlines + + float shape1 = mix(scanline1, scanline2, f); + float shape2 = mix(scanline1, scanline2, 1.0-f); + + float wt1 = st(f); + float wt2 = st(1.0-f); + + vec3 color00 = color1*wt1 + color2*wt2; + vec3 scolor0 = scolor1*wt1 + scolor2*wt2; + mcolor = (mcolor1*wt1 + mcolor2*wt2)/(wt1+wt2); + + ctmp = color00/(wt1+wt2); + vec3 sctmp = scolor0/(wt1+wt2); + mcolor = clamp(mix(ctmp, mcolor, 1.5), 0.0, 1.0); + + float wf1, wf2; + + vec3 cref1 = mix(sctmp, scolor1, beam_size); float creff1 = max(max(cref1.r,cref1.g),cref1.b); + vec3 cref2 = mix(sctmp, scolor2, beam_size); float creff2 = max(max(cref2.r,cref2.g),cref2.b); + + float f1 = f; + float f2 = 1.0-f; + + if (gsl < 0.5) { wf1 = sw0(f1,creff1,shape1); wf2 = sw0(f2,creff2,shape2);} else + if (gsl == 1.0) { wf1 = sw1(f1,creff1,shape1); wf2 = sw1(f2,creff2,shape2);} else + { wf1 = sw2(f1,creff1,shape1); wf2 = sw2(f2,creff2,shape2);} + + if ((wf1 + wf2) > 1.0) { float wtmp = 1.0/(wf1+wf2); wf1*=wtmp; wf2*=wtmp; } + + // Scanline darken 'edges' effect - need to uncomment it. + + // float ws1 = max(wf1 - scansub, 0.2*wf1*wf2); wf1 = ws1/(1.0 - wf1 + ws1); + // float ws2 = max(wf2 - scansub, 0.2*wf2*wf1); wf2 = ws2/(1.0 - wf2 + ws2); + + // Scanline saturation application + + vec3 w1 = vec3(wf1); vec3 w2 = vec3(wf2); + w3 = wf1+wf2; + + cref1 = color1 / (max(max(color1.r,color1.g),color1.b) + 0.00001); + cref2 = color2 / (max(max(color2.r,color2.g),color2.b) + 0.00001); + + w1 = mix(w1*mix(one, cref1*cref1*cref1, scans), w1, wf1); + w2 = mix(w2*mix(one, cref2*cref2*cref2, scans), w2, wf2); + + vec3 cd1 = one; vec3 cd2 = one; float vm = sqrt(abs(vertmask)); + + float v_high1 = 1.0 + 0.3*vm; + float v_high2 = 1.0 + 0.6*vm; + float v_low = 1.0 - vm; + + float ds1 = min(max(1.0-w3*w3, 2.5*f1), 1.0); + float ds2 = min(max(1.0-w3*w3, 2.5*f2), 1.0); + + if (vertmask < 0.0) + { + cd1 = mix(one, vec3(v_high2, v_low, v_low), ds1); + cd2 = mix(one, vec3(v_low, v_high1, v_high1), ds2); + } + else + { + cd1 = mix(one, vec3(v_high1, v_low, v_high1), ds1); + cd2 = mix(one, vec3(v_low, v_high2, v_low), ds2); + } + + color = gc(color1)*w1*cd1 + gc(color2)*w2*cd2; + + color = min(color, 1.0); +} + + if (interb) + { + color = gc(color1); + mcolor = clamp(mix(color1, mcolor1, 1.25), 0.0, 1.0); + } + + float mx = max(max(mcolor.r,mcolor.g),mcolor.b); + mx = pow(mx, 1.20/gamma_in); + + // Apply Mask + + vec3 orig1 = color; + vec3 cmask = one; + + vec2 maskcoord = gl_FragCoord.yx * 1.000001; + if (notate) maskcoord = maskcoord.yx; + + float smask = SlotMask(maskcoord, mx); + cmask*= Mask(maskcoord, mx); + + color = pow(color, vec3(mask_gamma/gamma_in)); + color = color*cmask; + color = min(color,1.0); + color = color*smask; + color = pow(color, vec3(gamma_in/mask_gamma)); + + cmask = min(cmask*smask, 1.0); + + if (interb) ctmp = color; + float colmx = pow( max( max(ctmp.r, ctmp.g), ctmp.b), 1.40/gamma_out); + float bb = mix(brightboost, brightboost1, colmx); + if (interb) bb = (abs(intera-0.5)<0.1) ? pow(0.80*bb, 0.65) : pow(bb, 0.70); + color*=bb; + + vec3 Glow = COMPAT_TEXTURE(GlowPass, pos).rgb; + vec3 Bloom = COMPAT_TEXTURE(BloomPass, pos).rgb; + float maxb = COMPAT_TEXTURE(BloomPass, pos).a; + float vig = COMPAT_TEXTURE(PrePass0, clamp(pos, 0.0+0.5*global.OriginalSize.zw, 1.0-0.5*global.OriginalSize.zw)).a; + + vec3 Bloom1 = min(Bloom*(orig1+color), max(0.5*(colmx + orig1 - color),0.0)); + color = color + bloom*Bloom1; + + color = min(color, mix(one, cmask, mclip)); + if (!interb) color = declip(color, pow(w3,0.60)); + + if (halation > 0.025) { + Bloom = mix(0.5*(Bloom + Bloom*Bloom), Bloom*Bloom, colmx); + color = color + (1.1-0.25*colmx)*(0.75+maxb)*Bloom*(0.75 + 0.70*pow(colmx,0.33333))*mix(1.0,w3,0.5*colmx)*mix(one,cmask,0.35 + 0.4*maxb)*halation; } + + color = pow(color, vec3(gamma_in/scangamma)); + + Glow = mix(Glow, 0.25*color, 0.7*colmx); + if (glow >= 0.0) color = color + 0.5*Glow*glow; else { cmask*=cmask; cmask*=cmask; color = color + (-glow)*cmask*Glow; } + + color = min(color, 1.0); + + color = pow(color, vec3(1.0/gamma_out)); + + FragColor = vec4(color*vig*humbar(mix(pos.y, pos.x, global.bardir)), corner(pos0)); +} diff --git a/crt/shaders/guest/advanced/crt-guest-advanced.slang b/crt/shaders/guest/advanced/crt-guest-advanced.slang new file mode 100644 index 0000000..ed08394 --- /dev/null +++ b/crt/shaders/guest/advanced/crt-guest-advanced.slang @@ -0,0 +1,822 @@ +#version 450 + +/* + CRT - Guest - Advanced + + Copyright (C) 2018-2021 guest(r) - guest.r@gmail.com + + Incorporates many good ideas and suggestions from Dr. Venom. + I would also like give thanks to many Libretro forums members for continuous feedback, suggestions and caring about the shader. + + 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 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +layout(push_constant) uniform Push +{ + float TATE, IOS, OS, BLOOM, brightboost, brightboost1, gsl, scanline1, scanline2, beam_min, beam_max, beam_size, + h_sharp, s_sharp, csize, bsize, warpX, warpY, glow, shadowMask, masksize, vertmask, + slotmask, slotmask1, slotwidth, double_slot, mcut, maskDark, maskLight, maskstr, spike; +} params; + +layout(std140, set = 0, binding = 0) uniform UBO +{ + mat4 MVP; + vec4 SourceSize; + vec4 OriginalSize; + vec4 OutputSize; + uint FrameCount; + float bloom; + float halation; + float scans; + float scansub; + float slotms; + float mclip; + float gamma_c; + float mask_gamma; + float smart_ei; + float ei_limit; + float sth; + float gamma_out; + float overscanY; + float intres; + float prescalex; + float c_shape; + float barspeed; + float barintensity; + float bardir; +} global; + + +#pragma parameter bogus_brightness "[ BRIGHTNESS SETTINGS ]:" 0.0 0.0 1.0 1.0 + +#pragma parameter glow " Glow Strength" 0.08 -2.0 2.0 0.01 +#define glow params.glow // Glow Strength + +#pragma parameter bloom " Bloom Strength" 0.0 0.0 2.0 0.05 +#define bloom global.bloom // bloom effect + +#pragma parameter halation " Halation Strength" 0.0 0.0 2.0 0.025 +#define halation global.halation // halation effect + +#pragma parameter gamma_c " Gamma correct" 1.0 0.50 2.0 0.02 +#define gamma_c global.gamma_c // adjust brightness + +#pragma parameter brightboost " Bright Boost Dark Pixels" 1.40 0.25 10.0 0.05 +#define brightboost params.brightboost // adjust brightness + +#pragma parameter brightboost1 " Bright Boost Bright Pixels" 1.10 0.25 3.00 0.025 +#define brightboost1 params.brightboost1 // adjust brightness + +#pragma parameter bogus_scanline "[ SCANLINE OPTIONS ]: " 0.0 0.0 1.0 1.0 + +#pragma parameter gsl " Scanline Type" 0.0 -1.0 2.0 1.0 +#define gsl params.gsl // Alternate scanlines + +#pragma parameter scanline1 " Scanline Beam Shape Center" 6.0 0.0 20.0 0.5 +#define scanline1 params.scanline1 // scanline param, vertical sharpness + +#pragma parameter scanline2 " Scanline Beam Shape Edges" 8.0 0.0 40.0 1.0 +#define scanline2 params.scanline2 // scanline param, vertical sharpness + +#pragma parameter beam_min " Scanline Shape Dark Pixels" 1.30 0.25 3.5 0.05 +#define beam_min params.beam_min // dark area beam min - narrow + +#pragma parameter beam_max " Scanline Shape Bright Pixels" 1.00 0.4 2.5 0.05 +#define beam_max params.beam_max // bright area beam max - wide + +#pragma parameter beam_size " Increased Bright Scanline Beam" 0.60 0.0 1.0 0.05 +#define beam_size params.beam_size // increased max. beam size + +#pragma parameter vertmask " Scanline Color Deconvergence" 0.0 -1.0 1.0 0.1 +#define vertmask params.vertmask // Scanline deconvergence colors + +#pragma parameter scans " Scanline Saturation" 0.60 0.0 1.0 0.05 +#define scans global.scans // scanline saturation + +// Scanline darken 'edges' effect - need to uncomment it. + +// #pragma parameter scansub " Scanline darken 'edges'" 0.0 0.0 0.30 0.005 +// #define scansub global.scansub // scanline substraction + +#pragma parameter spike " Scanline Spike Removal" 1.0 0.0 2.0 0.10 +#define spike params.spike + +#pragma parameter bogus_filtering "[ FILTERING OPTIONS ]: " 0.0 0.0 1.0 1.0 + +#pragma parameter h_sharp " Horizontal sharpness" 5.20 0.20 15.0 0.10 +#define h_sharp params.h_sharp // pixel sharpness + +#pragma parameter s_sharp " Substractive sharpness (1.0 recommended)" 0.50 0.0 1.5 0.10 +#define s_sharp params.s_sharp // substractive sharpness + +#pragma parameter smart_ei " Smart Edges Effect Strength" 0.0 0.0 20.0 0.25 +#define smart_ei global.smart_ei // smart edge handling + +#pragma parameter ei_limit " Smart Edges Effect Strength Limit" 2.0 1.0 12.0 0.1 +#define ei_limit global.ei_limit // smart edge handling + +#pragma parameter sth " Smart Edges Smoothing Threshold" 0.20 0.0 1.0 0.01 +#define sth global.sth // corner size + +#pragma parameter bogus_screen "[ SCREEN OPTIONS ]: " 0.0 0.0 1.0 1.0 + +#pragma parameter intres " Internal Resolution Y: 224p/240p, 1.5...y-dowsample" 0.0 0.0 6.0 0.5 // Joint parameter with linearize pass, values must match + +#pragma parameter TATE " TATE Mode" 0.0 0.0 1.0 1.0 +#define TATE params.TATE // Screen orientation + +#pragma parameter IOS " Integer Scaling: Odd:Y, Even:'X'+Y" 0.0 0.0 4.0 1.0 +#define IOS params.IOS // Smart Integer Scaling + +#pragma parameter OS " R. Bloom Overscan Mode" 1.0 0.0 2.0 1.0 +#define OS params.OS // Do overscan + +#pragma parameter BLOOM " Raster bloom %" 0.0 0.0 20.0 1.0 +#define BLOOM params.BLOOM // Bloom overscan percentage + +#pragma parameter csize " Corner size" 0.0 0.0 0.25 0.01 +#define csize params.csize // corner size + +#pragma parameter bsize " Border smoothness" 600.0 100.0 700.0 10.0 +#define bsize params.bsize // border smoothness + +#pragma parameter barspeed " Hum Bar Speed" 50.0 5.0 200.0 1.0 + +#pragma parameter barintensity " Hum Bar Intensity" 0.0 -1.0 1.0 0.01 + +#pragma parameter bardir " Hum Bar Direction" 0.0 0.0 1.0 1.0 + +#pragma parameter warpX " CurvatureX (default 0.03)" 0.0 0.0 0.25 0.01 +#define warpX params.warpX // Curvature X + +#pragma parameter warpY " CurvatureY (default 0.04)" 0.0 0.0 0.25 0.01 +#define warpY params.warpY // Curvature Y + +#pragma parameter c_shape " Curvature Shape" 0.25 0.05 0.60 0.05 +#define c_shape global.c_shape // curvature shape + +#pragma parameter overscanY " Overscan Y original pixels" 0.0 -50.0 50.0 1.0 +#define overscanY global.overscanY // OverscanY pixels + +#pragma parameter prescalex " Prescale-X Factor (for xBR...pre-shader)" 1.0 1.0 4.0 1.0 +#define prescalex global.prescalex // prescale-x factor + +#pragma parameter bogus_masks "[ CRT MASK OPTIONS ]: " 0.0 0.0 1.0 1.0 + +#pragma parameter shadowMask " CRT Mask: 0:CGWG, 1-4:Lottes, 5-7:'Trinitron'" 0.0 -1.0 8.0 1.0 +#define shadowMask params.shadowMask // Mask Style + +#pragma parameter maskstr " Mask Strength (0, 5-8)" 0.3 -0.5 1.0 0.05 +#define maskstr params.maskstr // CGWG Mask Strength + +#pragma parameter mcut " Mask 5-7 Low Strength" 1.10 0.0 2.0 0.05 +#define mcut params.mcut // Mask 5-7 dark color strength + +#pragma parameter masksize " CRT Mask Size (2.0 is nice in 4k)" 1.0 1.0 4.0 1.0 +#define masksize params.masksize // Mask Size + +#pragma parameter maskDark " Lottes maskDark" 0.5 0.0 2.0 0.05 +#define maskDark params.maskDark // Dark "Phosphor" + +#pragma parameter maskLight " Lottes maskLight" 1.5 0.0 2.0 0.05 +#define maskLight params.maskLight // Light "Phosphor" + +#pragma parameter mask_gamma " Mask gamma" 2.40 1.0 5.0 0.05 +#define mask_gamma global.mask_gamma // Mask application gamma + +#pragma parameter slotmask " Slot Mask Strength Bright Pixels" 0.0 0.0 1.0 0.05 +#define slotmask params.slotmask + +#pragma parameter slotmask1 " Slot Mask Strength Dark Pixels" 0.0 0.0 1.0 0.05 +#define slotmask1 params.slotmask1 + +#pragma parameter slotwidth " Slot Mask Width" 2.0 1.0 6.0 0.5 +#define slotwidth params.slotwidth // Slot Mask Width + +#pragma parameter double_slot " Slot Mask Height: 2x1 or 4x1" 1.0 1.0 2.0 1.0 +#define double_slot params.double_slot // Slot Mask Height + +#pragma parameter slotms " Slot Mask Size" 1.0 1.0 4.0 1.0 +#define slotms global.slotms // Slot Mask Size + +#pragma parameter mclip " Keep Mask effect with clipping" 0.50 0.0 1.0 0.05 +#define mclip global.mclip // Slot Mask Size + +#pragma parameter gamma_out "Gamma out" 2.4 1.0 5.0 0.05 +#define gamma_out global.gamma_out // output gamma + +#define COMPAT_TEXTURE(c,d) texture(c,d) +#define TEX0 vTexCoord + +#define OutputSize global.OutputSize +#define gl_FragCoord (vTexCoord * OutputSize.xy) + +#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 * 1.000001; +} + +#pragma stage fragment +layout(location = 0) in vec2 vTexCoord; +layout(location = 0) out vec4 FragColor; +layout(set = 0, binding = 2) uniform sampler2D LinearizePass; +layout(set = 0, binding = 3) uniform sampler2D AvgLumPass; +layout(set = 0, binding = 4) uniform sampler2D GlowPass; +layout(set = 0, binding = 5) uniform sampler2D BloomPass; +layout(set = 0, binding = 6) uniform sampler2D PrePass; + +#define eps 1e-10 + +float st(float x) +{ + return exp2(-10.0*x*x); +} + +float sw0(float x, float color, float scanline) +{ + float tmp = mix(beam_min, beam_max, color); + float ex = x*tmp; + ex = (gsl > -0.5) ? ex*ex : mix(ex*ex, ex*ex*ex, 0.4); + return exp2(-scanline*ex); +} + +float sw1(float x, float color, float scanline) +{ + x = mix (x, beam_min*x, max(x-0.4*color,0.0)); + float tmp = mix(1.2*beam_min, beam_max, color); + float ex = x*tmp; + return exp2(-scanline*ex*ex); +} + +float sw2(float x, float color, float scanline) +{ + float tmp = mix((2.5-0.5*color)*beam_min, beam_max, color); + tmp = mix(beam_max, tmp, pow(x, color+0.3)); + float ex = x*tmp; + return exp2(-scanline*ex*ex); +} + +// Shadow mask (1-4 from PD CRT Lottes shader). + +vec3 Mask(vec2 pos, float mx) +{ + pos = floor(pos/masksize); + vec3 mask = vec3(maskDark, maskDark, maskDark); + vec3 one = vec3(1.0); + float dark_compensate = mix(max( clamp( mix (mcut, maskstr, mx),0.0, 1.0) - 0.3, 0.0) + 1.0, 1.0, mx); + float mc = 1.0 - max(maskstr, 0.0); + + // No mask + if (shadowMask == -1.0) + { + mask = vec3(1.0); + } + + // Phosphor. + else if (shadowMask == 0.0) + { + pos.x = fract(pos.x*0.5); + if (pos.x < 0.5) { mask.r = 1.0; mask.g = mc; mask.b = 1.0; } + else { mask.r = mc; mask.g = 1.0; mask.b = mc; } + } + + // Very compressed TV style shadow mask. + else if (shadowMask == 1.0) + { + float line = maskLight; + float odd = 0.0; + + if (fract(pos.x/6.0) < 0.5) + odd = 1.0; + if (fract((pos.y + odd)/2.0) < 0.5) + line = maskDark; + + pos.x = fract(pos.x/3.0); + + if (pos.x < 0.333) mask.r = maskLight; + else if (pos.x < 0.666) mask.g = maskLight; + else mask.b = maskLight; + + mask*=line; + } + + // Aperture-grille. + else if (shadowMask == 2.0) + { + pos.x = fract(pos.x/3.0); + + if (pos.x < 0.333) mask.r = maskLight; + else if (pos.x < 0.666) mask.g = maskLight; + else mask.b = maskLight; + } + + // Stretched VGA style shadow mask (same as prior shaders). + else if (shadowMask == 3.0) + { + pos.x += pos.y*3.0; + pos.x = fract(pos.x/6.0); + + if (pos.x < 0.333) mask.r = maskLight; + else if (pos.x < 0.666) mask.g = maskLight; + else mask.b = maskLight; + } + + // VGA style shadow mask. + else if (shadowMask == 4.0) + { + pos.xy = floor(pos.xy*vec2(1.0, 0.5)); + pos.x += pos.y*3.0; + pos.x = fract(pos.x/6.0); + + if (pos.x < 0.333) mask.r = maskLight; + else if (pos.x < 0.666) mask.g = maskLight; + else mask.b = maskLight; + } + + // Trinitron mask 5 + else if (shadowMask == 5.0) + { + mask = vec3(0.0); + pos.x = fract(pos.x/2.0); + if (pos.x < 0.5) + { mask.r = 1.0; + mask.b = 1.0; + } + else mask.g = 1.0; + mask = clamp(mix( mix(one, mask, mcut), mix(one, mask, maskstr), mx), 0.0, 1.0) * dark_compensate; + } + + // Trinitron mask 6 + else if (shadowMask == 6.0) + { + mask = vec3(0.0); + pos.x = fract(pos.x/3.0); + if (pos.x < 0.333) mask.r = 1.0; + else if (pos.x < 0.666) mask.g = 1.0; + else mask.b = 1.0; + mask = clamp(mix( mix(one, mask, mcut), mix(one, mask, maskstr), mx), 0.0, 1.0) * dark_compensate; + } + + // BW Trinitron mask 7 + else if (shadowMask == 7.0) + { + float maskTmp = clamp(mix( mix(1.0, 0.0, mcut), mix(1.0, 0.0, maskstr), mx), 0.0, 1.0) * dark_compensate; + mask = vec3(maskTmp); + pos.x = fract(pos.x/2.0); + if (pos.x < 0.5) mask = vec3(1.0); + } + + // 4k mask + else + { + mask = vec3(mc); + pos.x = fract(pos.x * 0.25); + if (pos.x < 0.2) mask.r = 1.0; + else if (pos.x < 0.4) mask.rg = 1.0.xx; + else if (pos.x < 0.7) mask.gb = 1.0.xx; + else mask.b = 1.0; + } + + return mask; +} + +float SlotMask(vec2 pos, float m) +{ + if ((slotmask + slotmask1) == 0.0) return 1.0; + else + { + pos = floor(pos/slotms); + float mlen = slotwidth*2.0; + float px = fract(pos.x/mlen); + float py = floor(fract(pos.y/(2.0*double_slot))*2.0*double_slot); + float slot_dark = mix(1.0-slotmask1, 1.0-slotmask, m); + float slot = 1.0; + if (py == 0.0 && px < 0.5) slot = slot_dark; else + if (py == double_slot && px >= 0.5) slot = slot_dark; + + return slot; + } +} + +vec2 Warp(vec2 pos) +{ + pos = pos*2.0-1.0; + pos = mix(pos, vec2(pos.x*inversesqrt(1.0-c_shape*pos.y*pos.y), pos.y*inversesqrt(1.0-c_shape*pos.x*pos.x)), vec2(warpX, warpY)/c_shape); + return pos*0.5 + 0.5; +} + +vec2 Overscan(vec2 pos, float dx, float dy){ + pos=pos*2.0-1.0; + pos*=vec2(dx,dy); + return pos*0.5+0.5; +} + +float humbar(float pos) +{ + if (global.barintensity == 0.0) return 1.0; else + { + pos = (global.barintensity >= 0.0) ? pos : (1.0-pos); + pos = fract(pos + mod(float(global.FrameCount),global.barspeed)/(global.barspeed-1.0)); + pos = (global.barintensity < 0.0) ? pos : (1.0-pos); + return (1.0-global.barintensity) + global.barintensity*pos; + } +} + +// Borrowed from cgwg's crt-geom, under GPL + +float corner(vec2 coord) +{ + coord = min(coord, vec2(1.0)-coord) * vec2(1.0, OutputSize.y/OutputSize.x); + vec2 cdist = vec2(max(csize/3.0, max((1.0-smoothstep(100.0,600.0,bsize))*0.01,0.002))); + coord = (cdist - min(coord,cdist)); + float dist = sqrt(dot(coord,coord)); + return clamp((cdist.x-dist)*bsize,0.0, 1.0); +} + +vec3 declip(vec3 c, float b) +{ + float m = max(max(c.r,c.g),c.b); + if (m > b) c = c*b/m; + return c; +} + +vec3 gc(vec3 c) +{ + float mc = max(max(c.r,c.g),c.b); + float mg = pow(mc, 1.0/gamma_c); + return c * mg/(mc + eps); +} + +vec3 plant (vec3 tar, float r) +{ + float t = max(max(tar.r,tar.g),tar.b) + 0.00001; + return tar * r / t; +} + +void main() +{ + vec4 SourceSize = global.OriginalSize * vec4(prescalex, 1.0, 1.0/prescalex, 1.0); + + float lum = COMPAT_TEXTURE(AvgLumPass, vec2(0.5,0.5)).a; + + float gamma_in = 1.0/COMPAT_TEXTURE(LinearizePass, vec2(0.25,0.25)).a; + float intera = COMPAT_TEXTURE(LinearizePass, vec2(0.75,0.25)).a; + bool interb = (intera < 0.75); + bool notate = (TATE < 0.5); + + float SourceY = mix(SourceSize.y, SourceSize.x, TATE); + float sy = 1.0; + if (global.intres == 0.5) sy = SourceY/224.0; else + if (global.intres == 1.0) sy = SourceY/240.0; else + if (global.intres > 1.25) sy = global.intres; + if (notate) SourceSize*=vec4(1.0, 1.0/sy, 1.0, sy); else SourceSize*=vec4(1.0/sy, 1.0, sy, 1.0); + + // Calculating texel coordinates + + vec2 texcoord = TEX0.xy; + if (IOS > 0.0){ + vec2 ofactor = OutputSize.xy/SourceSize.xy; + vec2 intfactor = (IOS < 2.5) ? floor(ofactor) : ceil(ofactor); + vec2 diff = ofactor/intfactor; + float scan = mix(diff.y, diff.x, TATE); + texcoord = Overscan(texcoord, scan, scan); + if (IOS == 1.0 || IOS == 3.0) texcoord = mix(vec2(TEX0.x, texcoord.y), vec2(texcoord.x, TEX0.y), TATE); + } + + float factor = 1.00 + (1.0-0.5*OS)*BLOOM/100.0 - lum*BLOOM/100.0; + texcoord = Overscan(texcoord, factor, factor); + + texcoord = Overscan(texcoord, 1.0, (SourceSize.y - overscanY)/SourceSize.y); + + vec2 pos = Warp(texcoord); + vec2 pos0 = Warp(TEX0.xy); + + bool smarte = (smart_ei > 0.0 && notate); // smart edge interpolation on / off + + vec2 coffset = vec2(0.5, 0.5); + + vec2 ps = SourceSize.zw; + vec2 OGL2Pos = pos * SourceSize.xy - coffset; + vec2 fp = fract(OGL2Pos); + + vec2 dx = vec2(ps.x,0.0); + vec2 dy = vec2(0.0, ps.y); + + // Reading the texels + vec2 x2 = 2.0*dx; + vec2 y2 = 2.0*dy; + + vec2 offx = dx; + vec2 off2 = x2; + vec2 offy = dy; + float fpx = fp.x; + if(!notate) + { + offx = dy; + off2 = y2; + offy = dx; + fpx = fp.y; + } + float f = (notate) ? fp.y : fp.x; + + vec2 pC4 = floor(OGL2Pos) * ps + 0.5*ps; + + if (interb) pC4.y = pos.y; + + float zero = exp2(-h_sharp); + float sharp1 = s_sharp * zero; + + float fdivider = min(prescalex, 2.0); + + float wl3 = (2.0 + fpx)/fdivider; + float wl2 = (1.0 + fpx)/fdivider; + float wl1 = ( fpx)/fdivider; + float wr1 = (1.0 - fpx)/fdivider; + float wr2 = (2.0 - fpx)/fdivider; + float wr3 = (3.0 - fpx)/fdivider; + + wl3*=wl3; wl3 = exp2(-h_sharp*wl3); + wl2*=wl2; wl2 = exp2(-h_sharp*wl2); + wl1*=wl1; wl1 = exp2(-h_sharp*wl1); + wr1*=wr1; wr1 = exp2(-h_sharp*wr1); + wr2*=wr2; wr2 = exp2(-h_sharp*wr2); + wr3*=wr3; wr3 = exp2(-h_sharp*wr3); + + float fp1 = 1.-fpx; + + float twl3 = max(wl3 - sharp1, 0.0); + float twl2 = max(wl2 - sharp1, mix(-0.12, 0.0, 1.0-fp1*fp1)); float scl2 = max(twl2, 0.0); + float twl1 = max(wl1 - sharp1, 0.0); + float twr1 = max(wr1 - sharp1, 0.0); + float twr2 = max(wr2 - sharp1, mix(-0.12, 0.0, 1.0-fpx*fpx)); float scr2 = max(twr2, 0.0); + float twr3 = max(wr3 - sharp1, 0.0); + + bool sharp = (sharp1 > 0.0); + + float rwl3, rwl2, rwr2; + + float rwl1 = twl1; + float rwr1 = twr1; + vec3 c1, c2; + + if (smarte) + { + rwl3 = wl3; rwl2 = wl2; + rwl1 = wl1; rwr1 = wr1; + rwr2 = wr2; + twl3 = 0.0; twr3 = 0.0; + vec3 t = COMPAT_TEXTURE(AvgLumPass, pC4 - offy).xyz; + vec3 a = COMPAT_TEXTURE(AvgLumPass, pC4 ).xyz; + vec3 b = COMPAT_TEXTURE(AvgLumPass, pC4 + offy).xyz; + vec3 d = COMPAT_TEXTURE(AvgLumPass, pC4 +dy+dy).xyz; + c1 = (h_sharp > 2.6) ? a : min(a,(t + a + b)/3.0); c1 = max(c1 - sth, 0.0); + c2 = (h_sharp > 2.6) ? b : min(b,(a + b + d)/3.0); c2 = max(c2 - sth, 0.0); + } + + vec3 l3, l2, l1, r1, r2, r3, sl2, sl1, sr1, sr2, color1, color2, colmin, colmax; + + l3 = COMPAT_TEXTURE(LinearizePass, pC4 -off2).rgb; + l2 = COMPAT_TEXTURE(LinearizePass, pC4 -offx).rgb; + l1 = COMPAT_TEXTURE(LinearizePass, pC4 ).rgb; + r1 = COMPAT_TEXTURE(LinearizePass, pC4 +offx).rgb; + r2 = COMPAT_TEXTURE(LinearizePass, pC4 +off2).rgb; + r3 = COMPAT_TEXTURE(LinearizePass, pC4 +offx+off2).rgb; + + colmin = min(min(l1,r1), min(l2,r2)); + colmax = max(max(l1,r1), max(l2,r2)); + + if (smarte) + { + float pc = min(1.0 + smart_ei*c1.y, ei_limit); + float pl = min(1.0 + smart_ei*max(c1.y,c1.x), ei_limit); + float pr = min(1.0 + smart_ei*max(c1.y,c1.z), ei_limit); + twl1 = pow(abs(rwl1), pc); twr1 = pow(abs(rwr1), pc); + twl2 = pow(abs(rwl2), pl); twr2 = pow(abs(rwr2), pr); + float wmax = max(twl1, twr1); + float sharp_ei = s_sharp*pow(zero, pc)/wmax; + twl2 = max(twl2/wmax - sharp_ei, mix(-0.12, 0.0, 1.0-fp1*fp1)); + twl1 = max(twl1/wmax - sharp_ei, 0.0); + twr1 = max(twr1/wmax - sharp_ei, 0.0); + twr2 = max(twr2/wmax - sharp_ei, mix(-0.12, 0.0, 1.0-fpx*fpx)); + } + color1 = (l3*twl3 + l2*twl2 + l1*twl1 + r1*twr1 + r2*twr2 + r3*twr3)/(twl3+twl2+twl1+twr1+twr2+twr3); + + if (sharp) color1 = clamp(color1, colmin, colmax); + float ts = 0.033; + + float lm2 = max(max(l2.r,l2.g),l2.b); + float lm1 = max(max(l1.r,l1.g),l1.b); + float rm1 = max(max(r1.r,r1.g),r1.b); + float rm2 = max(max(r2.r,r2.g),r2.b); + + float swl2 = max(twl2, 0.0) * (lm2+ts); + float swl1 = twl1 * (lm1+ts); + float swr1 = twr1 * (rm1+ts); + float swr2 = max(twr2, 0.0) * (rm2+ts); + + float fscolor1 = (lm2*swl2 + lm1*swl1 + rm1*swr1 + rm2*swr2)/(swl2+swl1+swr1+swr2); + vec3 mcolor1 = vec3(fscolor1); + vec3 scolor1 = vec3(clamp(mix(max(max(color1.r,color1.g),color1.b), fscolor1, spike), 0.0, 1.0)); + + vec3 scolor2, mcolor2; + + if (!interb) +{ + pC4+=offy; + + l3 = COMPAT_TEXTURE(LinearizePass, pC4 -off2).rgb; + l2 = COMPAT_TEXTURE(LinearizePass, pC4 -offx).rgb; + l1 = COMPAT_TEXTURE(LinearizePass, pC4 ).rgb; + r1 = COMPAT_TEXTURE(LinearizePass, pC4 +offx).rgb; + r2 = COMPAT_TEXTURE(LinearizePass, pC4 +off2).rgb; + r3 = COMPAT_TEXTURE(LinearizePass, pC4 +offx+off2).rgb; + + colmin = min(min(l1,r1), min(l2,r2)); + colmax = max(max(l1,r1), max(l2,r2)); + + if (smarte) + { + float pc = min(1.0 + smart_ei*c2.y, ei_limit); + float pl = min(1.0 + smart_ei*max(c2.y,c2.x), ei_limit); + float pr = min(1.0 + smart_ei*max(c2.y,c2.z), ei_limit); + twl1 = pow(abs(rwl1), pc); twr1 = pow(abs(rwr1), pc); + twl2 = pow(abs(rwl2), pl); twr2 = pow(abs(rwr2), pr); + float wmax = max(twl1, twr1); + float sharp_ei = s_sharp*pow(zero, pc)/wmax; + twl2 = max(twl2/wmax - sharp_ei, mix(-0.12, 0.0, 1.0-fp1*fp1)); + twl1 = max(twl1/wmax - sharp_ei, 0.0); + twr1 = max(twr1/wmax - sharp_ei, 0.0); + twr2 = max(twr2/wmax - sharp_ei, mix(-0.12, 0.0, 1.0-fpx*fpx)); + } + color2 = (l3*twl3 + l2*twl2 + l1*twl1 + r1*twr1 + r2*twr2 + r3*twr3)/(twl3+twl2+twl1+twr1+twr2+twr3); + + if (sharp) color2 = clamp(color2, colmin, colmax); + + lm2 = max(max(l2.r,l2.g),l2.b); + lm1 = max(max(l1.r,l1.g),l1.b); + rm1 = max(max(r1.r,r1.g),r1.b); + rm2 = max(max(r2.r,r2.g),r2.b); + + swl2 = max(twl2, 0.0) * (lm2+ts); + swl1 = twl1 * (lm1+ts); + swr1 = twr1 * (rm1+ts); + swr2 = max(twr2, 0.0) * (rm2+ts); + + float fscolor2 = (lm2*swl2 + lm1*swl1 + rm1*swr1 + rm2*swr2)/(swl2+swl1+swr1+swr2); + mcolor2 = vec3(fscolor2); + scolor2 = vec3(clamp(mix(max(max(color2.r,color2.g),color2.b), fscolor2, spike), 0.0, 1.0)); +} + + vec3 ctmp; vec3 mcolor; float w3; vec3 color; + vec3 one = vec3(1.0); + +if (!interb) +{ + // calculating scanlines + + float shape1 = mix(scanline1, scanline2, f); + float shape2 = mix(scanline1, scanline2, 1.0-f); + + float wt1 = st(f); + float wt2 = st(1.0-f); + + vec3 color00 = color1*wt1 + color2*wt2; + vec3 scolor0 = scolor1*wt1 + scolor2*wt2; + mcolor = (mcolor1*wt1 + mcolor2*wt2)/(wt1+wt2); + + ctmp = color00/(wt1+wt2); + vec3 sctmp = scolor0/(wt1+wt2); + mcolor = clamp(mix(ctmp, mcolor, 1.25), 0.0, 1.0); + + float wf1, wf2; + + vec3 cref1 = mix(sctmp, scolor1, beam_size); float creff1 = max(max(cref1.r,cref1.g),cref1.b); + vec3 cref2 = mix(sctmp, scolor2, beam_size); float creff2 = max(max(cref2.r,cref2.g),cref2.b); + + float f1 = f; + float f2 = 1.0-f; + + if (gsl < 0.5) { wf1 = sw0(f1,creff1,shape1); wf2 = sw0(f2,creff2,shape2);} else + if (gsl == 1.0) { wf1 = sw1(f1,creff1,shape1); wf2 = sw1(f2,creff2,shape2);} else + { wf1 = sw2(f1,creff1,shape1); wf2 = sw2(f2,creff2,shape2);} + + if ((wf1 + wf2) > 1.0) { float wtmp = 1.0/(wf1+wf2); wf1*=wtmp; wf2*=wtmp; } + + // Scanline darken 'edges' effect - need to uncomment it. + + // float ws1 = max(wf1 - scansub, 0.2*wf1*wf2); wf1 = ws1/(1.0 - wf1 + ws1); + // float ws2 = max(wf2 - scansub, 0.2*wf2*wf1); wf2 = ws2/(1.0 - wf2 + ws2); + + // Scanline saturation application + + vec3 w1 = vec3(wf1); vec3 w2 = vec3(wf2); + w3 = wf1+wf2; + + cref1 = color1 / (max(max(color1.r,color1.g),color1.b) + 0.00001); + cref2 = color2 / (max(max(color2.r,color2.g),color2.b) + 0.00001); + + w1 = mix(w1*mix(one, cref1*cref1*cref1, scans), w1, wf1); + w2 = mix(w2*mix(one, cref2*cref2*cref2, scans), w2, wf2); + + vec3 cd1 = one; vec3 cd2 = one; float vm = sqrt(abs(vertmask)); + + float v_high1 = 1.0 + 0.3*vm; + float v_high2 = 1.0 + 0.6*vm; + float v_low = 1.0 - vm; + + float ds1 = min(max(1.0-w3*w3, 2.5*f1), 1.0); + float ds2 = min(max(1.0-w3*w3, 2.5*f2), 1.0); + + if (vertmask < 0.0) + { + cd1 = mix(one, vec3(v_high2, v_low, v_low), ds1); + cd2 = mix(one, vec3(v_low, v_high1, v_high1), ds2); + } + else + { + cd1 = mix(one, vec3(v_high1, v_low, v_high1), ds1); + cd2 = mix(one, vec3(v_low, v_high2, v_low), ds2); + } + + color = gc(color1)*w1*cd1 + gc(color2)*w2*cd2; + + color = min(color, 1.0); +} + + if (interb) + { + color = gc(color1); + mcolor = clamp(mix(color1, mcolor1, 1.25), 0.0, 1.0); + } + + float mx = max(max(mcolor.r,mcolor.g),mcolor.b); + mx = pow(mx, 1.20/gamma_in); + + // Apply Mask + + vec3 orig1 = color; + vec3 cmask = one; + + vec2 maskcoord = gl_FragCoord.yx * 1.000001; + if (notate) maskcoord = maskcoord.yx; + + float smask = SlotMask(maskcoord, mx); + cmask*= Mask(maskcoord, mx); + + color = pow(color, vec3(mask_gamma/gamma_in)); + color = color*cmask; + color = min(color,1.0); + color = color*smask; + color = pow(color, vec3(gamma_in/mask_gamma)); + + cmask = min(cmask*smask, 1.0); + + if (interb) ctmp = color; + float colmx = pow( max( max(ctmp.r, ctmp.g), ctmp.b), 1.40/gamma_out); + float bb = mix(brightboost, brightboost1, colmx); + if (interb) bb = (abs(intera-0.5)<0.1) ? pow(0.80*bb, 0.65) : pow(bb, 0.70); + color*=bb; + + vec3 Glow = COMPAT_TEXTURE(GlowPass, pos).rgb; + vec3 Bloom = COMPAT_TEXTURE(BloomPass, pos).rgb; + float maxb = COMPAT_TEXTURE(BloomPass, pos).a; + float vig = COMPAT_TEXTURE(PrePass, clamp(pos, 0.0+0.5*global.OriginalSize.zw, 1.0-0.5*global.OriginalSize.zw)).a; + + vec3 Bloom1 = min(Bloom*(orig1+color), max(0.5*(colmx + orig1 - color),0.0)); + color = color + bloom*Bloom1; + + color = min(color, mix(one, cmask, mclip)); + if (!interb) color = declip(color, pow(w3,0.60)); + + if (halation > 0.025) { + Bloom = mix(0.5*(Bloom + Bloom*Bloom), Bloom*Bloom, colmx); + color = color + (1.1-0.25*colmx)*(0.75+maxb)*Bloom*(0.75 + 0.70*pow(colmx,0.33333))*mix(1.0,w3,0.5*colmx)*mix(one,cmask,0.35 + 0.4*maxb)*halation; } + + Glow = mix(Glow, 0.25*color, 0.7*colmx); + if (glow >= 0.0) color = color + 0.5*Glow*glow; else { cmask*=cmask; cmask*=cmask; color = color + (-glow)*cmask*Glow; } + + color = min(color, 1.0); + + color = pow(color, vec3(1.0/gamma_out)); + + FragColor = vec4(color*vig*humbar(mix(pos.y, pos.x, global.bardir)), corner(pos0)); +} diff --git a/crt/shaders/guest/advanced/custom-fast-sharpen.slang b/crt/shaders/guest/advanced/custom-fast-sharpen.slang new file mode 100644 index 0000000..0fa6dbe --- /dev/null +++ b/crt/shaders/guest/advanced/custom-fast-sharpen.slang @@ -0,0 +1,85 @@ +#version 450 + +/* + Fast Sharpen Shader (Custom) + + Copyright (C) 2005 - 2019 guest(r) - guest.r@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 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +layout(push_constant) uniform Push +{ + vec4 SourceSize; + vec4 OriginalSize; + vec4 OutputSize; + uint FrameCount; + float SHARPEN, CONTR, DETAILS; +} params; + +#pragma parameter SHARPEN "Sharpen strength" 0.00 0.0 4.00 0.10 +#pragma parameter CONTR "Ammount of sharpening" 0.05 0.0 0.25 0.01 +#pragma parameter DETAILS "Details sharpened " 1.00 0.0 1.00 0.05 + +#define SHARPEN params.SHARPEN +#define CONTR params.CONTR +#define DETAILS params.DETAILS + +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 Source; + + +void main() +{ + vec2 g01 = vec2(-1.0, 0.0)*params.SourceSize.zw; + vec2 g21 = vec2( 1.0, 0.0)*params.SourceSize.zw; + + vec3 c01 = texture(Source, vTexCoord + g01).rgb; + vec3 c21 = texture(Source, vTexCoord + g21).rgb; + vec3 c11 = texture(Source, vTexCoord ).rgb; + + vec3 b11 = 0.5*(c01+c21); + + float contrast = max(max(c11.r,c11.g),c11.b); + contrast = mix(2.0*CONTR, CONTR, contrast); + + vec3 mn1 = min(c01,c21); mn1 = min(mn1,c11*(1.0-contrast)); + vec3 mx1 = max(c01,c21); mx1 = max(mx1,c11*(1.0+contrast)); + + vec3 dif = pow(mx1-mn1+0.0001, vec3(0.75,0.75,0.75)); + vec3 sharpen = mix(vec3(SHARPEN*DETAILS), vec3(SHARPEN), dif); + + c11 = clamp(mix(c11,b11,-sharpen), mn1,mx1); + + FragColor = vec4(c11,1.0); +} diff --git a/crt/shaders/guest/advanced/deconvergence.slang b/crt/shaders/guest/advanced/deconvergence.slang new file mode 100644 index 0000000..2ce761f --- /dev/null +++ b/crt/shaders/guest/advanced/deconvergence.slang @@ -0,0 +1,198 @@ +#version 450 + +/* + CRT - Guest - Advanced - Deconvergence pass + noise + + Copyright (C) 2021 guest(r) - guest.r@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 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +layout(push_constant) uniform Push +{ + vec4 OutputSize; + uint FrameCount; + float deconr; + float TATE; + float decons; + float addnoised; + float noiseresd; + float shadowMask; + float masksize; + float deconrr; + float deconrg; + float deconrb; + float deconrry; + float deconrgy; + float deconrby; + float deconsmooth; + float dctypex; + float dctypey; +} params; + +#pragma parameter TATE " TATE Mode" 0.0 0.0 1.0 1.0 +#define TATE params.TATE // Screen orientation + +#pragma parameter bogus_deconvergence11 "[ HORIZONTAL/VERTICAL DECONVERGENCE ]: " 0.0 0.0 1.0 1.0 + +#pragma parameter dctypex " Deconvergence type X : 0.0 - static, other - dynamic" 0.0 0.0 1.0 0.05 + +#pragma parameter dctypey " Deconvergence type Y : 0.0 - static, other - dynamic" 0.0 0.0 1.0 0.05 + +#pragma parameter deconrr " Horizontal Deconvergence Red Range" 0.0 -12.0 12.0 0.25 + +#pragma parameter deconrg " Horizontal Deconvergence Green Range" 0.0 -12.0 12.0 0.25 + +#pragma parameter deconrb " Horizontal Deconvergence Blue Range" 0.0 -12.0 12.0 0.25 + +#pragma parameter deconrry " Vertical Deconvergence Red Range" 0.0 -12.0 12.0 0.25 + +#pragma parameter deconrgy " Vertical Deconvergence Green Range" 0.0 -12.0 12.0 0.25 + +#pragma parameter deconrby " Vertical Deconvergence Blue Range" 0.0 -12.0 12.0 0.25 + +#pragma parameter decons " Deconvergence Strength (and Type)" 0.5 -4.0 4.0 0.10 +#define decons params.decons // Horizontal deconvergence colors strength + +#pragma parameter deconsmooth " Deconvergence Smoothing" 0.0 0.0 1.0 0.10 + +#pragma parameter addnoised " Add Noise" 0.0 -1.0 1.0 0.02 +#define addnoised params.addnoised // add noise + +#pragma parameter noiseresd " Noise Resolution" 2.0 0.0 10.0 1.0 +#define noiseresd params.noiseresd // add noise + + +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 Source; + +#define COMPAT_TEXTURE(c,d) texture(c,d) + + +// noise function: +// Dedicated to the public domain. +// If you want a real license, you may consider this MIT/BSD/CC0/WTFPL-licensed (take your pick). +// Adapted from ChuckNorris - shadertoy: https://www.shadertoy.com/view/XtK3Dz + +vec3 noise(vec3 v){ + if (addnoised < 0.0) v.z = -addnoised; else v.z = v.z/6000.0; + // ensure reasonable range + v = fract(v) + fract(v*1e4) + fract(v*1e-4); + // seed + v += vec3(0.12345, 0.6789, 0.314159); + // more iterations => more random + v = fract(v*dot(v, v)*123.456); + v = fract(v*dot(v, v)*123.456); + v = fract(v*dot(v, v)*123.456); + return v; +} + +void main() +{ + + vec3 color = COMPAT_TEXTURE(Source, vTexCoord).rgb; + vec3 result = color; + + if ((abs(params.deconrr) + abs(params.deconrg) + abs(params.deconrb) + abs(params.deconrry) + abs(params.deconrgy) + abs(params.deconrby)) > 0.20) + { + float step = 1.0; + float dstep = step; + step*= (TATE < 0.5) ? (params.OutputSize.z) : (params.OutputSize.w); + float stepy = (TATE < 0.5) ? (params.OutputSize.w) : (params.OutputSize.z); + float stepx = (TATE < 0.5) ? (params.OutputSize.z) : (params.OutputSize.w); + + vec2 sx = mix(vec2(stepx, 0.0), vec2(0.0, stepx), TATE); + + float ds = decons; + + vec2 dx = (TATE < 0.5) ? vec2(step, 0.0) : vec2(0.0, step); + vec2 dy = (TATE > 0.5) ? vec2(stepy, 0.0) : vec2(0.0, stepy); + + float posx = 2.0*vTexCoord.x - 1.0; + float posy = 2.0*vTexCoord.y - 1.0; + + if (params.dctypex > 0.025) + { + posx = sign(posx)*pow(abs(posx), 1.05-params.dctypex); + dx = posx * dx; + } + + if (params.dctypey > 0.025) + { + + posy = sign(posy)*pow(abs(posy), 1.05-params.dctypey); + dy = posy * dy; + } + + if (params.dctypex > 0.025 || params.dctypey > 0.025) ds *= sqrt(posx*posx*sign(params.dctypex) + posy*posy*sign(params.dctypey)); + + vec2 rc = params.deconrr * dx + params.deconrry*dy; + vec2 gc = params.deconrg * dx + params.deconrgy*dy; + vec2 bc = params.deconrb * dx + params.deconrby*dy; + + dx = (dx+dy) * params.deconsmooth; + + float r1 = COMPAT_TEXTURE(Source, vTexCoord + rc ).r; + float g1 = COMPAT_TEXTURE(Source, vTexCoord + gc ).g; + float b1 = COMPAT_TEXTURE(Source, vTexCoord + bc ).b; + + float r2 = COMPAT_TEXTURE(Source, vTexCoord + rc -dx).r; + float g2 = COMPAT_TEXTURE(Source, vTexCoord + gc -dx).g; + float b2 = COMPAT_TEXTURE(Source, vTexCoord + bc -dx).b; + + float r3 = COMPAT_TEXTURE(Source, vTexCoord + rc +dx).r; + float g3 = COMPAT_TEXTURE(Source, vTexCoord + gc +dx).g; + float b3 = COMPAT_TEXTURE(Source, vTexCoord + bc +dx).b; + + vec3 result1 = vec3(r1,g1,b1); + vec3 result2 = vec3(r2,g2,b2); + vec3 result3 = vec3(r3,g3,b3); + result = (result1+result2+result3)/3.0; + + vec3 dcolor = max(max(COMPAT_TEXTURE(Source, vTexCoord + sx).rgb, COMPAT_TEXTURE(Source, vTexCoord - sx).rgb), color); + + float mc = max(max(dcolor.r, dcolor.g), dcolor.b); + if (decons < 0.0) mc = 0.9; + + result = clamp(mix(color, sqrt(mix(result*result, color*result, sqrt(mc))), abs(ds)), min(result,color), max(result, color)); + } + + float rc = 0.6*sqrt(max(max(result.r, result.g), result.b))+0.4; + + if (abs(addnoised) > 0.01) result = mix(result, noise(vec3(floor(params.OutputSize.xy * vTexCoord / noiseresd), float(params.FrameCount))), 0.25*abs(addnoised) * rc); + + float corner = COMPAT_TEXTURE(Source, vTexCoord).a; + + FragColor = vec4(result*corner, 1.0); +} \ No newline at end of file diff --git a/crt/shaders/guest/advanced/gaussian_horizontal.slang b/crt/shaders/guest/advanced/gaussian_horizontal.slang new file mode 100644 index 0000000..c0be5a9 --- /dev/null +++ b/crt/shaders/guest/advanced/gaussian_horizontal.slang @@ -0,0 +1,99 @@ +#version 450 + +/* + Gaussian blur - horizontal pass, dynamic range, resizable + + Copyright (C) 2020 guest(r) - guest.r@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 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +layout(push_constant) uniform Push +{ + vec4 LinearizePassSize; + vec4 OriginalSize; + vec4 OutputSize; + uint FrameCount; + float SIZEH; + float SIGMA_H; +} params; + +#pragma parameter bogus_glow "[ GLOW PASS SETTINGS ]:" 0.0 0.0 1.0 1.0 + +#pragma parameter SIZEH " Horizontal Glow Radius" 6.0 1.0 50.0 1.0 +#define SIZEH params.SIZEH + +#pragma parameter SIGMA_H " Horizontal Glow Sigma" 1.20 0.20 15.0 0.10 +#define SIGMA_H params.SIGMA_H + +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 LinearizePass; + +#define COMPAT_TEXTURE(c,d) texture(c,d) + +float invsqrsigma = 1.0/(2.0*SIGMA_H*SIGMA_H); + +float gaussian(float x) +{ + return exp(-x*x*invsqrsigma); +} + +void main() +{ + vec4 SourceSize1 = params.OriginalSize; + float f = fract(SourceSize1.x * vTexCoord.x); + f = 0.5 - f; + vec2 tex = floor(SourceSize1.xy * vTexCoord)*SourceSize1.zw + 0.5*SourceSize1.zw; + vec3 color = vec3(0.0); + vec2 dx = vec2(SourceSize1.z, 0.0); + + float w; + float wsum = 0.0; + vec3 pixel; + float n = -SIZEH; + + do + { + pixel = COMPAT_TEXTURE(LinearizePass, tex + n*dx).rgb; + w = gaussian(n+f); + color = color + w * pixel; + wsum = wsum + w; + n = n + 1.0; + + } while (n <= SIZEH); + + color = color / wsum; + + FragColor = vec4(color, 1.0); +} \ No newline at end of file diff --git a/crt/shaders/guest/fast/smoothing - kopija.slang b/crt/shaders/guest/advanced/gaussian_vertical.slang similarity index 56% rename from crt/shaders/guest/fast/smoothing - kopija.slang rename to crt/shaders/guest/advanced/gaussian_vertical.slang index 6222be8..29da738 100644 --- a/crt/shaders/guest/fast/smoothing - kopija.slang +++ b/crt/shaders/guest/advanced/gaussian_vertical.slang @@ -1,9 +1,9 @@ #version 450 /* - Smart Smoothing Difference Shader + Gaussian blur - vertical pass, dynamic range, resizable - Copyright (C) 2019 guest(r) - guest.r@gmail.com + Copyright (C) 2020 guest(r) - guest.r@gmail.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -19,9 +19,7 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#pragma name SmoothPass +*/ layout(push_constant) uniform Push { @@ -29,13 +27,16 @@ layout(push_constant) uniform Push vec4 OriginalSize; vec4 OutputSize; uint FrameCount; - float STH; + float SIZEV; + float SIGMA_V; } params; -#pragma parameter STH "Smart Smoothing Threshold" 0.7 0.4 1.2 0.05 -#define STH params.STH -#define SourceSize params.SourceSize -#define COMPAT_TEXTURE(c,d) texture(c,d) + +#pragma parameter SIZEV " Vertical Glow Radius" 6.0 1.0 50.0 1.0 +#define SIZEV params.SIZEV + +#pragma parameter SIGMA_V " Vertical Glow Sigma" 1.20 0.20 15.0 0.10 +#define SIGMA_V params.SIGMA_V layout(std140, set = 0, binding = 0) uniform UBO { @@ -58,27 +59,40 @@ layout(location = 0) in vec2 vTexCoord; layout(location = 0) out vec4 FragColor; layout(set = 0, binding = 2) uniform sampler2D Source; -float df (vec3 A, vec3 B) +#define COMPAT_TEXTURE(c,d) texture(c,d) + +float invsqrsigma = 1.0/(2.0*SIGMA_V*SIGMA_V); + +float gaussian(float x) { - float diff = length(A-B); - float luma = clamp(length(0.5*min(A,B) + 0.25*(A+B) + 1e-8), 0.0001, 1.0); - float diff1 = diff/luma; - return 1.0 - clamp(7.0*(max(1.5*diff,diff1)-STH), 0.0, 1.0); + return exp(-x*x*invsqrsigma); } void main() { - vec2 dx = vec2(SourceSize.z, 0.0); - vec2 dy = vec2(0.0, SourceSize.w); + vec4 SourceSize1 = vec4(params.SourceSize.x, params.OriginalSize.y, params.SourceSize.z, params.OriginalSize.w); + float f = fract(SourceSize1.y * vTexCoord.y); + f = 0.5 - f; + vec2 tex = floor(SourceSize1.xy * vTexCoord)*SourceSize1.zw + 0.5*SourceSize1.zw; + vec3 color = vec3(0.0); + vec2 dy = vec2(0.0, SourceSize1.w); - vec3 l1 = COMPAT_TEXTURE(Source, vTexCoord.xy -dx).xyz; - vec3 ct = COMPAT_TEXTURE(Source, vTexCoord.xy ).xyz; - vec3 r1 = COMPAT_TEXTURE(Source, vTexCoord.xy +dx).xyz; + float w; + float wsum = 0.0; + vec3 pixel; + float n = -SIZEV; + + do + { + pixel = COMPAT_TEXTURE(Source, tex + n*dy).rgb; + w = gaussian(n+f); + color = color + w * pixel; + wsum = wsum + w; + n = n + 1.0; + + } while (n <= SIZEV); + + color = color / wsum; - float dl = df(ct, l1); - float dr = df(ct, r1); - - float resx = dl; float resy = dr; - - FragColor = vec4(resx,resy,1.0,1.0); + FragColor = vec4(color, 1.0); } \ No newline at end of file diff --git a/crt/shaders/guest/advanced/linearize-hires.slang b/crt/shaders/guest/advanced/linearize-hires.slang new file mode 100644 index 0000000..cbacce9 --- /dev/null +++ b/crt/shaders/guest/advanced/linearize-hires.slang @@ -0,0 +1,172 @@ +#version 450 + +/* + Interlacing + + Copyright (C) 2020 guest(r) - guest.r@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 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +layout(push_constant) uniform Push +{ + vec4 OriginalSize; + vec4 OutputSize; + uint FrameCount; + float GAMMA_INPUT; + float inter; + float interm; + float iscan; + float intres; + float iscans; + float downsample_levelx; + float downsample_levely; + float prescalex; +} params; + + +layout(std140, set = 0, binding = 0) uniform UBO +{ + vec4 SourceSize; + mat4 MVP; +} global; + + +#pragma parameter GAMMA_INPUT "Gamma Input" 2.4 1.0 5.0 0.05 +#define GAMMA_INPUT params.GAMMA_INPUT + +#pragma parameter bogus_interlacing "[ INTERLACING OPTIONS ]: " 0.0 0.0 0.0 1.0 + +#pragma parameter inter " Interlace Trigger Resolution :" 400.0 0.0 800.0 25.0 +#define inter params.inter // interlace resolution + +#pragma parameter interm " Interlace Mode: OFF, Normal 1-3, Interpolation 4-5" 1.0 0.0 5.0 1.0 +#define interm params.interm // interlace mode + +#pragma parameter iscan " Interlacing Scanline Effect" 0.20 0.0 1.0 0.05 +#define iscan params.iscan // interlacing effect scanlining + +#pragma parameter intres " Internal Resolution Y: 224p/240p, 1.5...y-dowsample" 0.0 0.0 6.0 0.5 // Joint parameter with main pass, values must match +#define intres params.intres // interlace resolution + +#pragma parameter downsample_levelx " Downsampling-X (High-res content, pre-scalers)" 0.0 0.0 3.0 0.05 +#define downsample_levelx params.downsample_levelx // downsample level + +#pragma parameter downsample_levely " Downsampling-Y (High-res content, pre-scalers)" 0.0 0.0 3.0 0.05 +#define downsample_levely params.downsample_levely // downsample level + +#pragma parameter iscans " Interlacing (Scanline) Saturation" 0.40 0.0 1.0 0.05 +#define iscans params.iscans // interlace saturation + +#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 * 1.000001; +} + +#pragma stage fragment +layout(location = 0) in vec2 vTexCoord; +layout(location = 0) out vec4 FragColor; +layout(set = 0, binding = 2) uniform sampler2D PrePass; + +#define COMPAT_TEXTURE(c,d) texture(c,d) + + +vec3 plant (vec3 tar, float r) +{ + float t = max(max(tar.r,tar.g),tar.b) + 0.00001; + return tar * r / t; +} + + +vec3 fetch_pixel(vec2 coord) +{ + vec2 dx = vec2(global.SourceSize.z, 0.0) * downsample_levelx; + vec2 dy = vec2(0.0, global.SourceSize.w) * downsample_levely; + vec2 d1 = dx + dy; + vec2 d2 = dx - dy; + + float sum = 15.0; + vec3 result = 3.0*COMPAT_TEXTURE(PrePass, coord ).rgb + + 2.0*COMPAT_TEXTURE(PrePass, coord + dx).rgb + + 2.0*COMPAT_TEXTURE(PrePass, coord - dx).rgb + + 2.0*COMPAT_TEXTURE(PrePass, coord + dy).rgb + + 2.0*COMPAT_TEXTURE(PrePass, coord - dy).rgb + + COMPAT_TEXTURE(PrePass, coord + d1).rgb + + COMPAT_TEXTURE(PrePass, coord - d1).rgb + + COMPAT_TEXTURE(PrePass, coord + d2).rgb + + COMPAT_TEXTURE(PrePass, coord - d2).rgb; + + return result/sum; +} + + +void main() +{ + vec3 c1 = COMPAT_TEXTURE(PrePass, vTexCoord).rgb; + vec3 c2 = COMPAT_TEXTURE(PrePass, vTexCoord + vec2(0.0, params.OriginalSize.w)).rgb; + + if ((downsample_levelx + downsample_levely) > 0.025) + { + c1 = fetch_pixel(vTexCoord); + c2 = fetch_pixel(vTexCoord + vec2(0.0, params.OriginalSize.w)); + } + + vec3 c = c1; + + float intera = 1.0; + float gamma_in = clamp(GAMMA_INPUT, 1.0, 5.0); + + float m1 = max(max(c1.r,c1.g),c1.b); + float m2 = max(max(c2.r,c2.g),c2.b); + vec3 df = abs(c1-c2); + + float d = max(max(df.r,df.g),df.b); + if (interm == 2.0) d = mix(0.1*d,10.0*d, step(m1/(m2+0.0001),m2/(m1+0.0001))); + + float r = m1; + + float yres_div = 1.0; if (intres > 1.25) yres_div = intres; + + if (inter <= params.OriginalSize.y/yres_div && interm > 0.5 && intres != 1.0 && intres != 0.5) + { + intera = 0.5; + float line_no = clamp(floor(mod(params.OriginalSize.y*vTexCoord.y, 2.0)), 0.0, 1.0); + float frame_no = clamp(floor(mod(float(params.FrameCount),2.0)), 0.0, 1.0); + float ii = abs(line_no-frame_no); + + if (interm < 3.5) + { + c2 = plant(mix(c2, c2*c2, iscans), max(max(c2.r,c2.g),c2.b)); + r = clamp(max(m1*ii, (1.0-iscan)*min(m1,m2)), 0.0, 1.0); + c = plant( mix(mix(c1,c2, min(mix(m1, 1.0-m2, min(m1,1.0-m1))/(d+0.00001),1.0)), c1, ii), r); + if (interm == 3.0) c = (1.0-0.5*iscan)*mix(c2, c1, ii); + intera = 0.0; + } + if (interm == 4.0) c = plant(mix(c, c*c, 0.5*iscans), max(max(c.r,c.g),c.b)); + if (interm == 5.0) { c = mix(c2, c1, 0.5); c = plant(mix(c, c*c, 0.5*iscans), max(max(c.r,c.g),c.b));} + } + c = pow(c, vec3(gamma_in)); + + if (vTexCoord.x > 0.5) gamma_in = intera; else gamma_in = 1.0/gamma_in; + + FragColor = vec4(c, gamma_in); +} \ No newline at end of file diff --git a/crt/shaders/guest/advanced/linearize-ntsc.slang b/crt/shaders/guest/advanced/linearize-ntsc.slang new file mode 100644 index 0000000..1466472 --- /dev/null +++ b/crt/shaders/guest/advanced/linearize-ntsc.slang @@ -0,0 +1,176 @@ +#version 450 + +/* + Interlacing + + Copyright (C) 2020 guest(r) - guest.r@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 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +layout(push_constant) uniform Push +{ + vec4 OriginalSize; + vec4 OutputSize; + uint FrameCount; + float GAMMA_INPUT; + float inter; + float interm; + float iscan; + float intres; + float iscans; + float downsample_levelx; + float downsample_levely; + float prescalex; +} params; + + +layout(std140, set = 0, binding = 0) uniform UBO +{ + vec4 SourceSize; + mat4 MVP; +} global; + + +#pragma parameter GAMMA_INPUT "Gamma Input" 2.0 1.0 5.0 0.05 +#define GAMMA_INPUT params.GAMMA_INPUT + +#pragma parameter bogus_interlacing "[ INTERLACING OPTIONS ]: " 0.0 0.0 0.0 1.0 + +#pragma parameter inter " Interlace Trigger Resolution :" 400.0 0.0 800.0 25.0 +#define inter params.inter // interlace resolution + +#pragma parameter interm " Interlace Mode: OFF, Normal 1-3, Interpolation 4-5" 1.0 0.0 5.0 1.0 +#define interm params.interm // interlace mode + +#pragma parameter iscan " Interlacing Scanline Effect" 0.20 0.0 1.0 0.05 +#define iscan params.iscan // interlacing effect scanlining + +#pragma parameter intres " Internal Resolution Y: 224p/240p, 1.5...y-dowsample" 0.0 0.0 6.0 0.5 // Joint parameter with main pass, values must match + +#define intres params.intres // interlace resolution + +#pragma parameter downsample_levelx " Downsampling-X (High-res content, pre-scalers)" 0.0 0.0 2.0 0.05 +#define downsample_levelx params.downsample_levelx // downsample level + +#pragma parameter downsample_levely " Downsampling-Y (High-res content, pre-scalers)" 0.0 0.0 2.0 0.05 +#define downsample_levely params.downsample_levely // downsample level + +#pragma parameter prescalex " Prescale-X Factor (for xBR...pre-shader)" 1.0 1.0 4.0 1.0 // Joint parameter with main pass, values must match +#define prescalex params.prescalex // prescale-x factor + +#pragma parameter iscans " Interlacing (Scanline) Saturation" 0.40 0.0 1.0 0.05 +#define iscans params.iscans // interlace saturation + +#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 * 1.000001; +} + +#pragma stage fragment +layout(location = 0) in vec2 vTexCoord; +layout(location = 0) out vec4 FragColor; +layout(set = 0, binding = 2) uniform sampler2D PrePass; + +#define COMPAT_TEXTURE(c,d) texture(c,d) + + +vec3 plant (vec3 tar, float r) +{ + float t = max(max(tar.r,tar.g),tar.b) + 0.00001; + return tar * r / t; +} + + +vec3 fetch_pixel(vec2 coord) +{ + vec2 dx = vec2(global.SourceSize.z, 0.0) * downsample_levelx; + vec2 dy = vec2(0.0, global.SourceSize.w) * downsample_levely; + vec2 d1 = dx + dy; + vec2 d2 = dx - dy; + + float sum = 15.0; + vec3 result = 3.0*COMPAT_TEXTURE(PrePass, coord ).rgb + + 2.0*COMPAT_TEXTURE(PrePass, coord + dx).rgb + + 2.0*COMPAT_TEXTURE(PrePass, coord - dx).rgb + + 2.0*COMPAT_TEXTURE(PrePass, coord + dy).rgb + + 2.0*COMPAT_TEXTURE(PrePass, coord - dy).rgb + + COMPAT_TEXTURE(PrePass, coord + d1).rgb + + COMPAT_TEXTURE(PrePass, coord - d1).rgb + + COMPAT_TEXTURE(PrePass, coord + d2).rgb + + COMPAT_TEXTURE(PrePass, coord - d2).rgb; + + return result/sum; +} + + +void main() +{ + vec3 c1 = COMPAT_TEXTURE(PrePass, vTexCoord).rgb; + vec3 c2 = COMPAT_TEXTURE(PrePass, vTexCoord + vec2(0.0, params.OriginalSize.w)).rgb; + + if ((downsample_levelx + downsample_levely) > 0.025) + { + c1 = fetch_pixel(vTexCoord); + c2 = fetch_pixel(vTexCoord + vec2(0.0, params.OriginalSize.w)); + } + + vec3 c = c1; + + float intera = 1.0; + float gamma_in = clamp(GAMMA_INPUT, 1.0, 5.0); + + float m1 = max(max(c1.r,c1.g),c1.b); + float m2 = max(max(c2.r,c2.g),c2.b); + vec3 df = abs(c1-c2); + + float d = max(max(df.r,df.g),df.b); + if (interm == 2.0) d = mix(0.1*d,10.0*d, step(m1/(m2+0.0001),m2/(m1+0.0001))); + + float r = m1; + + float yres_div = 1.0; if (intres > 1.25) yres_div = intres; + + if (inter <= params.OriginalSize.y/yres_div && interm > 0.5 && intres != 1.0 && intres != 0.5) + { + intera = 0.5; + float line_no = clamp(floor(mod(params.OriginalSize.y*vTexCoord.y, 2.0)), 0.0, 1.0); + float frame_no = clamp(floor(mod(float(params.FrameCount),2.0)), 0.0, 1.0); + float ii = abs(line_no-frame_no); + + if (interm < 3.5) + { + c2 = plant(mix(c2, c2*c2, iscans), max(max(c2.r,c2.g),c2.b)); + r = clamp(max(m1*ii, (1.0-iscan)*min(m1,m2)), 0.0, 1.0); + c = plant( mix(mix(c1,c2, min(mix(m1, 1.0-m2, min(m1,1.0-m1))/(d+0.00001),1.0)), c1, ii), r); + if (interm == 3.0) c = (1.0-0.5*iscan)*mix(c2, c1, ii); + intera = 0.0; + } + if (interm == 4.0) c = plant(mix(c, c*c, 0.5*iscans), max(max(c.r,c.g),c.b)); + if (interm == 5.0) { c = mix(c2, c1, 0.5); c = plant(mix(c, c*c, 0.5*iscans), max(max(c.r,c.g),c.b));} + } + c = pow(c, vec3(gamma_in)); + + if (vTexCoord.x > 0.5) gamma_in = intera; else gamma_in = 1.0/gamma_in; + + FragColor = vec4(c, gamma_in); +} \ No newline at end of file diff --git a/crt/shaders/guest/advanced/linearize.slang b/crt/shaders/guest/advanced/linearize.slang new file mode 100644 index 0000000..14b0472 --- /dev/null +++ b/crt/shaders/guest/advanced/linearize.slang @@ -0,0 +1,176 @@ +#version 450 + +/* + Interlacing + + Copyright (C) 2020 guest(r) - guest.r@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 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +layout(push_constant) uniform Push +{ + vec4 OriginalSize; + vec4 OutputSize; + uint FrameCount; + float GAMMA_INPUT; + float inter; + float interm; + float iscan; + float intres; + float iscans; + float downsample_levelx; + float downsample_levely; + float prescalex; +} params; + + +layout(std140, set = 0, binding = 0) uniform UBO +{ + vec4 SourceSize; + mat4 MVP; +} global; + + +#pragma parameter GAMMA_INPUT "Gamma Input" 2.4 1.0 5.0 0.05 +#define GAMMA_INPUT params.GAMMA_INPUT + +#pragma parameter bogus_interlacing "[ INTERLACING OPTIONS ]: " 0.0 0.0 0.0 1.0 + +#pragma parameter inter " Interlace Trigger Resolution :" 400.0 0.0 800.0 25.0 +#define inter params.inter // interlace resolution + +#pragma parameter interm " Interlace Mode: OFF, Normal 1-3, Interpolation 4-5" 1.0 0.0 5.0 1.0 +#define interm params.interm // interlace mode + +#pragma parameter iscan " Interlacing Scanline Effect" 0.20 0.0 1.0 0.05 +#define iscan params.iscan // interlacing effect scanlining + +#pragma parameter intres " Internal Resolution Y: 224p/240p, 1.5...y-dowsample" 0.0 0.0 6.0 0.5 // Joint parameter with main pass, values must match + +#define intres params.intres // interlace resolution + +#pragma parameter downsample_levelx " Downsampling-X (High-res content, pre-scalers)" 0.0 0.0 3.0 0.05 +#define downsample_levelx params.downsample_levelx // downsample level + +#pragma parameter downsample_levely " Downsampling-Y (High-res content, pre-scalers)" 0.0 0.0 3.0 0.05 +#define downsample_levely params.downsample_levely // downsample level + +#pragma parameter prescalex " Prescale-X Factor (for xBR...pre-shader)" 1.0 1.0 4.0 1.0 // Joint parameter with main pass, values must match +#define prescalex params.prescalex // prescale-x factor + +#pragma parameter iscans " Interlacing (Scanline) Saturation" 0.40 0.0 1.0 0.05 +#define iscans params.iscans // interlace saturation + +#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 * 1.000001; +} + +#pragma stage fragment +layout(location = 0) in vec2 vTexCoord; +layout(location = 0) out vec4 FragColor; +layout(set = 0, binding = 2) uniform sampler2D PrePass; + +#define COMPAT_TEXTURE(c,d) texture(c,d) + + +vec3 plant (vec3 tar, float r) +{ + float t = max(max(tar.r,tar.g),tar.b) + 0.00001; + return tar * r / t; +} + + +vec3 fetch_pixel(vec2 coord) +{ + vec2 dx = vec2(global.SourceSize.z, 0.0) * downsample_levelx; + vec2 dy = vec2(0.0, global.SourceSize.w) * downsample_levely; + vec2 d1 = dx + dy; + vec2 d2 = dx - dy; + + float sum = 15.0; + vec3 result = 3.0*COMPAT_TEXTURE(PrePass, coord ).rgb + + 2.0*COMPAT_TEXTURE(PrePass, coord + dx).rgb + + 2.0*COMPAT_TEXTURE(PrePass, coord - dx).rgb + + 2.0*COMPAT_TEXTURE(PrePass, coord + dy).rgb + + 2.0*COMPAT_TEXTURE(PrePass, coord - dy).rgb + + COMPAT_TEXTURE(PrePass, coord + d1).rgb + + COMPAT_TEXTURE(PrePass, coord - d1).rgb + + COMPAT_TEXTURE(PrePass, coord + d2).rgb + + COMPAT_TEXTURE(PrePass, coord - d2).rgb; + + return result/sum; +} + + +void main() +{ + vec3 c1 = COMPAT_TEXTURE(PrePass, vTexCoord).rgb; + vec3 c2 = COMPAT_TEXTURE(PrePass, vTexCoord + vec2(0.0, params.OriginalSize.w)).rgb; + + if ((downsample_levelx + downsample_levely) > 0.025) + { + c1 = fetch_pixel(vTexCoord); + c2 = fetch_pixel(vTexCoord + vec2(0.0, params.OriginalSize.w)); + } + + vec3 c = c1; + + float intera = 1.0; + float gamma_in = clamp(GAMMA_INPUT, 1.0, 5.0); + + float m1 = max(max(c1.r,c1.g),c1.b); + float m2 = max(max(c2.r,c2.g),c2.b); + vec3 df = abs(c1-c2); + + float d = max(max(df.r,df.g),df.b); + if (interm == 2.0) d = mix(0.1*d,10.0*d, step(m1/(m2+0.0001),m2/(m1+0.0001))); + + float r = m1; + + float yres_div = 1.0; if (intres > 1.25) yres_div = intres; + + if (inter <= params.OriginalSize.y/yres_div && interm > 0.5 && intres != 1.0 && intres != 0.5) + { + intera = 0.5; + float line_no = clamp(floor(mod(params.OriginalSize.y*vTexCoord.y, 2.0)), 0.0, 1.0); + float frame_no = clamp(floor(mod(float(params.FrameCount),2.0)), 0.0, 1.0); + float ii = abs(line_no-frame_no); + + if (interm < 3.5) + { + c2 = plant(mix(c2, c2*c2, iscans), max(max(c2.r,c2.g),c2.b)); + r = clamp(max(m1*ii, (1.0-iscan)*min(m1,m2)), 0.0, 1.0); + c = plant( mix(mix(c1,c2, min(mix(m1, 1.0-m2, min(m1,1.0-m1))/(d+0.00001),1.0)), c1, ii), r); + if (interm == 3.0) c = (1.0-0.5*iscan)*mix(c2, c1, ii); + intera = 0.0; + } + if (interm == 4.0) c = plant(mix(c, c*c, 0.5*iscans), max(max(c.r,c.g),c.b)); + if (interm == 5.0) { c = mix(c2, c1, 0.5); c = plant(mix(c, c*c, 0.5*iscans), max(max(c.r,c.g),c.b));} + } + c = pow(c, vec3(gamma_in)); + + if (vTexCoord.x > 0.5) gamma_in = intera; else gamma_in = 1.0/gamma_in; + + FragColor = vec4(c, gamma_in); +} \ No newline at end of file diff --git a/crt/shaders/guest/advanced/lut/inv-trinitron-lut.png b/crt/shaders/guest/advanced/lut/inv-trinitron-lut.png new file mode 100644 index 0000000000000000000000000000000000000000..ea93bda8aaf0a5d4769eb1f493817ad52c251578 GIT binary patch literal 67306 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

DQad>Xro#;&849d@P1uJqcKzGh`_ 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ⅆ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 literal 0 HcmV?d00001 diff --git a/crt/shaders/guest/advanced/lut/nec-lut.png b/crt/shaders/guest/advanced/lut/nec-lut.png new file mode 100644 index 0000000000000000000000000000000000000000..d18e00d6eaaceaee1d2f18cb8abff1d54c296e1d GIT binary patch literal 82816 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�^~ zsjLlD)^;ju50!PupMBh)eb%3S!JpmY&%WZHbhUlbjrK{m+9%y@%H2cd9rov)@aLWL=Uw#YwfOU{_@`d;PrcDT zHQYY+PWx1d%zu~^GJgb_|2&zWLl(>=3*My)7ElFCsDc$#!Br+p?w7ClUFvS=h(^a5FwOBT%{i{7J(Kcb44 zQpKPD`kBk(O;qtts(3F|e1s}K=`T6&FS+C|x$G}#^Os!nmjv5O!tHaq+UG#z{6EtD z?lDsLG^v|N>ZX#qH%Z-GQn!%OEv0lTDcxF1x0x!fr%LxxrAMgJlm624{?beSQjfp1 z&0iYumj&C)#P+hT_A-bpf0zMe`7>mB7FqrpS^gGTK94M4M3pb2Dr%?-BUNFdD(a~U z3sqsID(wCWr@zAGukiRQeE!OSzmjXO6x%Di+AATl>R|?xRb$DjNo3V@vg#eOYCc(2 zPU`iP-azSVDSaKKH&c2GrMFRfhhOjX>s@|5?yvUwt7(5V=dYI9t0V2z5LxpuPmnd^ z$eNeQnm5RrBCM!+)s$fsWmrcU>L|l5%FsX=Y?Q%48DPHw@f(2OK>7`ge--av zCAF`Lw6B6lexA>v+=o zGHHFCw7x@H-zTk$Nb54vx{|c6C9RuC>rTqLm$DtEY$qt&S;}^Svb9jQR=@44-*(+^ zyXm)e`fYK)4f5F^)X!%h<+DFe+FvE@`J}ywwChNF1!>olb_3}!k`5E;sHYtJD8~`X zaguVJqZ}70$7RZK#qYT4ciiwhZuuRz{f_Vb4#?+x&|shQ8J}~a&zVa)3rS}Q373&@ zB?(uP@G25sN5XX^Y$jm~1zRcDPQm9X_!0$Qrr44-R~&o#}5yh$SOk;sQ6vY13ZC6U!6@->NUA(34q(m*0M3UN?~lR{h+;-L^9 zg#`Ra(2s=uNS7b|(T_qt{5}JG_$VL#tPg+L2VVDqw|!t92|gmh5)!N+!IvahPl9bE z*iC}{B>0X3rzikZ0HFX*0g?hV1vozt{h-Sae)JQNkGxkeANjbCe8xw<=p%D{WWJ9q z@{#j>)FP5vMpB=X)K?_6k)*bh)VCyckfe^1)M<)pq$rf40QKv^P!#W{BtI4LQ$P7B z$j98PzmFN=W5)TIY#%e#$ISFGB|fIq$5i+jJ;~IN%vzG&M6x?bb`QxOBH804dxm10 zDHi*6S+IVJWhqvmSlQ1;{p?@;EaVe@*V`vN?i2pv6Q1)4FZ+b)KH*KD@Sacjz$bj{ z6PEjgRisc$3MNvhCxv~aaF`TMkiuCqbb$)FsgRcnwNs%EDioqZia!+dhyLadK|bYQ zQhmw*pEAOyr2CXipOWKKX84qMe9AnZ@{v#Z#HW1bQ&#(wuSsPKshCN{LMm2LIY}z# zN#!D`v`|VbrCgy&bn>g=RCm6`eKE? zSg|jz^TikW;!Az;6~6eFzW91yd@C9MhKx6maT^)8lW`{*zeL6_Q}HWQ{2CR%LB(%T z@!M4Vdw)FPkEgaJdbK6`wIznMB}TR-GJJ`NzQn7(#OuDqo4&-mzC@WXQQ=GIeTkL6 z#88^L%Q)PkqOy&hx1YeClGKy4ZuY4=eCi%jJxHp@ zNcA+SHj*kzs(@5UQl%-Cqg0VnyD0SsO8vY4ZaMj6TQaRJIl3)5zAc&AmVC7>IlV18 zt1VgLOV0Nt7y6P*e96yz$<@B(I$yHRm#p_C_xX~C$>eb|d4}w1CVMclhah_>vWKC1 zc&bODdLmTMk5tcpP(5jF+Nd^dbelG=O?#nDo7|@5wP`cjw71)|_u90wHm$;^E%Rxg z`?NJaZM{$1;?s8dG>cC=;?qu&+Bs6YKx%GM^OBmM)L2pzC{3ocD5d>GY5z&x9RT#B zq%zP*26~Z!rZUi54D>z&En*-&0~r`-9RqD)pnZ~5N#iYK^q?R+OdM0%hle&)S zwT12V4clu!+xsZn`!w6TiS2{2eY|WRe@EYrj=rIezMUP9L3F=|>))I1{}j_dlj%Q= z>Hjv<|9xgaIWs`d3|PesSjP<9!VLU|9k`zzc$6JMUUsm*V{k{ukWj~v z&W^_+dgvbwyH_82*wf6gEN0ki%<#9F;q#f{<;?JEX2dFH#5!ig7G}gZY}x@f?I@de znoVnBM`G+qFFUfmV^l}SsGA*6b#^=j(PQp0_CAl%V@ES%vzf8ene=y<^!ZGB1(ROQ zq<_Jrf6ZiUWiocN83)*mqwKgd?6_uj9LA3Gvg6x3#&>j#zu7Uqvtt58Pke;_^u%YF ziIbSj*O|=OOs0;>tYk8mGnrp7nO`$mTbZogY}Nrb>lmALhRtqfv)ydAm(6bP$iCh& z>1M~I&W=eCJ>}nW9yE~78B6C(W^&$Oa*CLoQYNR8$z9Imu4Z!AGr8NC+}&*MK{oFg zn|FrIYi9G@Y+fsy*WNMpddJk89aC?2OoizDM;J`!XVCdCGx;-^{9>k{j4Ak-DfpBr zSj`k{U<$S|1>Z6S2ibz-Y~fk9@B&-d!WOo&g;zTYuXoJ6)iLvS$4rPWdW0wFqVaUm zD|AslQ&hqfeZUkgW{N*!ioaxvH!#K9nc_W6@gcVOI9q&{Exy2(w6G;t*pjPk$&HSZ zTOB2LI_5z1{D;#GrFGBJx+%1-kkQRybRROhPZ-?_Mz@C1ZDe#i7~LMG^e|g`f-ODA zmR@8_TiDVoZ0R+&^hQTnxTEY&M;SzyKTIlJK7uZPo-WU&%V*N%?=s~JnDQk|`R7de zS4{aPreX(Ev6rbh%v79UE6%YM7ukx-Y(*PeagD7Ec2tHtD(`euLUh%`^r5SsqN^s- zRe5yPn{?G&rs^Z6YAI8-lF_ea^qU#|PDa0v(H~*-Ct3Y@R)2}rdsuxNs}Hc%!H#ON zqq?i38lr0+raxUXny$&BYo^gPZ_zdL=$eI0%`&E@hN&?!1`}hbXAJuogOxGZS%Z@` zxLAXSHTYOVfHiRJDzRf#SH~)dHa^T?+BlXrPNI#|Y2!PzaXxJoskJe+4yM-0*1FhQoUQe-wKQAHv9(f1ZKR_XqBlRxleB3ZZF-qDy+NCbXp@dM zRnVqt#XwpHJ5p)AeO^{l|3ua;E+Zrv7Wjyp=J3!7O%crzuHDg)NShg{i-Hhb` zV>!xLPBWG!#)7dHg0=YB29|9Q*an4dh;=kT0qcLE|H~lS`V4K&qODVD>zlOoJ=(f} zwl1NqD`@K)+PZ6>Z*!z|H(5s~>xi=sDByfhU)q^QJD;VUuh7mnXyi$!j;$oDJ? z1@QZ%2Jj~WczOWO4&YO1d=`zrOXDBV;A0vrr@=@FF>XR$O!@Rr2siSK)ywjb7^t`O@2a?pV8EpG_{_l zw$ju$G_{|mzGEl{L%|G%Fci*EK8B(hieo8}rMg(^N0x#D%)L?q%#Z*xI>5XTV5S6^ z83AT?fO(&07Sha8n)#e&*3irbn%Pb>yJ_|S%^qdg(+u0luqeX`yEU1%!J&77&I9gp7cY6%g_QLSaBC4hW?Ip@J5wXrYD{*3!ZzTG&AgduZVhEgYkT zGmOy0gfJ#VFd>QwF-%BcLNXhQvZ24SAt<2yu2(=A98jJPD9;6ymjcRb0p-nr@@_!+ zAfS94P^xLgKr6MhVxpCLTG>l0hiT;mt(>KmW=3%{ikDIRjKVTXh*1=_GsbrQjqQX2 zvETI%#D)iAV*{~?f!LHlY(^mVP9Qcf5L*z4eG-U&8i;>E$Jf#EIy!Eq;}$x8gpQx2 z1QJgN63+$_lLCpUfka^- zQ5;Cj44cL`T%r?~neHo0_f@9*2Gf0u z>AuZ$$Jy=#+nsVv?G;cT3#dZ^YFa=|52!B&)K>%Q^nm(iKz%o$mIc&`fLaw$R|eFt z0_w(qx}8>c)9L|Q{f<^o(JD->u$mK<;`IpkV$ z#I@uz*OJc$l9K|-yg)KPkbEbQoEu0k2qYH=lFI{0Lm+7kBsT|=I|9i)bn+mbJVqx^ z)5%7<2cdfa-9s`xG}FT|JtEW7#q|8Z^!%OadHkC8zGmKbX4%fO|OpSC8}11Rk2gLxnu_E)RXgLrZyRB@eCTA(H@^1;`>mHUV-7 z5G+850D%xhg&-yb2_Z-h{rCMR9@01dkNPS9lm4@O$`n3jCZF;ipYjo(vXoC<$)_6m zRFjZu7J6BPUN)hZL+A|)y%C`|2=$>teb`VRA=Fn1^@X^{?$PfL_+LHA_j{J_pTqZ` z$@hPc@Bb0se;GeuB|pH(4=@P>%)-D1VW3SI=nw`q3WE?~5D*4ap}}luun-!egoZ%e z&`05i4daJB#}CWpht1-L&E3AKD~-hui?{c`SdzIeV35Y zAY^A@aWCFu;(b#1iD~@A7x{^K{KPl;iSzi( zMSNxzpK0JTYx&GNK696lwO`2kPRKeXWHky|sE|zv+5S*A8_EuaCMlsw5I5x^a_;KI z<&5HUCh|Gc_?)-+ocH;haz01T=NS0hb$sp?KKC0TcfXK(RLDIo<1M6t{+!4nk&lYi>C8Mv-u(&UsS;t zRr5t(@I_zq#asE}-F)!@q4=0kd`2j47K+_MiB~9T7fP;&N^XWqIzuH8H~$d^a`VS> z^C$E3-{5scysnhjRr0#!ylyqGThHsZ@w(l-?x3JMCX}8LN}GjJw@}(Dl(q|{*F&W@ zL#4MvWe~UMkIL^gge%YB%3tQnXYl34e0dpP{xM(vDPR62U%r7a-_DnR%U2u{Dvk>k zXN8IjLPd*E(JEA26)LWWDsP1Jz@|GrnpK zU$v3fZ|C)Uc>N(>e_YU?6Z97ZeT$&KBIvIP`WvC@TcPSZp=yY$d6*QgW&~IBJXe#$ z)y(8--sNi+@HI>LniYJ_SA5MTzGeq+*vlIZ^M(_G;hbQ&C>SmahBm=)O)vxnLpWr( z6IumvYyZu7pFW&%6lZ*qGv;x|S)6e$Z~TZiF6E7%^TxHjaT9Oc$s70a#v{D(q)>Za zsJ$fAdW70Gp*A4Y28G&isJ1Iq3vrttra!lNG`Bg6+dPdky~Ua4ai)d5X&G;-;Y~)~ zWa3TrylEeAvhpT7Z*mGImtgV;CZA9j5b8LgP7Kv`h3X)#{$U1l_32#wWUhWXSN{%I zKcA~F=j!!*y@9W<c;WIqk%)@RT_6l&j0Cxy* zNPs&9I4;0Y(Dk5RoNE~88qc|2;#{wDu6H=s`-B>hiu}IojkIaM-KDI z2_8AiBNuq2g-2Qi+|Wz{G)i9_-`65gwf6!8smW>yWPT9mMJ2+(zryS&zW1Mn^SDJVQ_*pLA%*Wk)+{?%Pe4ORu0w0(8cvOi0Rft2u#P9kA6T^auF~P)( z!NjY<#2dlH+rh-#U}8Zqu{fCcG?@5;ORVD(bzH*CCH8TNBV6JnmpI2IE^vt!KGDi| zxAWZ{e0PZNR`~9i(ET@|8w&pXFMWeQKOX$~so>8OfyEwIhQ*E4T=hX9@dWlmn^Xe5|y~?ZCdG#i*cJgYRR}+E?1(T`4 zWZz(Na4?w`OpXmEUkoN+2_~lplWzu-bArjTV6q~ZtO_Pq29sX}lN*D{ZCr9Umps5F zzvGe)E(vo<7nk&KJ#Bo?HNGdv_uS%pZu33g^F7@{&to@!>3`#wAvb;*e&d(X!C#&Y z{xT`}OKwoh4{GlOwYfpDPWrv6S+G zl=6v`vO-E(Bc*PVQ+LX#`{Z6nUURv zsoz+s-%C=z8B)Izss9I3|4*d;E2REkNdq>?19r*-_Q?Z|$OG;2K&L#=B@e>oL8LN> zRtEFRU`ZJQiBCRY=x?N74U~qaOG96lhUH7cN~B>QO2a;phOLl>e>ihNPO|(CPLE0A=1S0 z(!^J#iG|X{ccqC7q=`$V%#~8+S}Ak0lvyulTI5WtoaKlru%jnJMMGC*^!3eNRpDX3hlJe(D`3t4|Wm0~Ply8&@Oj3baE@+So zY;wUVxu8)lK;=S0E~Ml_Rw)#eLPaTr#MzHjG(swRK`P3Vir$op=1E11q@pUR$RHKf zN=0>2(JrZYzg+yCTzpC{Zj_5rxtNfP{c;Jbl!TNLMJa*A`H#>`oIgsOKT(=LRhs{n zH2-}`S1#%FlFlIM)=9c8lI|Nxw_nyBm360OU6Wjj$)$u`>X%D9l+ut=+NqR6;-W|B zE0+I7EYFh4Uz5t;k;>;wMvG}5vwMNRj*4`v!yDXR8=WeEtjgkkgC3xs|yhqJ_28%W6V$Dlp%?zogSgI+LYCe`~ zK9y=#OEv4Inr%|ew^GeP$#7gYoRtk1WP@8aw91C7vf;XHxTP3wD+Wkh`*6l5MB{kT z_^N2k7mX#7@dL@YSTcSl8NZZ_8zkd)$+$-{9+Hg5W#d`dctNghk!!EWwO8fZ8*=R} zrS^_e3yGT_CPmylT-^MexH(7MTqtgSSKPcnGA)rzDL9WHVfu*mqs00b#riz4ewJ82SFHa?s$VMAe=gOp zmFhQ3^*g2deNz1q$$U~WpO?*-WV1&$x5?&!Y!1q1Q8sreW=P!kFayMWqs4t$;=b3! zeQ$~T=85|jiI!!OrAD$CC5uV2)JqnNWU)#XyJT@n7ME=C$QGY$3CI>swuo{=m(l=< z)`uA)TGK`AWYPM%XnjYt&KIraqE#&wn3u(Po;?VVWNG4Xn#et&k*g!qP`X)Ge91EmPmi6clD2G$_oB4l@(OOs>cj zicE>fl!;8G$W)8WDv_xbnL3d%i;P8NtP*olV$VzLC5gQ(v27B2O=54z>@AtSBeOrq zEEE>*(>p8-4GS4zVNzI_78YiS!n>mIp(rdCg-=D{3sLx56t;-ME>UO@1)C%|B*7^O zE=ll6f=?0xQYa{e!gA=29QsiXL1FnG%3TA(%BZmNTv&M}th^pp-VQ7CMCBt7*EGHbx566navH9WHA~Cj1jI9)7Ux~4eVr;t@|5l726ywLl_-QfTB*jrFPDpV| ziZfE2m*SEXkI3<#=hG-#KdtiaaK$;O9@O$c%_72O0ZJ5Aa%=9cU12FtK1ERfBxNL;h&!j|NKn& z=NH01zZ(Ae_3+Pchku?MRu_cT#bI@MSp7m&*NJMKsMd?>K2bd)swYJCoTy$9)fP!@ zmDF}g?U2-vq$-jclhnV-DilusOP_FZNH{qvoE#rcW`~n`;bcKLSrksr4<{FflS{(M z72)KU;pBQTxkXIw5|a&L(kdqHV)DG0yd)+si^(fe@~YHxUFx|h^>j)-aj7RE_dwxa zQo_IV4gWGI{L6^&FJr^Myb%87mGCdq!@tZ5|1u~1OKDiE2y0bgZDm;dDy(e`YuiL^ zx2WwGweLjDA!@LwxkSw)YHgBsP10^i+AT@DEot9NTDNp}0CYWE{VF5&b_T+%+^cGW4m)(5%SN>5-wcBST9g!#<7- z`!q7_%gC?|k>T5;!}mmoAC8VV5gl7Q=AMk^I-|L+Xf7Vj^F{OMSRNnClVW*L*NjIS5Sfu4 z$$vSLpC8FDiR6D6$^Rshzao1*S zBhg|IEheMIOsrUlmB_IYsB8YCJ<&CPLS+7w$o#^{{C6YsKZ?v>8kxT`qBBNxrijiQ z(OIH8TU6(W>fopjiRwVKl!}%z(NZB+D#uEpu0@YFtZUJ8kwv+Y@>!AcxsmdPk@97c z@|sAwF;Z@dl$#^v4UuwNwER@GqA^;5Mk|PD1r@DeqZLA|LWxyCUCSOJrK>8ftLnwB zs=P?mn~|z{k*Y4zOswbj)e^k##^`V$v ziRq!Pl@C|byQ}7@uA0oQnrU4%Z%1n0kJOY$YV?tsRgs!?k(w=$nr|XC2O>2`qcx|a zhNh?iiyFL9gFkBMh#EpMLubqYb*+7beqC#y=~|oJWt`q+oEB4YeH zV%!=r?v5A_M2yEG#xqf4bJXaL)_SA0?a|un(b}8Q+Rj)l)V28$26b&t@7nxQ*XIAP zRy&Pus@nTFen4bUyvpdkD&y5FlOt_$Yzt0pRg@M*Z7V9YD%uoL+X700AfyPGFgDDo zOo>d1Oo>d1Os6s(WN7g0eG&zV#hq}Sd1Mjv17gF*rGXhXpX&_ctVCRR}`3ZJ@j-B(da|w2?!p`+t{T8i$r&hmDt3RaG zAJ^*7X!UBmUP7*GX@gunkn3*b%0{jc$n`vOy@FhCVb^=u{V{e=!|qww{UvrU#qQPE zy#c$oYVKW{d%xyBthrBU?z5U()fPyo;Tk;ck>_v7GYEMeLY^m)=LO_>6?w)XPZjpm zV2>SpW@C>Fdm6B34fbrro^6_Ex8~WeEjywuJE1K*r!7O;G6{LF*k^BIXA4l>tNFIgcQY4Q@aut$mkUR~^vyuEIl9yt6HI_GE zc?)jZiJSJ~rbD>tnAY@z)^tH@`ble&ROL!HsLE}sGC)WPFT_Pm%FCGUg#;F)~&lV=Xei#>O|;2xB9P4H+A!uyG#$jJ2OZ`&m+x zztu@i_EeMo)#SZu@*y=jLQOuaCP%Buay410rfeuxgHm>snvGKPQECZFtwO1FD76`- zw&T>dIQ1P)HQ|(kQ_VP~;S|+Ul4|{1d)2x{weC=@0jl+&YCWb}PpQ^O)f%H(Z>ZKg zsx?uy>X79?mJ?YnWHlgbHL^A!YYVb=AZstS4r1$jeCag46vLO|_)-hLB&q3tYNw`e zQq#Ap>HcbZh?>q((|KyTKuwQQ)32%Nx773mH8V-gOi?qRq0AhVS%5Mgl<}g>MwHo# zGP_V_AI==YnPWKf1I}E)nFP)l_?H1dS_PNg1k#-#Wr6erNH2g?3Q`3~)gVm6>-N#>4!5*B-qP-DOFN0QZw++##m&&+F6fX09iD)WFG9ys=vWCI zCql<5&}jzkG?#W-OmA38Z&*)n*g|jINpIXoZ#>-6`FKm`vn`z^a?90p`4_mX3v~Gh zbjg9PPeRuhp=%j*t%Pn9q1zPbHUqlNrQH|P?yG3`^|Z$p+G8i}v5)pV+|u(zOV6_{ zw@T!WU-h~s=+zZ^-wnNoLGP!a_bBLH2EE^gJ|94zDbQyo^qEKdE~b4~(Z1_x-z~J? zPTFrD?RU7P--(v~XIuJ9WWd!7{0|t|9R?17fe*pJr(s|*3@V2~Z^NJuV9-<;G!q8T zql1^w!K>)t4Rr8UI%F3evY!q)OoyCkx#wKVJrWsu4O#yJSv?_Z5M(_JSl$*`Gu9e44$4X0N6>8)(i}nzM`M?58{B1OUH_hKq^N-NsCtHS}YZ)$) zg4VSo1-C=NJy7r%6cj?iXeb;Dg%hCgLn!;k@5Y>_@SgCA1a=QidUfGEvR@O zDr%s@4i!$Qa6^R$DjI2ppH>EGWtdh*Xr)Xm6d)4Cp{W&o)fMrxiU zH7`Q#YfxJOwbf8t2es3ob`I1ofZF9y`xUMIn$~_pYrmzn-_g1zT6db(#b}*G?5$`= z?0+Nn!NmSBu|G}hqrhGU_DZl%1p8#Le+G^(z_AbzP5AH4C-a*}asrwLhAEWLw)cqrMOQfL{T}Z>7q+uxWAf4Dxo6_dwHk(DXfAzURki zQ=B$Qx^ktqy3$ow`s>Ply7DMdo+iqRM0t%U$_08lK~GD1^OZX4&41OK@79}#>dm=&b0KLiA+a(Nt0XZSiPe&r zoy43Z<|45M5?ceY4G`N3v7Hdx2eCsCI}Wik5c`qFexfl+k6-C8dc3zDAFRiR>G2VI z{5d`T5{Zu`@pnjkB8g8X@#!R9PvUM8_mH@kBz%zA28rE}*bj-rkT?N}vyf0Bq0@w< z8-LJ2H+tyC-*w|Y-FQSdp3;qxy77u`yh)4+#F#{ksl=E;j4z0>kQmE}(MSwGF#=$O zz&HTL5im}IaSjXwKNI@1q$hu?jh?(oPu{L42k6P6dNNl}7U;=Qdh#_rIZjW$Pm&*z zIGlceU7)FP5vK~if;>T8nvhNQxfia_cpq)tKVJftwB08)}}{Z>ca>aJV;bn717 z8m3#rb?aH(8m(L9x>ccDHr=Wr)-+f_cB}q^JTRS~{v!1?PPv5PlAJEf}>glKS^hiBDMo*8`)9>i%iF&$DPdiA)Nir^y zSxPdiNoGCCY$2H)B(sNP4npR8$ee~u3^H-Zw7@R|fc+URUC*UExO5+va=G*@m&S1E zEiP4Ysg_F)F3l0r0wFCI(pn*H64G`d?Ge&JAsxFY{c!QW?f3fM{&TAcMx-e#d_vs|=UE81=nZMTcId&G4I#dXInwmWmNoy4xcLi<1H zz}xrc9q#8H@_2{mc!yVbhjF}P74KNfJ5J{v=kQJoMW+>_(^_%ECUL`dal;;Q<3Vxb zv5PmJx!74^xBQtd9eI~NyvqZ;OFr-NJn#A{?>dfmwehZXyxVl%Z4U3YP;^@%x~~=8 zH;e8&M323q$05<<*u|b_F7}kz9oN?DpE~hgeR;13d9UHT_w&5>tGxGk-rL4|*YQ4| z@jhShK8r-36{7Dt(RZ`xyF>KdEBYN0{f>!#XD;@a*nmGX@J2qcKOZ=h4;;Y=ZfsM9v|Rb6n({y*Nx_ zk6$784{l+(ck$dDp8F)veUayu^4vMY}}NK2da76rB)7XD=2>Y}B7A?!k%&vf_t%@zcDxm=~Ax;&*uQBwjp~7tiFy z^LWV;Ub0%0Y!D?|MaeEvvR{-O5hW+Y=yPJU#7bM&mX-dMl@4a5kFe5bcxeeQeVv!S z%ga9GWuNe}&w1H=Ubd8%trle)MApHTk-mL0=R+Y=Didfal-1a87y~k}IbK5j-o6T)5ZfoE+FSq%GEg)I>*2LtUh5aNK~WnL zwGmM(i@H;yu36Mc%-*_NnEg&>&tmow%s!IYUuE`j++M})wcPID_Ihr2bH_67Xygt* zcLaqaEF4kckcC4Lj%MMI*zDGIXS45S&SA{?By+yNoUbuw1$WxGvyMBbbLSlHT)>^n zx$`UT{F*zz5zcQ#{dc0iNz|Vf^)XQ|F;^?vG1uRiYY=li%v?`1*C^&HW3Ec(n#f&~ zx$86T`hvR`a`y`EUd!E^xO+Qy?-B0rg!_BpJ}ulAgj-?_t?0-ada;IkS;M2Op@21% zFi$!2yv;lxaL*L(nZZ4Cxn~jgtmK|`+_RZ`c5u%g;W;Qg$AsqxvFw6aCYft4W54D7 zx0{%^AM-xIyt&L<#Jn#t?^x!2mw6{K?@8te{+<#d3j|=}<;a7!UG6Pq^E^EgEx3a(h7RX_NCs<%43%tq#<5=K*7WkM2 zrm?_m9{7?68hBt04{YSYZ9KS(2lw;f5fMBgf@eh#iJ)YLu8xH|vCthXG=zm7W}&B8 z=tUMPWufsbWMiRP7ILtVlZD(ovcee9M*ZxYERx(_D!OB`!`&X7g3uY&Q2bo9{K7A2FMsGMisu z0jB|2{>`tl=5eg~eHQze#Xe=RSu8f6#g?$xDi&MMW1D$w2aoOHv4cGJJ&&E{u?sww z5HZP&U#Yzr?`g*GGUE@L@yE@0ff+A0<7F&f!QwU+uVHaJi_d29FIjvki?3$!4J^Ku zCwB70KAt$l6UTVs2cGzmCw>wM$uzFi*);AjjX|c7V;aLv<5|;q$uwR!jklRGkr{Q& zn9huPX1JN*Va6I}Y-GkZX6)j|er_D*#&K?(;fBf$o&Qox{=xNTvYVOgZzk_GlMkE8 zC(YzYGdadgzF{WcHItKAatcd+#*%YbY5_|vW2r`#^08EarFQew0iHU-Qzv-pEKebx zB0MFT)*o~>t=mlNZqs_uv>rFDXH099X}xAz<4o&4)B4D?K4I2OX3b^RB4({%)>q8> znpr_+g_#xM)=_SqZ8r literal 0 HcmV?d00001 diff --git a/crt/shaders/guest/advanced/lut/ntsc-lut.png b/crt/shaders/guest/advanced/lut/ntsc-lut.png new file mode 100644 index 0000000000000000000000000000000000000000..ef7e883fd01549de8039b1f0b8976834f4928a2c GIT binary patch literal 84693 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

q&mg=nQ`LF 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 0.5) imgColor.rgb = imgColor.rgb * (255.0 / 239.0); + + imgColor.rgb = min(imgColor.rgb, 1.0); + + vec3 color = imgColor.rgb; + + if (int(TNTC) == 0) + { + color.rgb = imgColor.rgb; + } + else + { + float lutlow = LUTLOW/255.0; float invLS = 1.0/LS; + vec3 lut_ref = imgColor.rgb + lutlow*(1.0 - pow(imgColor.rgb, 0.333.xxx)); + float lutb = lut_ref.b * (1.0-0.5*invLS); + lut_ref.rg = lut_ref.rg * (1.0-invLS) + 0.5*invLS; + float tile1 = ceil (lutb * (LS-1.0)); + float tile0 = max(tile1 - 1.0, 0.0); + float f = fract(lutb * (LS-1.0)); if (f == 0.0) f = 1.0; + vec2 coord0 = vec2(tile0 + lut_ref.r, lut_ref.g)*vec2(invLS, 1.0); + vec2 coord1 = vec2(tile1 + lut_ref.r, lut_ref.g)*vec2(invLS, 1.0); + vec4 color1, color2, res; + + if (int(TNTC) == 1) + { + color1 = COMPAT_TEXTURE(SamplerLUT1, coord0); + color2 = COMPAT_TEXTURE(SamplerLUT1, coord1); + res = mix(color1, color2, f); + } + else if (int(TNTC) == 2) + { + color1 = COMPAT_TEXTURE(SamplerLUT2, coord0); + color2 = COMPAT_TEXTURE(SamplerLUT2, coord1); + res = mix(color1, color2, f); + } + else if (int(TNTC) == 3) + { + color1 = COMPAT_TEXTURE(SamplerLUT3, coord0); + color2 = COMPAT_TEXTURE(SamplerLUT3, coord1); + res = mix(color1, color2, f); + } + else if (int(TNTC) == 4) + { + color1 = COMPAT_TEXTURE(SamplerLUT4, coord0); + color2 = COMPAT_TEXTURE(SamplerLUT4, coord1); + res = mix(color1, color2, f); + } + + res.rgb = fix_lut (res.rgb, imgColor.rgb); + + color = mix(imgColor.rgb, res.rgb, min(TNTC,1.0)); + } + + vec3 c = clamp(color, 0.0, 1.0); + + float p; + mat3 m_out; + + if (CS == 0.0) { p = 2.2; m_out = ToSRGB; } else + if (CS == 1.0) { p = 2.2; m_out = ToModern; } else + if (CS == 2.0) { p = 2.6; m_out = ToDCI; } else + if (CS == 3.0) { p = 2.2; m_out = ToAdobe; } else + if (CS == 4.0) { p = 2.4; m_out = ToREC; } + + color = pow(c, vec3(p)); + + mat3 m_in = Profile0; + + if (CP == 0.0) { m_in = Profile0; } else + if (CP == 1.0) { m_in = Profile1; } else + if (CP == 2.0) { m_in = Profile2; } else + if (CP == 3.0) { m_in = Profile3; } else + if (CP == 4.0) { m_in = Profile4; } else + if (CP == 5.0) { m_in = Profile5; } + + color = m_in*color; + color = m_out*color; + + color = clamp(color, 0.0, 1.0); + + color = pow(color, vec3(1.0/p)); + + if (CP == -1.0) color = c; + + vec3 scolor1 = normalize(pow(color + 0.000000001, vec3(wp_saturation)))*length(color); + float luma = dot(color, vec3(0.2126, 0.7152, 0.0722)); + vec3 scolor2 = mix(vec3(luma), color, wp_saturation); + color = (wp_saturation > 1.0) ? scolor1 : scolor2; + + p = 2.2; + + color = pow(color, vec3(p)); + + color = clamp(color, 0.0, 1.0); + + vec3 warmer = D65_to_D55*color; + warmer = ToSRGB*warmer; + + vec3 cooler = D65_to_D93*color; + cooler = ToSRGB*cooler; + + float m = abs(WP)/100.0; + + vec3 comp = (WP < 0.0) ? cooler : warmer; + + color = mix(color, comp, m); + color = pow(max(color, 0.0), vec3(1.0/p)); + + color = color + aftglow.rgb + bp; + + FragColor = vec4(color, vignette(vTexCoord.xy)); +} \ No newline at end of file diff --git a/crt/shaders/guest/avg-lum0.slang b/crt/shaders/guest/avg-lum0.slang deleted file mode 100644 index f2ef96f..0000000 --- a/crt/shaders/guest/avg-lum0.slang +++ /dev/null @@ -1,63 +0,0 @@ -#version 450 - -// Avg. Luminance Smoothing - -layout(push_constant) uniform Push -{ - vec4 SourceSize; - vec4 OriginalSize; - vec4 OutputSize; - uint FrameCount; -} params; - -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 OriginalHistory1; -layout(set = 0, binding = 3) uniform sampler2D OriginalHistory2; -layout(set = 0, binding = 4) uniform sampler2D OriginalHistory3; -layout(set = 0, binding = 5) uniform sampler2D OriginalHistory4; -layout(set = 0, binding = 6) uniform sampler2D OriginalHistory5; -layout(set = 0, binding = 7) uniform sampler2D OriginalHistory6; -layout(set = 0, binding = 8) uniform sampler2D OriginalHistory7; - -#define PrevTexture OriginalHistory1 -#define Prev1Texture OriginalHistory2 -#define Prev2Texture OriginalHistory3 -#define Prev3Texture OriginalHistory4 -#define Prev4Texture OriginalHistory5 -#define Prev5Texture OriginalHistory6 -#define Prev6Texture OriginalHistory7 - -#define TEX0 vTexCoord -#define COMPAT_TEXTURE(c,d) texture(c,d) - - -void main() -{ - vec3 color = COMPAT_TEXTURE(PrevTexture, TEX0.xy).rgb; - color+= COMPAT_TEXTURE(Prev6Texture, TEX0.xy).rgb; - color+= COMPAT_TEXTURE(Prev5Texture, TEX0.xy).rgb; - color+= COMPAT_TEXTURE(Prev4Texture, TEX0.xy).rgb; - color+= COMPAT_TEXTURE(Prev3Texture, TEX0.xy).rgb; - color+= COMPAT_TEXTURE(Prev2Texture, TEX0.xy).rgb; - color+= COMPAT_TEXTURE(Prev1Texture, TEX0.xy).rgb; - - FragColor = vec4(color/7.0,1.0); -} \ No newline at end of file diff --git a/crt/shaders/guest/crt-sm/blur_horiz-sm.slang b/crt/shaders/guest/crt-sm/blur_horiz-sm.slang index 66aed49..a144a66 100644 --- a/crt/shaders/guest/crt-sm/blur_horiz-sm.slang +++ b/crt/shaders/guest/crt-sm/blur_horiz-sm.slang @@ -14,9 +14,9 @@ layout(push_constant) uniform Push // Lower values might need more taps. // Adapted from crt-easymode-halation by Easymode. -#pragma parameter TAPSH "H. Glow Radius" 5.0 1.0 10.0 1.0 +#pragma parameter TAPSH "H. Glow Radius" 4.0 1.0 10.0 1.0 #define TAPSH params.TAPSH -#pragma parameter GLOW_FALLOFF_H "Horizontal Glow Grade" 0.25 0.00 1.5 0.02 +#pragma parameter GLOW_FALLOFF_H "Horizontal Glow Grade" 0.35 0.00 1.5 0.02 #define GLOW_FALLOFF_H params.GLOW_FALLOFF_H layout(std140, set = 0, binding = 0) uniform UBO diff --git a/crt/shaders/guest/crt-sm/blur_vert-sm.slang b/crt/shaders/guest/crt-sm/blur_vert-sm.slang index da76dd6..60f5851 100644 --- a/crt/shaders/guest/crt-sm/blur_vert-sm.slang +++ b/crt/shaders/guest/crt-sm/blur_vert-sm.slang @@ -15,9 +15,9 @@ layout(push_constant) uniform Push // Adapted from crt-easymode-halation by Easymode. // Parameter lines go here: -#pragma parameter TAPSV "V. Glow Radius" 5.0 1.0 10.0 1.0 +#pragma parameter TAPSV "V. Glow Radius" 4.0 1.0 10.0 1.0 #define TAPSV params.TAPSV -#pragma parameter GLOW_FALLOFF_V "Vertical Glow Grade" 0.25 0.00 1.5 0.02 +#pragma parameter GLOW_FALLOFF_V "Vertical Glow Grade" 0.35 0.00 1.5 0.02 #define GLOW_FALLOFF_V params.GLOW_FALLOFF_V layout(std140, set = 0, binding = 0) uniform UBO diff --git a/crt/shaders/guest/crt-sm/crt-guest-sm.slang b/crt/shaders/guest/crt-sm/crt-guest-sm.slang index 7fc9823..9c05780 100644 --- a/crt/shaders/guest/crt-sm/crt-guest-sm.slang +++ b/crt/shaders/guest/crt-sm/crt-guest-sm.slang @@ -65,12 +65,14 @@ layout(std140, set = 0, binding = 0) uniform UBO float warpx; float warpy; float bloom; + float halation; float autobrm; float sclip; } global; #pragma parameter bglow "Base Glow" 0.0 0.0 1.0 0.01 #pragma parameter bloom "Bloom" 0.40 0.0 2.0 0.05 +#pragma parameter halation "Halation" 0.0 0.0 2.0 0.05 #pragma parameter autobrm "Automatic Brightness (Mask)" 0.5 0.0 1.0 0.1 #pragma parameter smart "1:Smart 2:Crop 3:Overscan Y Integer Scaling" 0.0 0.0 3.0 1.0 #pragma parameter brightboost1 "Bright boost dark colors" 1.40 0.5 5.0 0.10 @@ -99,6 +101,7 @@ layout(std140, set = 0, binding = 0) uniform UBO #define brightboost1 params.brightboost1 #define brightboost2 params.brightboost2 #define bloom global.bloom +#define halation global.halation #define stype params.stype #define scanline1 params.scanline1 #define scanline2 params.scanline2 @@ -242,6 +245,8 @@ void main() vec2 OGL2Pos = tex * SourceSize1.xy - vec2(0.5,0.5); vec2 fp = fract(OGL2Pos); + float fpx = fp.x; + float fp1 = 1.0-fpx; vec2 pC4 = (floor(OGL2Pos) + vec2(0.5)) * SourceSize1.zw; @@ -256,15 +261,20 @@ void main() float wr1 = 1.0 - fp.x; float wr2 = 2.0 - fp.x; - wl2*=wl2; wl2 = exp2(-h_sharp*wl2); float sl2 = wl2; - wl1*=wl1; wl1 = exp2(-h_sharp*wl1); float sl1 = wl1; - wr1*=wr1; wr1 = exp2(-h_sharp*wr1); float sr1 = wr1; - wr2*=wr2; wr2 = exp2(-h_sharp*wr2); float sr2 = wr2; + wl2*=wl2; wl2 = exp2(-h_sharp*wl2); + wl1*=wl1; wl1 = exp2(-h_sharp*wl1); + wr1*=wr1; wr1 = exp2(-h_sharp*wr1); + wr2*=wr2; wr2 = exp2(-h_sharp*wr2); - wl2 = max(wl2 - zero, mix(0.0,mix(-0.14, -0.035, fp.x),float(cubic > 0.05))); + wl2 = max(wl2 - zero, mix(0.0,mix(-0.14, 0.0, 1.0-fp1*fp1),float(cubic > 0.05))); wl1 = max(wl1 - zero, 0.0); wr1 = max(wr1 - zero, 0.0); - wr2 = max(wr2 - zero, mix(0.0,mix(-0.14, -0.035, 1.-fp.x),float(cubic > 0.05))); + wr2 = max(wr2 - zero, mix(0.0,mix(-0.14, 0.0, 1.0-fpx*fpx),float(cubic > 0.05))); + + float sl2 = max(wl2,0.0); + float sl1 = wl1; + float sr1 = wr1; + float sr2 = max(wr2,0.0); float wtt = 1.0/(wl2+wl1+wr1+wr2); float wts = 1.0/(sl2+sl1+sr1+sr2); @@ -281,11 +291,11 @@ void main() if (cubic > 0.05) color1 = clamp(color1, colmin, colmax); - l1*=l1; l1*=l1; r1*=r1; r1*=r1; l2*=l2; l2*=l2; r2*=r2; r2*=r2; + l1*=l1; l1*=l1*l1; r1*=r1; r1*=r1*r1; l2*=l2; l2*=l2*l2; r2*=r2; r2*=r2*r2; vec3 scolor1 = (sl2*l2+sl1*l1+sr1*r1+sr2*r2)*wts; - scolor1 = pow(scolor1, vec3(1.0/4.0)); vec3 mscolor1 = scolor1; + scolor1 = pow(scolor1, vec3(1.0/6.0)); vec3 mscolor1 = scolor1; - scolor1 = mix(color1, scolor1, 1.1); + scolor1 = mix(color1, scolor1, 1.0); pC4+=dy; l2 = COMPAT_TEXTURE(LinPass, pC4 - dx).rgb; @@ -300,11 +310,11 @@ void main() if (cubic > 0.05) color2 = clamp(color2, colmin, colmax); - l1*=l1; l1*=l1; r1*=r1; r1*=r1; l2*=l2; l2*=l2; r2*=r2; r2*=r2; + l1*=l1; l1*=l1*l1; r1*=r1; r1*=r1*r1; l2*=l2; l2*=l2*l2; r2*=r2; r2*=r2*r2; vec3 scolor2 = (sl2*l2+sl1*l1+sr1*r1+sr2*r2)*wts; - scolor2 = pow(scolor2, vec3(1.0/4.0)); vec3 mscolor2 = scolor2; + scolor2 = pow(scolor2, vec3(1.0/6.0)); vec3 mscolor2 = scolor2; - scolor2 = mix(color2, scolor2, 1.1); + scolor2 = mix(color2, scolor2, 1.0); float f1 = fp.y; float f2 = 1.0 - fp.y; @@ -353,11 +363,11 @@ void main() vec3 ctemp = (t1*color1 + t2*color2)*wt; vec3 orig = ctemp; float pixbr = max(max(orig.r,orig.g),orig.b); vec3 one = vec3(1.0); - vec3 tmp1 = clamp(mix(orig, msctemp, 1.75),0.0,1.0); + vec3 tmp1 = clamp(mix(orig, msctemp, 1.25),0.0,1.0); ctemp = w1+w2; float w3 = max(max(ctemp.r,ctemp.g),ctemp.b); - tmp1 = pow(tmp1, vec3(0.75)); + tmp1 = pow(tmp1, vec3(0.65)); float pixbr1 = max(max(tmp1.r,tmp1.g),tmp1.b); float maskd = mix(min(maskdark,1.0), 0.25*max(maskbright,0.0), pixbr1); if (mask == 3.0 || mask == 4.0) maskd*=1.33; maskd = mix(1.0, 1.0/(1.0-0.5*maskd), autobrm); @@ -436,6 +446,8 @@ void main() vec3 Bloom = COMPAT_TEXTURE(Source, tex).rgb; vec3 Bglow = COMPAT_TEXTURE(LinPass, tex).rgb; Bglow = clamp(Bloom - Bglow,0.0,1.0); + vec3 hglow = 0.5*(Bloom + Bglow); + float maxb = max(max(hglow.r,hglow.g),hglow.b); maxb*=maxb; vec3 Bloom1 = 2.0*Bloom*Bloom; Bloom1 = min(Bloom1, 0.75); @@ -452,6 +464,10 @@ void main() color = min(color,1.0); color = declip(color, pow(w3, 1.0-sclip)); + float colmx = pixbr1; + Bloom = mix(0.5*(Bloom + Bloom*Bloom), Bloom*Bloom, colmx); + color = color + 0.75*(0.75+maxb)*Bloom*(0.75+sqrt(colmx))*mix(1.0,w3,0.5*colmx)*mix(one,cmask,0.35 + 0.4*maxb)*halation; + color = color + bglow*Bglow; color = min(color, mix(cmask,one,sclip)); diff --git a/crt/shaders/guest/fast/bloom_horizontal.slang b/crt/shaders/guest/fast/bloom_horizontal.slang new file mode 100644 index 0000000..1e88d1d --- /dev/null +++ b/crt/shaders/guest/fast/bloom_horizontal.slang @@ -0,0 +1,103 @@ +#version 450 + +/* + Gaussian blur - horizontal pass, dynamic range, resizable + + Copyright (C) 2021 guest(r) - guest.r@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 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#pragma format R16G16B16A16_SFLOAT + +layout(push_constant) uniform Push +{ + vec4 LinearizePassSize; + vec4 OriginalSize; + vec4 OutputSize; + uint FrameCount; + float SIZEHB; + float SIGMA_HB; +} params; + +#pragma parameter bogus_glow "[ GLOW/BLOOM PASS SETTINGS ]:" 0.0 0.0 1.0 1.0 + +#pragma parameter SIZEHB " H. Bloom/Halation/Glow Radius" 4.0 1.0 30.0 1.0 +#define SIZEHB params.SIZEHB + +#pragma parameter SIGMA_HB " Horizontal Bloom/Halation/Glow Sigma" 0.70 0.5 15.0 0.05 +#define SIGMA_HB params.SIGMA_HB + +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 LinearizePass; + +#define COMPAT_TEXTURE(c,d) texture(c,d) + +float invsqrsigma = 1.0/(2.0*SIGMA_HB*SIGMA_HB); + +float gaussian(float x) +{ + return exp(-x*x*invsqrsigma); +} + +void main() +{ + vec4 SourceSize1 = params.OriginalSize; + float f = fract(SourceSize1.x * vTexCoord.x); + f = 0.5 - f; + vec2 tex = floor(SourceSize1.xy * vTexCoord)*SourceSize1.zw + 0.5*SourceSize1.zw; + vec4 color = vec4(0.0); + vec2 dx = vec2(SourceSize1.z, 0.0); + + float w; + float wsum = 0.0; + vec4 pixel; + float n = -SIZEHB; + + do + { + pixel = COMPAT_TEXTURE(LinearizePass, tex + n*dx); + w = gaussian(n+f); + pixel.a = max(max(pixel.r, pixel.g),pixel.b); + pixel.a*=pixel.a*pixel.a; + color = color + w * pixel; + wsum = wsum + w; + n = n + 1.0; + + } while (n <= SIZEHB); + + color = color / wsum; + + FragColor = vec4(color.rgb, pow(color.a, 0.333333)); +} \ No newline at end of file diff --git a/crt/shaders/guest/fast/bloom_vertical.slang b/crt/shaders/guest/fast/bloom_vertical.slang new file mode 100644 index 0000000..bc7a423 --- /dev/null +++ b/crt/shaders/guest/fast/bloom_vertical.slang @@ -0,0 +1,102 @@ +#version 450 + +/* + Gaussian blur - vertical pass, dynamic range, resizable + + Copyright (C) 2020 guest(r) - guest.r@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 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#pragma format R16G16B16A16_SFLOAT + +layout(push_constant) uniform Push +{ + vec4 SourceSize; + vec4 OriginalSize; + vec4 OutputSize; + uint FrameCount; + float SIZEVB; + float SIGMA_VB; +} params; + + +#pragma parameter SIZEVB " V. Bloom/Halation/Glow Radius" 4.0 1.0 30.0 1.0 +#define SIZEVB params.SIZEVB + +#pragma parameter SIGMA_VB " Vertical Bloom/Halation/Glow Sigma" 0.70 0.5 15.0 0.05 +#define SIGMA_VB params.SIGMA_VB + +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 Source; + +#define COMPAT_TEXTURE(c,d) texture(c,d) + +float invsqrsigma = 1.0/(2.0*SIGMA_VB*SIGMA_VB); + +float gaussian(float x) +{ + return exp(-x*x*invsqrsigma); +} + +void main() +{ + vec4 SourceSize1 = vec4(params.SourceSize.x, params.OriginalSize.y, params.SourceSize.z, params.OriginalSize.w); + + float f = fract(SourceSize1.y * vTexCoord.y); + f = 0.5 - f; + vec2 tex = floor(SourceSize1.xy * vTexCoord)*SourceSize1.zw + 0.5*SourceSize1.zw; + vec4 color = vec4(0.0); + vec2 dy = vec2(0.0, SourceSize1.w); + + float w; + float wsum = 0.0; + vec4 pixel; + float n = -SIZEVB; + + do + { + pixel = COMPAT_TEXTURE(Source, tex + n*dy); + w = gaussian(n+f); + pixel.a*=pixel.a*pixel.a; + color = color + w * pixel; + wsum = wsum + w; + n = n + 1.0; + + } while (n <= SIZEVB); + + color = color / wsum; + + FragColor = vec4(color.rgb, pow(color.a, 0.175)); +} \ No newline at end of file diff --git a/crt/shaders/guest/fast/crt-guest-advanced-pass1.slang b/crt/shaders/guest/fast/crt-guest-advanced-pass1.slang new file mode 100644 index 0000000..21af228 --- /dev/null +++ b/crt/shaders/guest/fast/crt-guest-advanced-pass1.slang @@ -0,0 +1,166 @@ +#version 450 + +/* + CRT - Guest - Advanced - Fast - Pass1 + + Copyright (C) 2018-2021 guest(r) - guest.r@gmail.com + + Incorporates many good ideas and suggestions from Dr. Venom. + I would also like give thanks to many Libretro forums members for continuous feedback, suggestions and caring about the shader. + + 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 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +layout(push_constant) uniform Push +{ + vec4 SourceSize; + vec4 OriginalSize; + vec4 OutputSize; + uint FrameCount; + float IOS, h_sharp, s_sharp, spike; + float prescalex; +} params; + +layout(std140, set = 0, binding = 0) uniform UBO +{ + mat4 MVP; +} global; + + +#pragma parameter bogus_filtering "[ FILTERING OPTIONS ]: " 0.0 0.0 1.0 1.0 + +#pragma parameter h_sharp " Horizontal sharpness" 5.20 0.20 15.0 0.10 +#define h_sharp params.h_sharp // pixel sharpness + +#pragma parameter s_sharp " Substractive sharpness (1.0 recommended)" 0.50 0.0 1.5 0.10 +#define s_sharp params.s_sharp // substractive sharpness + +#pragma parameter spike " Scanline Spike Removal" 1.0 0.0 2.0 0.10 +#define spike params.spike + +#pragma parameter prescalex " Prescale-X Factor (for xBR...pre-shader)" 1.0 1.0 4.0 1.0 // Joint parameter with linearize pass, values must match +#define prescalex params.prescalex // prescale-x factor + +#define COMPAT_TEXTURE(c,d) texture(c,d) +#define TEX0 vTexCoord + +#define OutputSize params.OutputSize +#define gl_FragCoord (vTexCoord * OutputSize.xy) + +#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 * 1.000001; +} + +#pragma stage fragment +layout(location = 0) in vec2 vTexCoord; +layout(location = 0) out vec4 FragColor; +layout(set = 0, binding = 2) uniform sampler2D LinearizePass; + + +void main() +{ + vec4 SourceSize = params.OriginalSize * vec4(prescalex, 1.0, 1.0/prescalex, 1.0); + + float intera = COMPAT_TEXTURE(LinearizePass, vec2(0.75,0.25)).a; + + // Calculating texel coordinates + + vec2 texcoord = TEX0.xy; + + vec2 pos = texcoord; + + vec2 coffset = vec2(0.5, 0.5); + + vec2 ps = SourceSize.zw; + vec2 OGL2Pos = pos * SourceSize.xy - coffset; + float fpx = fract(OGL2Pos.x); + + vec2 offx = vec2(ps.x,0.0); + + // Reading the texels + vec2 x2 = 2.0*offx; + + vec2 pC4 = floor(OGL2Pos) * ps + 0.5*ps; + + float zero = exp2(-h_sharp); + float sharp1 = s_sharp * zero; + + float fdivider = min(prescalex, 2.0); + + float wl3 = (2.0 + fpx)/fdivider; + float wl2 = (1.0 + fpx)/fdivider; + float wl1 = ( fpx)/fdivider; + float wr1 = (1.0 - fpx)/fdivider; + float wr2 = (2.0 - fpx)/fdivider; + float wr3 = (3.0 - fpx)/fdivider; + + wl3*=wl3; wl3 = exp2(-h_sharp*wl3); + wl2*=wl2; wl2 = exp2(-h_sharp*wl2); + wl1*=wl1; wl1 = exp2(-h_sharp*wl1); + wr1*=wr1; wr1 = exp2(-h_sharp*wr1); + wr2*=wr2; wr2 = exp2(-h_sharp*wr2); + wr3*=wr3; wr3 = exp2(-h_sharp*wr3); + + float fp1 = 1.-fpx; + + float twl3 = max(wl3 - sharp1, 0.0); + float twl2 = max(wl2 - sharp1, mix(-0.12, 0.0, 1.0-fp1*fp1)); + float twl1 = max(wl1 - sharp1, 0.0); + float twr1 = max(wr1 - sharp1, 0.0); + float twr2 = max(wr2 - sharp1, mix(-0.12, 0.0, 1.0-fpx*fpx)); + float twr3 = max(wr3 - sharp1, 0.0); + + bool sharp = (sharp1 > 0.0); + + vec3 l3, l2, l1, r1, r2, r3, color1, colmin, colmax; + + l3 = COMPAT_TEXTURE(LinearizePass, pC4 -x2).rgb; + l2 = COMPAT_TEXTURE(LinearizePass, pC4 -offx).rgb; + l1 = COMPAT_TEXTURE(LinearizePass, pC4 ).rgb; + r1 = COMPAT_TEXTURE(LinearizePass, pC4 +offx).rgb; + r2 = COMPAT_TEXTURE(LinearizePass, pC4 +x2).rgb; + r3 = COMPAT_TEXTURE(LinearizePass, pC4 +offx+x2).rgb; + + colmin = min(min(l1,r1), min(l2,r2)); + colmax = max(max(l1,r1), max(l2,r2)); + + color1 = (l3*twl3 + l2*twl2 + l1*twl1 + r1*twr1 + r2*twr2 + r3*twr3)/(twl3+twl2+twl1+twr1+twr2+twr3); + + if (sharp) color1 = clamp(color1, colmin, colmax); + float ts = 0.033; + + float lm2 = max(max(l2.r,l2.g),l2.b); + float lm1 = max(max(l1.r,l1.g),l1.b); + float rm1 = max(max(r1.r,r1.g),r1.b); + float rm2 = max(max(r2.r,r2.g),r2.b); + + float swl2 = max(twl2,0.0) * (lm2+ts); + float swl1 = twl1 * (lm1+ts); + float swr1 = twr1 * (rm1+ts); + float swr2 = max(twr2,0.0) * (rm2+ts); + + float fscolor1 = (lm2*swl2 + lm1*swl1 + rm1*swr1 + rm2*swr2)/(swl2+swl1+swr1+swr2); + float sresult = clamp(mix(max(max(color1.r,color1.g),color1.b), fscolor1, spike), 0.0, 1.0); + + FragColor = vec4(color1, sresult); +} \ No newline at end of file diff --git a/crt/shaders/guest/fast/crt-guest-advanced-pass1f.slang b/crt/shaders/guest/fast/crt-guest-advanced-pass1f.slang new file mode 100644 index 0000000..1e4a298 --- /dev/null +++ b/crt/shaders/guest/fast/crt-guest-advanced-pass1f.slang @@ -0,0 +1,159 @@ +#version 450 + +/* + CRT - Guest - Advanced - Fast - Pass1 + + Copyright (C) 2018-2021 guest(r) - guest.r@gmail.com + + Incorporates many good ideas and suggestions from Dr. Venom. + I would also like give thanks to many Libretro forums members for continuous feedback, suggestions and caring about the shader. + + 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 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +layout(push_constant) uniform Push +{ + vec4 SourceSize; + vec4 OriginalSize; + vec4 OutputSize; + uint FrameCount; + float IOS, h_sharp, s_sharp, spike; +} params; + +layout(std140, set = 0, binding = 0) uniform UBO +{ + mat4 MVP; +} global; + + +#pragma parameter bogus_filtering "[ FILTERING OPTIONS ]: " 0.0 0.0 1.0 1.0 + +#pragma parameter h_sharp " Horizontal sharpness" 5.20 0.20 15.0 0.10 +#define h_sharp params.h_sharp // pixel sharpness + +#pragma parameter s_sharp " Substractive sharpness (1.0 recommended)" 0.50 0.0 1.5 0.10 +#define s_sharp params.s_sharp // substractive sharpness + +#pragma parameter spike " Scanline Spike Removal" 1.0 0.0 2.0 0.10 +#define spike params.spike + +#define COMPAT_TEXTURE(c,d) texture(c,d) +#define TEX0 vTexCoord + +#define SourceSize params.OriginalSize +#define OutputSize params.OutputSize +#define gl_FragCoord (vTexCoord * OutputSize.xy) + +#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 * 1.000001; +} + +#pragma stage fragment +layout(location = 0) in vec2 vTexCoord; +layout(location = 0) out vec4 FragColor; +layout(set = 0, binding = 2) uniform sampler2D LinearizePass; + + +void main() +{ + float intera = COMPAT_TEXTURE(LinearizePass, vec2(0.75,0.25)).a; + + // Calculating texel coordinates + + vec2 texcoord = TEX0.xy; + + vec2 pos = texcoord; + + vec2 coffset = vec2(0.5, 0.5); + + vec2 ps = SourceSize.zw; + vec2 OGL2Pos = pos * SourceSize.xy - coffset; + float fpx = fract(OGL2Pos.x); + + vec2 offx = vec2(ps.x,0.0); + + // Reading the texels + vec2 x2 = 2.0*offx; + + vec2 pC4 = floor(OGL2Pos) * ps + 0.5*ps; + + float zero = exp2(-h_sharp); + float sharp1 = s_sharp * zero; + + float wl3 = 2.0 + fpx; + float wl2 = 1.0 + fpx; + float wl1 = fpx; + float wr1 = 1.0 - fpx; + float wr2 = 2.0 - fpx; + float wr3 = 3.0 - fpx; + + wl3*=wl3; wl3 = exp2(-h_sharp*wl3); + wl2*=wl2; wl2 = exp2(-h_sharp*wl2); + wl1*=wl1; wl1 = exp2(-h_sharp*wl1); + wr1*=wr1; wr1 = exp2(-h_sharp*wr1); + wr2*=wr2; wr2 = exp2(-h_sharp*wr2); + wr3*=wr3; wr3 = exp2(-h_sharp*wr3); + + float fp1 = 1.-fpx; + + float twl3 = max(wl3 - sharp1, 0.0); + float twl2 = max(wl2 - sharp1, mix(-0.12, 0.0, 1.0-fp1*fp1)); + float twl1 = max(wl1 - sharp1, 0.0); + float twr1 = max(wr1 - sharp1, 0.0); + float twr2 = max(wr2 - sharp1, mix(-0.12, 0.0, 1.0-fpx*fpx)); + float twr3 = max(wr3 - sharp1, 0.0); + + bool sharp = (sharp1 > 0.0); + + vec3 l3, l2, l1, r1, r2, r3, color1, colmin, colmax; + + l3 = COMPAT_TEXTURE(LinearizePass, pC4 -x2).rgb; + l2 = COMPAT_TEXTURE(LinearizePass, pC4 -offx).rgb; + l1 = COMPAT_TEXTURE(LinearizePass, pC4 ).rgb; + r1 = COMPAT_TEXTURE(LinearizePass, pC4 +offx).rgb; + r2 = COMPAT_TEXTURE(LinearizePass, pC4 +x2).rgb; + r3 = COMPAT_TEXTURE(LinearizePass, pC4 +offx+x2).rgb; + + colmin = min(min(l1,r1), min(l2,r2)); + colmax = max(max(l1,r1), max(l2,r2)); + + color1 = (l3*twl3 + l2*twl2 + l1*twl1 + r1*twr1 + r2*twr2 + r3*twr3)/(twl3+twl2+twl1+twr1+twr2+twr3); + + if (sharp) color1 = clamp(color1, colmin, colmax); + float ts = 0.033; + + float lm2 = max(max(l2.r,l2.g),l2.b); + float lm1 = max(max(l1.r,l1.g),l1.b); + float rm1 = max(max(r1.r,r1.g),r1.b); + float rm2 = max(max(r2.r,r2.g),r2.b); + + float swl2 = max(twl2,0.0) * (lm2+ts); + float swl1 = twl1 * (lm1+ts); + float swr1 = twr1 * (rm1+ts); + float swr2 = max(twr2,0.0) * (rm2+ts); + + float fscolor1 = (lm2*swl2 + lm1*swl1 + rm1*swr1 + rm2*swr2)/(swl2+swl1+swr1+swr2); + float sresult = clamp(mix(max(max(color1.r,color1.g),color1.b), fscolor1, spike), 0.0, 1.0); + + FragColor = vec4(color1, sresult); +} \ No newline at end of file diff --git a/crt/shaders/guest/fast/crt-guest-advanced-pass2.slang b/crt/shaders/guest/fast/crt-guest-advanced-pass2.slang new file mode 100644 index 0000000..69a1309 --- /dev/null +++ b/crt/shaders/guest/fast/crt-guest-advanced-pass2.slang @@ -0,0 +1,601 @@ +#version 450 + +/* + CRT - Guest - Advanced - Fast - Pass2 + + Copyright (C) 2018-2021 guest(r) - guest.r@gmail.com + + Incorporates many good ideas and suggestions from Dr. Venom. + I would also like give thanks to many Libretro forums members for continuous feedback, suggestions and caring about the shader. + + 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 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +layout(push_constant) uniform Push +{ + float brightboost, brightboost1, gsl, scanline1, scanline2, beam_min, beam_max, beam_size, + glow, shadowMask, masksize, vertmask, slotmask, slotwidth, double_slot, mcut, maskDark, maskLight, + maskstr, inters, bloom, halation, scans, slotms, mclip, gamma_c, gamma_out, DER, DEG, DEB, DES, IOS; +} params; + +layout(std140, set = 0, binding = 0) uniform UBO +{ + mat4 MVP; + vec4 SourceSize; + vec4 OriginalSize; + vec4 OutputSize; + uint FrameCount; + float addnoise; + float warpX; + float warpY; + float csize; + float bsize; + float intres; + float c_shape; + float barspeed; + float barintensity; + float bardir; + float slotmask1; +} global; + +#pragma parameter bogus_screen "[ SCREEN OPTIONS ]: " 0.0 0.0 1.0 1.0 + +#pragma parameter intres " Internal Resolution Y: 224p/240p, 1.5...y-dowsample" 0.0 0.0 6.0 0.5 // Joint parameter with linearize pass, values must match + +#pragma parameter IOS " Integer Scaling: Odd:Y, Even:'X'+Y" 0.0 0.0 4.0 1.0 +#define IOS params.IOS // Smart Integer Scaling + +#pragma parameter warpX " CurvatureX (default 0.03)" 0.0 0.0 0.25 0.01 +#define warpX global.warpX // Curvature X + +#pragma parameter warpY " CurvatureY (default 0.04)" 0.0 0.0 0.25 0.01 +#define warpY global.warpY // Curvature Y + +#pragma parameter c_shape " Curvature Shape" 0.25 0.05 0.60 0.05 +#define c_shape global.c_shape // curvature shape + +#pragma parameter csize " Corner size" 0.0 0.0 0.25 0.01 +#define csize global.csize // corner size + +#pragma parameter bsize " Border smoothness" 400.0 100.0 700.0 10.0 +#define bsize global.bsize // border smoothness + +#pragma parameter barspeed " Hum Bar Speed" 50.0 5.0 200.0 1.0 + +#pragma parameter barintensity " Hum Bar Intensity" 0.0 -1.0 1.0 0.01 + +#pragma parameter bardir " Hum Bar Direction" 0.0 0.0 1.0 1.0 + +#pragma parameter bogus_brightness "[ BRIGHTNESS SETTINGS ]:" 0.0 0.0 1.0 1.0 + +#pragma parameter glow " Glow Strength" 0.08 -2.0 2.0 0.01 +#define glow params.glow // Glow Strength + +#pragma parameter bloom " Bloom Strength" 0.0 0.0 2.0 0.05 +#define bloom params.bloom // bloom effect + +#pragma parameter halation " Halation Strength" 0.0 0.0 2.0 0.025 +#define halation params.halation // halation effect + +#pragma parameter gamma_c " Gamma correct" 1.0 0.50 2.0 0.02 +#define gamma_c params.gamma_c // adjust brightness + +#pragma parameter brightboost " Bright Boost Dark Pixels" 1.40 0.25 10.0 0.05 +#define brightboost params.brightboost // adjust brightness + +#pragma parameter brightboost1 " Bright Boost Bright Pixels" 1.10 0.25 3.00 0.025 +#define brightboost1 params.brightboost1 // adjust brightness + +#pragma parameter bogus_scanline "[ SCANLINE OPTIONS ]: " 0.0 0.0 1.0 1.0 + +#pragma parameter gsl " Scanline Type" 0.0 -1.0 2.0 1.0 +#define gsl params.gsl // Alternate scanlines + +#pragma parameter scanline1 " Scanline Beam Shape Center" 6.0 0.0 20.0 0.5 +#define scanline1 params.scanline1 // scanline param, vertical sharpness + +#pragma parameter scanline2 " Scanline Beam Shape Edges" 8.0 3.0 40.0 1.0 +#define scanline2 params.scanline2 // scanline param, vertical sharpness + +#pragma parameter beam_min " Scanline Shape Dark Pixels" 1.30 0.25 3.5 0.05 +#define beam_min params.beam_min // dark area beam min - narrow + +#pragma parameter beam_max " Scanline Shape Bright Pixels" 1.00 0.4 2.5 0.05 +#define beam_max params.beam_max // bright area beam max - wide + +#pragma parameter beam_size " Increased Bright Scanline Beam" 0.60 0.0 1.0 0.05 +#define beam_size params.beam_size // increased max. beam size + +#pragma parameter vertmask " Scanline Color Deconvergence" 0.0 -1.0 1.0 0.1 +#define vertmask params.vertmask // Scanline deconvergence colors + +#pragma parameter scans " Scanline Saturation" 0.60 0.0 1.0 0.05 +#define scans params.scans // scanline saturation + +#pragma parameter bogus_masks "[ CRT MASK OPTIONS ]: " 0.0 0.0 1.0 1.0 + +#pragma parameter shadowMask " CRT Mask: 0:CGWG, 1-4:Lottes, 5-7:'Trinitron'" 0.0 -1.0 8.0 1.0 +#define shadowMask params.shadowMask // Mask Style + +#pragma parameter maskstr " Mask Strength (0, 5-8)" 0.3 -0.5 1.0 0.05 +#define maskstr params.maskstr // CGWG Mask Strength + +#pragma parameter mcut " Mask 5-7 Low Strength" 1.10 0.0 2.0 0.05 +#define mcut params.mcut // Mask 5-7 dark color strength + +#pragma parameter masksize " CRT Mask Size (2.0 is nice in 4k)" 1.0 1.0 4.0 1.0 +#define masksize params.masksize // Mask Size + +#pragma parameter maskDark " Lottes maskDark" 0.5 0.0 2.0 0.05 +#define maskDark params.maskDark // Dark "Phosphor" + +#pragma parameter maskLight " Lottes maskLight" 1.5 0.0 2.0 0.05 +#define maskLight params.maskLight // Light "Phosphor" + +#pragma parameter slotmask " Slot Mask Strength Bright Pixels" 0.0 0.0 1.0 0.05 +#define slotmask params.slotmask + +#pragma parameter slotmask1 " Slot Mask Strength Dark Pixels" 0.0 0.0 1.0 0.05 +#define slotmask1 global.slotmask1 + +#pragma parameter slotwidth " Slot Mask Width" 2.0 1.0 6.0 0.5 +#define slotwidth params.slotwidth // Slot Mask Width + +#pragma parameter double_slot " Slot Mask Height: 2x1 or 4x1" 1.0 1.0 2.0 1.0 +#define double_slot params.double_slot // Slot Mask Height + +#pragma parameter slotms " Slot Mask Size" 1.0 1.0 4.0 1.0 +#define slotms params.slotms // Slot Mask Size + +#pragma parameter mclip " Keep Mask effect with clipping" 0.50 0.0 1.0 0.05 +#define mclip params.mclip // Slot Mask Size + +#pragma parameter gamma_out "Gamma out" 2.4 1.0 5.0 0.05 +#define gamma_out params.gamma_out // output gamma + +#define COMPAT_TEXTURE(c,d) texture(c,d) +#define TEX0 vTexCoord + +#define OutputSize global.OutputSize +#define gl_FragCoord (vTexCoord * OutputSize.xy) + +#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 * 1.000001; +} + +#pragma stage fragment +layout(location = 0) in vec2 vTexCoord; +layout(location = 0) out vec4 FragColor; +layout(set = 0, binding = 2) uniform sampler2D Source; +layout(set = 0, binding = 3) uniform sampler2D LinearizePass; +layout(set = 0, binding = 4) uniform sampler2D BloomPass; +layout(set = 0, binding = 5) uniform sampler2D PrePass; + +#define eps 1e-10 + +float st(float x) +{ + return exp2(-10.0*x*x); +} + +float sw0(float x, float color, float scanline) +{ + float tmp = mix(beam_min, beam_max, color); + float ex = x*tmp; + ex = (gsl > -0.5) ? ex*ex : mix(ex*ex, ex*ex*ex, 0.4); + return exp2(-scanline*ex); +} + +float sw1(float x, float color, float scanline) +{ + x = mix (x, beam_min*x, max(x-0.4*color,0.0)); + float tmp = mix(1.2*beam_min, beam_max, color); + float ex = x*tmp; + return exp2(-scanline*ex*ex); +} + +float sw2(float x, float color, float scanline) +{ + float tmp = mix((2.5-0.5*color)*beam_min, beam_max, color); + tmp = mix(beam_max, tmp, pow(x, color+0.3)); + float ex = x*tmp; + return exp2(-scanline*ex*ex); +} + +// Shadow mask (1-4 from PD CRT Lottes shader). + +vec3 Mask(vec2 pos, float mx) +{ + pos = floor(pos/masksize); + vec3 mask = vec3(maskDark, maskDark, maskDark); + vec3 one = vec3(1.0); + float dark_compensate = mix(max( clamp( mix (mcut, maskstr, mx),0.0, 1.0) - 0.3, 0.0) + 1.0, 1.0, mx); + float mc = 1.0 - max(maskstr, 0.0); + + // No mask + if (shadowMask == -1.0) + { + mask = vec3(1.0); + } + + // Phosphor. + else if (shadowMask == 0.0) + { + pos.x = fract(pos.x*0.5); + if (pos.x < 0.5) { mask.r = 1.0; mask.g = mc; mask.b = 1.0; } + else { mask.r = mc; mask.g = 1.0; mask.b = mc; } + } + + // Very compressed TV style shadow mask. + else if (shadowMask == 1.0) + { + float line = maskLight; + float odd = 0.0; + + if (fract(pos.x/6.0) < 0.5) + odd = 1.0; + if (fract((pos.y + odd)/2.0) < 0.5) + line = maskDark; + + pos.x = fract(pos.x/3.0); + + if (pos.x < 0.333) mask.r = maskLight; + else if (pos.x < 0.666) mask.g = maskLight; + else mask.b = maskLight; + + mask*=line; + } + + // Aperture-grille. + else if (shadowMask == 2.0) + { + pos.x = fract(pos.x/3.0); + + if (pos.x < 0.333) mask.r = maskLight; + else if (pos.x < 0.666) mask.g = maskLight; + else mask.b = maskLight; + } + + // Stretched VGA style shadow mask (same as prior shaders). + else if (shadowMask == 3.0) + { + pos.x += pos.y*3.0; + pos.x = fract(pos.x/6.0); + + if (pos.x < 0.333) mask.r = maskLight; + else if (pos.x < 0.666) mask.g = maskLight; + else mask.b = maskLight; + } + + // VGA style shadow mask. + else if (shadowMask == 4.0) + { + pos.xy = floor(pos.xy*vec2(1.0, 0.5)); + pos.x += pos.y*3.0; + pos.x = fract(pos.x/6.0); + + if (pos.x < 0.333) mask.r = maskLight; + else if (pos.x < 0.666) mask.g = maskLight; + else mask.b = maskLight; + } + + // Trinitron mask 5 + else if (shadowMask == 5.0) + { + mask = vec3(0.0); + pos.x = fract(pos.x/2.0); + if (pos.x < 0.5) + { mask.r = 1.0; + mask.b = 1.0; + } + else mask.g = 1.0; + mask = clamp(mix( mix(one, mask, mcut), mix(one, mask, maskstr), mx), 0.0, 1.0) * dark_compensate; + } + + // Trinitron mask 6 + else if (shadowMask == 6.0) + { + mask = vec3(0.0); + pos.x = fract(pos.x/3.0); + if (pos.x < 0.333) mask.r = 1.0; + else if (pos.x < 0.666) mask.g = 1.0; + else mask.b = 1.0; + mask = clamp(mix( mix(one, mask, mcut), mix(one, mask, maskstr), mx), 0.0, 1.0) * dark_compensate; + } + + // BW Trinitron mask 7 + else if (shadowMask == 7.0) + { + float maskTmp = clamp(mix( mix(1.0, 0.0, mcut), mix(1.0, 0.0, maskstr), mx), 0.0, 1.0) * dark_compensate; + mask = vec3(maskTmp); + pos.x = fract(pos.x/2.0); + if (pos.x < 0.5) mask = vec3(1.0); + } + + // 4k mask + else + { + mask = vec3(mc); + pos.x = fract(pos.x * 0.25); + if (pos.x < 0.2) mask.r = 1.0; + else if (pos.x < 0.4) mask.rg = 1.0.xx; + else if (pos.x < 0.7) mask.gb = 1.0.xx; + else mask.b = 1.0; + } + + return mask; +} + +float SlotMask(vec2 pos, float m) +{ + if (slotmask == 0.0) return 1.0; + else + { + pos = floor(pos/slotms); + float mlen = slotwidth*2.0; + float px = fract(pos.x/mlen); + float py = floor(fract(pos.y/(2.0*double_slot))*2.0*double_slot); + float slot_dark = mix(1.0-slotmask1, 1.0-slotmask, m); + float slot = 1.0; + if (py == 0.0 && px < 0.5) slot = slot_dark; else + if (py == double_slot && px >= 0.5) slot = slot_dark; + + return slot; + } +} + + +vec3 declip(vec3 c, float b) +{ + float m = max(max(c.r,c.g),c.b); + if (m > b) c = c*b/m; + return c; +} + +vec3 gc(vec3 c) +{ + float mc = max(max(c.r,c.g),c.b); + float mg = pow(mc, 1.0/gamma_c); + return c * mg/(mc + eps); +} + +vec3 plant (vec3 tar, float r) +{ + float t = max(max(tar.r,tar.g),tar.b) + 0.00001; + return tar * r / t; +} + +vec2 Overscan(vec2 pos, float dx, float dy){ + pos=pos*2.0-1.0; + pos*=vec2(dx,dy); + return pos*0.5+0.5; +} + +vec2 Warp(vec2 pos) +{ + pos = pos*2.0-1.0; + pos = mix(pos, vec2(pos.x*inversesqrt(1.0-c_shape*pos.y*pos.y), pos.y*inversesqrt(1.0-c_shape*pos.x*pos.x)), vec2(warpX, warpY)/c_shape); + return pos*0.5 + 0.5; +} + +float humbar(float pos) +{ + if (global.barintensity == 0.0) return 1.0; else + { + pos = (global.barintensity >= 0.0) ? pos : (1.0-pos); + pos = fract(pos + mod(float(global.FrameCount),global.barspeed)/(global.barspeed-1.0)); + pos = (global.barintensity < 0.0) ? pos : (1.0-pos); + return (1.0-global.barintensity) + global.barintensity*pos; + } +} + +// Borrowed from cgwg's crt-geom, under GPL + +float corner(vec2 coord) +{ + coord = min(coord, vec2(1.0)-coord) * vec2(1.0, OutputSize.y/OutputSize.x); + vec2 cdist = vec2(max(csize/3.0, max((1.0-smoothstep(100.0,600.0,bsize))*0.01,0.002))); + coord = (cdist - min(coord,cdist)); + float dist = sqrt(dot(coord,coord)); + return clamp((cdist.x-dist)*bsize,0.0, 1.0); +} + +void main() +{ + vec4 SourceSize = vec4(global.SourceSize.x, global.OriginalSize.y, global.SourceSize.z, global.OriginalSize.w); + float gamma_in = 1.0/COMPAT_TEXTURE(LinearizePass, vec2(0.25,0.25)).a; + float intera = COMPAT_TEXTURE(LinearizePass, vec2(0.75,0.25)).a; + bool interb = (intera < 0.75); + + float SourceY = SourceSize.y; + float sy = 1.0; + if (global.intres == 0.5) sy = SourceY/224.0; else + if (global.intres == 1.0) sy = SourceY/240.0; else + if (global.intres > 1.25) sy = global.intres; + SourceSize*=vec4(1.0, 1.0/sy, 1.0, sy); + + // Calculating texel coordinates + + vec2 texcoord = TEX0.xy; + + if (IOS > 0.0){ + vec2 ofactor = OutputSize.xy/SourceSize.xy; + vec2 intfactor = (IOS < 2.5) ? floor(ofactor) : ceil(ofactor); + vec2 diff = ofactor/intfactor; + float scan = diff.y; + texcoord = Overscan(texcoord, scan, scan); + if (IOS == 1.0 || IOS == 3.0) texcoord = vec2(TEX0.x, texcoord.y); + } + + vec2 pos = Warp(texcoord); + vec2 cpos = (IOS > 2.5) ? TEX0 : texcoord; + float corner0 = corner(Warp(cpos)); + + float coffset = 0.5; + + vec2 ps = SourceSize.zw; + float OGL2Pos = pos.y * SourceSize.y - coffset; + float f = fract(OGL2Pos); + + vec2 dx = vec2(ps.x,0.0); + vec2 dy = vec2(0.0, ps.y); + + // Reading the texels + + vec2 pC4; + + pC4.y = floor(OGL2Pos) * ps.y + 0.5*ps.y; + pC4.x = pos.x; + + if (interb) pC4.y = pos.y; + + vec3 color1 = COMPAT_TEXTURE(Source, pC4 ).rgb; + vec3 scolor1 = COMPAT_TEXTURE(Source, pC4 ).aaa; + + pC4+=dy; + + vec3 color2 = COMPAT_TEXTURE(Source, pC4 ).rgb; + vec3 scolor2 = COMPAT_TEXTURE(Source, pC4 ).aaa; + + // calculating scanlines + + vec3 ctmp; vec3 mcolor; float w3; vec3 color; + vec3 one = vec3(1.0); + +if (!interb) +{ + float shape1 = mix(scanline1, scanline2, f); + float shape2 = mix(scanline1, scanline2, 1.0-f); + + float wt1 = st(f); + float wt2 = st(1.0-f); + + vec3 color00 = color1*wt1 + color2*wt2; + vec3 scolor0 = scolor1*wt1 + scolor2*wt2; + + ctmp = color00/(wt1+wt2); + vec3 sctmp = max(scolor0/(wt1+wt2), ctmp); + mcolor = sctmp; + + float wf1, wf2; + + vec3 cref1 = mix(sctmp, scolor1, beam_size); float creff1 = max(max(cref1.r,cref1.g),cref1.b); + vec3 cref2 = mix(sctmp, scolor2, beam_size); float creff2 = max(max(cref2.r,cref2.g),cref2.b); + + float f1 = f; + float f2 = 1.0-f; + + if (gsl < 0.5) { wf1 = sw0(f1,creff1,shape1); wf2 = sw0(f2,creff2,shape2);} else + if (gsl == 1.0) { wf1 = sw1(f1,creff1,shape1); wf2 = sw1(f2,creff2,shape2);} else + { wf1 = sw2(f1,creff1,shape1); wf2 = sw2(f2,creff2,shape2);} + + if ((wf1 + wf2) > 1.0) { float wtmp = 1.0/(wf1+wf2); wf1*=wtmp; wf2*=wtmp; } + + // Scanline saturation application + + vec3 w1 = vec3(wf1); vec3 w2 = vec3(wf2); + w3 = wf1+wf2; + + cref1 = color1 / (max(max(color1.r,color1.g),color1.b) + 0.00001); + cref2 = color2 / (max(max(color2.r,color2.g),color2.b) + 0.00001); + + w1 = mix(w1*mix(one, cref1*cref1*cref1, scans), w1, wf1); + w2 = mix(w2*mix(one, cref2*cref2*cref2, scans), w2, wf2); + + vec3 cd1 = one; vec3 cd2 = one; float vm = sqrt(abs(vertmask)); + + float v_high1 = 1.0 + 0.3*vm; + float v_high2 = 1.0 + 0.6*vm; + float v_low = 1.0 - vm; + + float ds1 = min(max(1.0-w3*w3, 2.5*f1), 1.0); + float ds2 = min(max(1.0-w3*w3, 2.5*f2), 1.0); + + if (vertmask < 0.0) + { + cd1 = mix(one, vec3(v_high2, v_low, v_low), ds1); + cd2 = mix(one, vec3(v_low, v_high1, v_high1), ds2); + } + else + { + cd1 = mix(one, vec3(v_high1, v_low, v_high1), ds1); + cd2 = mix(one, vec3(v_low, v_high2, v_low), ds2); + } + + color = gc(color1)*w1*cd1 + gc(color2)*w2*cd2; + color = min(color, 1.0); +} + + if (interb) + { + color = gc(color1); + mcolor = clamp(mix(color1, scolor1, 1.25), 0.0, 1.0); + } + + float mx = max(max(mcolor.r,mcolor.g),mcolor.b); + mx = pow(mx, 1.20/gamma_in); + + // Apply Mask + + vec3 orig1 = color; + vec3 cmask = one; + + float smask = SlotMask(gl_FragCoord.xy * 1.000001, mx); + cmask*= Mask(gl_FragCoord.xy * 1.000001, mx); + + color = color*cmask; + color = min(color,1.0); + color = color*smask; + + cmask = min(cmask*smask, 1.0); + + if (interb) ctmp = color; + float colmx = pow( max( max(ctmp.r, ctmp.g), ctmp.b), 1.40/gamma_out); + float bb = mix(brightboost, brightboost1, colmx); + if (interb) bb = (abs(intera-0.5)<0.1) ? pow(0.80*bb, 0.65) : pow(bb, 0.70); + color*=bb; + + vec3 Glow = COMPAT_TEXTURE(BloomPass, pos ).rgb; + vec3 Bloom = Glow; + float maxb = COMPAT_TEXTURE(BloomPass, pos ).a; + float vig = COMPAT_TEXTURE(PrePass, clamp(pos, 0.0+0.5*global.OriginalSize.zw, 1.0-0.5*global.OriginalSize.zw)).a; + + vec3 Bloom1 = min(Bloom*(orig1+color), max(0.5*(colmx + orig1 - color),0.0)); + color = color + bloom*Bloom1; + + color = min(color, mix(one, cmask, mclip)); + if (!interb) color = declip(color, pow(w3,0.60)); + + if (halation > 0.025) { + Bloom = mix(0.5*(Bloom + Bloom*Bloom), Bloom*Bloom, colmx); + color = color + (1.1-0.25*colmx)*(0.75+maxb)*Bloom*(0.75 + 0.70*pow(colmx,0.33333))*mix(1.0,w3,0.5*colmx)*mix(one,cmask,0.35 + 0.4*maxb)*halation; } + + Glow = mix(Glow, 0.25*color, 0.7*colmx); + if (glow >= 0.0) color = color + 0.5*Glow*glow; else { cmask*=cmask; cmask*=cmask; color = color + (-glow)*cmask*Glow; } + + color = min(color, 1.0); + + color = pow(color, vec3(1.0/gamma_out)); + + FragColor = vec4(color*vig*humbar(mix(pos.y, pos.x, global.bardir)), corner0); +} \ No newline at end of file diff --git a/crt/shaders/guest/fast/crt-guest-advanced-pass2f.slang b/crt/shaders/guest/fast/crt-guest-advanced-pass2f.slang new file mode 100644 index 0000000..8b7ab4c --- /dev/null +++ b/crt/shaders/guest/fast/crt-guest-advanced-pass2f.slang @@ -0,0 +1,633 @@ +#version 450 + +/* + CRT - Guest - Advanced - Fastest - Pass2 + + Copyright (C) 2018-2021 guest(r) - guest.r@gmail.com + + Incorporates many good ideas and suggestions from Dr. Venom. + I would also like give thanks to many Libretro forums members for continuous feedback, suggestions and caring about the shader. + + 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 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +layout(push_constant) uniform Push +{ + float brightboost, brightboost1, gsl, scanline1, scanline2, beam_min, beam_max, beam_size, + glow, shadowMask, masksize, vertmask, slotmask, slotwidth, double_slot, mcut, maskDark, maskLight, + maskstr, inters, bloom, halation, scans, slotms, mclip, gamma_c, gamma_out, DER, DEG, DEB, DES, IOS; +} params; + +layout(std140, set = 0, binding = 0) uniform UBO +{ + mat4 MVP; + vec4 SourceSize; + vec4 OriginalSize; + vec4 OutputSize; + uint FrameCount; + float warpX; + float warpY; + float csize; + float bsize; + float c_shape; + float slotmask1; +} global; + +#pragma parameter bogus_screen "[ SCREEN OPTIONS ]: " 0.0 0.0 1.0 1.0 + +#pragma parameter IOS " Integer Scaling: Odd:Y, Even:'X'+Y" 0.0 0.0 4.0 1.0 +#define IOS params.IOS // Smart Integer Scaling + +#pragma parameter warpX " CurvatureX (default 0.03)" 0.0 0.0 0.25 0.01 +#define warpX global.warpX // Curvature X + +#pragma parameter warpY " CurvatureY (default 0.04)" 0.0 0.0 0.25 0.01 +#define warpY global.warpY // Curvature Y + +#pragma parameter c_shape " Curvature Shape" 0.25 0.05 0.60 0.05 +#define c_shape global.c_shape // curvature shape + +#pragma parameter csize " Corner size" 0.0 0.0 0.25 0.01 +#define csize global.csize // corner size + +#pragma parameter bsize " Border smoothness" 400.0 100.0 700.0 10.0 +#define bsize global.bsize // border smoothness + +#pragma parameter bogus_brightness "[ BRIGHTNESS SETTINGS ]:" 0.0 0.0 1.0 1.0 + +#pragma parameter glow " Glow Strength" 0.08 0.0 2.0 0.01 +#define glow params.glow // Glow Strength + +#pragma parameter bloom " Bloom Strength" 0.0 0.0 2.0 0.05 +#define bloom params.bloom // bloom effect + +#pragma parameter halation " Halation Strength" 0.0 0.0 1.0 0.025 +#define halation params.halation // halation effect + +#pragma parameter gamma_c " Gamma correct" 1.0 0.50 2.0 0.02 +#define gamma_c params.gamma_c // adjust brightness + +#pragma parameter brightboost " Bright Boost Dark Pixels" 1.40 0.25 10.0 0.05 +#define brightboost params.brightboost // adjust brightness + +#pragma parameter brightboost1 " Bright Boost Bright Pixels" 1.10 0.25 3.00 0.025 +#define brightboost1 params.brightboost1 // adjust brightness + +#pragma parameter bogus_scanline "[ SCANLINE OPTIONS ]: " 0.0 0.0 1.0 1.0 + +#pragma parameter gsl " Scanline Type" 0.0 -1.0 2.0 1.0 +#define gsl params.gsl // Alternate scanlines + +#pragma parameter scanline1 " Scanline Beam Shape Center" 6.0 0.0 20.0 0.5 +#define scanline1 params.scanline1 // scanline param, vertical sharpness + +#pragma parameter scanline2 " Scanline Beam Shape Edges" 8.0 3.0 40.0 1.0 +#define scanline2 params.scanline2 // scanline param, vertical sharpness + +#pragma parameter beam_min " Scanline Shape Dark Pixels" 1.30 0.25 3.5 0.05 +#define beam_min params.beam_min // dark area beam min - narrow + +#pragma parameter beam_max " Scanline Shape Bright Pixels" 1.00 0.4 2.5 0.05 +#define beam_max params.beam_max // bright area beam max - wide + +#pragma parameter beam_size " Increased Bright Scanline Beam" 0.60 0.0 1.0 0.05 +#define beam_size params.beam_size // increased max. beam size + +#pragma parameter vertmask " Scanline Color Deconvergence" 0.0 -1.0 1.0 0.1 +#define vertmask params.vertmask // Scanline deconvergence colors + +#pragma parameter scans " Scanline Saturation" 0.60 0.0 1.0 0.05 +#define scans params.scans // scanline saturation + +#pragma parameter bogus_masks "[ CRT MASK OPTIONS ]: " 0.0 0.0 1.0 1.0 + +#pragma parameter shadowMask " CRT Mask: 0:CGWG, 1-4:Lottes, 5-7:'Trinitron'" 0.0 -1.0 8.0 1.0 +#define shadowMask params.shadowMask // Mask Style + +#pragma parameter maskstr " Mask Strength (0, 5-8)" 0.3 -0.5 1.0 0.05 +#define maskstr params.maskstr // CGWG Mask Strength + +#pragma parameter mcut " Mask 5-7 Low Strength" 1.10 0.0 2.0 0.05 +#define mcut params.mcut // Mask 5-7 dark color strength + +#pragma parameter masksize " CRT Mask Size (2.0 is nice in 4k)" 1.0 1.0 4.0 1.0 +#define masksize params.masksize // Mask Size + +#pragma parameter maskDark " Lottes maskDark" 0.5 0.0 2.0 0.05 +#define maskDark params.maskDark // Dark "Phosphor" + +#pragma parameter maskLight " Lottes maskLight" 1.5 0.0 2.0 0.05 +#define maskLight params.maskLight // Light "Phosphor" + +#pragma parameter slotmask " Slot Mask Strength Bright Pixels" 0.0 0.0 1.0 0.05 +#define slotmask params.slotmask + +#pragma parameter slotmask1 " Slot Mask Strength Dark Pixels" 0.0 0.0 1.0 0.05 +#define slotmask1 global.slotmask1 + +#pragma parameter slotwidth " Slot Mask Width" 2.0 1.0 6.0 0.5 +#define slotwidth params.slotwidth // Slot Mask Width + +#pragma parameter double_slot " Slot Mask Height: 2x1 or 4x1" 1.0 1.0 2.0 1.0 +#define double_slot params.double_slot // Slot Mask Height + +#pragma parameter slotms " Slot Mask Size" 1.0 1.0 4.0 1.0 +#define slotms params.slotms // Slot Mask Size + +#pragma parameter mclip " Keep Mask effect with clipping" 0.50 0.0 1.0 0.05 +#define mclip params.mclip // Slot Mask Size + +#pragma parameter bogus_deconvergence22 "[ HORIZONTAL DECONVERGENCE ]:" 0.0 0.0 1.0 1.0 + +#pragma parameter DER " Deconvergence Red offset" 0.0 -15.0 15.0 0.5 + +#pragma parameter DEG " Deconvergence Green offset" 0.0 -15.0 15.0 0.5 + +#pragma parameter DEB " Deconvergence Blue offset" 0.0 -15.0 15.0 0.5 + +#pragma parameter DES " Deconvergence Strength" 0.7 0.0 1.0 0.05 + +#pragma parameter gamma_out "Gamma out" 2.4 1.0 5.0 0.05 +#define gamma_out params.gamma_out // output gamma + +#pragma parameter inters " Interlacing Effect Smoothness" 0.0 0.0 0.5 0.05 // Joint parameter with linearize pass, values must match +#define inters params.inters // interlacing effect smoothing + +#define COMPAT_TEXTURE(c,d) texture(c,d) +#define TEX0 vTexCoord + +#define SourceSize global.SourceSize +#define OutputSize global.OutputSize +#define gl_FragCoord (vTexCoord * OutputSize.xy) + +#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 * 1.000001; +} + +#pragma stage fragment +layout(location = 0) in vec2 vTexCoord; +layout(location = 0) out vec4 FragColor; +layout(set = 0, binding = 2) uniform sampler2D Source; +layout(set = 0, binding = 3) uniform sampler2D LinearizePass; +layout(set = 0, binding = 4) uniform sampler2D PrePassDontChange; +layout(set = 0, binding = 5) uniform sampler2D Pass2Feedback; + +#define eps 1e-10 + +float st(float x) +{ + return exp2(-10.0*x*x); +} + +float sw0(float x, float color, float scanline) +{ + float tmp = mix(beam_min, beam_max, color); + float ex = x*tmp; + ex = (gsl > -0.5) ? ex*ex : mix(ex*ex, ex*ex*ex, 0.4); + return exp2(-scanline*ex); +} + +float sw1(float x, float color, float scanline) +{ + x = mix (x, beam_min*x, max(x-0.4*color,0.0)); + float tmp = mix(1.2*beam_min, beam_max, color); + float ex = x*tmp; + return exp2(-scanline*ex*ex); +} + +float sw2(float x, float color, float scanline) +{ + float tmp = mix((2.5-0.5*color)*beam_min, beam_max, color); + tmp = mix(beam_max, tmp, pow(x, color+0.3)); + float ex = x*tmp; + return exp2(-scanline*ex*ex); +} + +// Shadow mask (1-4 from PD CRT Lottes shader). + +vec3 Mask(vec2 pos, float mx) +{ + pos = floor(pos/masksize); + vec3 mask = vec3(maskDark, maskDark, maskDark); + vec3 one = vec3(1.0); + float dark_compensate = mix(max( clamp( mix (mcut, maskstr, mx),0.0, 1.0) - 0.3, 0.0) + 1.0, 1.0, mx); + float mc = 1.0 - max(maskstr, 0.0); + + // No mask + if (shadowMask == -1.0) + { + mask = vec3(1.0); + } + + // Phosphor. + else if (shadowMask == 0.0) + { + pos.x = fract(pos.x*0.5); + if (pos.x < 0.5) { mask.r = 1.0; mask.g = mc; mask.b = 1.0; } + else { mask.r = mc; mask.g = 1.0; mask.b = mc; } + } + + // Very compressed TV style shadow mask. + else if (shadowMask == 1.0) + { + float line = maskLight; + float odd = 0.0; + + if (fract(pos.x/6.0) < 0.5) + odd = 1.0; + if (fract((pos.y + odd)/2.0) < 0.5) + line = maskDark; + + pos.x = fract(pos.x/3.0); + + if (pos.x < 0.333) mask.r = maskLight; + else if (pos.x < 0.666) mask.g = maskLight; + else mask.b = maskLight; + + mask*=line; + } + + // Aperture-grille. + else if (shadowMask == 2.0) + { + pos.x = fract(pos.x/3.0); + + if (pos.x < 0.333) mask.r = maskLight; + else if (pos.x < 0.666) mask.g = maskLight; + else mask.b = maskLight; + } + + // Stretched VGA style shadow mask (same as prior shaders). + else if (shadowMask == 3.0) + { + pos.x += pos.y*3.0; + pos.x = fract(pos.x/6.0); + + if (pos.x < 0.333) mask.r = maskLight; + else if (pos.x < 0.666) mask.g = maskLight; + else mask.b = maskLight; + } + + // VGA style shadow mask. + else if (shadowMask == 4.0) + { + pos.xy = floor(pos.xy*vec2(1.0, 0.5)); + pos.x += pos.y*3.0; + pos.x = fract(pos.x/6.0); + + if (pos.x < 0.333) mask.r = maskLight; + else if (pos.x < 0.666) mask.g = maskLight; + else mask.b = maskLight; + } + + // Trinitron mask 5 + else if (shadowMask == 5.0) + { + mask = vec3(0.0); + pos.x = fract(pos.x/2.0); + if (pos.x < 0.5) + { mask.r = 1.0; + mask.b = 1.0; + } + else mask.g = 1.0; + mask = clamp(mix( mix(one, mask, mcut), mix(one, mask, maskstr), mx), 0.0, 1.0) * dark_compensate; + } + + // Trinitron mask 6 + else if (shadowMask == 6.0) + { + mask = vec3(0.0); + pos.x = fract(pos.x/3.0); + if (pos.x < 0.333) mask.r = 1.0; + else if (pos.x < 0.666) mask.g = 1.0; + else mask.b = 1.0; + mask = clamp(mix( mix(one, mask, mcut), mix(one, mask, maskstr), mx), 0.0, 1.0) * dark_compensate; + } + + // BW Trinitron mask 7 + else if (shadowMask == 7.0) + { + float maskTmp = clamp(mix( mix(1.0, 0.0, mcut), mix(1.0, 0.0, maskstr), mx), 0.0, 1.0) * dark_compensate; + mask = vec3(maskTmp); + pos.x = fract(pos.x/2.0); + if (pos.x < 0.5) mask = vec3(1.0); + } + + // 4k mask + else + { + mask = vec3(mc); + pos.x = fract(pos.x * 0.25); + if (pos.x < 0.2) mask.r = 1.0; + else if (pos.x < 0.4) mask.rg = 1.0.xx; + else if (pos.x < 0.7) mask.gb = 1.0.xx; + else mask.b = 1.0; + } + + return mask; +} + +float SlotMask(vec2 pos, float m) +{ + if (slotmask == 0.0) return 1.0; + else + { + pos = floor(pos/slotms); + float mlen = slotwidth*2.0; + float px = fract(pos.x/mlen); + float py = floor(fract(pos.y/(2.0*double_slot))*2.0*double_slot); + float slot_dark = mix(1.0-slotmask1, 1.0-slotmask, m); + float slot = 1.0; + if (py == 0.0 && px < 0.5) slot = slot_dark; else + if (py == double_slot && px >= 0.5) slot = slot_dark; + + return slot; + } +} + + +vec3 declip(vec3 c, float b) +{ + float m = max(max(c.r,c.g),c.b); + if (m > b) c = c*b/m; + return c; +} + +vec3 gc(vec3 c) +{ + float mc = max(max(c.r,c.g),c.b); + float mg = pow(mc, 1.0/gamma_c); + return c * mg/(mc + eps); +} + +vec3 plant (vec3 tar, float r) +{ + float t = max(max(tar.r,tar.g),tar.b) + 0.00001; + return tar * r / t; +} + +vec2 Overscan(vec2 pos, float dx, float dy){ + pos=pos*2.0-1.0; + pos*=vec2(dx,dy); + return pos*0.5+0.5; +} + +vec2 Warp(vec2 pos) +{ + pos = pos*2.0-1.0; + pos = mix(pos, vec2(pos.x*inversesqrt(1.0-c_shape*pos.y*pos.y), pos.y*inversesqrt(1.0-c_shape*pos.x*pos.x)), vec2(warpX, warpY)/c_shape); + return pos*0.5 + 0.5; +} + +// Borrowed from cgwg's crt-geom, under GPL + +float corner(vec2 coord) +{ + coord = min(coord, vec2(1.0)-coord) * vec2(1.0, OutputSize.y/OutputSize.x); + vec2 cdist = vec2(max(csize/3.0, max((1.0-smoothstep(100.0,600.0,bsize))*0.01,0.002))); + coord = (cdist - min(coord,cdist)); + float dist = sqrt(dot(coord,coord)); + return clamp((cdist.x-dist)*bsize,0.0, 1.0); +} + +vec3 calculate_bloom (vec2 pos, vec2 x, vec2 y) +{ + return ( COMPAT_TEXTURE(LinearizePass, pos -x -y).rgb * 0.091849 + + COMPAT_TEXTURE(LinearizePass, pos -y).rgb * 0.119368 + + COMPAT_TEXTURE(LinearizePass, pos +x -y).rgb * 0.091849 + + COMPAT_TEXTURE(LinearizePass, pos -x ).rgb * 0.119368 + + COMPAT_TEXTURE(LinearizePass, pos ).rgb * 0.155131 + + COMPAT_TEXTURE(LinearizePass, pos +x ).rgb * 0.119368 + + COMPAT_TEXTURE(LinearizePass, pos -x +y).rgb * 0.091849 + + COMPAT_TEXTURE(LinearizePass, pos +y).rgb * 0.119368 + + COMPAT_TEXTURE(LinearizePass, pos +x +y).rgb * 0.091849 ); +} + +void main() +{ + float intera = COMPAT_TEXTURE(LinearizePass, vec2(0.75,0.25)).a; + bool interb = (intera < 0.75); + vec4 result = COMPAT_TEXTURE(Pass2Feedback, vTexCoord); + + vec2 texcoord = TEX0.xy; + + if (IOS > 0.0){ + vec2 ofactor = OutputSize.xy/SourceSize.xy; + vec2 intfactor = (IOS < 2.5) ? floor(ofactor) : ceil(ofactor); + vec2 diff = ofactor/intfactor; + float scan = diff.y; + texcoord = Overscan(texcoord, scan, scan); + if (IOS == 1.0 || IOS == 3.0) texcoord = vec2(TEX0.x, texcoord.y); + } + + vec2 yy = vec2(0.0, global.OriginalSize.w); + vec2 xx = vec2(global.OriginalSize.z, 0.0); + vec2 y2 = yy+yy; + vec2 pos = Warp(texcoord); + vec2 pc4 = pos; + pc4 = floor(pc4 * global.OriginalSize.xy - vec2(0.0, 0.5)) * global.OriginalSize.zw + 0.5*global.OriginalSize.zw; + + float same2 = COMPAT_TEXTURE(PrePassDontChange,pc4-yy).a; + float same3 = COMPAT_TEXTURE(PrePassDontChange,pc4 ).a; + float same4 = COMPAT_TEXTURE(PrePassDontChange,pc4+yy).a; + float same5 = COMPAT_TEXTURE(PrePassDontChange,pc4+y2).a; + + float refresh1 = 30.0; + float refresh2 = round(TEX0.y*29.0); + + bool frames = (floor(mod(float(global.FrameCount), refresh1)) == refresh2); + bool not_same = (same2 + same3 + same4 + same5) > 0.25; + +if ( not_same || frames || interb ) + +{ + + float gamma_in = 1.0/COMPAT_TEXTURE(LinearizePass, vec2(0.25,0.25)).a; + + // Calculating texel coordinates + + vec2 cpos = (IOS > 2.5) ? TEX0 : texcoord; + float corner0 = corner(Warp(cpos)); + + float coffset = 0.5; + + vec2 ps = SourceSize.zw; + float OGL2Pos = pos.y * SourceSize.y - coffset; + float f = fract(OGL2Pos); + + vec2 dx = vec2(ps.x,0.0); + vec2 dy = vec2(0.0, ps.y); + + // Reading the texels + + vec2 pC4; + + pC4.y = floor(OGL2Pos) * ps.y + 0.5*ps.y; + pC4.x = pos.x; + + if (interb) pC4.y = pos.y - inters * SourceSize.w; + + vec3 color1 = COMPAT_TEXTURE(Source, pC4 ).rgb; + vec3 dcolor1 = color1; + dcolor1.r = COMPAT_TEXTURE(Source, pC4 + dx*params.DER).r; + dcolor1.g = COMPAT_TEXTURE(Source, pC4 + dx*params.DEG).g; + dcolor1.b = COMPAT_TEXTURE(Source, pC4 + dx*params.DEB).b; + color1 = mix(color1, dcolor1, params.DES); + + vec3 scolor1 = COMPAT_TEXTURE(Source, pC4 ).aaa; + + if (interb) pC4.y = pos.y + inters * SourceSize.w; else + pC4+=dy; + + vec3 color2 = COMPAT_TEXTURE(Source, pC4 ).rgb; + vec3 dcolor2 = color2; + dcolor2.r = COMPAT_TEXTURE(Source, pC4 + dx*params.DER).r; + dcolor2.g = COMPAT_TEXTURE(Source, pC4 + dx*params.DEG).g; + dcolor2.b = COMPAT_TEXTURE(Source, pC4 + dx*params.DEB).b; + color2 = mix(color2, dcolor2, params.DES); + + vec3 scolor2 = COMPAT_TEXTURE(Source, pC4 ).aaa; + + // calculating scanlines + + vec3 ctmp; vec3 mcolor; float w3; vec3 color; + vec3 one = vec3(1.0); + +if (!interb) +{ + float shape1 = mix(scanline1, scanline2, f); + float shape2 = mix(scanline1, scanline2, 1.0-f); + + float wt1 = st(f); + float wt2 = st(1.0-f); + + vec3 color00 = color1*wt1 + color2*wt2; + vec3 scolor0 = scolor1*wt1 + scolor2*wt2; + + ctmp = color00/(wt1+wt2); + vec3 sctmp = max(scolor0/(wt1+wt2), ctmp); + mcolor = sctmp; + + float wf1, wf2; + + vec3 cref1 = mix(sctmp, scolor1, beam_size); float creff1 = max(max(cref1.r,cref1.g),cref1.b); + vec3 cref2 = mix(sctmp, scolor2, beam_size); float creff2 = max(max(cref2.r,cref2.g),cref2.b); + + float f1 = f; + float f2 = 1.0-f; + + if (gsl < 0.5) { wf1 = sw0(f1,creff1,shape1); wf2 = sw0(f2,creff2,shape2);} else + if (gsl == 1.0) { wf1 = sw1(f1,creff1,shape1); wf2 = sw1(f2,creff2,shape2);} else + { wf1 = sw2(f1,creff1,shape1); wf2 = sw2(f2,creff2,shape2);} + + if ((wf1 + wf2) > 1.0) { float wtmp = 1.0/(wf1+wf2); wf1*=wtmp; wf2*=wtmp; } + + // Scanline saturation application + + vec3 w1 = vec3(wf1); vec3 w2 = vec3(wf2); + w3 = wf1+wf2; + + cref1 = color1 / (max(max(color1.r,color1.g),color1.b) + 0.00001); + cref2 = color2 / (max(max(color2.r,color2.g),color2.b) + 0.00001); + + w1 = mix(w1*mix(one, cref1*cref1*cref1, scans), w1, wf1); + w2 = mix(w2*mix(one, cref2*cref2*cref2, scans), w2, wf2); + + vec3 cd1 = one; vec3 cd2 = one; float vm = sqrt(abs(vertmask)); + + float v_high1 = 1.0 + 0.3*vm; + float v_high2 = 1.0 + 0.6*vm; + float v_low = 1.0 - vm; + + float ds1 = min(max(1.0-w3*w3, 2.5*f1), 1.0); + float ds2 = min(max(1.0-w3*w3, 2.5*f2), 1.0); + + if (vertmask < 0.0) + { + cd1 = mix(one, vec3(v_high2, v_low, v_low), ds1); + cd2 = mix(one, vec3(v_low, v_high1, v_high1), ds2); + } + else + { + cd1 = mix(one, vec3(v_high1, v_low, v_high1), ds1); + cd2 = mix(one, vec3(v_low, v_high2, v_low), ds2); + } + + color = gc(color1)*w1*cd1 + gc(color2)*w2*cd2; + color = min(color, 1.0); +} + + if (interb) + { + color = gc(0.5*(color1+color2)); + mcolor = clamp(mix(color1, scolor1, 1.25), 0.0, 1.0); + } + + float mx = max(max(mcolor.r,mcolor.g),mcolor.b); + mx = pow(mx, 1.20/gamma_in); + + // Apply Mask + + vec3 orig1 = color; + vec3 cmask = one; + + float smask = SlotMask(gl_FragCoord.xy * 1.000001, mx); + cmask*= Mask(gl_FragCoord.xy * 1.000001, mx); + + color = color*cmask; + color = min(color,1.0); + color = color*smask; + + cmask = min(cmask*smask, 1.0); + + if (interb) ctmp = color; + float colmx = pow( max( max(ctmp.r, ctmp.g), ctmp.b), 1.40/gamma_out); + float bb = mix(brightboost, brightboost1, colmx); + if (interb) bb = (abs(intera-0.5)<0.1) ? pow(0.80*bb, 0.65) : pow(bb, 0.60); + color*=bb; + + vec3 Glow = calculate_bloom (pos, xx, 0.75*yy); + float maxb = max(max(Glow.r, Glow.g),Glow.b); + Glow = pow(Glow, 1.4.xxx); + vec3 Bloom = Glow; + + vec3 Bloom1 = min(Bloom*(orig1+color), max(0.5*(colmx + orig1 - color),0.0)); + color = color + bloom*Bloom1; + + color = min(color, mix(one, cmask, mclip)); + if (!interb) color = declip(color, pow(w3,0.60)); + + if (halation > 0.025) { + Bloom = mix(0.5*(Bloom + Bloom*Bloom), Bloom*Bloom, colmx); + color = color + 0.75*(0.75+maxb)*Bloom*(0.4+sqrt(colmx))*mix(1.0,w3,0.5*colmx)*mix(one,cmask,0.35 + 0.4*maxb)*halation; } + + Glow = mix(Glow, 0.25*color, 0.7*colmx); + color = color + 0.5*glow*Glow; + + color = pow(color, vec3(1.0/gamma_out)); + color = min(color, 1.0); + + result = vec4(color*corner0, corner0); +} + FragColor = result; +} \ No newline at end of file diff --git a/crt/shaders/guest/fast/deconvergence-f.slang b/crt/shaders/guest/fast/deconvergence-f.slang new file mode 100644 index 0000000..dac9614 --- /dev/null +++ b/crt/shaders/guest/fast/deconvergence-f.slang @@ -0,0 +1,176 @@ +#version 450 + +/* + CRT - Guest - Advanced - Deconvergence pass (NTSC) + noise + + Copyright (C) 2021 guest(r) - guest.r@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 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +layout(push_constant) uniform Push +{ + vec4 OutputSize; + uint FrameCount; + float decons; + float addnoised; + float noiseresd; + float deconrr; + float deconrg; + float deconrb; + float deconrry; + float deconrgy; + float deconrby; + float dctypex; + float dctypey; +} params; + +#pragma parameter bogus_deconvergence11 "[ HORIZONTAL/VERTICAL DECONVERGENCE ]: " 0.0 0.0 1.0 1.0 + +#pragma parameter dctypex " Deconvergence type X : 0.0 - static, other - dynamic" 0.0 0.0 1.0 0.05 + +#pragma parameter dctypey " Deconvergence type Y : 0.0 - static, other - dynamic" 0.0 0.0 1.0 0.05 + +#pragma parameter deconrr " Horizontal Deconvergence Red Range" 0.0 -12.0 12.0 0.25 + +#pragma parameter deconrg " Horizontal Deconvergence Green Range" 0.0 -12.0 12.0 0.25 + +#pragma parameter deconrb " Horizontal Deconvergence Blue Range" 0.0 -12.0 12.0 0.25 + +#pragma parameter deconrry " Vertical Deconvergence Red Range" 0.0 -12.0 12.0 0.25 + +#pragma parameter deconrgy " Vertical Deconvergence Green Range" 0.0 -12.0 12.0 0.25 + +#pragma parameter deconrby " Vertical Deconvergence Blue Range" 0.0 -12.0 12.0 0.25 + +#pragma parameter decons " Deconvergence Strength (and Type)" 0.5 -4.0 4.0 0.10 +#define decons params.decons // Horizontal deconvergence colors strength + +#pragma parameter addnoised " Add Noise" 0.0 -1.0 1.0 0.02 +#define addnoised params.addnoised // add noise + +#pragma parameter noiseresd " Noise Resolution" 2.0 0.0 10.0 1.0 +#define noiseresd params.noiseresd // add noise + + +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 Source; + +#define COMPAT_TEXTURE(c,d) texture(c,d) + +vec3 plant (vec3 tar, float r) +{ + float t = max(max(tar.r,tar.g),tar.b) + 0.00001; + return tar * r / t; +} + +// noise function: +// Dedicated to the public domain. +// If you want a real license, you may consider this MIT/BSD/CC0/WTFPL-licensed (take your pick). +// Adapted from ChuckNorris - shadertoy: https://www.shadertoy.com/view/XtK3Dz + +vec3 noise(vec3 v){ + if (addnoised < 0.0) v.z = -addnoised; else v.z = v.z/6000.0; + // ensure reasonable range + v = fract(v) + fract(v*1e4) + fract(v*1e-4); + // seed + v += vec3(0.12345, 0.6789, 0.314159); + // more iterations => more random + v = fract(v*dot(v, v)*123.456); + v = fract(v*dot(v, v)*123.456); + v = fract(v*dot(v, v)*123.456); + return v; +} + +void main() +{ + + vec3 color = COMPAT_TEXTURE(Source, vTexCoord).rgb; + vec3 result = color; + + if ((abs(params.deconrr) + abs(params.deconrg) + abs(params.deconrb) + abs(params.deconrry) + abs(params.deconrgy) + abs(params.deconrby)) > 0.20) + { + float stepx = params.OutputSize.z; + float stepy = params.OutputSize.w; + + vec2 dx = vec2(stepx, 0.0); + vec2 dy = vec2(0.0, stepy); + + float ds = decons; + + float posx = 2.0*vTexCoord.x - 1.0; + float posy = 2.0*vTexCoord.y - 1.0; + + if (params.dctypex > 0.025) + { + posx = sign(posx)*pow(abs(posx), 1.05-params.dctypex); + dx = posx * dx; + } + + if (params.dctypey > 0.025) + { + + posy = sign(posy)*pow(abs(posy), 1.05-params.dctypey); + dy = posy * dy; + } + + if (params.dctypex > 0.025 || params.dctypey > 0.025) ds *= sqrt(posx*posx*sign(params.dctypex) + posy*posy*sign(params.dctypey)); + + vec2 rc = params.deconrr * dx + params.deconrry*dy; + vec2 gc = params.deconrg * dx + params.deconrgy*dy; + vec2 bc = params.deconrb * dx + params.deconrby*dy; + + float r = COMPAT_TEXTURE(Source, vTexCoord + rc ).r; + float g = COMPAT_TEXTURE(Source, vTexCoord + gc ).g; + float b = COMPAT_TEXTURE(Source, vTexCoord + bc ).b; + + result = vec3(r,g,b); + + vec3 dcolor = max(max(COMPAT_TEXTURE(Source, vTexCoord + dx).rgb, COMPAT_TEXTURE(Source, vTexCoord - dx).rgb), color); + + float mc = max(max(dcolor.r, dcolor.g), dcolor.b); + if (decons < 0.0) mc = 0.9; + float dclamp = min(2.0-0.40*abs(ds),1.0); + + result = clamp(mix(color, sqrt(mix(result*result, color*result, sqrt(mc))), abs(ds)), dclamp*min(result,color), min(1.0/dclamp*max(result, color),1.0)); + } + + float rc = 0.6*sqrt(max(max(result.r, result.g), result.b))+0.4; + + if (abs(addnoised) > 0.01) result = mix(result, noise(vec3(floor(params.OutputSize.xy * vTexCoord / noiseresd), float(params.FrameCount))), 0.25*abs(addnoised) * rc); + + float corner = COMPAT_TEXTURE(Source, vTexCoord).a; + + FragColor = vec4(result*corner, 1.0); +} \ No newline at end of file diff --git a/crt/shaders/guest/fast/linearize.slang b/crt/shaders/guest/fast/linearize.slang new file mode 100644 index 0000000..2a21a03 --- /dev/null +++ b/crt/shaders/guest/fast/linearize.slang @@ -0,0 +1,176 @@ +#version 450 + +/* + Interlacing + + Copyright (C) 2020 guest(r) - guest.r@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 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +layout(push_constant) uniform Push +{ + vec4 OriginalSize; + vec4 OutputSize; + uint FrameCount; + float GAMMA_INPUT; + float inter; + float interm; + float iscan; + float intres; + float iscans; + float downsample_levelx; + float downsample_levely; + float prescalex; +} params; + + +layout(std140, set = 0, binding = 0) uniform UBO +{ + vec4 SourceSize; + mat4 MVP; +} global; + + +#pragma parameter GAMMA_INPUT "Gamma Input" 2.4 1.0 5.0 0.05 +#define GAMMA_INPUT params.GAMMA_INPUT + +#pragma parameter bogus_interlacing "[ INTERLACING OPTIONS ]: " 0.0 0.0 0.0 1.0 + +#pragma parameter inter " Interlace Trigger Resolution :" 400.0 0.0 800.0 25.0 +#define inter params.inter // interlace resolution + +#pragma parameter interm " Interlace Mode: OFF, Normal 1-3, Interpolation 4-5" 1.0 0.0 5.0 1.0 +#define interm params.interm // interlace mode + +#pragma parameter iscan " Interlacing Scanline Effect" 0.20 0.0 1.0 0.05 +#define iscan params.iscan // interlacing effect scanlining + +#pragma parameter intres " Internal Resolution Y: 224p/240p, 1.5...y-dowsample" 0.0 0.0 6.0 0.5 // Joint parameter with main pass, values must match + +#define intres params.intres // interlace resolution + +#pragma parameter downsample_levelx " Downsampling-X (High-res content, pre-scalers)" 0.0 0.0 2.0 0.05 +#define downsample_levelx params.downsample_levelx // downsample level + +#pragma parameter downsample_levely " Downsampling-Y (High-res content, pre-scalers)" 0.0 0.0 2.0 0.05 +#define downsample_levely params.downsample_levely // downsample level + +#pragma parameter prescalex " Prescale-X Factor (for xBR...pre-shader)" 1.0 1.0 4.0 1.0 // Joint parameter with main pass, values must match +#define prescalex params.prescalex // prescale-x factor + +#pragma parameter iscans " Interlacing (Scanline) Saturation" 0.40 0.0 1.0 0.05 +#define iscans params.iscans // interlace saturation + +#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 * 1.000001; +} + +#pragma stage fragment +layout(location = 0) in vec2 vTexCoord; +layout(location = 0) out vec4 FragColor; +layout(set = 0, binding = 2) uniform sampler2D PrePass; + +#define COMPAT_TEXTURE(c,d) texture(c,d) + + +vec3 plant (vec3 tar, float r) +{ + float t = max(max(tar.r,tar.g),tar.b) + 0.00001; + return tar * r / t; +} + + +vec3 fetch_pixel(vec2 coord) +{ + vec2 dx = vec2(global.SourceSize.z, 0.0) * downsample_levelx; + vec2 dy = vec2(0.0, global.SourceSize.w) * downsample_levely; + vec2 d1 = dx + dy; + vec2 d2 = dx - dy; + + float sum = 15.0; + vec3 result = 3.0*COMPAT_TEXTURE(PrePass, coord ).rgb + + 2.0*COMPAT_TEXTURE(PrePass, coord + dx).rgb + + 2.0*COMPAT_TEXTURE(PrePass, coord - dx).rgb + + 2.0*COMPAT_TEXTURE(PrePass, coord + dy).rgb + + 2.0*COMPAT_TEXTURE(PrePass, coord - dy).rgb + + COMPAT_TEXTURE(PrePass, coord + d1).rgb + + COMPAT_TEXTURE(PrePass, coord - d1).rgb + + COMPAT_TEXTURE(PrePass, coord + d2).rgb + + COMPAT_TEXTURE(PrePass, coord - d2).rgb; + + return result/sum; +} + + +void main() +{ + vec3 c1 = COMPAT_TEXTURE(PrePass, vTexCoord).rgb; + vec3 c2 = COMPAT_TEXTURE(PrePass, vTexCoord + vec2(0.0, params.OriginalSize.w)).rgb; + + if ((downsample_levelx + downsample_levely) > 0.025) + { + c1 = fetch_pixel(vTexCoord); + c2 = fetch_pixel(vTexCoord + vec2(0.0, params.OriginalSize.w)); + } + + vec3 c = c1; + + float intera = 1.0; + float gamma_in = clamp(GAMMA_INPUT, 1.0, 5.0); + + float m1 = max(max(c1.r,c1.g),c1.b); + float m2 = max(max(c2.r,c2.g),c2.b); + vec3 df = abs(c1-c2); + + float d = max(max(df.r,df.g),df.b); + if (interm == 2.0) d = mix(0.1*d,10.0*d, step(m1/(m2+0.0001),m2/(m1+0.0001))); + + float r = m1; + + float yres_div = 1.0; if (intres > 1.25) yres_div = intres; + + if (inter <= params.OriginalSize.y/yres_div && interm > 0.5 && intres != 1.0 && intres != 0.5) + { + intera = 0.5; + float line_no = clamp(floor(mod(params.OriginalSize.y*vTexCoord.y, 2.0)), 0.0, 1.0); + float frame_no = clamp(floor(mod(float(params.FrameCount),2.0)), 0.0, 1.0); + float ii = abs(line_no-frame_no); + + if (interm < 3.5) + { + c2 = plant(mix(c2, c2*c2, iscans), max(max(c2.r,c2.g),c2.b)); + r = clamp(max(m1*ii, (1.0-iscan)*min(m1,m2)), 0.0, 1.0); + c = plant( mix(mix(c1,c2, min(mix(m1, 1.0-m2, min(m1,1.0-m1))/(d+0.00001),1.0)), c1, ii), r); + if (interm == 3.0) c = (1.0-0.5*iscan)*mix(c2, c1, ii); + intera = 0.0; + } + if (interm == 4.0) c = plant(mix(c, c*c, 0.5*iscans), max(max(c.r,c.g),c.b)); + if (interm == 5.0) { c = mix(c2, c1, 0.5); c = plant(mix(c, c*c, 0.5*iscans), max(max(c.r,c.g),c.b));} + } + c = pow(c, vec3(gamma_in)); + + if (vTexCoord.x > 0.5) gamma_in = intera; else gamma_in = 1.0/gamma_in; + + FragColor = vec4(c, gamma_in); +} \ No newline at end of file diff --git a/crt/shaders/guest/fast/linearizef.slang b/crt/shaders/guest/fast/linearizef.slang new file mode 100644 index 0000000..bba8cce --- /dev/null +++ b/crt/shaders/guest/fast/linearizef.slang @@ -0,0 +1,129 @@ +#version 450 + +/* + Interlacing + + Copyright (C) 2020 guest(r) - guest.r@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 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + + +layout(push_constant) uniform Push +{ + vec4 OriginalSize; + vec4 OutputSize; + vec4 SourceSize; + uint FrameCount; + float GAMMA_INPUT; + float inter; + float interm; + float inters; + float iscan; +} params; + + +layout(std140, set = 0, binding = 0) uniform UBO +{ + mat4 MVP; +} global; + + +#pragma parameter GAMMA_INPUT "Gamma Input" 2.4 1.0 5.0 0.05 +#define GAMMA_INPUT params.GAMMA_INPUT + +#pragma parameter bogus_interlacing "[ INTERLACING OPTIONS ]: " 0.0 0.0 0.0 1.0 + +#pragma parameter inter " Interlace Trigger Resolution :" 400.0 0.0 800.0 25.0 +#define inter params.inter // interlace resolution + +#pragma parameter interm " Interlace Mode: OFF, Normal 1-3, Interpolation 4-5" 1.0 0.0 5.0 1.0 +#define interm params.interm // interlace mode + +#pragma parameter inters " Interlacing Effect Smoothness" 0.0 0.0 0.5 0.05 // // Joint parameter with main pass, values must match +#define inters params.inters // interlacing effect smoothing + +#pragma parameter iscan " Interlacing Scanline Effect" 0.20 0.0 1.0 0.05 +#define iscan params.iscan // interlacing effect scanlining + + +#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 * 1.000001; +} + +#pragma stage fragment +layout(location = 0) in vec2 vTexCoord; +layout(location = 0) out vec4 FragColor; +layout(set = 0, binding = 2) uniform sampler2D Source; + +#define COMPAT_TEXTURE(c,d) texture(c,d) +#define SourceSize params.SourceSize + + +vec3 plant (vec3 tar, float r) +{ + float t = max(max(tar.r,tar.g),tar.b) + 0.00001; + return tar * r / t; +} + + +void main() +{ + vec3 c1 = COMPAT_TEXTURE(Source, vTexCoord).rgb; + vec3 c2 = COMPAT_TEXTURE(Source, vTexCoord + vec2(0.0, 1.0/params.OriginalSize.y)).rgb; + vec3 c = c1; + + float intera = 1.0; + float gamma_in = clamp(GAMMA_INPUT, 1.0, 5.0); + + float m1 = max(max(c1.r,c1.g),c1.b); + float m2 = max(max(c2.r,c2.g),c2.b); + vec3 df = abs(c1-c2); + + float d = max(max(df.r,df.g),df.b); + if (interm == 2.0) d = mix(0.1*d,10.0*d, step(m1/(m2+0.0001),m2/(m1+0.0001))); + + float r = m1; + + if (inter <= params.OriginalSize.y && interm > 0.5) + { + intera = 0.5; + float line_no = clamp(floor(mod(params.OriginalSize.y*vTexCoord.y, 2.0)), 0.0, 1.0); + float frame_no = clamp(floor(mod(float(params.FrameCount),2.0)), 0.0, 1.0); + float ii = abs(line_no-frame_no); + + if (interm < 3.5) + { + r = clamp(max(m1*ii, (1.0-iscan)*min(m1,m2)), 0.0, 1.0); + c = plant( mix(mix(c1,c2, min(mix(m1, 1.0-m2, min(m1,1.0-m1))/(d+0.00001),1.0)), c1, ii), r); + if (interm == 3.0) c = (1.0-0.5*iscan)*mix(c2, c1, ii); + intera = 0.0; + } + if (interm == 5.0) { c = mix(c2, c1, 0.5); } + } + c = pow(c, vec3(gamma_in)); + + if (vTexCoord.x > 0.5) gamma_in = intera; else gamma_in = 1.0/gamma_in; + + FragColor = vec4(c, gamma_in); +} \ No newline at end of file diff --git a/crt/shaders/guest/fast/pre-shaders.slang b/crt/shaders/guest/fast/pre-shaders.slang new file mode 100644 index 0000000..e68dd01 --- /dev/null +++ b/crt/shaders/guest/fast/pre-shaders.slang @@ -0,0 +1,349 @@ +#version 450 + +/* + CRT Advanced color altering + + Copyright (C) 2019-2021 guest(r) and Dr. Venom + + 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 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +layout(push_constant) uniform Push +{ + vec4 SourceSize; + vec4 OriginalSize; + vec4 OutputSize; + uint FrameCount; + float TNTC; + float LS; + float LUTLOW, LUTBR; + float CP, CS; + float BP; + float WP; + float wp_saturation; + float vigstr; + float vigdef; + float sega_fix; + float pre_bb; +} params; + +#pragma parameter bogus_color "[ COLOR TWEAKS ]:" 0.0 0.0 1.0 1.0 + +#pragma parameter CS " Display Gamut: sRGB, Modern, DCI, Adobe, Rec.2020" 0.0 0.0 4.0 1.0 + +#pragma parameter CP " CRT Profile: EBU | P22 | SMPTE-C | Philips | Trin." 0.0 -1.0 5.0 1.0 + +#define CP params.CP +#define CS params.CS + +#pragma parameter TNTC " LUT Colors: Trin. | invTrin. | Nec Mult. | NTSC" 0.0 0.0 4.0 1.0 +#define TNTC params.TNTC + +#pragma parameter LS " LUT Size" 32.0 32.0 64.0 32.0 +#define LS params.LS + +#define LUTLOW 5.0 // "Fix LUT Dark - Range" from 0.0 to 50.0 - RGB singletons + +#define LUTBR 1.0 // "Fix LUT Brightness" from 0.0 to 1.0 + +#pragma parameter WP " Color Temperature %" 0.0 -100.0 100.0 5.0 + +#pragma parameter wp_saturation " Saturation Adjustment" 1.0 0.0 2.0 0.05 + +#pragma parameter pre_bb " Brightness Adjustment" 1.0 0.0 2.0 0.01 + +#define WP params.WP +#define wp_saturation params.wp_saturation + +#pragma parameter sega_fix " Sega Brightness Fix" 0.0 0.0 1.0 1.0 + +#pragma parameter BP " Raise Black Level" 0.0 0.0 25.0 1.0 +#define BP params.BP + +#pragma parameter vigstr " Vignette Strength" 0.0 0.0 2.0 0.025 + +#pragma parameter vigdef " Vignette Definition" 7.0 0.4 15.0 0.2 + + +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 Source; +layout(set = 0, binding = 3) uniform sampler2D SamplerLUT1; +layout(set = 0, binding = 4) uniform sampler2D SamplerLUT2; +layout(set = 0, binding = 5) uniform sampler2D SamplerLUT3; +layout(set = 0, binding = 6) uniform sampler2D SamplerLUT4; + +#define COMPAT_TEXTURE(c,d) texture(c,d) + + +// Color profile matrices + +const mat3 Profile0 = +mat3( + 0.412391, 0.212639, 0.019331, + 0.357584, 0.715169, 0.119195, + 0.180481, 0.072192, 0.950532 +); + +const mat3 Profile1 = +mat3( + 0.430554, 0.222004, 0.020182, + 0.341550, 0.706655, 0.129553, + 0.178352, 0.071341, 0.939322 +); + +const mat3 Profile2 = +mat3( + 0.396686, 0.210299, 0.006131, + 0.372504, 0.713766, 0.115356, + 0.181266, 0.075936, 0.967571 +); + +const mat3 Profile3 = +mat3( + 0.393521, 0.212376, 0.018739, + 0.365258, 0.701060, 0.111934, + 0.191677, 0.086564, 0.958385 +); + +const mat3 Profile4 = +mat3( + 0.392258, 0.209410, 0.016061, + 0.351135, 0.725680, 0.093636, + 0.166603, 0.064910, 0.850324 +); + +const mat3 Profile5 = +mat3( + 0.377923, 0.195679, 0.010514, + 0.317366, 0.722319, 0.097826, + 0.207738, 0.082002, 1.076960 +); + +const mat3 ToSRGB = +mat3( + 3.240970, -0.969244, 0.055630, +-1.537383, 1.875968, -0.203977, +-0.498611, 0.041555, 1.056972 +); + +const mat3 ToModern = +mat3( + 2.791723, -0.894766, 0.041678, +-1.173165, 1.815586, -0.130886, +-0.440973, 0.032000, 1.002034 +); + +const mat3 ToDCI = +mat3( + 2.493497, -0.829489, 0.035846, +-0.931384, 1.762664, -0.076172, +-0.402711, 0.023625, 0.956885 +); + +const mat3 ToAdobe = +mat3( + 2.041588, -0.969244, 0.013444, +-0.565007, 1.875968, -0.11836, +-0.344731, 0.041555, 1.015175 +); + +const mat3 ToREC = +mat3( + 1.716651, -0.666684, 0.017640, +-0.355671, 1.616481, -0.042771, +-0.253366, 0.015769, 0.942103 +); + +// Color temperature matrices + +const mat3 D65_to_D55 = mat3 ( + 0.4850339153, 0.2500956126, 0.0227359648, + 0.3488957224, 0.6977914447, 0.1162985741, + 0.1302823568, 0.0521129427, 0.6861537456); + + +const mat3 D65_to_D93 = mat3 ( + 0.3683017655, 0.1899055978, 0.0172641453, + 0.3555467892, 0.7110935785, 0.1185155964, + 0.2475020592, 0.0990008237, 1.3035108450); + + +vec3 fix_lut(vec3 lutcolor, vec3 ref) +{ + float r = length(ref); + float l = length(lutcolor); + float m = max(max(ref.r,ref.g),ref.b); + ref = normalize(lutcolor + 0.0000001) * mix(r, l, pow(m,1.25)); + return mix(lutcolor, ref, LUTBR); +} + +vec2 ctransform (vec2 inputc) +{ + return vec2( inputc.x * sqrt(1.0 - 0.5*inputc.y*inputc.y), inputc.y * sqrt(1.0 - 0.5*inputc.x*inputc.x)); +} + +float vignette (vec2 coords) +{ + vec2 ccoords = ctransform(2.0*(coords-0.5)); + ccoords = ccoords * ccoords; + float vstr = sqrt(ccoords.x+ccoords.y); + vstr = pow(vstr, params.vigdef); + return max(mix(1.0, 1.0-vstr, params.vigstr), 0.0); +} + +void main() +{ + vec4 imgColor = COMPAT_TEXTURE(Source, vTexCoord.xy); + + float w = float ((imgColor.r + imgColor.g + imgColor.b) < 1.5/255.0); + float bp = w * BP/255.0; + + imgColor.rgb = imgColor.rgb * params.pre_bb; + + if (params.sega_fix > 0.5) imgColor.rgb = imgColor.rgb * (255.0 / 239.0); + + imgColor.rgb = min(imgColor.rgb, 1.0); + + vec3 color = imgColor.rgb; + + if (int(TNTC) == 0) + { + color.rgb = imgColor.rgb; + } + else + { + float lutlow = LUTLOW/255.0; float invLS = 1.0/LS; + vec3 lut_ref = imgColor.rgb + lutlow*(1.0 - pow(imgColor.rgb, 0.333.xxx)); + float lutb = lut_ref.b * (1.0-0.5*invLS); + lut_ref.rg = lut_ref.rg * (1.0-invLS) + 0.5*invLS; + float tile1 = ceil (lutb * (LS-1.0)); + float tile0 = max(tile1 - 1.0, 0.0); + float f = fract(lutb * (LS-1.0)); if (f == 0.0) f = 1.0; + vec2 coord0 = vec2(tile0 + lut_ref.r, lut_ref.g)*vec2(invLS, 1.0); + vec2 coord1 = vec2(tile1 + lut_ref.r, lut_ref.g)*vec2(invLS, 1.0); + vec4 color1, color2, res; + + if (int(TNTC) == 1) + { + color1 = COMPAT_TEXTURE(SamplerLUT1, coord0); + color2 = COMPAT_TEXTURE(SamplerLUT1, coord1); + res = mix(color1, color2, f); + } + else if (int(TNTC) == 2) + { + color1 = COMPAT_TEXTURE(SamplerLUT2, coord0); + color2 = COMPAT_TEXTURE(SamplerLUT2, coord1); + res = mix(color1, color2, f); + } + else if (int(TNTC) == 3) + { + color1 = COMPAT_TEXTURE(SamplerLUT3, coord0); + color2 = COMPAT_TEXTURE(SamplerLUT3, coord1); + res = mix(color1, color2, f); + } + else if (int(TNTC) == 4) + { + color1 = COMPAT_TEXTURE(SamplerLUT4, coord0); + color2 = COMPAT_TEXTURE(SamplerLUT4, coord1); + res = mix(color1, color2, f); + } + + if (imgColor.r == 0.0) res.r = 0.0; + if (imgColor.g == 0.0) res.g = 0.0; + if (imgColor.b == 0.0) res.b = 0.0; + + res.rgb = fix_lut (res.rgb, imgColor.rgb); + + color = mix(imgColor.rgb, res.rgb, min(TNTC,1.0)); + } + + vec3 c = clamp(color, 0.0, 1.0); + + float p; + mat3 m_out; + + if (CS == 0.0) { p = 2.2; m_out = ToSRGB; } else + if (CS == 1.0) { p = 2.2; m_out = ToModern; } else + if (CS == 2.0) { p = 2.6; m_out = ToDCI; } else + if (CS == 3.0) { p = 2.2; m_out = ToAdobe; } else + if (CS == 4.0) { p = 2.4; m_out = ToREC; } + + color = pow(c, vec3(p)); + + mat3 m_in = Profile0; + + if (CP == 0.0) { m_in = Profile0; } else + if (CP == 1.0) { m_in = Profile1; } else + if (CP == 2.0) { m_in = Profile2; } else + if (CP == 3.0) { m_in = Profile3; } else + if (CP == 4.0) { m_in = Profile4; } else + if (CP == 5.0) { m_in = Profile5; } + + color = m_in*color; + color = m_out*color; + + color = clamp(color, 0.0, 1.0); + + color = pow(color, vec3(1.0/p)); + + if (CP == -1.0) color = c; + + vec3 scolor1 = normalize(pow(color + 0.000000001, vec3(wp_saturation)))*length(color); + float luma = dot(color, vec3(0.2126, 0.7152, 0.0722)); + vec3 scolor2 = mix(vec3(luma), color, wp_saturation); + color = (wp_saturation > 1.0) ? scolor1 : scolor2; + + p = 2.2; + + color = pow(color, vec3(p)); + + color = clamp(color, 0.0, 1.0); + + vec3 warmer = D65_to_D55*color; + warmer = ToSRGB*warmer; + + vec3 cooler = D65_to_D93*color; + cooler = ToSRGB*cooler; + + float m = abs(WP)/100.0; + + vec3 comp = (WP < 0.0) ? cooler : warmer; + + color = mix(color, comp, m); + color = pow(max(color, 0.0), vec3(1.0/p)); + + color = color + bp; + + FragColor = vec4(color, vignette(vTexCoord.xy)); +} \ No newline at end of file diff --git a/crt/shaders/guest/fast/pre-shadersf.slang b/crt/shaders/guest/fast/pre-shadersf.slang new file mode 100644 index 0000000..bc2ebe7 --- /dev/null +++ b/crt/shaders/guest/fast/pre-shadersf.slang @@ -0,0 +1,265 @@ +#version 450 + +/* + CRT Advanced color altering + + Copyright (C) 2019-2021 guest(r) and Dr. Venom + + 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 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +layout(push_constant) uniform Push +{ + vec4 SourceSize; + vec4 OriginalSize; + vec4 OutputSize; + uint FrameCount; + float TNTC; + float LS; + float LUTLOW, LUTBR; + float WP; + float wp_saturation; + float BP; + float vigstr; + float vigdef; + float sega_fix; + float pre_bb; +} params; + +#pragma parameter bogus_color "[ COLOR TWEAKS ]:" 0.0 0.0 1.0 1.0 + +#pragma parameter TNTC " LUT Colors: Trin. | invTrin. | Nec Mult. | NTSC" 0.0 0.0 4.0 1.0 +#define TNTC params.TNTC + +#pragma parameter LS " LUT Size" 32.0 32.0 64.0 32.0 +#define LS params.LS + +#define LUTLOW 5.0 // "Fix LUT Dark - Range" from 0.0 to 50.0 - RGB singletons + +#define LUTBR 1.0 // "Fix LUT Brightness" from 0.0 to 1.0 + +#pragma parameter WP " Color Temperature %" 0.0 -100.0 100.0 5.0 +#pragma parameter wp_saturation " Saturation Adjustment" 1.0 0.0 2.0 0.05 +#pragma parameter pre_bb " Brightness Adjustment" 1.0 0.0 2.0 0.01 + +#pragma parameter sega_fix " Sega Brightness Fix" 0.0 0.0 1.0 1.0 + +#pragma parameter BP " Raise Black Level" 0.0 0.0 25.0 1.0 + +#pragma parameter vigstr " Vignette Strength" 0.0 0.0 2.0 0.025 + +#pragma parameter vigdef " Vignette Definition" 7.0 0.4 15.0 0.2 + + +#define WP params.WP +#define wp_saturation params.wp_saturation +#define BP params.BP + + +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; + +#define gl_FragCoord (vTexCoord * params.OutputSize.xy) + +void main() +{ + gl_Position = global.MVP * Position; + vTexCoord = TexCoord * 1.00001; +} + +#pragma stage fragment +layout(location = 0) in vec2 vTexCoord; +layout(location = 0) out vec4 FragColor; +layout(set = 0, binding = 2) uniform sampler2D Source; +layout(set = 0, binding = 3) uniform sampler2D SamplerLUT1; +layout(set = 0, binding = 4) uniform sampler2D SamplerLUT2; +layout(set = 0, binding = 5) uniform sampler2D SamplerLUT3; +layout(set = 0, binding = 6) uniform sampler2D SamplerLUT4; +layout(set = 0, binding = 7) uniform sampler2D OriginalHistory0; +layout(set = 0, binding = 8) uniform sampler2D OriginalHistory1; + +#define COMPAT_TEXTURE(c,d) texture(c,d) + + +// Color profile matrices + +const mat3 ToSRGB = +mat3( + 3.240970, -0.969244, 0.055630, +-1.537383, 1.875968, -0.203977, +-0.498611, 0.041555, 1.056972 +); + + +// Color temperature matrices + +const mat3 D65_to_D55 = mat3 ( + 0.4850339153, 0.2500956126, 0.0227359648, + 0.3488957224, 0.6977914447, 0.1162985741, + 0.1302823568, 0.0521129427, 0.6861537456); + + +const mat3 D65_to_D93 = mat3 ( + 0.3683017655, 0.1899055978, 0.0172641453, + 0.3555467892, 0.7110935785, 0.1185155964, + 0.2475020592, 0.0990008237, 1.3035108450); + + +vec3 fix_lut(vec3 lutcolor, vec3 ref) +{ + float r = length(ref); + float l = length(lutcolor); + float m = max(max(ref.r,ref.g),ref.b); + ref = normalize(lutcolor + 0.0000001) * mix(r, l, pow(m,1.25)); + return mix(lutcolor, ref, LUTBR); +} + +vec2 ctransform (vec2 inputc) +{ + return vec2( inputc.x * sqrt(1.0 - 0.5*inputc.y*inputc.y), inputc.y * sqrt(1.0 - 0.5*inputc.x*inputc.x)); +} + +float vignette (vec2 coords) +{ + vec2 ccoords = ctransform(2.0*(coords-0.5)); + ccoords = ccoords * ccoords; + float vstr = sqrt(ccoords.x+ccoords.y); + vstr = pow(vstr, params.vigdef); + return max(mix(1.0, 1.0-vstr, params.vigstr), 0.0); +} + + +void main() +{ + vec4 imgColor = COMPAT_TEXTURE(Source, vTexCoord.xy); + + float w = float ((imgColor.r + imgColor.g + imgColor.b) < 1.5/255.0); + + float bp = w * BP/255.0; + + imgColor.rgb = imgColor.rgb * params.pre_bb; + + if (params.sega_fix > 0.5) imgColor.rgb = imgColor.rgb * (255.0 / 239.0); + + imgColor.rgb = min(imgColor.rgb, 1.0); + + vec3 color = imgColor.rgb; + + if (int(TNTC) == 0) + { + color.rgb = imgColor.rgb; + } + else + { + float lutlow = LUTLOW/255.0; float invLS = 1.0/LS; + vec3 lut_ref = imgColor.rgb + lutlow*(1.0 - pow(imgColor.rgb, 0.333.xxx)); + float lutb = lut_ref.b * (1.0-0.5*invLS); + lut_ref.rg = lut_ref.rg * (1.0-invLS) + 0.5*invLS; + float tile1 = ceil (lutb * (LS-1.0)); + float tile0 = max(tile1 - 1.0, 0.0); + float f = fract(lutb * (LS-1.0)); if (f == 0.0) f = 1.0; + vec2 coord0 = vec2(tile0 + lut_ref.r, lut_ref.g)*vec2(invLS, 1.0); + vec2 coord1 = vec2(tile1 + lut_ref.r, lut_ref.g)*vec2(invLS, 1.0); + vec4 color1, color2, res; + + if (int(TNTC) == 1) + { + color1 = COMPAT_TEXTURE(SamplerLUT1, coord0); + color2 = COMPAT_TEXTURE(SamplerLUT1, coord1); + res = mix(color1, color2, f); + } + else if (int(TNTC) == 2) + { + color1 = COMPAT_TEXTURE(SamplerLUT2, coord0); + color2 = COMPAT_TEXTURE(SamplerLUT2, coord1); + res = mix(color1, color2, f); + } + else if (int(TNTC) == 3) + { + color1 = COMPAT_TEXTURE(SamplerLUT3, coord0); + color2 = COMPAT_TEXTURE(SamplerLUT3, coord1); + res = mix(color1, color2, f); + } + else if (int(TNTC) == 4) + { + color1 = COMPAT_TEXTURE(SamplerLUT4, coord0); + color2 = COMPAT_TEXTURE(SamplerLUT4, coord1); + res = mix(color1, color2, f); + } + + res.rgb = fix_lut (res.rgb, imgColor.rgb); + + color = mix(imgColor.rgb, res.rgb, min(TNTC,1.0)); + } + + vec3 c = clamp(color, 0.0, 1.0); + + vec3 scolor1 = normalize(pow(color + 0.000000001, vec3(wp_saturation)))*length(color); + float luma = dot(color, vec3(0.2126, 0.7152, 0.0722)); + vec3 scolor2 = mix(vec3(luma), color, wp_saturation); + color = (wp_saturation > 1.0) ? scolor1 : scolor2; + + float p = 2.2; + + color = pow(color, vec3(p)); + + color = clamp(color, 0.0, 1.0); + + vec3 warmer = D65_to_D55*color; + warmer = ToSRGB*warmer; + + vec3 cooler = D65_to_D93*color; + cooler = ToSRGB*cooler; + + float m = abs(WP)/100.0; + + vec3 comp = (WP < 0.0) ? cooler : warmer; + + color = mix(color, comp, m); + color = pow(max(color, 0.0), vec3(1.0/p)); + + color = color + bp; + + vec2 dx = vec2(params.SourceSize.z, 0.0); + vec2 x2 = 2.5 *dx; + vec2 xx = dx + dx; + vec2 x3 = x2 + dx; + vec2 pC4 = vTexCoord; + + vec3 ol3 = COMPAT_TEXTURE(OriginalHistory1, pC4 -x2).rgb; + vec3 ol2 = COMPAT_TEXTURE(OriginalHistory1, pC4 -dx).rgb; + vec3 ol1 = COMPAT_TEXTURE(OriginalHistory1, pC4 ).rgb; + vec3 or1 = COMPAT_TEXTURE(OriginalHistory1, pC4 +dx).rgb; + vec3 or2 = COMPAT_TEXTURE(OriginalHistory1, pC4 +xx).rgb; + + vec3 cl3 = COMPAT_TEXTURE(OriginalHistory0, pC4 -x2).rgb; + vec3 cl2 = COMPAT_TEXTURE(OriginalHistory0, pC4 -dx).rgb; + vec3 cl1 = COMPAT_TEXTURE(OriginalHistory0, pC4 ).rgb; + vec3 cr1 = COMPAT_TEXTURE(OriginalHistory0, pC4 +dx).rgb; + vec3 cr2 = COMPAT_TEXTURE(OriginalHistory0, pC4 +xx).rgb; + + vec3 res = abs(ol3-cl3) + abs(ol2-cl2) + abs(ol1-cl1) + abs(or1-cr1) + abs(or2-cr2); + float res1 = 1.0; + if ((res.r+res.g+res.b) == 0.0) res1 = 0.0; + FragColor = vec4(color * vignette(vTexCoord.xy), res1); +} \ No newline at end of file diff --git a/ntsc/shaders/ntsc-adaptive/ntsc-pass1.slang b/ntsc/shaders/ntsc-adaptive/ntsc-pass1.slang index 4f08010..4a52a2a 100644 --- a/ntsc/shaders/ntsc-adaptive/ntsc-pass1.slang +++ b/ntsc/shaders/ntsc-adaptive/ntsc-pass1.slang @@ -3,6 +3,7 @@ // NTSC-Adaptive // based on Themaister's NTSC shader + layout(std140, set = 0, binding = 0) uniform UBO { mat4 MVP; @@ -10,14 +11,17 @@ layout(std140, set = 0, binding = 0) uniform UBO vec4 OriginalSize; vec4 SourceSize; uint FrameCount; - float quality, ntsc_sat, cust_fringing, cust_artifacting, ntsc_bright; + float quality, ntsc_sat, cust_fringing, cust_artifacting, ntsc_bright, ntsc_scale, ntsc_fields, ntsc_phase; } global; -#pragma parameter quality "Presets (Svideo=0 Composite=1 RF=2 Custom=-1)" 0.0 -1.0 2.0 1.0 -#pragma parameter ntsc_sat "Color Saturation" 1.0 0.0 2.0 0.01 -#pragma parameter ntsc_bright "Brightness" 1.0 0.0 1.5 0.01 -#pragma parameter cust_fringing "Custom Fringing Value" 0.0 0.0 5.0 0.1 -#pragma parameter cust_artifacting "Custom Artifacting Value" 0.0 0.0 5.0 0.1 +#pragma parameter quality "NTSC Preset (Svideo=0 Composite=1 RF=2 Custom=-1)" 1.0 -1.0 2.0 1.0 +#pragma parameter ntsc_fields "NTSC Merge Fields" 0.0 0.0 1.0 1.0 +#pragma parameter ntsc_phase "NTSC Phase: Auto | 2 phase | 3 phase" 1.0 1.0 3.0 1.0 +#pragma parameter ntsc_scale "NTSC Resolution Scaling" 1.0 0.20 3.0 0.05 +#pragma parameter ntsc_sat "NTSC Color Saturation" 1.0 0.0 2.0 0.01 +#pragma parameter ntsc_bright "NTSC Brightness" 1.0 0.0 1.5 0.01 +#pragma parameter cust_fringing "NTSC Custom Fringing Value" 0.0 0.0 5.0 0.1 +#pragma parameter cust_artifacting "NTSC Custom Artifacting Value" 0.0 0.0 5.0 0.1 #define PI 3.14159265 @@ -32,18 +36,29 @@ layout(location = 4) out float SATURATION; layout(location = 5) out float FRINGING; layout(location = 6) out float ARTIFACTING; layout(location = 7) out float CHROMA_MOD_FREQ; +layout(location = 8) out float MERGE; void main() { + float res = global.ntsc_scale; + float OriginalSize = global.OriginalSize.x; gl_Position = global.MVP * Position; vTexCoord = TexCoord; - pix_no = TexCoord * global.SourceSize.xy * (global.OutputSize.xy / global.SourceSize.xy); - phase = (global.OriginalSize.x > 300.0) ? 2.0 : 3.0; + if (res < 1.0) pix_no = TexCoord * global.SourceSize.xy * (res * global.OutputSize.xy / global.SourceSize.xy); else + pix_no = TexCoord * global.SourceSize.xy * ( global.OutputSize.xy / global.SourceSize.xy); + phase = (global.ntsc_phase < 1.5) ? ((OriginalSize > 300.0) ? 2.0 : 3.0) : ((global.ntsc_phase > 2.5) ? 3.0 : 2.0); + + res = max(res, 1.0); CHROMA_MOD_FREQ = (phase < 2.5) ? (4.0 * PI / 15.0) : (PI / 3.0); - ARTIFACTING = (global.quality > -0.5) ? global.quality : global.cust_artifacting; + ARTIFACTING = (global.quality > -0.5) ? global.quality * 0.5*(res+1.0) : global.cust_artifacting; FRINGING = (global.quality > -0.5) ? global.quality : global.cust_fringing; SATURATION = global.ntsc_sat; - BRIGHTNESS = global.ntsc_bright; + BRIGHTNESS = global.ntsc_bright; + pix_no.x = pix_no.x * res; + + MERGE = (int(global.quality) == 2 || phase < 2.5) ? 0.0 : 1.0; + MERGE = (int(global.quality) == -1) ? global.ntsc_fields : MERGE; + } #pragma stage fragment @@ -55,6 +70,7 @@ layout(location = 4) in float SATURATION; layout(location = 5) in float FRINGING; layout(location = 6) in float ARTIFACTING; layout(location = 7) in float CHROMA_MOD_FREQ; +layout(location = 8) in float MERGE; layout(location = 0) out vec4 FragColor; layout(set = 0, binding = 2) uniform sampler2D Source; @@ -85,9 +101,23 @@ void main() { vec3 col = texture(Source, vTexCoord).rgb; vec3 yiq = rgb2yiq(col); + vec3 yiq2 = yiq; - float chroma_phase = (phase < 2.5) ? PI * (mod(pix_no.y, 2.0) + mod(global.FrameCount, 2.)) : 0.6667 * PI * (mod(pix_no.y, 3.0) + mod(global.FrameCount, 2.)); + float mod1 = 2.0; + float mod2 = 3.0; +if (MERGE > 0.5) +{ + float chroma_phase2 = (phase < 2.5) ? PI * (mod(pix_no.y, mod1) + mod(global.FrameCount+1, 2.)) : 0.6667 * PI * (mod(pix_no.y, mod2) + mod(global.FrameCount+1, 2.)); + float mod_phase2 = chroma_phase2 + pix_no.x * CHROMA_MOD_FREQ; + float i_mod2 = cos(mod_phase2); + float q_mod2 = sin(mod_phase2); + yiq2.yz *= vec2(i_mod2, q_mod2); // Modulate. + yiq2 *= mix_mat; // Cross-talk. + yiq2.yz *= vec2(i_mod2, q_mod2); // Demodulate. +} + + float chroma_phase = (phase < 2.5) ? PI * (mod(pix_no.y, mod1) + mod(global.FrameCount, 2.)) : 0.6667 * PI * (mod(pix_no.y, mod2) + mod(global.FrameCount, 2.)); float mod_phase = chroma_phase + pix_no.x * CHROMA_MOD_FREQ; float i_mod = cos(mod_phase); @@ -96,5 +126,8 @@ void main() yiq.yz *= vec2(i_mod, q_mod); // Modulate. yiq *= mix_mat; // Cross-talk. yiq.yz *= vec2(i_mod, q_mod); // Demodulate. + + yiq = (MERGE < 0.5) ? yiq : 0.5*(yiq+yiq2); + FragColor = vec4(yiq, 1.0); } diff --git a/ntsc/shaders/ntsc-adaptive/ntsc-pass2.slang b/ntsc/shaders/ntsc-adaptive/ntsc-pass2.slang index 4a58cad..97b1dcd 100644 --- a/ntsc/shaders/ntsc-adaptive/ntsc-pass2.slang +++ b/ntsc/shaders/ntsc-adaptive/ntsc-pass2.slang @@ -3,6 +3,7 @@ // NTSC-Adaptive // based on Themaister's NTSC shader + layout(std140, set = 0, binding = 0) uniform UBO { mat4 MVP; @@ -10,9 +11,14 @@ layout(std140, set = 0, binding = 0) uniform UBO vec4 OriginalSize; vec4 SourceSize; float linearize; + float ntsc_scale; + float ntsc_phase; + float auto_res; } global; -#pragma parameter linearize "Linearize Output Gamma" 0.0 0.0 1.0 1.0 +#pragma parameter ntsc_scale "NTSC Resolution Scaling" 1.0 0.20 3.0 0.05 +#pragma parameter ntsc_phase "NTSC Phase: Auto | 2 phase | 3 phase" 1.0 1.0 3.0 1.0 +#pragma parameter linearize "NTSC Linearize Output Gamma" 0.0 0.0 1.0 1.0 #pragma stage vertex layout(location = 0) in vec4 Position; @@ -184,9 +190,11 @@ const float chroma_filter_3_phase[25] = float[25]( void main() { - float phase = (global.OriginalSize.x > 300.0) ? 2.0 : 3.0; - float one_x = global.SourceSize.z; + float res = global.ntsc_scale; + float OriginalSize = global.OriginalSize.x; + float one_x = global.SourceSize.z / res; vec3 signal = vec3(0.0); + float phase = (global.ntsc_phase < 1.5) ? ((OriginalSize > 300.0) ? 2.0 : 3.0) : ((global.ntsc_phase > 2.5) ? 3.0 : 2.0); if(phase < 2.5) { @@ -276,4 +284,4 @@ void main() FragColor = vec4(rgb, 1.0); if(global.linearize < 0.5) return; else FragColor = pow(FragColor, vec4(2.2)); -} +} diff --git a/presets/crt-guest-advanced-ntsc.slangp b/presets/crt-guest-advanced-ntsc.slangp new file mode 100644 index 0000000..a112977 --- /dev/null +++ b/presets/crt-guest-advanced-ntsc.slangp @@ -0,0 +1,134 @@ +shaders = 17 + +shader0 = ../stock.slang +filter_linear0 = false +scale_type0 = source +scale0 = 1.0 + +shader1 = ../stock.slang +filter_linear1 = false +scale_type1 = source +scale1 = 1.0 +alias1 = StockPass + +shader2 = ../crt/shaders/guest/advanced/afterglow0.slang +filter_linear2 = true +scale_type2 = source +scale2 = 1.0 +alias2 = AfterglowPass + +shader3 = ../crt/shaders/guest/advanced/pre-shaders-afterglow.slang +filter_linear3 = true +scale_type3 = source +scale3 = 1.0 +alias3 = PrePass0 + +textures = "SamplerLUT1;SamplerLUT2;SamplerLUT3;SamplerLUT4" +SamplerLUT1 = ../crt/shaders/guest/advanced/lut/trinitron-lut.png +SamplerLUT1_linear = true +SamplerLUT2 = ../crt/shaders/guest/advanced/lut/inv-trinitron-lut.png +SamplerLUT2_linear = true +SamplerLUT3 = ../crt/shaders/guest/advanced/lut/nec-lut.png +SamplerLUT3_linear = true +SamplerLUT4 = ../crt/shaders/guest/advanced/lut/ntsc-lut.png +SamplerLUT4_linear = true + +# custom ntsc shaders + +shader4 = ../ntsc/shaders/ntsc-adaptive/ntsc-pass1.slang +shader5 = ../ntsc/shaders/ntsc-adaptive/ntsc-pass2.slang + +filter_linear4 = false +filter_linear5 = true + +scale_type_x4 = source +scale_type_y4 = source +scale_x4 = 4.0 +scale_y4 = 1.0 +frame_count_mod4 = 2 +float_framebuffer4 = true + +scale_type5 = source +scale_x5 = 0.5 +scale_y5 = 1.0 + +shader6 = ../crt/shaders/guest/advanced/custom-fast-sharpen.slang +filter_linear6 = true +scale_type6 = source +scale_x6 = 1.0 +scale_y6 = 1.0 +alias6 = NtscPass + +shader7 = ../crt/shaders/guest/advanced/convert-ntsc.slang +filter_linear7 = true +scale_type7 = source +scale_x7 = 0.5 +scale_y7 = 1.0 + +shader8 = ../stock.slang +filter_linear8 = true +scale_type8 = source +scale_x8 = 1.0 +scale_y8 = 1.0 +alias8 = PrePass +mipmap_input8 = true + +shader9 = ../crt/shaders/guest/advanced/avg-lum-ntsc.slang +filter_linear9 = true +scale_type9 = source +scale9 = 1.0 +mipmap_input9 = true +alias9 = AvgLumPass + +shader10 = ../crt/shaders/guest/advanced/linearize-ntsc.slang +filter_linear10 = true +scale_type10 = source +scale10 = 1.0 +alias10 = LinearizePass +float_framebuffer10 = true + +shader11 = ../crt/shaders/guest/advanced/gaussian_horizontal.slang +filter_linear11 = true +scale_type_x11 = absolute +scale_x11 = 640.0 +scale_type_y11 = source +scale_y11 = 1.0 +float_framebuffer11 = true + +shader12 = ../crt/shaders/guest/advanced/gaussian_vertical.slang +filter_linear12 = true +scale_type_x12 = absolute +scale_x12 = 640.0 +scale_type_y12 = absolute +scale_y12 = 480.0 +float_framebuffer12 = true +alias12 = GlowPass + +shader13 = ../crt/shaders/guest/advanced/bloom_horizontal.slang +filter_linear13 = true +scale_type_x13 = absolute +scale_x13 = 640.0 +scale_type_y13 = absolute +scale_y13 = 480.0 +float_framebuffer13 = true + +shader14 = ../crt/shaders/guest/advanced/bloom_vertical.slang +filter_linear14 = true +scale_type_x14 = absolute +scale_x14 = 640.0 +scale_type_y14 = absolute +scale_y14 = 480.0 +float_framebuffer14 = true +alias14 = BloomPass + +shader15 = ../crt/shaders/guest/advanced/crt-guest-advanced-ntsc.slang +filter_linear15 = true +scale_type15 = viewport +scale_x15 = 1.0 +scale_y15 = 1.0 + +shader16 = ../crt/shaders/guest/advanced/deconvergence.slang +filter_linear16 = true +scale_type16 = viewport +scale_x16 = 1.0 +scale_y16 = 1.0 diff --git a/presets/crt-guest-dr-venom2-hires.slangp b/presets/crt-guest-dr-venom2-hires.slangp deleted file mode 100644 index fcdc08b..0000000 --- a/presets/crt-guest-dr-venom2-hires.slangp +++ /dev/null @@ -1,64 +0,0 @@ -shaders = 8 - -shader0 = ../stock.slang -filter_linear0 = false -scale_type0 = source -scale0 = 1.0 -alias0 = StockPass - -shader1 = ../crt/shaders/guest/crt-gdv-new/afterglow0.slang -filter_linear1 = false -scale_type1 = source -scale1 = 1.0 -alias1 = AfterglowPass - -shader2 = ../crt/shaders/guest/crt-gdv-new/pre-shaders-afterglow.slang -filter_linear2 = false -scale_type2 = source -scale2 = 1.0 -alias2 = PrePass - -textures = "SamplerLUT1;SamplerLUT2;SamplerLUT3;SamplerLUT4" -SamplerLUT1 = ../crt/shaders/guest/lut/sony_trinitron1.png -SamplerLUT1_linear = true -SamplerLUT2 = ../crt/shaders/guest/lut/sony_trinitron2.png -SamplerLUT2_linear = true -SamplerLUT3 = ../crt/shaders/guest/lut/other1.png -SamplerLUT3_linear = true -SamplerLUT4 = ../crt/shaders/guest/lut/custom_lut.png -SamplerLUT4_linear = true - -shader3 = ../crt/shaders/guest/crt-gdv-new/avg-lum-ntsc.slang -filter_linear3 = true -scale_type3 = source -scale3 = 1.0 -mipmap_input3 = true -alias3 = AvgLumPass - -shader4 = ../crt/shaders/guest/crt-gdv-new/linearize-ntsc.slang -filter_linear4 = true -scale_type4 = source -scale4 = 1.0 -alias4 = LinearizePass -float_framebuffer4 = true # comment this line for max precision - -shader5 = ../crt/shaders/guest/crt-gdv-new/gaussian_horizontal.slang -filter_linear5 = true -scale_type_x5 = viewport -scale_x5 = 0.5 -scale_type_y5 = source -scale_y5 = 1.0 - -shader6 = ../crt/shaders/guest/crt-gdv-new/gaussian_vertical.slang -filter_linear6 = true -scale_type_x6 = viewport -scale_x6 = 0.5 -scale_type_y6 = viewport -scale_y6 = 0.5 -alias6 = GlowPass - -shader7 = ../crt/shaders/guest/crt-gdv-new/crt-guest-dr-venom2-hires.slang -filter_linear7 = true -scale_type7 = viewport -scale_x7 = 1.0 -scale_y7 = 1.0 diff --git a/presets/crt-guest-dr-venom2-ntsc.slangp b/presets/crt-guest-dr-venom2-ntsc.slangp deleted file mode 100644 index a99533b..0000000 --- a/presets/crt-guest-dr-venom2-ntsc.slangp +++ /dev/null @@ -1,84 +0,0 @@ -shaders = 10 - -shader0 = ../stock.slang -filter_linear0 = false -scale_type0 = source -scale0 = 1.0 -alias0 = StockPass - -shader1 = ../crt/shaders/guest/crt-gdv-new/afterglow0.slang -filter_linear1 = false -scale_type1 = source -scale1 = 1.0 -alias1 = AfterglowPass - -shader2 = ../crt/shaders/guest/crt-gdv-new/pre-shaders-afterglow.slang -filter_linear2 = false -scale_type2 = source -scale2 = 1.0 - -textures = "SamplerLUT1;SamplerLUT2;SamplerLUT3;SamplerLUT4" -SamplerLUT1 = ../crt/shaders/guest/lut/sony_trinitron1.png -SamplerLUT1_linear = true -SamplerLUT2 = ../crt/shaders/guest/lut/sony_trinitron2.png -SamplerLUT2_linear = true -SamplerLUT3 = ../crt/shaders/guest/lut/other1.png -SamplerLUT3_linear = true -SamplerLUT4 = ../crt/shaders/guest/lut/custom_lut.png -SamplerLUT4_linear = true - -shader3 = ../ntsc/shaders/ntsc-adaptive/ntsc-pass1.slang -shader4 = ../ntsc/shaders/ntsc-adaptive/ntsc-pass2.slang - -filter_linear3 = false -filter_linear4 = false - -scale_type_x3 = source -scale_type_y3 = source -scale_x3 = 4.0 -scale_y3 = 1.0 -frame_count_mod3 = 2 -float_framebuffer3 = true - -scale_type4 = source -scale_x4 = 0.5 -scale_y4 = 1.0 -alias4 = PrePass - -shader5 = ../crt/shaders/guest/crt-gdv-new/avg-lum-ntsc.slang -filter_linear5 = true -scale_type5 = source -scale5 = 1.0 -mipmap_input5 = true -alias5 = AvgLumPass - -shader6 = ../crt/shaders/guest/crt-gdv-new/linearize-ntsc.slang -filter_linear6 = true -scale_type6 = source -scale6 = 1.0 -alias6 = LinearizePass -float_framebuffer6 = true # comment this line for max precision - -shader7 = ../crt/shaders/guest/crt-gdv-new/gaussian_horizontal.slang -filter_linear7 = true -scale_type_x7 = viewport -scale_x7 = 0.5 -scale_type_y7 = source -scale_y7 = 1.0 - -shader8 = ../crt/shaders/guest/crt-gdv-new/gaussian_vertical.slang -filter_linear8 = true -scale_type_x8 = viewport -scale_x8 = 0.5 -scale_type_y8 = viewport -scale_y8 = 0.5 -alias8 = GlowPass - -shader9 = ../crt/shaders/guest/crt-gdv-new/crt-guest-dr-venom2-ntsc.slang -filter_linear9 = true -scale_type9 = viewport -scale_x9 = 1.0 -scale_y9 = 1.0 - -parameters = "quality" -quality = 1.0