duckstation

duckstation, but archived from the revision just before upstream changed it to a proprietary software project, this version is the libre one
git clone https://git.neptards.moe/u3shit/duckstation.git
Log | Files | Refs | README | LICENSE

crt-hyllian-sinc.fx (12221B)


      1 #include "ReShade.fxh"
      2 
      3 /*
      4     Hyllian's CRT-sinc Shader
      5 
      6     Copyright (C) 2011-2024 Hyllian
      7 
      8     Permission is hereby granted, free of charge, to any person obtaining a copy
      9     of this software and associated documentation files (the "Software"), to deal
     10     in the Software without restriction, including without limitation the rights
     11     to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     12     copies of the Software, and to permit persons to whom the Software is
     13     furnished to do so, subject to the following conditions:
     14 
     15     The above copyright notice and this permission notice shall be included in
     16     all copies or substantial portions of the Software.
     17 
     18     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     19     IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     20     FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
     21     AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     22     LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
     23     OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
     24     THE SOFTWARE.
     25 */
     26 
     27 
     28 
     29 uniform int HFILTER_PROFILE <
     30     ui_type = "combo";
     31     ui_items = "Custom\0Composite\0Composite Soft\0";
     32     ui_label = "H-FILTER PROFILE";
     33 > = 0;
     34 
     35 uniform float SHP <
     36     ui_type = "drag";
     37     ui_min = 0.50;
     38     ui_max = 1.0;
     39     ui_step = 0.01;
     40     ui_label = "CUSTOM H-FILTER SHARPNESS";
     41 > = 1.0;
     42 
     43 uniform bool CRT_ANTI_RINGING <
     44     ui_type = "radio";
     45     ui_label = "ANTI RINGING";
     46 > = true;
     47 
     48 uniform bool SHARPNESS_HACK <
     49     ui_type = "radio";
     50     ui_label = "SHARPNESS HACK";
     51 > = false;
     52 
     53 uniform float CRT_InputGamma <
     54     ui_type = "drag";
     55     ui_min = 1.0;
     56     ui_max = 5.0;
     57     ui_step = 0.1;
     58     ui_label = "INPUT GAMMA";
     59 > = 2.4;
     60 
     61 uniform float CRT_OutputGamma <
     62     ui_type = "drag";
     63     ui_min = 1.0;
     64     ui_max = 5.0;
     65     ui_step = 0.05;
     66     ui_label = "OUTPUT GAMMA";
     67 > = 2.2;
     68 
     69 uniform int MASK_LAYOUT <
     70     ui_type = "combo";
     71     ui_items = "0-Off\0"
     72                "1-Aperture Classic\0""2-Aperture1 RGB 1080p\0""3-Aperture2 RGB 1080p\0""4-Aperture1 RGB 4k\0""5-Aperture2 RGB 4k\0""6-Aperture3 RGB 4k\0"
     73                "7-Shadow Classic\0""8-Shadow1 1080p\0""9-Shadow2 1080p\0""10-Shadow1 4k\0"
     74                "11-Slot1 1080p\0""12-Slot2 1080p\0""13-Slot1 4k\0""14-Slot1 4k\0""15-Slot1 8k\0";
     75     ui_category = "CRT Mask";
     76     ui_label = "MASK LAYOUT";
     77 > = 1;
     78 
     79 uniform int MONITOR_SUBPIXELS <
     80     ui_type = "combo";
     81     ui_items = "RGB\0BGR\0";
     82     ui_category = "CRT Mask";
     83     ui_label = "MONITOR SUBPIXELS LAYOUT";
     84 > = 0;
     85 
     86 uniform float BRIGHTBOOST <
     87     ui_type = "drag";
     88     ui_min = 0.0;
     89     ui_max = 3.0;
     90     ui_step = 0.05;
     91     ui_label = "BRIGHTNESS BOOST";
     92 > = 1.0;
     93 
     94 uniform float BEAM_MIN_WIDTH <
     95     ui_type = "drag";
     96     ui_min = 0.0;
     97     ui_max = 1.0;
     98     ui_step = 0.01;
     99     ui_label = "MIN BEAM WIDTH";
    100 > = 0.86;
    101 
    102 uniform float BEAM_MAX_WIDTH <
    103     ui_type = "drag";
    104     ui_min = 0.0;
    105     ui_max = 1.0;
    106     ui_step = 0.01;
    107     ui_label = "MAX BEAM WIDTH";
    108 > = 1.0;
    109 
    110 uniform float SCANLINES_STRENGTH <
    111     ui_type = "drag";
    112     ui_min = 0.0;
    113     ui_max = 1.0;
    114     ui_step = 0.01;
    115     ui_label = "SCANLINES STRENGTH";
    116 > = 0.72;
    117 
    118 uniform int SCANLINES_SHAPE <
    119     ui_type = "combo";
    120     ui_items = "Sinc\0Gaussian\0";
    121     ui_label = "SCANLINES SHAPE";
    122 > = 1.0;
    123 
    124 uniform float SCANLINES_CUTOFF <
    125     ui_type = "drag";
    126     ui_min = 0.0;
    127     ui_max = 1000.0;
    128     ui_step = 1.0;
    129     ui_label = "SCANLINES CUTOFF";
    130     ui_tooltip = "Max vertical native resolution above which scanlines are disabled.";
    131 > = 390.0;
    132 
    133 uniform bool SCANLINES_HIRES <
    134     ui_type = "radio";
    135     ui_label = "HIGH RESOLUTION SCANLINES";
    136 > = false;
    137 
    138 uniform float POST_BRIGHTNESS <
    139     ui_type = "drag";
    140     ui_min = 1.0;
    141     ui_max = 3.0;
    142     ui_step = 0.05;
    143     ui_label = "POST-BRIGHTNESS";
    144 > = 1.00;
    145 
    146 uniform bool VSCANLINES <
    147     ui_type = "radio";
    148     ui_label = "VERTICAL SCANLINES";
    149 > = false;
    150 
    151 
    152 uniform float2 NormalizedNativePixelSize < source = "normalized_native_pixel_size"; >;
    153 uniform float  BufferWidth               < source = "bufferwidth"; >;
    154 uniform float  BufferHeight              < source = "bufferheight"; >;
    155 uniform float2 BufferToViewportRatio     < source = "buffer_to_viewport_ratio"; >;
    156 uniform float2 ViewportSize              < source = "viewportsize"; >;
    157 uniform float  ViewportWidth             < source = "viewportwidth"; >;
    158 uniform float  ViewportHeight            < source = "viewportheight"; >;
    159 uniform float  UpscaleMultiplier         < source = "upscale_multiplier"; >;
    160 
    161 
    162 #include "../misc/include/mask.fxh"
    163 #include "../misc/include/geom.fxh"
    164 
    165 
    166 sampler2D sBackBuffer{Texture=ReShade::BackBufferTex;AddressU=BORDER;AddressV=BORDER;AddressW=BORDER;MagFilter=POINT;MinFilter=POINT;};
    167 
    168 texture2D tBackBufferLinear{Width=BUFFER_WIDTH;Height=BUFFER_HEIGHT;Format=RGBA16f;};
    169 sampler2D sBackBufferLinear{Texture=tBackBufferLinear;AddressU=CLAMP;AddressV=CLAMP;AddressW=CLAMP;MagFilter=POINT;MinFilter=POINT;};
    170 
    171 #define GAMMA_IN(color)    pow(color, float3(CRT_InputGamma, CRT_InputGamma, CRT_InputGamma))
    172 #define GAMMA_OUT(color)   pow(color, float3(1.0 / CRT_OutputGamma, 1.0 / CRT_OutputGamma, 1.0 / CRT_OutputGamma))
    173 
    174 #define SCANLINES_STRENGTH (-0.16*SCANLINES_SHAPE+SCANLINES_STRENGTH)
    175 #define CORNER_SMOOTHNESS (80.0*pow(CORNER_SMOOTHNESS,10.0))
    176 
    177 #define pi    3.1415926535897932384626433832795
    178 
    179 #define RADIUS  2.0  // No need for more than 2-taps
    180 
    181 float2 get_hfilter_profile()
    182 {
    183     float2 hf_profile = float2(SHP, RADIUS);
    184 
    185     if      (HFILTER_PROFILE == 1) hf_profile = float2(0.78, 2.0); // SNES composite
    186     else if (HFILTER_PROFILE == 2) hf_profile = float2(0.65, 2.0); // Genesis composite
    187 
    188     return hf_profile;
    189 }
    190 
    191 /* Some window functions for tests. */
    192 float4 sinc(float4 x)              { return sin(pi*x)*(1.0/(pi*x+0.001.xxxx)); }
    193 float4 hann_window(float4 x)       { return 0.5 * ( 1.0 - cos( 0.5 * pi * ( x + 2.0 ) ) ); }
    194 float4 blackman_window(float4 x)   { return 0.42 - 0.5*cos(0.5*pi*(x+2.0)) + 0.08*cos(pi*(x+2.0)); }
    195 float4 lanczos(float4 x, float a)  { return sinc(x) * sinc(x / a); }
    196 float4 blackman(float4 x, float a) { return sinc(x) * blackman_window(x); }
    197 float4 hann(float4 x, float a)     { return sinc(x) * hann_window(x); }
    198 
    199 float4 resampler4(float4 x, float2 hfp)
    200 {
    201     return blackman(x * hfp.x, hfp.y);
    202 }
    203 
    204 
    205 #define wa    (0.5*pi)
    206 #define wb    (pi)
    207 
    208 float3 resampler3(float3 x)
    209 {
    210     float3 res;
    211 
    212     res.x = (x.x<=0.001) ?  1.0  :  sin(x.x*wa)*sin(x.x*wb)/(wa*wb*x.x*x.x);
    213     res.y = (x.y<=0.001) ?  1.0  :  sin(x.y*wa)*sin(x.y*wb)/(wa*wb*x.y*x.y);
    214     res.z = (x.z<=0.001) ?  1.0  :  sin(x.z*wa)*sin(x.z*wb)/(wa*wb*x.z*x.z);
    215 
    216     return res;
    217 }
    218 
    219 float3 get_scanlines(float3 d0, float3 d1, float3 color0, float3 color1)
    220 {
    221     if (SCANLINES_SHAPE > 0.5) {
    222         d0 = exp(-16.0*d0*d0);
    223         d1 = exp(-16.0*d1*d1);
    224     }
    225     else {
    226         d0 = clamp(2.0*d0, 0.0, 1.0);
    227         d1 = clamp(2.0*d1, 0.0, 1.0);
    228         d0 = resampler3(d0);
    229         d1 = resampler3(d1);
    230     }
    231 
    232     return (BRIGHTBOOST*(color0*d0+color1*d1));
    233 }
    234 
    235 float4 PS_BackBufferLinear(float4 vpos: SV_Position, float2 vTexCoord : TEXCOORD) : SV_Target
    236 {
    237 //    float2 tc = (floor(vTexCoord / NormalizedNativePixelSize) + 0.5.xx) * NormalizedNativePixelSize;
    238 
    239     return float4(GAMMA_IN(tex2D(sBackBuffer, vTexCoord).rgb), 1.0);
    240 }
    241 
    242 struct ST_VertexOut
    243 {
    244     float2 sinangle    : TEXCOORD1;
    245     float2 cosangle    : TEXCOORD2;
    246     float3 stretch     : TEXCOORD3;
    247     float2 TextureSize : TEXCOORD4;
    248 };
    249 
    250 
    251 // Vertex shader generating a triangle covering the entire screen
    252 void VS_CRT_Geom(in uint id : SV_VertexID, out float4 position : SV_Position, out float2 texcoord : TEXCOORD, out ST_VertexOut vVARS)
    253 {
    254     texcoord.x = (id == 2) ? 2.0 : 0.0;
    255     texcoord.y = (id == 1) ? 2.0 : 0.0;
    256     position = float4(texcoord * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
    257 
    258     // Screen centering
    259     texcoord = texcoord - float2(centerx,centery)/100.0;
    260 
    261     float2 SourceSize = 1.0/NormalizedNativePixelSize;
    262     float shp_hack = 1.0 + float(SHARPNESS_HACK);
    263 
    264  
    265     // Precalculate a bunch of useful values we'll need in the fragment
    266     // shader.
    267     vVARS.sinangle    = sin(float2(geom_x_tilt, geom_y_tilt));
    268     vVARS.cosangle    = cos(float2(geom_x_tilt, geom_y_tilt));
    269     vVARS.stretch     = maxscale(vVARS.sinangle, vVARS.cosangle);
    270     vVARS.TextureSize = lerp(float2(shp_hack*SourceSize.x, SourceSize.y), float2(SourceSize.x, shp_hack*SourceSize.y), VSCANLINES);
    271 }
    272 
    273 
    274 float4 PS_CRT_Hyllian(float4 vpos: SV_Position, float2 vTexCoord : TEXCOORD0, in ST_VertexOut vVARS) : SV_Target
    275 {
    276     float2 OutputSize = float2(BufferWidth, BufferHeight);
    277 
    278     float2 TextureSize = vVARS.TextureSize;
    279 
    280     float2 dx = lerp(float2(1.0/TextureSize.x, 0.0), float2(0.0, 1.0/TextureSize.y), VSCANLINES);
    281     float2 dy = lerp(float2(0.0, 1.0/TextureSize.y), float2(1.0/TextureSize.x, 0.0), VSCANLINES);
    282 
    283     // Texture coordinates of the texel containing the active pixel.
    284     float2 WarpedTexCoord = (geom_curvature == true) ? transform(vTexCoord, vVARS.sinangle, vVARS.cosangle, vVARS.stretch) : vTexCoord;
    285 
    286     float cval = corner((WarpedTexCoord-0.5.xx) * BufferToViewportRatio + 0.5.xx);
    287 
    288     float2 pix_coord = WarpedTexCoord*TextureSize - 0.5.xx;
    289   
    290     float2 tc = ( (SCANLINES_HIRES == true) ? (lerp(float2(floor(pix_coord.x), pix_coord.y), float2(pix_coord.x, floor(pix_coord.y)), VSCANLINES) + float2(0.5, 0.5)) : (floor(pix_coord) + float2(0.5, 0.5)) )/TextureSize;
    291 
    292     float2 fp = lerp(frac(pix_coord), frac(pix_coord.yx), VSCANLINES);
    293 
    294     float3 c00 = tex2D(sBackBufferLinear, tc     - dx).xyz;
    295     float3 c01 = tex2D(sBackBufferLinear, tc         ).xyz;
    296     float3 c02 = tex2D(sBackBufferLinear, tc     + dx).xyz;
    297     float3 c03 = tex2D(sBackBufferLinear, tc + 2.0*dx).xyz;
    298 
    299     float3 c10, c11, c12, c13;
    300 
    301     if (SCANLINES_HIRES == false)
    302     {
    303         c10 = tex2D(sBackBufferLinear, tc     - dx + dy).xyz;
    304         c11 = tex2D(sBackBufferLinear, tc          + dy).xyz;
    305         c12 = tex2D(sBackBufferLinear, tc     + dx + dy).xyz;
    306         c13 = tex2D(sBackBufferLinear, tc + 2.0*dx + dy).xyz;
    307     }
    308     else { c10 = c00; c11 = c01; c12 = c02; c13 = c03;}
    309 
    310     float4x3 color_matrix0 = float4x3(c00, c01, c02, c03);
    311     float4x3 color_matrix1 = float4x3(c10, c11, c12, c13);
    312 
    313     float2 hfp = get_hfilter_profile();
    314 
    315     float4 weights = resampler4(float4(1.0+fp.x, fp.x, 1.0-fp.x, 2.0-fp.x), hfp);
    316 
    317     float3 color0   = mul(weights, color_matrix0)/dot(weights, 1.0.xxxx);
    318     float3 color1   = mul(weights, color_matrix1)/dot(weights, 1.0.xxxx);
    319 
    320     // Get min/max samples
    321     float3 min_sample0 = min(c01,c02);
    322     float3 max_sample0 = max(c01,c02);
    323     float3 min_sample1 = min(c11,c12);
    324     float3 max_sample1 = max(c11,c12);
    325   
    326     // Anti-ringing
    327     float3 aux = color0;
    328     color0 = clamp(color0, min_sample0, max_sample0);
    329     color0 = lerp(aux, color0, CRT_ANTI_RINGING);
    330     aux = color1;
    331     color1 = clamp(color1, min_sample1, max_sample1);
    332     color1 = lerp(aux, color1, CRT_ANTI_RINGING);
    333 
    334     float pos0 = fp.y;
    335     float pos1 = 1 - fp.y;
    336 
    337     float3 lum0 = lerp(BEAM_MIN_WIDTH.xxx, BEAM_MAX_WIDTH.xxx, color0);
    338     float3 lum1 = lerp(BEAM_MIN_WIDTH.xxx, BEAM_MAX_WIDTH.xxx, color1);
    339 
    340     float3 d0 = SCANLINES_STRENGTH*pos0/(lum0*lum0+0.0000001.xxx);
    341     float3 d1 = SCANLINES_STRENGTH*pos1/(lum1*lum1+0.0000001.xxx);
    342 
    343     float3 color  = (vVARS.TextureSize.y <= SCANLINES_CUTOFF) ? get_scanlines(d0, d1, color0, color1) : tex2D(sBackBufferLinear, WarpedTexCoord.xy).xyz;
    344 
    345     color *=  BRIGHTBOOST;
    346 
    347     color  = GAMMA_OUT(color);
    348 
    349     float2 mask_coords =vTexCoord.xy * OutputSize.xy;
    350 
    351     mask_coords = lerp(mask_coords.xy, mask_coords.yx, VSCANLINES);
    352 
    353     color.rgb*=GAMMA_OUT(mask_weights(mask_coords, MASK_LAYOUT, MONITOR_SUBPIXELS, MASK_DARK_STRENGTH, MASK_LIGHT_STRENGTH));
    354     
    355     float4 res = float4(POST_BRIGHTNESS*color, 1.0);
    356 
    357     res.rgb = res.rgb * cval.xxx;
    358 
    359     return float4(res.rgb, 1.0);
    360 }
    361 
    362 technique CRT_Hyllian
    363 {
    364    pass
    365    {
    366        VertexShader = PostProcessVS;
    367        PixelShader  = PS_BackBufferLinear;
    368        RenderTarget = tBackBufferLinear;
    369    }
    370    pass
    371    {
    372        VertexShader = VS_CRT_Geom;
    373        PixelShader  = PS_CRT_Hyllian;
    374    }
    375 }