From 9189588554dd8b13a5e329b65837f6afc89651f2 Mon Sep 17 00:00:00 2001 From: Hyllian Date: Sun, 23 Jun 2024 21:16:51 -0300 Subject: [PATCH] Shaders: Add new pack of shaders (reshade) (#3232) - Add crt-geom, super-xbr, geom, multi-LUT, deblur-luma, bicubic and lanczos3. All .fx shaders; - Added some LUTs. --- .../reshade/Shaders/{ => crt}/crt-cyclon.fx | 0 .../shaders/reshade/Shaders/crt/crt-geom.fx | 628 ++++++++++++++++++ .../Shaders/edge-smoothing/super-xbr.fx | 423 ++++++++++++ .../reshade/Shaders/interpolation/bicubic.fx | 143 ++++ .../reshade/Shaders/interpolation/lanczos3.fx | 144 ++++ .../reshade/Shaders/misc/deblur-luma.fx | 151 +++++ .../shaders/reshade/Shaders/misc/geom.fx | 367 ++++++++++ .../shaders/reshade/Shaders/misc/multi-LUT.fx | 75 +++ .../Textures/multi-LUT/grade-composite.png | Bin 0 -> 30241 bytes .../reshade/Textures/multi-LUT/grade-rgb.png | Bin 0 -> 14762 bytes 10 files changed, 1931 insertions(+) rename data/resources/shaders/reshade/Shaders/{ => crt}/crt-cyclon.fx (100%) create mode 100644 data/resources/shaders/reshade/Shaders/crt/crt-geom.fx create mode 100644 data/resources/shaders/reshade/Shaders/edge-smoothing/super-xbr.fx create mode 100644 data/resources/shaders/reshade/Shaders/interpolation/bicubic.fx create mode 100644 data/resources/shaders/reshade/Shaders/interpolation/lanczos3.fx create mode 100644 data/resources/shaders/reshade/Shaders/misc/deblur-luma.fx create mode 100644 data/resources/shaders/reshade/Shaders/misc/geom.fx create mode 100644 data/resources/shaders/reshade/Shaders/misc/multi-LUT.fx create mode 100644 data/resources/shaders/reshade/Textures/multi-LUT/grade-composite.png create mode 100644 data/resources/shaders/reshade/Textures/multi-LUT/grade-rgb.png diff --git a/data/resources/shaders/reshade/Shaders/crt-cyclon.fx b/data/resources/shaders/reshade/Shaders/crt/crt-cyclon.fx similarity index 100% rename from data/resources/shaders/reshade/Shaders/crt-cyclon.fx rename to data/resources/shaders/reshade/Shaders/crt/crt-cyclon.fx diff --git a/data/resources/shaders/reshade/Shaders/crt/crt-geom.fx b/data/resources/shaders/reshade/Shaders/crt/crt-geom.fx new file mode 100644 index 000000000..a1d050ef0 --- /dev/null +++ b/data/resources/shaders/reshade/Shaders/crt/crt-geom.fx @@ -0,0 +1,628 @@ +#include "ReShade.fxh" + +/* + CRT-interlaced + + Copyright (C) 2010-2012 cgwg, Themaister and DOLLS + + 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. + + (cgwg gave their consent to have the original version of this shader + distributed under the GPL in this message: + + http://board.byuu.org/viewtopic.php?p=26075#p26075 + + "Feel free to distribute my shaders under the GPL. After all, the + barrel distortion code was taken from the Curvature shader, which is + under the GPL." + ) + This shader variant is pre-configured with screen curvature +*/ + + +uniform float CRTgamma < + ui_type = "drag"; + ui_min = 0.1; + ui_max = 5.0; + ui_step = 0.1; + ui_label = "CRTGeom Target Gamma"; +> = 2.4; + +uniform float monitorgamma < + ui_type = "drag"; + ui_min = 0.1; + ui_max = 5.0; + ui_step = 0.1; + ui_label = "CRTGeom Monitor Gamma"; +> = 2.2; + +uniform float d < + ui_type = "drag"; + ui_category = "Curvature"; + ui_min = 0.1; + ui_max = 3.0; + ui_step = 0.1; + ui_label = "CRTGeom Distance"; +> = 1.5; + +uniform bool CURVATURE < + ui_category = "Curvature"; + ui_type = "radio"; + ui_label = "CRTGeom Curvature Toggle"; +> = 1.0; + +uniform float invert_aspect < + ui_type = "drag"; + ui_category = "Curvature"; + ui_min = 0.0; + ui_max = 1.0; + ui_step = 1.0; + ui_label = "CRTGeom Curvature Aspect Inversion"; +> = 0.0; + +uniform float R < + ui_type = "drag"; + ui_category = "Curvature"; + ui_min = 0.1; + ui_max = 10.0; + ui_step = 0.1; + ui_label = "CRTGeom Curvature Radius"; +> = 2.0; + +uniform float cornersize < + ui_type = "drag"; + ui_category = "Curvature"; + ui_min = 0.001; + ui_max = 1.0; + ui_step = 0.005; + ui_label = "CRTGeom Corner Size"; +> = 0.03; + +uniform float cornersmooth < + ui_type = "drag"; + ui_category = "Curvature"; + ui_min = 80.0; + ui_max = 2000.0; + ui_step = 100.0; + ui_label = "CRTGeom Corner Smoothness"; +> = 1000.0; + +uniform float x_tilt < + ui_type = "drag"; + ui_category = "Curvature"; + ui_min = -0.5; + ui_max = 0.5; + ui_step = 0.05; + ui_label = "CRTGeom Horizontal Tilt"; +> = 0.0; + +uniform float y_tilt < + ui_type = "drag"; + ui_category = "Curvature"; + ui_min = -0.5; + ui_max = 0.5; + ui_step = 0.05; + ui_label = "CRTGeom Vertical Tilt"; +> = 0.0; + +uniform float overscan_x < + ui_type = "drag"; + ui_min = -125.0; + ui_max = 125.0; + ui_step = 0.5; + ui_label = "CRTGeom Horiz. Overscan %"; +> = 100.0; + +uniform float overscan_y < + ui_type = "drag"; + ui_min = -125.0; + ui_max = 125.0; + ui_step = 0.5; + ui_label = "CRTGeom Vert. Overscan %"; +> = 100.0; + +uniform float DOTMASK < + ui_type = "drag"; + ui_min = 0.0; + ui_max = 1.0; + ui_step = 0.05; + ui_label = "CRTGeom Dot Mask Strength"; +> = 0.3; + +uniform float SHARPER < + ui_type = "drag"; + ui_min = 1.0; + ui_max = 3.0; + ui_step = 1.0; + ui_label = "CRTGeom Sharpness"; +> = 1.0; + +uniform float scanline_weight < + ui_type = "drag"; + ui_min = 0.1; + ui_max = 0.5; + ui_step = 0.05; + ui_label = "CRTGeom Scanline Weight"; +> = 0.3; + +uniform float vertical_scanlines < + ui_type = "drag"; + ui_min = 0.0; + ui_max = 1.0; + ui_step = 1.0; + ui_label = "CRTGeom Vertical Scanlines"; +> = 0.0; + +uniform float lum < + ui_type = "drag"; + ui_min = 0.0; + ui_max = 1.0; + ui_step = 0.01; + ui_label = "CRTGeom Luminance"; +> = 0.0; + +uniform float interlace_detect < + ui_type = "drag"; + ui_min = 0.0; + ui_max = 1.0; + ui_step = 1.0; + ui_label = "CRTGeom Interlacing Simulation"; +> = 1.0; + + + +uniform float FrameCount < source = "framecount"; >; +uniform float2 BufferViewportRatio < source = "buffer_to_viewport_ratio"; >; +uniform float2 InternalPixelSize < source = "internal_pixel_size"; >; +uniform float2 NativePixelSize < source = "native_pixel_size"; >; +uniform float2 NormalizedInternalPixelSize < source = "normalized_internal_pixel_size"; >; +uniform float2 NormalizedNativePixelSize < source = "normalized_native_pixel_size"; >; +uniform float UpscaleMultiplier < source = "upscale_multiplier"; >; +uniform float2 ViewportSize < source = "viewportsize"; >; + + +// Comment the next line to disable interpolation in linear gamma (and +// gain speed). +#define LINEAR_PROCESSING + +// Enable 3x oversampling of the beam profile; improves moire effect caused by scanlines+curvature +#define OVERSAMPLE + +// Use the older, purely gaussian beam profile; uncomment for speed +//#define USEGAUSSIAN + +// Macros. +#define FIX(c) max(abs(c), 1e-5); +#define PI 3.141592653589 + +#ifdef LINEAR_PROCESSING +# define TEX2D(c) pow(tex2D(ReShade::BackBuffer, (c)), float4(CRTgamma,CRTgamma,CRTgamma,CRTgamma)) +#else +# define TEX2D(c) tex2D(ReShade::BackBuffer, (c)) +#endif + +// aspect ratio +#define aspect (invert_aspect>0.5?float2(0.75,1.0):float2(1.0,0.75)) +#define overscan (float2(1.01,1.01)); + + +struct ST_VertexOut +{ + float2 sinangle : TEXCOORD1; + float2 cosangle : TEXCOORD2; + float3 stretch : TEXCOORD3; + float2 ilfac : TEXCOORD4; + float2 one : TEXCOORD5; + float mod_factor : TEXCOORD6; + float2 TextureSize : TEXCOORD7; +}; + + +float vs_intersect(float2 xy, float2 sinangle, float2 cosangle) +{ + float A = dot(xy,xy) + d*d; + float B = 2.0*(R*(dot(xy,sinangle)-d*cosangle.x*cosangle.y)-d*d); + float C = d*d + 2.0*R*d*cosangle.x*cosangle.y; + + return (-B-sqrt(B*B-4.0*A*C))/(2.0*A); +} + +float2 vs_bkwtrans(float2 xy, float2 sinangle, float2 cosangle) +{ + float c = vs_intersect(xy, sinangle, cosangle); + float2 point = (float2(c, c)*xy - float2(-R, -R)*sinangle) / float2(R, R); + float2 poc = point/cosangle; + + float2 tang = sinangle/cosangle; + float A = dot(tang, tang) + 1.0; + float B = -2.0*dot(poc, tang); + float C = dot(poc, poc) - 1.0; + + float a = (-B + sqrt(B*B - 4.0*A*C))/(2.0*A); + float2 uv = (point - a*sinangle)/cosangle; + float r = FIX(R*acos(a)); + + return uv*r/sin(r/R); +} + +float2 vs_fwtrans(float2 uv, float2 sinangle, float2 cosangle) +{ + float r = FIX(sqrt(dot(uv,uv))); + uv *= sin(r/R)/r; + float x = 1.0-cos(r/R); + float D = d/R + x*cosangle.x*cosangle.y+dot(uv,sinangle); + + return d*(uv*cosangle-x*sinangle)/D; +} + +float3 vs_maxscale(float2 sinangle, float2 cosangle) +{ + float2 c = vs_bkwtrans(-R * sinangle / (1.0 + R/d*cosangle.x*cosangle.y), sinangle, cosangle); + float2 a = float2(0.5,0.5)*aspect; + + float2 lo = float2(vs_fwtrans(float2(-a.x, c.y), sinangle, cosangle).x, + vs_fwtrans(float2( c.x, -a.y), sinangle, cosangle).y)/aspect; + + float2 hi = float2(vs_fwtrans(float2(+a.x, c.y), sinangle, cosangle).x, + vs_fwtrans(float2( c.x, +a.y), sinangle, cosangle).y)/aspect; + + return float3((hi+lo)*aspect*0.5,max(hi.x-lo.x,hi.y-lo.y)); +} + + + +// Vertex shader generating a triangle covering the entire screen +void VS_CRT_Geom(in uint id : SV_VertexID, out float4 position : SV_Position, out float2 texcoord : TEXCOORD, out ST_VertexOut vVARS) +{ + texcoord.x = (id == 2) ? 2.0 : 0.0; + texcoord.y = (id == 1) ? 2.0 : 0.0; + position = float4(texcoord * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0); + + float2 SourceSize = 1.0/NormalizedNativePixelSize; + float2 OutputSize = ViewportSize*BufferViewportRatio; + + // Precalculate a bunch of useful values we'll need in the fragment + // shader. + vVARS.sinangle = sin(float2(x_tilt, y_tilt)); + vVARS.cosangle = cos(float2(x_tilt, y_tilt)); + vVARS.stretch = vs_maxscale(vVARS.sinangle, vVARS.cosangle); + + if(vertical_scanlines < 0.5) + { + vVARS.TextureSize = float2(SHARPER * SourceSize.x, SourceSize.y); + + vVARS.ilfac = float2(1.0, clamp(floor(SourceSize.y/(interlace_detect > 0.5 ? 200.0 : 1000)), 1.0, 2.0)); + + // The size of one texel, in texture-coordinates. + vVARS.one = vVARS.ilfac / vVARS.TextureSize; + + // Resulting X pixel-coordinate of the pixel we're drawing. + vVARS.mod_factor = texcoord.x * SourceSize.x * OutputSize.x / SourceSize.x; + }else{ + vVARS.TextureSize = float2(SourceSize.x, SHARPER * SourceSize.y); + + vVARS.ilfac = float2(clamp(floor(SourceSize.x/(interlace_detect > 0.5 ? 200.0 : 1000)), 1.0, 2.0), 1.0); + + // The size of one texel, in texture-coordinates. + vVARS.one = vVARS.ilfac / vVARS.TextureSize; + + // Resulting X pixel-coordinate of the pixel we're drawing. + vVARS.mod_factor = texcoord.y * SourceSize.y * OutputSize.y / SourceSize.y; + } +} + + + +float intersect(float2 xy, float2 sinangle, float2 cosangle) +{ + float A = dot(xy,xy) + d*d; + float B, C; + + if(vertical_scanlines < 0.5) + { + B = 2.0*(R*(dot(xy,sinangle) - d*cosangle.x*cosangle.y) - d*d); + C = d*d + 2.0*R*d*cosangle.x*cosangle.y; + }else{ + B = 2.0*(R*(dot(xy,sinangle) - d*cosangle.y*cosangle.x) - d*d); + C = d*d + 2.0*R*d*cosangle.y*cosangle.x; + } + + return (-B-sqrt(B*B - 4.0*A*C))/(2.0*A); +} + +float2 bkwtrans(float2 xy, float2 sinangle, float2 cosangle) +{ + float c = intersect(xy, sinangle, cosangle); + float2 point = (float2(c, c)*xy - float2(-R, -R)*sinangle) / float2(R, R); + float2 poc = point/cosangle; + float2 tang = sinangle/cosangle; + + float A = dot(tang, tang) + 1.0; + float B = -2.0*dot(poc, tang); + float C = dot(poc, poc) - 1.0; + + float a = (-B + sqrt(B*B - 4.0*A*C)) / (2.0*A); + float2 uv = (point - a*sinangle) / cosangle; + float r = FIX(R*acos(a)); + + return uv*r/sin(r/R); +} + +float2 fwtrans(float2 uv, float2 sinangle, float2 cosangle) +{ + float r = FIX(sqrt(dot(uv, uv))); + uv *= sin(r/R)/r; + float x = 1.0 - cos(r/R); + float D; + + if(vertical_scanlines < 0.5) + D = d/R + x*cosangle.x*cosangle.y + dot(uv,sinangle); + else + D = d/R + x*cosangle.y*cosangle.x + dot(uv,sinangle); + + return d*(uv*cosangle - x*sinangle)/D; +} + +float3 maxscale(float2 sinangle, float2 cosangle) +{ + if(vertical_scanlines < 0.5) + { + float2 c = bkwtrans(-R * sinangle / (1.0 + R/d*cosangle.x*cosangle.y), sinangle, cosangle); + float2 a = float2(0.5, 0.5)*aspect; + + float2 lo = float2(fwtrans(float2(-a.x, c.y), sinangle, cosangle).x, + fwtrans(float2( c.x, -a.y), sinangle, cosangle).y)/aspect; + float2 hi = float2(fwtrans(float2(+a.x, c.y), sinangle, cosangle).x, + fwtrans(float2( c.x, +a.y), sinangle, cosangle).y)/aspect; + + return float3((hi+lo)*aspect*0.5,max(hi.x-lo.x, hi.y-lo.y)); + }else{ + float2 c = bkwtrans(-R * sinangle / (1.0 + R/d*cosangle.y*cosangle.x), sinangle, cosangle); + float2 a = float2(0.5, 0.5)*aspect; + + float2 lo = float2(fwtrans(float2(-a.y, c.x), sinangle, cosangle).y, + fwtrans(float2( c.y, -a.x), sinangle, cosangle).x)/aspect; + float2 hi = float2(fwtrans(float2(+a.y, c.x), sinangle, cosangle).y, + fwtrans(float2( c.y, +a.x), sinangle, cosangle).x)/aspect; + + return float3((hi+lo)*aspect*0.5,max(hi.y-lo.y, hi.x-lo.x)); + } +} + +// Calculate the influence of a scanline on the current pixel. +// +// 'distance' is the distance in texture coordinates from the current +// pixel to the scanline in question. +// 'color' is the colour of the scanline at the horizontal location of +// the current pixel. +float4 scanlineWeights(float distance, float4 color) +{ + // "wid" controls the width of the scanline beam, for each RGB + // channel The "weights" lines basically specify the formula + // that gives you the profile of the beam, i.e. the intensity as + // a function of distance from the vertical center of the + // scanline. In this case, it is gaussian if width=2, and + // becomes nongaussian for larger widths. Ideally this should + // be normalized so that the integral across the beam is + // independent of its width. That is, for a narrower beam + // "weights" should have a higher peak at the center of the + // scanline than for a wider beam. + #ifdef USEGAUSSIAN + float4 wid = 0.3 + 0.1 * pow(color, float4(3.0, 3.0, 3.0, 3.0)); + float dsw = distance / scanline_weight; + float4 weights = float4(dsw, dsw, dsw, dsw); + + return (lum + 0.4) * exp(-weights * weights) / wid; + #else + float4 wid = 2.0 + 2.0 * pow(color, float4(4.0, 4.0, 4.0, 4.0)); + float dsw = distance / scanline_weight; + float4 weights = float4(dsw, dsw, dsw, dsw); + + return (lum + 1.4) * exp(-pow(weights * rsqrt(0.5 * wid), wid)) / (0.6 + 0.2 * wid); + #endif +} + +float2 transform(float2 coord, float2 sinangle, float2 cosangle, float3 stretch) +{ + coord = (coord - float2(0.5, 0.5))*aspect*stretch.z + stretch.xy; + + return (bkwtrans(coord, sinangle, cosangle) / + float2(overscan_x / 100.0, overscan_y / 100.0)/aspect + float2(0.5, 0.5)); +} + +float corner(float2 coord) +{ + coord = (coord - float2(0.5, 0.5)) * float2(overscan_x / 100.0, overscan_y / 100.0) + float2(0.5, 0.5); + coord = min(coord, float2(1.0, 1.0) - coord) * aspect; + float2 cdist = float2(cornersize, cornersize); + coord = (cdist - min(coord, cdist)); + float dist = sqrt(dot(coord, coord)); + + if(vertical_scanlines < 0.5) + return clamp((cdist.x - dist)*cornersmooth, 0.0, 1.0); + else + return clamp((cdist.y - dist)*cornersmooth, 0.0, 1.0); +} + +float fwidth(float value){ + return abs(ddx(value)) + abs(ddy(value)); +} + + +float4 PS_CRT_Geom(float4 vpos: SV_Position, float2 vTexCoord : TEXCOORD, in ST_VertexOut vVARS) : SV_Target +{ + // Here's a helpful diagram to keep in mind while trying to + // understand the code: + // + // | | | | | + // ------------------------------- + // | | | | | + // | 01 | 11 | 21 | 31 | <-- current scanline + // | | @ | | | + // ------------------------------- + // | | | | | + // | 02 | 12 | 22 | 32 | <-- next scanline + // | | | | | + // ------------------------------- + // | | | | | + // + // Each character-cell represents a pixel on the output + // surface, "@" represents the current pixel (always somewhere + // in the bottom half of the current scan-line, or the top-half + // of the next scanline). The grid of lines represents the + // edges of the texels of the underlying texture. + + // Texture coordinates of the texel containing the active pixel. + float2 xy; + if (CURVATURE > 0.5) + xy = transform(vTexCoord, vVARS.sinangle, vVARS.cosangle, vVARS.stretch); + else + xy = vTexCoord; + + float cval = corner(xy); + + // Of all the pixels that are mapped onto the texel we are + // currently rendering, which pixel are we currently rendering? + float2 ilvec; + if(vertical_scanlines < 0.5) + ilvec = float2(0.0, vVARS.ilfac.y * interlace_detect > 1.5 ? (float(FrameCount) % 2.0) : 0.0); + else + ilvec = float2(vVARS.ilfac.x * interlace_detect > 1.5 ? (float(FrameCount) % 2.0) : 0.0, 0.0); + + float2 ratio_scale = (xy * vVARS.TextureSize - float2(0.5, 0.5) + ilvec) / vVARS.ilfac; + float2 uv_ratio = frac(ratio_scale); + + // Snap to the center of the underlying texel. + xy = (floor(ratio_scale)*vVARS.ilfac + float2(0.5, 0.5) - ilvec) / vVARS.TextureSize; + + // Calculate Lanczos scaling coefficients describing the effect + // of various neighbour texels in a scanline on the current + // pixel. + float4 coeffs; + if(vertical_scanlines < 0.5) + coeffs = PI * float4(1.0 + uv_ratio.x, uv_ratio.x, 1.0 - uv_ratio.x, 2.0 - uv_ratio.x); + else + coeffs = PI * float4(1.0 + uv_ratio.y, uv_ratio.y, 1.0 - uv_ratio.y, 2.0 - uv_ratio.y); + + // Prevent division by zero. + coeffs = FIX(coeffs); + + // Lanczos2 kernel. + coeffs = 2.0 * sin(coeffs) * sin(coeffs / 2.0) / (coeffs * coeffs); + + // Normalize. + coeffs /= dot(coeffs, float4(1.0, 1.0, 1.0, 1.0)); + + // Calculate the effective colour of the current and next + // scanlines at the horizontal location of the current pixel, + // using the Lanczos coefficients above. + float4 col, col2; + if(vertical_scanlines < 0.5) + { + col = clamp( + mul(coeffs, float4x4( + TEX2D(xy + float2(-vVARS.one.x, 0.0)), + TEX2D(xy), + TEX2D(xy + float2(vVARS.one.x, 0.0)), + TEX2D(xy + float2(2.0 * vVARS.one.x, 0.0)) + )), + 0.0, 1.0 + ); + col2 = clamp( + mul(coeffs, float4x4( + TEX2D(xy + float2(-vVARS.one.x, vVARS.one.y)), + TEX2D(xy + float2(0.0, vVARS.one.y)), + TEX2D(xy + vVARS.one), + TEX2D(xy + float2(2.0 * vVARS.one.x, vVARS.one.y)) + )), + 0.0, 1.0 + ); + }else{ + col = clamp( + mul(coeffs, float4x4( + TEX2D(xy + float2(0.0, -vVARS.one.y)), + TEX2D(xy), + TEX2D(xy + float2(0.0, vVARS.one.y)), + TEX2D(xy + float2(0.0, 2.0 * vVARS.one.y)) + )), + 0.0, 1.0 + ); + col2 = clamp( + mul(coeffs, float4x4( + TEX2D(xy + float2(vVARS.one.x, -vVARS.one.y)), + TEX2D(xy + float2(vVARS.one.x, 0.0)), + TEX2D(xy + vVARS.one), + TEX2D(xy + float2(vVARS.one.x, 2.0 * vVARS.one.y)) + )), + 0.0, 1.0 + ); + } + +#ifndef LINEAR_PROCESSING + col = pow(col , float4(CRTgamma, CRTgamma, CRTgamma, CRTgamma)); + col2 = pow(col2, float4(CRTgamma, CRTgamma, CRTgamma, CRTgamma)); +#endif + + // Calculate the influence of the current and next scanlines on + // the current pixel. + float4 weights, weights2; + if(vertical_scanlines < 0.5) + { + weights = scanlineWeights(uv_ratio.y, col); + weights2 = scanlineWeights(1.0 - uv_ratio.y, col2); + + #ifdef OVERSAMPLE + float filter = fwidth(ratio_scale.y); + uv_ratio.y = uv_ratio.y + 1.0/3.0*filter; + weights = (weights + scanlineWeights(uv_ratio.y, col))/3.0; + weights2 = (weights2 + scanlineWeights(abs(1.0 - uv_ratio.y), col2))/3.0; + uv_ratio.y = uv_ratio.y - 2.0/3.0*filter; + weights = weights + scanlineWeights(abs(uv_ratio.y), col)/3.0; + weights2 = weights2 + scanlineWeights(abs(1.0 - uv_ratio.y), col2)/3.0; + #endif + }else{ + weights = scanlineWeights(uv_ratio.x, col); + weights2 = scanlineWeights(1.0 - uv_ratio.x, col2); + + #ifdef OVERSAMPLE + float filter = fwidth(ratio_scale.x); + uv_ratio.x = uv_ratio.x + 1.0/3.0*filter; + weights = (weights + scanlineWeights(uv_ratio.x, col))/3.0; + weights2 = (weights2 + scanlineWeights(abs(1.0 - uv_ratio.x), col2))/3.0; + uv_ratio.x = uv_ratio.x - 2.0/3.0*filter; + weights = weights + scanlineWeights(abs(uv_ratio.x), col)/3.0; + weights2 = weights2 + scanlineWeights(abs(1.0 - uv_ratio.x), col2)/3.0; + #endif + } + + float3 mul_res = (col * weights + col2 * weights2).rgb; + mul_res *= float3(cval, cval, cval); + + // dot-mask emulation: + // Output pixels are alternately tinted green and magenta. + float3 dotMaskWeights = lerp( + float3(1.0, 1.0 - DOTMASK, 1.0), + float3(1.0 - DOTMASK, 1.0, 1.0 - DOTMASK), + floor((vVARS.mod_factor % 2.0)) + ); + + mul_res *= dotMaskWeights; + + // Convert the image gamma for display on our output device. + mul_res = pow(mul_res, float3(1.0 / monitorgamma, 1.0 / monitorgamma, 1.0 / monitorgamma)); + + return float4(mul_res, 1.0); +} + + +technique CRT_Geom +{ + pass + { + VertexShader = VS_CRT_Geom; + PixelShader = PS_CRT_Geom; + } +} diff --git a/data/resources/shaders/reshade/Shaders/edge-smoothing/super-xbr.fx b/data/resources/shaders/reshade/Shaders/edge-smoothing/super-xbr.fx new file mode 100644 index 000000000..561263ada --- /dev/null +++ b/data/resources/shaders/reshade/Shaders/edge-smoothing/super-xbr.fx @@ -0,0 +1,423 @@ +#include "ReShade.fxh" + +/* + ******* Super XBR Shader ******* + + Copyright (c) 2015-2024 Hyllian - sergiogdb@gmail.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +*/ + + +uniform float XBR_EDGE_STR_P0 < + ui_category = "Super-xBR:"; + ui_type = "drag"; + ui_min = 0.0; + ui_max = 5.0; + ui_step = 0.5; + ui_label = "Xbr - Edge Strength p0"; +> = 5.0; + +uniform float XBR_WEIGHT < + ui_category = "Super-xBR:"; + ui_type = "drag"; + ui_min = 0.0; + ui_max = 1.0; + ui_step = 0.1; + ui_label = "Xbr - Filter Weight"; +> = 1.0; + + +uniform float JINC2_WINDOW_SINC < + ui_category = "Jinc2:"; + ui_type = "drag"; + ui_min = 0.0; + ui_max = 1.0; + ui_step = 0.01; + ui_label = "Window Sinc Param"; +> = 0.5; + +uniform float JINC2_SINC < + ui_category = "Jinc2:"; + ui_type = "drag"; + ui_min = 0.0; + ui_max = 1.0; + ui_step = 0.01; + ui_label = "Sinc Param"; +> = 0.88; + +uniform float JINC2_AR_STRENGTH < + ui_category = "Jinc2:"; + ui_type = "drag"; + ui_min = 0.0; + ui_max = 1.0; + ui_step = 0.01; + ui_label = "Anti-ringing Strength"; +> = 0.5; + +uniform float2 BufferToViewportRatio < source = "buffer_to_viewport_ratio"; >; +uniform float2 NormalizedNativePixelSize < source = "normalized_native_pixel_size"; >; + +texture2D tBackBufferY{Width=BUFFER_WIDTH;Height=BUFFER_HEIGHT;Format=RGBA8;}; +sampler2D sBackBufferY{Texture=tBackBufferY;AddressU=BORDER;AddressV=BORDER;AddressW=BORDER;MagFilter=POINT;MinFilter=POINT;}; + +texture2D tSuper_xBR_P0 < pooled = true; > {Width=BUFFER_WIDTH;Height=BUFFER_HEIGHT;Format=RGBA8;}; +sampler2D sSuper_xBR_P0{Texture=tSuper_xBR_P0;AddressU=CLAMP;AddressV=CLAMP;AddressW=CLAMP;MagFilter=POINT;MinFilter=POINT;}; + +texture2D tSuper_xBR_P1 < pooled = true; > {Width=BUFFER_WIDTH;Height=BUFFER_HEIGHT;Format=RGBA8;}; +sampler2D sSuper_xBR_P1{Texture=tSuper_xBR_P1;AddressU=CLAMP;AddressV=CLAMP;AddressW=CLAMP;MagFilter=POINT;MinFilter=POINT;}; + +texture2D tSuper_xBR_P2 < pooled = true; > {Width=BUFFER_WIDTH;Height=BUFFER_HEIGHT;Format=RGBA8;}; +sampler2D sSuper_xBR_P2{Texture=tSuper_xBR_P2;AddressU=CLAMP;AddressV=CLAMP;AddressW=CLAMP;MagFilter=POINT;MinFilter=POINT;}; + +#define Y float3(.2126,.7152,.0722) + +static const float wp0[6] = {2.0, 1.0, -1.0, 4.0, -1.0, 1.0}; +static const float wp1[6] = {1.0, 0.0, 0.0, 0.0, 0.0, 0.0}; +static const float wp2[6] = {0.0, 0.0, 0.0, 1.0, 0.0, 0.0}; + +float luma(float3 color) +{ + return dot(color, Y); +} + +float df(float A, float B) +{ + return abs(A-B); +} + +/* + P1 + |P0|B |C |P1| C F4 |a0|b1|c2|d3| + |D |E |F |F4| B F I4 |b0|c1|d2|e3| |e1|i1|i2|e2| + |G |H |I |I4| P0 E A I P3 |c0|d1|e2|f3| |e3|i3|i4|e4| + |P2|H5|I5|P3| D H I5 |d0|e1|f2|g3| + G H5 + P2 +*/ + + +float d_wd(float wp[6], float b0, float b1, float c0, float c1, float c2, float d0, float d1, float d2, float d3, float e1, float e2, float e3, float f2, float f3) +{ + return (wp[0]*(df(c1,c2) + df(c1,c0) + df(e2,e1) + df(e2,e3)) + wp[1]*(df(d2,d3) + df(d0,d1)) + wp[2]*(df(d1,d3) + df(d0,d2)) + wp[3]*df(d1,d2) + wp[4]*(df(c0,c2) + df(e1,e3)) + wp[5]*(df(b0,b1) + df(f2,f3))); +} + + +float hv_wd(float wp[6], float i1, float i2, float i3, float i4, float e1, float e2, float e3, float e4) +{ + return ( wp[3]*(df(i1,i2)+df(i3,i4)) + wp[0]*(df(i1,e1)+df(i2,e2)+df(i3,e3)+df(i4,e4)) + wp[2]*(df(i1,e2)+df(i3,e4)+df(e1,i2)+df(e3,i4))); +} + +float3 min4(float3 a, float3 b, float3 c, float3 d) +{ + return min(a, min(b, min(c, d))); +} + +float3 max4(float3 a, float3 b, float3 c, float3 d) +{ + return max(a, max(b, max(c, d))); +} + +float max4float(float a, float b, float c, float d) +{ + return max(a, max(b, max(c, d))); +} + +float3 super_xbr(float wp[6], float4 P0, float4 B, float4 C, float4 P1, float4 D, float4 E, float4 F, float4 F4, float4 G, float4 H, float4 I, float4 I4, float4 P2, float4 H5, float4 I5, float4 P3) +{ + float b = B.a; float c = C.a; float d = D.a; float e = E.a; + float f = F.a; float g = G.a; float h = H.a; float i = I.a; + float i4 = I4.a; float p0 = P0.a; float i5 = I5.a; float p1 = P1.a; + float h5 = H5.a; float p2 = P2.a; float f4 = F4.a; float p3 = P3.a; + + /* Calc edgeness in diagonal directions. */ + float d_edge = (d_wd( wp, d, b, g, e, c, p2, h, f, p1, h5, i, f4, i5, i4 ) - d_wd( wp, c, f4, b, f, i4, p0, e, i, p3, d, h, i5, g, h5 )); + + /* Calc edgeness in horizontal/vertical directions. */ + float hv_edge = (hv_wd(wp, f, i, e, h, c, i5, b, h5) - hv_wd(wp, e, f, h, i, d, f4, g, i4)); + + float limits = XBR_EDGE_STR_P0 + 0.000001; + float edge_strength = smoothstep(0.0, limits, abs(d_edge)); + + float4 w1, w2; + float3 c3, c4; + + float weight1 = (XBR_WEIGHT*1.29633/10.0); + float weight2 = (XBR_WEIGHT*1.75068/10.0/2.0); + + /* Filter weights. Two taps only. */ + w1 = float4(-weight1, weight1+0.50, weight1+0.50, -weight1); + w2 = float4(-weight2, weight2+0.25, weight2+0.25, -weight2); + c3 = mul(w2, float4x3(D.rgb+G.rgb, E.rgb+H.rgb, F.rgb+I.rgb, F4.rgb+I4.rgb)); + c4 = mul(w2, float4x3(C.rgb+B.rgb, F.rgb+E.rgb, I.rgb+H.rgb, I5.rgb+H5.rgb)); + + /* Filtering and normalization in four direction generating four colors. */ + float3 c1 = mul(w1, float4x3( P2.rgb, H.rgb, F.rgb, P1.rgb )); + float3 c2 = mul(w1, float4x3( P0.rgb, E.rgb, I.rgb, P3.rgb )); + + /* Smoothly blends the two strongest directions (one in diagonal and the other in vert/horiz direction). */ + float3 color = lerp(lerp(c1, c2, step(0.0, d_edge)), lerp(c3, c4, step(0.0, hv_edge)), 1. - edge_strength); + + /* Anti-ringing code. */ + float3 min_sample = min4( E.rgb, F.rgb, H.rgb, I.rgb ); + float3 max_sample = max4( E.rgb, F.rgb, H.rgb, I.rgb ); + color = clamp(color, min_sample, max_sample); + + return color; +} + +float4 BackBufferY(float4 pos: SV_Position, float2 vTexCoord : TEXCOORD) : SV_Target +{ + float3 color = tex2D(ReShade::BackBuffer, vTexCoord.xy).rgb; + + return float4(color, luma(color)); +} + + +float4 Super_xBR_P0(float4 pos: SV_Position, float2 vTexCoord : TEXCOORD) : SV_Target +{ + float2 ps = NormalizedNativePixelSize; + + float4 P0 = tex2D(sBackBufferY, vTexCoord.xy + ps*float2(-1.0, -1.0)); + float4 B = tex2D(sBackBufferY, vTexCoord.xy + ps*float2( 0.0, -1.0)); + float4 C = tex2D(sBackBufferY, vTexCoord.xy + ps*float2( 1.0, -1.0)); + float4 P1 = tex2D(sBackBufferY, vTexCoord.xy + ps*float2( 2.0, -1.0)); + + float4 D = tex2D(sBackBufferY, vTexCoord.xy + ps*float2(-1.0, 0.0)); + float4 E = tex2D(sBackBufferY, vTexCoord.xy ); + float4 F = tex2D(sBackBufferY, vTexCoord.xy + ps*float2( 1.0, 0.0)); + float4 F4 = tex2D(sBackBufferY, vTexCoord.xy + ps*float2( 2.0, 0.0)); + + float4 G = tex2D(sBackBufferY, vTexCoord.xy + ps*float2(-1.0, 1.0)); + float4 H = tex2D(sBackBufferY, vTexCoord.xy + ps*float2( 0.0, 1.0)); + float4 I = tex2D(sBackBufferY, vTexCoord.xy + ps*float2( 1.0, 1.0)); + float4 I4 = tex2D(sBackBufferY, vTexCoord.xy + ps*float2( 2.0, 1.0)); + + float4 P2 = tex2D(sBackBufferY, vTexCoord.xy + ps*float2(-1.0, 2.0)); + float4 H5 = tex2D(sBackBufferY, vTexCoord.xy + ps*float2( 0.0, 2.0)); + float4 I5 = tex2D(sBackBufferY, vTexCoord.xy + ps*float2( 1.0, 2.0)); + float4 P3 = tex2D(sBackBufferY, vTexCoord.xy + ps*float2( 2.0, 2.0)); + + float3 color = super_xbr(wp0, P0, B, C, P1, D, E, F, F4, G, H, I, I4, P2, H5, I5, P3); + + return float4(color, luma(color)); +} + + + + +float4 Super_xBR_P1(float4 pos: SV_Position, float2 vTexCoord : TEXCOORD) : SV_Target +{ + float2 ps = NormalizedNativePixelSize; + + //Skip pixels on wrong grid + float2 fp = frac(vTexCoord.xy / ps); + float2 dir = fp - float2(0.5,0.5); + + if ((dir.x*dir.y)>0.0) + { + return lerp( tex2D(sBackBufferY, vTexCoord), tex2D(sSuper_xBR_P0, vTexCoord), step(0.0, dir.x)); + } + else + { + float2 g1 = (fp.x>0.5) ? float2(0.5*ps.x, 0.0) : float2(0.0, 0.5*ps.y); + float2 g2 = (fp.x>0.5) ? float2(0.0, 0.5*ps.y) : float2(0.5*ps.x, 0.0); + + float4 P0 = tex2D( sBackBufferY, vTexCoord -3.0*g1 ); + float4 P1 = tex2D(sSuper_xBR_P0, vTexCoord -3.0*g2); + float4 P2 = tex2D(sSuper_xBR_P0, vTexCoord +3.0*g2); + float4 P3 = tex2D( sBackBufferY, vTexCoord +3.0*g1 ); + + float4 B = tex2D(sSuper_xBR_P0, vTexCoord -2.0*g1 -g2); + float4 C = tex2D( sBackBufferY, vTexCoord -g1 -2.0*g2); + float4 D = tex2D(sSuper_xBR_P0, vTexCoord -2.0*g1 +g2); + float4 E = tex2D( sBackBufferY, vTexCoord -g1 ); + float4 F = tex2D(sSuper_xBR_P0, vTexCoord -g2); + float4 G = tex2D( sBackBufferY, vTexCoord -g1 +2.0*g2); + float4 H = tex2D(sSuper_xBR_P0, vTexCoord +g2); + float4 I = tex2D( sBackBufferY, vTexCoord +g1 ); + + float4 F4 = tex2D(sBackBufferY, vTexCoord +g1 -2.0*g2); + float4 I4 = tex2D( sSuper_xBR_P0, vTexCoord +2.0*g1 -g2); + float4 H5 = tex2D(sBackBufferY, vTexCoord +g1 +2.0*g2); + float4 I5 = tex2D( sSuper_xBR_P0, vTexCoord +2.0*g1 +g2); + + float3 color = super_xbr(wp1, P0, B, C, P1, D, E, F, F4, G, H, I, I4, P2, H5, I5, P3); + + return float4(color, luma(color)); + } +} + + +float4 Super_xBR_P2(float4 pos: SV_Position, float2 vTexCoord : TEXCOORD) : SV_Target +{ + float2 ps = 0.5*NormalizedNativePixelSize; + + float4 P0 = tex2D(sSuper_xBR_P1, vTexCoord.xy + ps*float2(-2.0, -2.0)); + float4 B = tex2D(sSuper_xBR_P1, vTexCoord.xy + ps*float2(-1.0, -2.0)); + float4 C = tex2D(sSuper_xBR_P1, vTexCoord.xy + ps*float2( 0.0, -2.0)); + float4 P1 = tex2D(sSuper_xBR_P1, vTexCoord.xy + ps*float2( 1.0, -2.0)); + + float4 D = tex2D(sSuper_xBR_P1, vTexCoord.xy + ps*float2(-2.0, -1.0)); + float4 E = tex2D(sSuper_xBR_P1, vTexCoord.xy + ps*float2(-1.0, -1.0)); + float4 F = tex2D(sSuper_xBR_P1, vTexCoord.xy + ps*float2( 0.0, -1.0)); + float4 F4 = tex2D(sSuper_xBR_P1, vTexCoord.xy + ps*float2( 1.0, -1.0)); + + float4 G = tex2D(sSuper_xBR_P1, vTexCoord.xy + ps*float2(-2.0, 0.0)); + float4 H = tex2D(sSuper_xBR_P1, vTexCoord.xy + ps*float2(-1.0, 0.0)); + float4 I = tex2D(sSuper_xBR_P1, vTexCoord.xy + ps*float2( 0.0, 0.0)); + float4 I4 = tex2D(sSuper_xBR_P1, vTexCoord.xy + ps*float2( 1.0, 0.0)); + + float4 P2 = tex2D(sSuper_xBR_P1, vTexCoord.xy + ps*float2(-2.0, 1.0)); + float4 H5 = tex2D(sSuper_xBR_P1, vTexCoord.xy + ps*float2(-1.0, 1.0)); + float4 I5 = tex2D(sSuper_xBR_P1, vTexCoord.xy + ps*float2( 0.0, 1.0)); + float4 P3 = tex2D(sSuper_xBR_P1, vTexCoord.xy + ps*float2( 1.0, 1.0)); + + float3 color = super_xbr(wp2, P0, B, C, P1, D, E, F, F4, G, H, I, I4, P2, H5, I5, P3); + + return float4(color, 1.0); +} + + +#define halfpi 1.5707963267948966192313216916398 +#define pi 3.1415926535897932384626433832795 +#define wa (JINC2_WINDOW_SINC*pi) +#define wb (JINC2_SINC*pi) + + + +// Calculates the distance between two points +float dst(float2 pt1, float2 pt2) +{ + float2 v = pt2 - pt1; + return sqrt(dot(v,v)); +} + + + +float4 resampler(float4 x) +{ + float4 res; + + res.x = (x.x==0.0) ? (wa*wb) : sin(x.x*wa)*sin(x.x*wb)/(x.x*x.x); + res.y = (x.y==0.0) ? (wa*wb) : sin(x.y*wa)*sin(x.y*wb)/(x.y*x.y); + res.z = (x.z==0.0) ? (wa*wb) : sin(x.z*wa)*sin(x.z*wb)/(x.z*x.z); + res.w = (x.w==0.0) ? (wa*wb) : sin(x.w*wa)*sin(x.w*wb)/(x.w*x.w); + + return res; +} + + +float4 Jinc2(float4 pos: SV_Position, float2 vTexCoord : TEXCOORD) : SV_Target +{ + float2 ps = 0.5*NormalizedNativePixelSize; + + float3 color; + float4x4 weights; + + float2 dx = float2(1.0, 0.0); + float2 dy = float2(0.0, 1.0); + + float2 pc = vTexCoord / ps; + + float2 tc = (floor(pc-float2(0.5,0.5))+float2(0.5,0.5)); + + weights[0] = resampler(float4(dst(pc, tc -dx -dy), dst(pc, tc -dy), dst(pc, tc +dx -dy), dst(pc, tc+2.0*dx -dy))); + weights[1] = resampler(float4(dst(pc, tc -dx ), dst(pc, tc ), dst(pc, tc +dx ), dst(pc, tc+2.0*dx ))); + weights[2] = resampler(float4(dst(pc, tc -dx +dy), dst(pc, tc +dy), dst(pc, tc +dx +dy), dst(pc, tc+2.0*dx +dy))); + weights[3] = resampler(float4(dst(pc, tc -dx+2.0*dy), dst(pc, tc +2.0*dy), dst(pc, tc +dx+2.0*dy), dst(pc, tc+2.0*dx+2.0*dy))); + + //weights[0][0] = weights[0][3] = weights[3][0] = weights[3][3] = 0.0; + + dx = dx * ps; + dy = dy * ps; + tc = tc * ps; + + // reading the texels + + float3 c00 = tex2D(sSuper_xBR_P2, tc -dx -dy).xyz; + float3 c10 = tex2D(sSuper_xBR_P2, tc -dy).xyz; + float3 c20 = tex2D(sSuper_xBR_P2, tc +dx -dy).xyz; + float3 c30 = tex2D(sSuper_xBR_P2, tc+2.0*dx -dy).xyz; + float3 c01 = tex2D(sSuper_xBR_P2, tc -dx ).xyz; + float3 c11 = tex2D(sSuper_xBR_P2, tc ).xyz; + float3 c21 = tex2D(sSuper_xBR_P2, tc +dx ).xyz; + float3 c31 = tex2D(sSuper_xBR_P2, tc+2.0*dx ).xyz; + float3 c02 = tex2D(sSuper_xBR_P2, tc -dx +dy).xyz; + float3 c12 = tex2D(sSuper_xBR_P2, tc +dy).xyz; + float3 c22 = tex2D(sSuper_xBR_P2, tc +dx +dy).xyz; + float3 c32 = tex2D(sSuper_xBR_P2, tc+2.0*dx +dy).xyz; + float3 c03 = tex2D(sSuper_xBR_P2, tc -dx+2.0*dy).xyz; + float3 c13 = tex2D(sSuper_xBR_P2, tc +2.0*dy).xyz; + float3 c23 = tex2D(sSuper_xBR_P2, tc +dx+2.0*dy).xyz; + float3 c33 = tex2D(sSuper_xBR_P2, tc+2.0*dx+2.0*dy).xyz; + + color = mul(weights[0], float4x3(c00, c10, c20, c30)); + color+= mul(weights[1], float4x3(c01, c11, c21, c31)); + color+= mul(weights[2], float4x3(c02, c12, c22, c32)); + color+= mul(weights[3], float4x3(c03, c13, c23, c33)); + color = color/(dot(mul(weights, float4(1.,1.,1.,1.)), float4(1.,1.,1.,1.))); + + // Anti-ringing + // Get min/max samples + float3 min_sample = min4(c11, c21, c12, c22); + float3 max_sample = max4(c11, c21, c12, c22); + + float3 aux = color; + + color = clamp(color, min_sample, max_sample); + color = lerp(aux, color, JINC2_AR_STRENGTH); + + return float4(color, 1.0); +} + + +technique Super_xBR +{ + pass PS_BackBufferY + { + VertexShader = PostProcessVS; + PixelShader = BackBufferY; + RenderTarget = tBackBufferY; + } + pass PS_Super_xBR_P0 + { + VertexShader = PostProcessVS; + PixelShader = Super_xBR_P0; + RenderTarget = tSuper_xBR_P0; + } + pass PS_Super_xBR_P1 + { + VertexShader = PostProcessVS; + PixelShader = Super_xBR_P1; + RenderTarget = tSuper_xBR_P1; + } + pass PS_Super_xBR_P2 + { + VertexShader = PostProcessVS; + PixelShader = Super_xBR_P2; + RenderTarget = tSuper_xBR_P2; + } + pass PS_Jinc2 + { + VertexShader = PostProcessVS; + PixelShader = Jinc2; + } +} diff --git a/data/resources/shaders/reshade/Shaders/interpolation/bicubic.fx b/data/resources/shaders/reshade/Shaders/interpolation/bicubic.fx new file mode 100644 index 000000000..a650d4548 --- /dev/null +++ b/data/resources/shaders/reshade/Shaders/interpolation/bicubic.fx @@ -0,0 +1,143 @@ +#include "../ReShade.fxh" + +/* + Bicubic multipass Shader + + Copyright (C) 2011-2022 Hyllian - sergiogdb@gmail.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +*/ + +uniform int BICUBIC_FILTER < + ui_type = "combo"; + ui_items = "Bicubic\0Catmull-Rom\0B-Spline\0Hermite\0"; + ui_label = "Bicubic Filter"; + ui_tooltip = "Bicubic: balanced. Catmull-Rom: sharp. B-Spline: blurred. Hermite: soft pixelized."; +> = 0; + + +uniform bool B_ANTI_RINGING < + ui_type = "radio"; + ui_label = "Bicubic Anti-Ringing"; +> = false; + +uniform float2 NormalizedNativePixelSize < source = "normalized_native_pixel_size"; >; +uniform float2 BufferToViewportRatio < source = "buffer_to_viewport_ratio"; >; +uniform float2 ViewportSize < source = "viewportsize"; >; + +texture2D tBicubic_P0{Width=BUFFER_WIDTH;Height=BUFFER_HEIGHT;Format=RGBA8;}; +sampler2D sBicubic_P0{Texture=tBicubic_P0;AddressU=CLAMP;AddressV=CLAMP;AddressW=CLAMP;MagFilter=POINT;MinFilter=POINT;}; + + +#define AR_STRENGTH 1.0 + +// Classic Mitchell-Netravali bicubic parameters + +float4x4 get_inv() +{ + float bf = 1.0/3.0; + float cf = 1.0/3.0; + + if (BICUBIC_FILTER == 1) {bf = 0.0; cf = 0.5;} + if (BICUBIC_FILTER == 2) {bf = 1.0; cf = 0.0;} + if (BICUBIC_FILTER == 3) {bf = 0.0; cf = 0.0;} + + return float4x4( (-bf - 6.0*cf)/6.0, (3.0*bf + 12.0*cf)/6.0, (-3.0*bf - 6.0*cf)/6.0, bf/6.0, + (12.0 - 9.0*bf - 6.0*cf)/6.0, (-18.0 + 12.0*bf + 6.0*cf)/6.0, 0.0, (6.0 - 2.0*bf)/6.0, + -(12.0 - 9.0*bf - 6.0*cf)/6.0, (18.0 - 15.0*bf - 12.0*cf)/6.0, (3.0*bf + 6.0*cf)/6.0, bf/6.0, + (bf + 6.0*cf)/6.0, -cf, 0.0, 0.0); + + +} + +float3 bicubic_ar(float fp, float3 C0, float3 C1, float3 C2, float3 C3) +{ + float4 weights = mul(get_inv(), float4(fp*fp*fp, fp*fp, fp, 1.0)); + float3 color = mul(weights, float4x3( C0, C1, C2, C3 )); + + // Anti-ringing + if (B_ANTI_RINGING == true) + { + float3 aux = color; + float3 min_sample = min(min(C0, C1), min(C2, C3)); + float3 max_sample = max(max(C0, C1), max(C2, C3)); + color = clamp(color, min_sample, max_sample); + color = lerp(aux, color, step(0.0, (C0-C1)*(C2-C3))); + } + + return color; +} + + +float4 Bicubic_X(float4 pos: SV_Position, float2 uv_tx : TEXCOORD) : SV_Target +{ + // Both dimensions are unfiltered, so it looks for lores pixels. + float2 ps = NormalizedNativePixelSize; + float2 posi = uv_tx.xy + ps * float2(0.5, 0.0); + float2 fp = frac(posi / ps); + + float2 tc = posi - (fp + 0.5) * ps; + + float3 C0 = tex2D(ReShade::BackBuffer, tc + ps*float2(-1.0, 1.0)).rgb; + float3 C1 = tex2D(ReShade::BackBuffer, tc + ps*float2( 0.0, 1.0)).rgb; + float3 C2 = tex2D(ReShade::BackBuffer, tc + ps*float2( 1.0, 1.0)).rgb; + float3 C3 = tex2D(ReShade::BackBuffer, tc + ps*float2( 2.0, 1.0)).rgb; + + float3 color = bicubic_ar(fp.x, C0, C1, C2, C3); + + return float4(color, 1.0); +} + + +float4 Bicubic_Y(float4 pos: SV_Position, float2 uv_tx : TEXCOORD) : SV_Target +{ + // One must be careful here. Horizontal dimension is already filtered, so it looks for x in hires. + float2 ps = float2(1.0/(ViewportSize.x*BufferToViewportRatio.x), NormalizedNativePixelSize.y); + float2 posi = uv_tx.xy + ps * float2(0.5, 0.5); + float2 fp = frac(posi / ps); + + float2 tc = posi - (fp + 0.5) * ps; + + float3 C0 = tex2D(sBicubic_P0, tc + ps*float2(1.0, -1.0)).rgb; + float3 C1 = tex2D(sBicubic_P0, tc + ps*float2(1.0, 0.0)).rgb; + float3 C2 = tex2D(sBicubic_P0, tc + ps*float2(1.0, 1.0)).rgb; + float3 C3 = tex2D(sBicubic_P0, tc + ps*float2(1.0, 2.0)).rgb; + + float3 color = bicubic_ar(fp.y, C0, C1, C2, C3); + + return float4(color, 1.0); +} + +technique Bicubic +{ + + pass PS_Bicubic_X + { + VertexShader = PostProcessVS; + PixelShader = Bicubic_X; + RenderTarget = tBicubic_P0; + } + pass PS_Bicubic_Y + { + VertexShader = PostProcessVS; + PixelShader = Bicubic_Y; + } + +} diff --git a/data/resources/shaders/reshade/Shaders/interpolation/lanczos3.fx b/data/resources/shaders/reshade/Shaders/interpolation/lanczos3.fx new file mode 100644 index 000000000..ed23a11b5 --- /dev/null +++ b/data/resources/shaders/reshade/Shaders/interpolation/lanczos3.fx @@ -0,0 +1,144 @@ +#include "ReShade.fxh" + +/* + Lanczos3 - Multipass code by Hyllian 2022. + +*/ + + +/* + Copyright (C) 2010 Team XBMC + http://www.xbmc.org + Copyright (C) 2011 Stefanos A. + http://www.opentk.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, 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 XBMC; see the file COPYING. If not, write to +the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. +http://www.gnu.org/copyleft/gpl.html +*/ + + +uniform bool LANCZOS3_ANTI_RINGING < + ui_type = "radio"; + ui_label = "Lanczos3 Anti-Ringing"; +> = true; + +uniform float2 NormalizedNativePixelSize < source = "normalized_native_pixel_size"; >; +uniform float2 BufferToViewportRatio < source = "buffer_to_viewport_ratio"; >; +uniform float2 ViewportSize < source = "viewportsize"; >; + +texture2D tLanczos3_P0{Width=BUFFER_WIDTH;Height=BUFFER_HEIGHT;Format=RGBA8;}; +sampler2D sLanczos3_P0{Texture=tLanczos3_P0;AddressU=CLAMP;AddressV=CLAMP;AddressW=CLAMP;MagFilter=POINT;MinFilter=POINT;}; + +#define AR_STRENGTH 1.0 +#define FIX(c) (max(abs(c),1e-5)) +#define PI 3.1415926535897932384626433832795 +#define radius 3.0 + +float3 weight3(float x) +{ + float3 Sample = FIX(2.0 * PI * float3(x - 1.5, x - 0.5, x + 0.5)); + + // Lanczos3. Note: we normalize outside this function, so no point in multiplying by radius. + return sin(Sample) * sin(Sample / radius) / (Sample * Sample); +} + +float3 lanczos3ar(float fp, float3 C0, float3 C1, float3 C2, float3 C3, float3 C4, float3 C5) +{ + float3 w1 = weight3(0.5 - fp * 0.5); + float3 w2 = weight3(1.0 - fp * 0.5); + + float sum = dot( w1, float3(1.,1.,1.)) + dot( w2, float3(1.,1.,1.)); + w1 /= sum; + w2 /= sum; + + float3 color = mul(w1, float3x3( C0, C2, C4 )) + mul(w2, float3x3( C1, C3, C5)); + + // Anti-ringing + if (LANCZOS3_ANTI_RINGING == true) + { + float3 aux = color; + float3 min_sample = min(min(C1, C2), min(C3, C4)); + float3 max_sample = max(max(C1, C2), max(C3, C4)); + color = clamp(color, min_sample, max_sample); + color = lerp(aux, color, AR_STRENGTH*step(0.0, (C1-C2)*(C3-C4))); + } + + return color; +} + + +float4 Lanczos3_X(float4 pos: SV_Position, float2 uv_tx : TEXCOORD) : SV_Target +{ + // Both dimensions are unfiltered, so it looks for lores pixels. + float2 ps = NormalizedNativePixelSize; + float2 posi = uv_tx.xy + ps * float2(0.5, 0.0); + float2 fp = frac(posi / ps); + + float2 xystart = posi - (fp + 0.5) * ps; + + float ypos = xystart.y + ps.y; + + float3 C0 = tex2D(ReShade::BackBuffer, float2(xystart.x - ps.x * 2.0, ypos)).rgb; + float3 C1 = tex2D(ReShade::BackBuffer, float2(xystart.x - ps.x * 1.0, ypos)).rgb; + float3 C2 = tex2D(ReShade::BackBuffer, float2(xystart.x , ypos)).rgb; + float3 C3 = tex2D(ReShade::BackBuffer, float2(xystart.x + ps.x * 1.0, ypos)).rgb; + float3 C4 = tex2D(ReShade::BackBuffer, float2(xystart.x + ps.x * 2.0, ypos)).rgb; + float3 C5 = tex2D(ReShade::BackBuffer, float2(xystart.x + ps.x * 3.0, ypos)).rgb; + + float3 color = lanczos3ar(fp.x, C0, C1, C2, C3, C4, C5); + + return float4(color, 1.0); +} + + +float4 Lanczos3_Y(float4 pos: SV_Position, float2 uv_tx : TEXCOORD) : SV_Target +{ + // One must be careful here. Horizontal dimension is already filtered, so it looks for x in hires. + float2 ps = float2(1.0/(ViewportSize.x*BufferToViewportRatio.x), NormalizedNativePixelSize.y); + float2 posi = uv_tx.xy + ps * float2(0.5, 0.5); + float2 fp = frac(posi / ps); + + float2 xystart = posi - (fp + 0.5) * ps; + + float xpos = xystart.x + ps.x; + + float3 C0 = tex2D(sLanczos3_P0, float2(xpos, xystart.y - ps.y * 2.0)).rgb; + float3 C1 = tex2D(sLanczos3_P0, float2(xpos, xystart.y - ps.y * 1.0)).rgb; + float3 C2 = tex2D(sLanczos3_P0, float2(xpos, xystart.y )).rgb; + float3 C3 = tex2D(sLanczos3_P0, float2(xpos, xystart.y + ps.y * 1.0)).rgb; + float3 C4 = tex2D(sLanczos3_P0, float2(xpos, xystart.y + ps.y * 2.0)).rgb; + float3 C5 = tex2D(sLanczos3_P0, float2(xpos, xystart.y + ps.y * 3.0)).rgb; + + float3 color = lanczos3ar(fp.y, C0, C1, C2, C3, C4, C5); + + return float4(color, 1.0); +} + +technique Lanczos3 +{ + + pass PS_Lanczos3_X + { + VertexShader = PostProcessVS; + PixelShader = Lanczos3_X; + RenderTarget = tLanczos3_P0; + } + pass PS_Lanczos3_Y + { + VertexShader = PostProcessVS; + PixelShader = Lanczos3_Y; + } + +} diff --git a/data/resources/shaders/reshade/Shaders/misc/deblur-luma.fx b/data/resources/shaders/reshade/Shaders/misc/deblur-luma.fx new file mode 100644 index 000000000..7d095542a --- /dev/null +++ b/data/resources/shaders/reshade/Shaders/misc/deblur-luma.fx @@ -0,0 +1,151 @@ +#include "ReShade.fxh" + +/* + Deblur-Luma Shader + + Copyright (C) 2005 - 2024 guest(r) - guest.r@gmail.com + + Luma adaptation by Hyllian + + 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. +*/ + +uniform float OFFSET < + ui_type = "drag"; + ui_min = 0.25; + ui_max = 4.0; + ui_step = 0.25; + ui_label = "Deblur offset"; +> = 2.0; + +uniform float DEBLUR < + ui_type = "drag"; + ui_min = 1.0; + ui_max = 7.0; + ui_step = 0.25; + ui_label = "Deblur str."; +> = 1.75; + +uniform float SMART < + ui_type = "drag"; + ui_min = 0.0; + ui_max = 1.0; + ui_step = 0.05; + ui_label = "Smart deblur"; +> = 1.0; + +uniform float2 ViewportSize < source = "viewportsize"; >; + + +static const float3 luma = float3(0.299,0.587,0.114); +static const float4 res = float4(0.0001, 0.0001, 0.0001, 0.0001); +static const float4 uno = float4(1.,1.,1.,1.); + + +float min8(float4 a4, float4 b4) +{ + float4 ab4 = min(a4, b4); float2 ab2 = min(ab4.xy, ab4.zw); return min(ab2.x, ab2.y); +} + +float max8(float4 a4, float4 b4) +{ + float4 ab4 = max(a4, b4); float2 ab2 = max(ab4.xy, ab4.zw); return max(ab2.x, ab2.y); +} + + +struct ST_VertexOut +{ + float4 t1 : TEXCOORD1; + float4 t2 : TEXCOORD2; + float4 t3 : TEXCOORD3; +}; + + +// Vertex shader generating a triangle covering the entire screen +void VS_Deblur_Luma(in uint id : SV_VertexID, out float4 position : SV_Position, out float2 texcoord : TEXCOORD, out ST_VertexOut vVARS) +{ + texcoord.x = (id == 2) ? 2.0 : 0.0; + texcoord.y = (id == 1) ? 2.0 : 0.0; + position = float4(texcoord * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0); + + float dx = OFFSET/ViewportSize.x; + float dy = OFFSET/ViewportSize.y; + + vVARS.t1 = texcoord.xxxy + float4( -dx, 0.0, dx, -dy); // c00 c10 c20 + vVARS.t2 = texcoord.xxxy + float4( -dx, 0.0, dx, 0.0); // c01 c11 c21 + vVARS.t3 = texcoord.xxxy + float4( -dx, 0.0, dx, dy); // c02 c12 c22 +} + + +float4 PS_Deblur_Luma(float4 vpos: SV_Position, float2 vTexCoord : TEXCOORD, in ST_VertexOut vVARS) : SV_Target +{ + + float3 c11 = tex2D(ReShade::BackBuffer, vVARS.t2.yw).xyz; + float3 c00 = tex2D(ReShade::BackBuffer, vVARS.t1.xw).xyz; + float3 c20 = tex2D(ReShade::BackBuffer, vVARS.t1.zw).xyz; + float3 c22 = tex2D(ReShade::BackBuffer, vVARS.t3.zw).xyz; + float3 c02 = tex2D(ReShade::BackBuffer, vVARS.t3.xw).xyz; + float3 c10 = tex2D(ReShade::BackBuffer, vVARS.t1.yw).xyz; + float3 c21 = tex2D(ReShade::BackBuffer, vVARS.t2.zw).xyz; + float3 c12 = tex2D(ReShade::BackBuffer, vVARS.t3.yw).xyz; + float3 c01 = tex2D(ReShade::BackBuffer, vVARS.t2.xw).xyz; + + float4x3 chv = float4x3(c10, c01, c21, c12); + float4x3 cdi = float4x3(c00, c02, c20, c22); + + float4 CHV = mul(chv, luma); + float4 CDI = mul(cdi, luma); + float C11 = dot(c11, luma); + + float mn1 = min8(CHV, CDI); + float mx1 = max8(CHV, CDI); + + float2 mnmx = float2(min(C11, mn1), max(C11, mx1)); + + float2 dif = abs(float2(C11, C11) - mnmx) + res.xy; + + dif = pow(dif, float2(DEBLUR, DEBLUR)); + + float D11 = dot(dif, mnmx.yx)/(dif.x + dif.y); + + float k11 = 1.0/(abs(C11 - D11) + res.x); + + float4 khv = float4(1.0/(abs(CHV-float4(D11, D11, D11, D11)) + res)); + float4 kdi = float4(1.0/(abs(CDI-float4(D11, D11, D11, D11)) + res)); + + float avg = (dot(khv + kdi, uno) + k11)/10.0; + + khv = max(khv-float4(avg, avg, avg, avg), float4(0.0, 0.0, 0.0, 0.0)); + kdi = max(kdi-float4(avg, avg, avg, avg), float4(0.0, 0.0, 0.0, 0.0)); + k11 = max(k11-avg, 0.0); + + float3 d11 = (mul(khv, chv) + mul(kdi, cdi) + (k11 + res.x)*c11) / (dot(khv + kdi, uno) + k11 + res.x); + + float contrast = mnmx.y - mnmx.x; + c11 = lerp(c11, d11, clamp(1.75*contrast-0.125, 0.0, 1.0)); + c11 = lerp(d11, c11, SMART); + + return float4(c11, 1.0); +} + + +technique Deblur_Luma +{ + pass + { + VertexShader = VS_Deblur_Luma; + PixelShader = PS_Deblur_Luma; + } +} diff --git a/data/resources/shaders/reshade/Shaders/misc/geom.fx b/data/resources/shaders/reshade/Shaders/misc/geom.fx new file mode 100644 index 000000000..3cda0a313 --- /dev/null +++ b/data/resources/shaders/reshade/Shaders/misc/geom.fx @@ -0,0 +1,367 @@ +#include "ReShade.fxh" + +/* + Geom Shader - a modified CRT-Geom without CRT features made to be appended/integrated + into any other shaders and provide curvature/warping/oversampling features. + + Adapted by Hyllian (2024). +*/ + + +/* + CRT-interlaced + + Copyright (C) 2010-2012 cgwg, Themaister and DOLLS + + 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. + + (cgwg gave their consent to have the original version of this shader + distributed under the GPL in this message: + + http://board.byuu.org/viewtopic.php?p=26075#p26075 + + "Feel free to distribute my shaders under the GPL. After all, the + barrel distortion code was taken from the Curvature shader, which is + under the GPL." + ) + This shader variant is pre-configured with screen curvature +*/ + + + +uniform float geom_curvature < + ui_type = "drag"; + ui_min = 0.0; + ui_max = 1.0; + ui_step = 1.0; + ui_label = "Geom Curvature Toggle"; +> = 1.0; + +uniform float geom_R < + ui_type = "drag"; + ui_min = 0.1; + ui_max = 10.0; + ui_step = 0.1; + ui_label = "Geom Curvature Radius"; +> = 2.0; + +uniform float geom_d < + ui_type = "drag"; + ui_min = 0.1; + ui_max = 3.0; + ui_step = 0.1; + ui_label = "Geom Distance"; +> = 1.5; + +uniform float geom_invert_aspect < + ui_type = "drag"; + ui_min = 0.0; + ui_max = 1.0; + ui_step = 1.0; + ui_label = "Geom Curvature Aspect Inversion"; +> = 0.0; + +uniform float geom_cornersize < + ui_type = "drag"; + ui_min = 0.001; + ui_max = 1.0; + ui_step = 0.005; + ui_label = "Geom Corner Size"; +> = 0.03; + +uniform float geom_cornersmooth < + ui_type = "drag"; + ui_min = 80.0; + ui_max = 2000.0; + ui_step = 100.0; + ui_label = "Geom Corner Smoothness"; +> = 1000.0; + +uniform float geom_x_tilt < + ui_type = "drag"; + ui_min = -0.5; + ui_max = 0.5; + ui_step = 0.05; + ui_label = "Geom Horizontal Tilt"; +> = 0.0; + +uniform float geom_y_tilt < + ui_type = "drag"; + ui_min = -0.5; + ui_max = 0.5; + ui_step = 0.05; + ui_label = "Geom Vertical Tilt"; +> = 0.0; + +uniform float geom_overscan_x < + ui_type = "drag"; + ui_min = -125.0; + ui_max = 125.0; + ui_step = 0.5; + ui_label = "Geom Horiz. Overscan %"; +> = 100.0; + +uniform float geom_overscan_y < + ui_type = "drag"; + ui_min = -125.0; + ui_max = 125.0; + ui_step = 0.5; + ui_label = "Geom Vert. Overscan %"; +> = 100.0; + +uniform float geom_lum < + ui_type = "drag"; + ui_min = 0.5; + ui_max = 2.0; + ui_step = 0.01; + ui_label = "Geom Luminance"; +> = 1.0; + +uniform float geom_target_gamma < + ui_type = "drag"; + ui_min = 0.1; + ui_max = 5.0; + ui_step = 0.1; + ui_label = "Geom Target Gamma"; +> = 2.4; + +uniform float geom_monitor_gamma < + ui_type = "drag"; + ui_min = 0.1; + ui_max = 5.0; + ui_step = 0.1; + ui_label = "Geom Monitor Gamma"; +> = 2.2; + + +uniform float2 BufferViewportRatio < source = "buffer_to_viewport_ratio"; >; +uniform float2 NormalizedNativePixelSize < source = "normalized_native_pixel_size"; >; +uniform float2 ViewportSize < source = "viewportsize"; >; + +// Comment the next line to disable interpolation in linear gamma (and +// gain speed). +#define LINEAR_PROCESSING + +// Enable 3x oversampling of the beam profile; improves moire effect caused by scanlines+curvature +#define OVERSAMPLE + +// Use the older, purely gaussian beam profile; uncomment for speed +//#define USEGAUSSIAN + +// Macros. +#define FIX(c) max(abs(c), 1e-5); +#define PI 3.141592653589 + +#ifdef LINEAR_PROCESSING +# define TEX2D(c) pow(tex2D(ReShade::BackBuffer, (c)), float4(geom_target_gamma,geom_target_gamma,geom_target_gamma,geom_target_gamma)) +#else +# define TEX2D(c) tex2D(ReShade::BackBuffer, (c)) +#endif + +// aspect ratio +#define aspect (geom_invert_aspect>0.5?float2(0.75,1.0):float2(1.0,0.75)) +#define overscan (float2(1.01,1.01)); + + +struct ST_VertexOut +{ + float2 sinangle : TEXCOORD1; + float2 cosangle : TEXCOORD2; + float3 stretch : TEXCOORD3; + float2 TextureSize : TEXCOORD4; +}; + + +float vs_intersect(float2 xy, float2 sinangle, float2 cosangle) +{ + float A = dot(xy,xy) + geom_d*geom_d; + float B = 2.0*(geom_R*(dot(xy,sinangle)-geom_d*cosangle.x*cosangle.y)-geom_d*geom_d); + float C = geom_d*geom_d + 2.0*geom_R*geom_d*cosangle.x*cosangle.y; + + return (-B-sqrt(B*B-4.0*A*C))/(2.0*A); +} + +float2 vs_bkwtrans(float2 xy, float2 sinangle, float2 cosangle) +{ + float c = vs_intersect(xy, sinangle, cosangle); + float2 point = (float2(c, c)*xy - float2(-geom_R, -geom_R)*sinangle) / float2(geom_R, geom_R); + float2 poc = point/cosangle; + + float2 tang = sinangle/cosangle; + float A = dot(tang, tang) + 1.0; + float B = -2.0*dot(poc, tang); + float C = dot(poc, poc) - 1.0; + + float a = (-B + sqrt(B*B - 4.0*A*C))/(2.0*A); + float2 uv = (point - a*sinangle)/cosangle; + float r = FIX(geom_R*acos(a)); + + return uv*r/sin(r/geom_R); +} + +float2 vs_fwtrans(float2 uv, float2 sinangle, float2 cosangle) +{ + float r = FIX(sqrt(dot(uv,uv))); + uv *= sin(r/geom_R)/r; + float x = 1.0-cos(r/geom_R); + float D = geom_d/geom_R + x*cosangle.x*cosangle.y+dot(uv,sinangle); + + return geom_d*(uv*cosangle-x*sinangle)/D; +} + +float3 vs_maxscale(float2 sinangle, float2 cosangle) +{ + float2 c = vs_bkwtrans(-geom_R * sinangle / (1.0 + geom_R/geom_d*cosangle.x*cosangle.y), sinangle, cosangle); + float2 a = float2(0.5,0.5)*aspect; + + float2 lo = float2(vs_fwtrans(float2(-a.x, c.y), sinangle, cosangle).x, + vs_fwtrans(float2( c.x, -a.y), sinangle, cosangle).y)/aspect; + + float2 hi = float2(vs_fwtrans(float2(+a.x, c.y), sinangle, cosangle).x, + vs_fwtrans(float2( c.x, +a.y), sinangle, cosangle).y)/aspect; + + return float3((hi+lo)*aspect*0.5,max(hi.x-lo.x,hi.y-lo.y)); +} + + + +// Vertex shader generating a triangle covering the entire screen +void VS_CRT_Geom(in uint id : SV_VertexID, out float4 position : SV_Position, out float2 texcoord : TEXCOORD, out ST_VertexOut vVARS) +{ + texcoord.x = (id == 2) ? 2.0 : 0.0; + texcoord.y = (id == 1) ? 2.0 : 0.0; + position = float4(texcoord * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0); + + // float2 SourceSize = 1.0/NormalizedNativePixelSize; + float2 SourceSize = ViewportSize*BufferViewportRatio; + + // Precalculate a bunch of useful values we'll need in the fragment + // shader. + vVARS.sinangle = sin(float2(geom_x_tilt, geom_y_tilt)); + vVARS.cosangle = cos(float2(geom_x_tilt, geom_y_tilt)); + vVARS.stretch = vs_maxscale(vVARS.sinangle, vVARS.cosangle); + vVARS.TextureSize = float2(SourceSize.x, SourceSize.y); +} + + + +float intersect(float2 xy, float2 sinangle, float2 cosangle) +{ + float A = dot(xy,xy) + geom_d*geom_d; + float B, C; + + B = 2.0*(geom_R*(dot(xy,sinangle) - geom_d*cosangle.x*cosangle.y) - geom_d*geom_d); + C = geom_d*geom_d + 2.0*geom_R*geom_d*cosangle.x*cosangle.y; + + return (-B-sqrt(B*B - 4.0*A*C))/(2.0*A); +} + +float2 bkwtrans(float2 xy, float2 sinangle, float2 cosangle) +{ + float c = intersect(xy, sinangle, cosangle); + float2 point = (float2(c, c)*xy - float2(-geom_R, -geom_R)*sinangle) / float2(geom_R, geom_R); + float2 poc = point/cosangle; + float2 tang = sinangle/cosangle; + + float A = dot(tang, tang) + 1.0; + float B = -2.0*dot(poc, tang); + float C = dot(poc, poc) - 1.0; + + float a = (-B + sqrt(B*B - 4.0*A*C)) / (2.0*A); + float2 uv = (point - a*sinangle) / cosangle; + float r = FIX(geom_R*acos(a)); + + return uv*r/sin(r/geom_R); +} + +float2 fwtrans(float2 uv, float2 sinangle, float2 cosangle) +{ + float r = FIX(sqrt(dot(uv, uv))); + uv *= sin(r/geom_R)/r; + float x = 1.0 - cos(r/geom_R); + float D; + + D = geom_d/geom_R + x*cosangle.x*cosangle.y + dot(uv,sinangle); + + return geom_d*(uv*cosangle - x*sinangle)/D; +} + +float3 maxscale(float2 sinangle, float2 cosangle) +{ + float2 c = bkwtrans(-geom_R * sinangle / (1.0 + geom_R/geom_d*cosangle.x*cosangle.y), sinangle, cosangle); + float2 a = float2(0.5, 0.5)*aspect; + + float2 lo = float2(fwtrans(float2(-a.x, c.y), sinangle, cosangle).x, + fwtrans(float2( c.x, -a.y), sinangle, cosangle).y)/aspect; + float2 hi = float2(fwtrans(float2(+a.x, c.y), sinangle, cosangle).x, + fwtrans(float2( c.x, +a.y), sinangle, cosangle).y)/aspect; + + return float3((hi+lo)*aspect*0.5,max(hi.x-lo.x, hi.y-lo.y)); +} + +float2 transform(float2 coord, float2 sinangle, float2 cosangle, float3 stretch) +{ + coord = (coord - float2(0.5, 0.5))*aspect*stretch.z + stretch.xy; + + return (bkwtrans(coord, sinangle, cosangle) / + float2(geom_overscan_x / 100.0, geom_overscan_y / 100.0)/aspect + float2(0.5, 0.5)); +} + +float corner(float2 coord) +{ + coord = (coord - float2(0.5, 0.5)) * float2(geom_overscan_x / 100.0, geom_overscan_y / 100.0) + float2(0.5, 0.5); + coord = min(coord, float2(1.0, 1.0) - coord) * aspect; + float2 cdist = float2(geom_cornersize, geom_cornersize); + coord = (cdist - min(coord, cdist)); + float dist = sqrt(dot(coord, coord)); + + return clamp((cdist.x - dist)*geom_cornersmooth, 0.0, 1.0); +} + +float fwidth(float value){ + return abs(ddx(value)) + abs(ddy(value)); +} + + +float4 PS_CRT_Geom(float4 vpos: SV_Position, float2 vTexCoord : TEXCOORD, in ST_VertexOut vVARS) : SV_Target +{ + // Texture coordinates of the texel containing the active pixel. + float2 xy; + + if (geom_curvature > 0.5) + xy = transform(vTexCoord, vVARS.sinangle, vVARS.cosangle, vVARS.stretch); + else + xy = vTexCoord; + + float cval = corner(xy); + + float2 uv_ratio = frac((xy * vVARS.TextureSize - float2(0.5, 0.5)) / vVARS.TextureSize); + + float4 col = TEX2D(xy); + +#ifndef LINEAR_PROCESSING + col = pow(col , float4(geom_target_gamma, geom_target_gamma, geom_target_gamma, geom_target_gamma)); +#endif + + col.rgb *= (geom_lum * step(0.0, uv_ratio.y)); + + float3 mul_res = col.rgb * float3(cval, cval, cval); + + // Convert the image gamma for display on our output device. + mul_res = pow(mul_res, float3(1.0 / geom_monitor_gamma, 1.0 / geom_monitor_gamma, 1.0 / geom_monitor_gamma)); + + return float4(mul_res, 1.0); +} + + +technique CRT_Geom +{ + pass + { + VertexShader = VS_CRT_Geom; + PixelShader = PS_CRT_Geom; + } +} diff --git a/data/resources/shaders/reshade/Shaders/misc/multi-LUT.fx b/data/resources/shaders/reshade/Shaders/misc/multi-LUT.fx new file mode 100644 index 000000000..c1215e4b7 --- /dev/null +++ b/data/resources/shaders/reshade/Shaders/misc/multi-LUT.fx @@ -0,0 +1,75 @@ +#include "ReShade.fxh" + + +// Multi-LUT Shader + +// A simple shader that can load 2 LUTs. +// Can turn LUT off too. + + + +uniform int LUT_selector < + ui_type = "combo"; + ui_items = "Off\0Grade-RGB\0Grade-Composite\0"; + ui_label = "LUT selector"; + ui_tooltip = "Off: nothing. Grade-RGB: rgb trinitron colors. Grade-Composite: composite trinitron colors."; +> = 1; + + +texture tLUT1{Width=1024;Height=32;}; +sampler SamplerLUT1{Texture=tLUT1;}; + +texture tLUT2{Width=1024;Height=32;}; +sampler SamplerLUT2{Texture=tLUT2;}; + +// This shouldn't be necessary but it seems some undefined values can +// creep in and each GPU vendor handles that differently. This keeps +// all values within a safe range +float4 mixfix(float4 a, float4 b, float c) +{ + return (a.z < 1.0) ? lerp(a, b, c) : a; +} + +float4 multiLUT(float4 vpos: SV_Position, float2 vTexCoord : TEXCOORD0) : SV_Target +{ + float4 imgColor = tex2D(ReShade::BackBuffer, vTexCoord.xy); + + if (LUT_selector > 0) + { + //float LUT_Size = lerp(textureSize(SamplerLUT1, 0).y, textureSize(SamplerLUT2, 0).y, LUT_selector_param - 1.0); + float LUT_Size = 32.0; + float4 color1, color2 = float4(0.,0.,0.,0.); + float red, green, blue1, blue2, mixer = 0.0; + + red = ( imgColor.r * (LUT_Size - 1.0) + 0.4999 ) / (LUT_Size * LUT_Size); + green = ( imgColor.g * (LUT_Size - 1.0) + 0.4999 ) / LUT_Size; + blue1 = (floor( imgColor.b * (LUT_Size - 1.0) ) / LUT_Size) + red; + blue2 = (ceil( imgColor.b * (LUT_Size - 1.0) ) / LUT_Size) + red; + mixer = clamp(max((imgColor.b - blue1) / (blue2 - blue1), 0.0), 0.0, 32.0); + + if(LUT_selector == 1) + { + color1 = tex2D(SamplerLUT1, float2( blue1, green )); + color2 = tex2D(SamplerLUT1, float2( blue2, green )); + } + else + { + color1 = tex2D(SamplerLUT2, float2( blue1, green )); + color2 = tex2D(SamplerLUT2, float2( blue2, green )); + } + imgColor = mixfix(color1, color2, mixer); + } + + return imgColor; +} + + + +technique multiLUT +{ + pass PS_multiLUT + { + VertexShader = PostProcessVS; + PixelShader = multiLUT; + } +} diff --git a/data/resources/shaders/reshade/Textures/multi-LUT/grade-composite.png b/data/resources/shaders/reshade/Textures/multi-LUT/grade-composite.png new file mode 100644 index 0000000000000000000000000000000000000000..45c7fe64c360350678eebb8aea8613d9c5ebc7ce GIT binary patch literal 30241 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 z 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