Removing scanlines from CRT-Geom Halation

Me again. Hope this question isn’t too inane.

I would like to remove the scanlines and other lines from CRT Geom Halation. Why not use Sharpness you ask? That’s actually too sharp. I like the fuzziness of CRT Geom. But I don’t care for the scanlines. I don’t think they directly add anything, and they require integer scaling. I would prefer to have non-integer scaline to make use of the entire screen.

So, which parts are you wanting to keep? You can get the corner parts, curvature parts, and/or the dot-mask parts on their own. The scanlines and interlacing detection/correction go together because it uses the scanlines to mask half of the lines each frame (as I understand it).

I broke it down in quark format (higan’s new multipass GLSL format) if you’d like to take a look at the individual passes: https://github.com/hizzlekizzle/quark-s … ted.shader

I was wondering how to do something similar for my android phone as the whole shader is too expensive for it.

Is it possible to have just the linear interpolation in gamma? or linear + lanczo (I fear this will be already too much calculation)?

This is my thinking. A perfect CRT emulation is impossible at our current display resolutions We’ll need something on the order of 8000p to do good shadowmask replication. However, such perfect emulation of CRT screens is not necessary. Instead we should focus on the overall effect, rather than trying to replicate all of the nitty gritty details. So pixels should have a kind of roundness like on a CRT. CRT Geom does this pretty well. The black lines however, I’m not sure if they directly do anything. I’ve heard that they help sharpen the image. I’m not sure. I do think they are a bit problematic because they require interger scaling to properly work, which means blackbars at the top and bottom of the scrreen. I would like to use as much of the screen as possible. So something like 1440x1080.

So I would like the black lines (what are they supposed to be called?) removed. I do know that they are an integral part of CRT Geom, so I don’t know if they can be removed easily. It’s not just something applied on top. Would it be possible to make them so faint that they are transparent/invisible? This might require an entirely new shader.

So basically LCDs are not CRTs and shouldn’t try to pretend they are, and shaders should focus on adapting SD games to HD screens to keep the same quality of image, without trying to focus on perfect CRT emulation.

I broke it down in quark format (higan’s new multipass GLSL format) if you’d like to take a look at the individual passes: https://github.com/hizzlekizzle/quark-s … ted.shader

I guess this is higan only. I’ll test this out tomorrow when I have time.

About this quark format shader, it’s an adaptation of crt-geom I think.

I just noticed crt-interlaced-halation-pass2.cg is a more advanced variation of those CRT shaders. The scaling is better using the full screen while the older function called radialDistortion in crt-geom leaves some black space at the top and bottom of your LCD.

You also can use tweaks like tilt angle, simulated distance from viewer to monitor, radius of curvature which aren’t working in crt-geom (just using a simpler “distortion” value with less customization).

I was trying to get rid of the “Interlation” part of this crt-interlaced-halation-pass2 by trying to adapt the parts from crt-geom contained in #ifdef INTERLACED #else to make it work with Sega Master System and others (black screen if interlaced is activated). I managed to get a picture but with some issues like artefacts around the scanlines.

@Tatsuya79 I’ve already done that, actually :slight_smile: viewtopic.php?f=6&t=1115#p9901 To use it standalone, you’ll also want to replace any of the ORIG stuff with IN structs.

@Tripulent Yes, I agree that the overall effect vs the little details are not necessarily the same task. I recommend checking out aliaspider’s GTU shader. I think he posted about it here in the shader subforum, and here’s an older version if the new one doesn’t work for you, as it doesn’t for me: https://code.google.com/p/interpolation … r&can=2&q= Its goal is exactly what you described: to get the feel of a CRT without focusing on all of the phosphors and curvature, etc. that are the main features of cgwg’s CRT shaders.

Thanks I missed that, but… I did the same modification you did in fact!

It’s not working great: I just tried Alex Kid in Miracle World with Genesis Plus GX and your code, there is the problem I mentioned before. Scanlines are bugged with some kind of artefacts. But I didn’t do the ORIG/IN modification, or to be exact I failed at it. Could it be the reason for this bug?

Try this one:

/* COMPATIBILITY
   - HLSL compilers
   - Cg   compilers
*/

/*
    CRT-interlaced-halation shader - pass2

    Like the CRT-interlaced shader, but adds a subtle glow around bright areas
    of the screen.

    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."
    )
*/

        // Comment the next line to disable interpolation in linear gamma (and
        // gain speed).
        #define LINEAR_PROCESSING

        // Enable screen curvature.
        #define CURVATURE

        // Enable 3x oversampling of the beam profile
        #define OVERSAMPLE

        // Use the older, purely gaussian beam profile
        //#define USEGAUSSIAN
      
      // Use interlacing detection; may interfere with other shaders if combined
      #define INTERLACED

        // Macros.
        #define FIX(c) max(abs(c), 1e-5);
        #define PI 3.141592653589

        #ifdef LINEAR_PROCESSING
        #       define TEX2D(c) pow(tex2D(IN.texture, (c)), float4(CRTgamma))
        #else
        #       define TEX2D(c) tex2D(IN.texture, (c))
        #endif



                // START of parameters

                // gamma of simulated CRT
                static float CRTgamma = 2.4;
                // gamma of display monitor (typically 2.2 is correct)
                static float monitorgamma = 2.2;
                // overscan (e.g. 1.02 for 2% overscan)
                static float2 overscan = float2(1.01,1.01);
                // aspect ratio
                static float2 aspect = float2(1.0, 0.75);
                // lengths are measured in units of (approximately) the width
                // of the monitor simulated distance from viewer to monitor
                static float d = 1.5;
                // radius of curvature
                static float R = 2.0;
                // tilt angle in radians
                // (behavior might be a bit wrong if both components are
                // nonzero)
                const static float2 angle = float2(0.0,-0.0);
                // size of curved corners
                static float cornersize = 0.03;
                // border smoothness parameter
                // decrease if borders are too aliased
                static float cornersmooth = 80.0;

                // END of parameters


        float 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 bkwtrans(float2 xy, float2 sinangle, float2 cosangle)
        {
                float c = intersect(xy, sinangle, cosangle);
                float2 point = float2(c)*xy;
                point -= float2(-R)*sinangle;
                point /= float2(R);
                float2 tang = sinangle/cosangle;
                float2 poc = point/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 = d/R + x*cosangle.x*cosangle.y+dot(uv,sinangle);
                return d*(uv*cosangle-x*sinangle)/D;
        }

        float3 maxscale(float2 sinangle, float2 cosangle)
        {
                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));
        }

        // 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));
                float4 weights = float4(distance / wid);
                return 0.4 * exp(-weights * weights) / wid;
        #else
                float4 wid = 2.0 + 2.0 * pow(color, float4(4.0));
                float4 weights = float4(distance / 0.3);
                return 1.4 * exp(-pow(weights * rsqrt(0.5 * wid), wid)) / (0.6 + 0.2 * wid);
        #endif
        }

struct input
{
    float2 tex_coord;
    float2 video_size;
    float2 texture_size;
    float2 output_size;
    sampler2D texture;
	float frame_count;
};

struct out_vertex {
    float4 position : POSITION;
    float4 color : COLOR;
    float2 texCoord : TEXCOORD0;
        float2 one;
        float mod_factor;
        float2 ilfac;
        float3 stretch;
        float2 sinangle;
        float2 cosangle;
};

/* VERTEX_SHADER */
out_vertex main_vertex
(
    float4 position : POSITION,
    float4 color : COLOR,
    float2 texCoord : TEXCOORD0,
    uniform float4x4 modelViewProj,
    uniform input IN
)
{
    out_vertex OUT;

    OUT.position = mul(modelViewProj, position);
    OUT.color = color;

    // Precalculate a bunch of useful values we'll need in the fragment
    // shader.
    OUT.sinangle = sin(angle);
    OUT.cosangle = cos(angle);
    OUT.stretch = maxscale(OUT.sinangle, OUT.cosangle);
	OUT.texCoord = texCoord;

    OUT.ilfac = float2(1.0,floor(IN.video_size.y/200.0));

    // The size of one texel, in texture-coordinates.
    OUT.one = OUT.ilfac / IN.texture_size;

    // Resulting X pixel-coordinate of the pixel we're drawing.
    OUT.mod_factor = texCoord.x * IN.texture_size.x * IN.output_size.x / IN.video_size.x;

    return OUT;
}

/* FRAGMENT SHADER */
float4 main_fragment(in out_vertex VAR, uniform input IN) : COLOR
{
                // 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.
        #ifdef CURVATURE
                float2 cd = VAR.texCoord;
                cd *= IN.texture_size / IN.video_size;
                cd = (cd-float2(0.5))*aspect*VAR.stretch.z+VAR.stretch.xy;
                float2 xy =  (bkwtrans(cd, VAR.sinangle, VAR.cosangle)/overscan/aspect+float2(0.5)) * IN.video_size / IN.texture_size;

        #else
                float2 xy = VAR.texCoord;
        #endif
                float2 cd2 = xy;
                cd2 *= IN.texture_size / IN.video_size;
                cd2 = (cd2 - float2(0.5)) * overscan + float2(0.5);
                cd2 = min(cd2, float2(1.0)-cd2) * aspect;
                float2 cdist = float2(cornersize);
                cd2 = (cdist - min(cd2,cdist));
                float dist = sqrt(dot(cd2,cd2));
                float cval = clamp((cdist.x-dist)*cornersmooth,0.0, 1.0);

                float2 xy2 = ((xy*IN.texture_size/IN.video_size-float2(0.5))*float2(1.0,1.0)+float2(0.5))*IN.video_size/IN.texture_size;
                // Of all the pixels that are mapped onto the texel we are
                // currently rendering, which pixel are we currently rendering?
                float2 ilfloat = float2(0.0,VAR.ilfac.y > 1.5 ? fmod(float(IN.frame_count),2.0) : 0.0);
      #ifdef INTERLACED
                float2 ratio_scale = (xy * IN.texture_size - float2(0.5) + ilfloat)/VAR.ilfac;
      #else
            float2 ratio_scale = xy * IN.texture_size - float2(0.5);
      #endif
      
        #ifdef OVERSAMPLE
                //float filter = fwidth(ratio_scale.y);
                float filter = (IN.video_size / (IN.output_size * IN.texture_size) * ratio_scale.y);
        #endif
                float2 uv_ratio = frac(ratio_scale);

                // Snap to the center of the underlying texel.
      #ifdef INTERLACED
                xy = (floor(ratio_scale)*VAR.ilfac + float2(0.5) - ilfloat) / IN.texture_size;
      #else
            xy = (floor(ratio_scale) + float2(0.5)) / IN.texture_size;
      #endif

                // Calculate Lanczos scaling coefficients describing the effect
                // of various neighbour texels in a scanline on the current
                // pixel.
                float4 coeffs = PI * float4(1.0 + uv_ratio.x, uv_ratio.x, 1.0 - uv_ratio.x, 2.0 - uv_ratio.x);

                // 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));

                // 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  = clamp(mul(coeffs, float4x4(
                    TEX2D(xy + float2(-VAR.one.x, 0.0)),
                    TEX2D(xy),
                    TEX2D(xy + float2(VAR.one.x, 0.0)),
                    TEX2D(xy + float2(2.0 * VAR.one.x, 0.0)))),
            0.0, 1.0);
    float4 col2 = clamp(mul(coeffs, float4x4(
                    TEX2D(xy + float2(-VAR.one.x, VAR.one.y)),
                    TEX2D(xy + float2(0.0, VAR.one.y)),
                    TEX2D(xy + VAR.one),
                    TEX2D(xy + float2(2.0 * VAR.one.x, VAR.one.y)))),
            0.0, 1.0);


        #ifndef LINEAR_PROCESSING
                col  = pow(col , float4(CRTgamma));
                col2 = pow(col2, float4(CRTgamma));
        #endif

                // Calculate the influence of the current and next scanlines on
                // the current pixel.
                float4 weights  = scanlineWeights(uv_ratio.y, col);
                float4 weights2 = scanlineWeights(1.0 - uv_ratio.y, col2);
        #ifdef OVERSAMPLE
                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
                float3 mul_res  = (col * weights + col2 * weights2).rgb;
                mul_res += pow(tex2D(IN.texture, xy2).rgb, float3(monitorgamma))*0.1;
                mul_res *= float3(cval);

                // dot-mask emulation:
                // Output pixels are alternately tinted green and magenta.
                float3 dotMaskWeights = lerp(
                        float3(1.0, 0.7, 1.0),
                        float3(0.7, 1.0, 0.7),
                        floor(fmod(VAR.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));

                // Color the texel.
                return float4(mul_res, 1.0);
}

If this works for everyone, we could probably get rid of the one with the simplified curvature function.

Nope, still the same issue.

Commenting Interlaced and launching a Master System game still gives those unaligned scanlines and aliased picture.

There’s also something different than crt-geom: if you comment the part for the shadowmask

/*
  float3 dotMaskWeights = lerp(
          float3(1.0, 0.7, 1.0),
          float3(0.7, 1.0, 0.7),
          floor(mod(co.mod_factor, 2.0))
      );
                    
  mul_res *= dotMaskWeights;
  */

You’ll get a black screen, while it’s working for crt-geom. I just changed everything to 1.0 in crt-interlaced-halation-pass2 to remove the dots. Don’t know why that happens.

If you comment out the interlacing detection, it starts looking fine on my machine, but commenting out curvature may help, too. It seems to work fine with Genesis games (at least on my end), so it seems to be something up specifically with SMS games.

All systems that give a black screen with crt-halation interlaced ON will have the bad scanlines with interlaced OFF. These are: -Master System -GameGear -GameBoy -GameBoy Advanced -Wonderswan -NeoGeo Pocket

All the others are OK like they were with interlaced ON already, such as the megadrive.

Just realised the Master System could be 192p for most games, all those systems are even lower. Tried several games in Mame: those 192p or less are showing the same issue, while 208p is OK (yes found one with that resolution lol).

edit: tested Micro Machines, one of the few games in 224p on the Master System, no issue with it when it has switched to this mode after the logo. So that’s it, problem with low resolutions.

This is a trivial thing to fix. The “interlacing detection” was written with SNES in mind, and I did it very lazily:

OUT.ilfac = float2(1.0,floor(IN.video_size.y/200.0));

We want this to equal 1 for uninterlaced content and 2 for interlaced content. Since the complaints are all associated with having a height of less than 200 pixels, this should fix it:

OUT.ilfac = float2(1.0,clamp(floor(IN.video_size.y/200.0),1.0,2.0));

You’re the man!

Now working for everything.

This should be it with all fixes and more switchable options if you want to post it on github Hunterk. (and if you want to tweak it)

    /* COMPATIBILITY
       - HLSL compilers
       - Cg   compilers
    */

    /*
        CRT-interlaced-halation shader - pass2

        Like the CRT-interlaced shader, but adds a subtle glow around bright areas
        of the screen.

        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."
        )
    */

            // Comment the next line to disable interpolation in linear gamma (and
            // gain speed).
            #define LINEAR_PROCESSING

            // Enable screen curvature.
            #define CURVATURE

            // Enable 3x oversampling of the beam profile
            //#define OVERSAMPLE

            // Use the older, purely gaussian beam profile
            #define USEGAUSSIAN
          
            // Use interlacing detection; may interfere with other shaders if combined
            #define INTERLACED
			
			// Enable Dot-mask emulation:
            // Output pixels are alternately tinted green and magenta.
			//#define DOTMASK
			
			//Enable if using several shaders. 
			//Disabling it reduces moiré for single pass.
			//#define MULTIPASS
			
            // Macros.
            #define FIX(c) max(abs(c), 1e-5);
            #define PI 3.141592653589

            #ifdef LINEAR_PROCESSING
            #       define TEX2D(c) pow(tex2D(ORIG.texture, (c)), float4(CRTgamma))
            #else
            #       define TEX2D(c) tex2D(ORIG.texture, (c))
            #endif



                    // START of parameters

                    // gamma of simulated CRT
                    static float CRTgamma = 2.4;
                    // gamma of display monitor (typically 2.2 is correct)
                    static float monitorgamma = 2.2;
                    // overscan (e.g. 1.02 for 2% overscan)
                    static float2 overscan = float2(1.0,1.0);
                    // aspect ratio
                    static float2 aspect = float2(1.0, 0.75);
                    // lengths are measured in units of (approximately) the width
                    // of the monitor simulated distance from viewer to monitor
                    static float d = 2.0;
                    // radius of curvature
                    static float R = 2.0;
                    // tilt angle in radians
                    // (behavior might be a bit wrong if both components are
                    // nonzero)
                    const static float2 angle = float2(0.0,-0.15);
                    // size of curved corners
                    static float cornersize = 0.01;
                    // border smoothness parameter
                    // decrease if borders are too aliased
                    static float cornersmooth = 800.0;

                    // END of parameters


            float 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 bkwtrans(float2 xy, float2 sinangle, float2 cosangle)
            {
                    float c = intersect(xy, sinangle, cosangle);
                    float2 point = float2(c)*xy;
                    point -= float2(-R)*sinangle;
                    point /= float2(R);
                    float2 tang = sinangle/cosangle;
                    float2 poc = point/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 = d/R + x*cosangle.x*cosangle.y+dot(uv,sinangle);
                    return d*(uv*cosangle-x*sinangle)/D;
            }

            float3 maxscale(float2 sinangle, float2 cosangle)
            {
                    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));
            }

            // 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));
                    float4 weights = float4(distance / wid);
                    return 0.4 * exp(-weights * weights) / wid;
            #else
                    float4 wid = 2.0 + 2.0 * pow(color, float4(4.0));
                    float4 weights = float4(distance / 0.3);
                    return 1.4 * exp(-pow(weights * rsqrt(0.5 * wid), wid)) / (0.6 + 0.2 * wid);
            #endif
            }

    struct orig
    {
        float2 tex_coord;
        uniform float2 video_size;
        uniform float2 texture_size;
        uniform float2 output_size;
        uniform sampler2D texture;
    };


    struct input
    {
        float2 video_size;
        float2 texture_size;
        float2 output_size;
        float frame_count;
        float frame_direction;
        float frame_rotation;
    };


    struct out_vertex {
        float4 position : POSITION;
        float4 color : COLOR;
        float2 texCoord : TEXCOORD0;
            float2 one;
            float mod_factor;
            float2 ilfac;
            float3 stretch;
            float2 sinangle;
            float2 cosangle;
    };



    /* VERTEX_SHADER */
    out_vertex main_vertex
    (
        float4 position : POSITION,
        float4 color : COLOR,
        float2 texCoord : TEXCOORD0,

        uniform float4x4 modelViewProj,
        orig ORIG,
        uniform input IN
    )
    {

        out_vertex OUT;

        OUT.position = mul(modelViewProj, position);
        OUT.color = color;


                    // Precalculate a bunch of useful values we'll need in the fragment
                    // shader.
                    OUT.sinangle = sin(angle);
                    OUT.cosangle = cos(angle);
                    OUT.stretch = maxscale(OUT.sinangle, OUT.cosangle);
        OUT.texCoord = texCoord;


                    OUT.ilfac = float2(1.0,clamp(floor(IN.video_size.y/200.0),1.0,2.0));

                    // The size of one texel, in texture-coordinates.
                    OUT.one = OUT.ilfac / ORIG.texture_size;

                    // Resulting X pixel-coordinate of the pixel we're drawing.
                    OUT.mod_factor = texCoord.x * ORIG.texture_size.x * IN.output_size.x / ORIG.video_size.x;

        return OUT;
    }

	
    /* FRAGMENT SHADER */
    float4 main_fragment(in out_vertex VAR, uniform sampler2D decal : TEXUNIT0, orig ORIG, uniform input IN) : COLOR
    {

    /*        float2 transform(float2 coord)
            {
                    coord *= ORIG.texture_size / ORIG.video_size;
                    coord = (coord-float2(0.5))*aspect*stretch.z+stretch.xy;
                    return (bkwtrans(coord)/overscan/aspect+float2(0.5)) * ORIG.video_size / ORIG.texture_size;
            }

            float corner(float2 coord)
            {
                    coord *= ORIG.texture_size / ORIG.video_size;
                    coord = (coord - float2(0.5)) * overscan + float2(0.5);
                    coord = min(coord, float2(1.0)-coord) * aspect;
                    float2 cdist = float2(cornersize);
                    coord = (cdist - min(coord,cdist));
                    float dist = sqrt(dot(coord,coord));
                    return clamp((cdist.x-dist)*cornersmooth,0.0, 1.0);
            }
    */

                    // 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.
            #ifdef CURVATURE
                    float2 cd = VAR.texCoord;
                    cd *= ORIG.texture_size / ORIG.video_size;
                    cd = (cd-float2(0.5))*aspect*VAR.stretch.z+VAR.stretch.xy;
                    float2 xy =  (bkwtrans(cd, VAR.sinangle, VAR.cosangle)/overscan/aspect+float2(0.5)) * ORIG.video_size / ORIG.texture_size;

            #else
                    float2 xy = VAR.texCoord;
            #endif
                    float2 cd2 = xy;
                    cd2 *= ORIG.texture_size / ORIG.video_size;
                    cd2 = (cd2 - float2(0.5)) * overscan + float2(0.5);
                    cd2 = min(cd2, float2(1.0)-cd2) * aspect;
                    float2 cdist = float2(cornersize);
                    cd2 = (cdist - min(cd2,cdist));
                    float dist = sqrt(dot(cd2,cd2));
                    float cval = clamp((cdist.x-dist)*cornersmooth,0.0, 1.0);

                    float2 xy2 = ((xy*ORIG.texture_size/ORIG.video_size-float2(0.5))*float2(1.0,1.0)+float2(0.5))*IN.video_size/IN.texture_size;
                    // Of all the pixels that are mapped onto the texel we are
                    // currently rendering, which pixel are we currently rendering?
                    float2 ilfloat = float2(0.0,VAR.ilfac.y > 1.5 ? fmod(float(IN.frame_count),2.0) : 0.0);
          #ifdef INTERLACED
                    float2 ratio_scale = (xy * IN.texture_size - float2(0.5) + ilfloat)/VAR.ilfac;
          #else
                float2 ratio_scale = xy * IN.texture_size - float2(0.5);
          #endif
          
            #ifdef OVERSAMPLE
                    //float filter = fwidth(ratio_scale.y);
                    float filter = IN.video_size.y / IN.output_size.y;
            #endif
                    float2 uv_ratio = frac(ratio_scale);

                    // Snap to the center of the underlying texel.
          #ifdef INTERLACED
                    xy = (floor(ratio_scale)*VAR.ilfac + float2(0.5) - ilfloat) / IN.texture_size;
          #else
                xy = (floor(ratio_scale) + float2(0.5)) / IN.texture_size;
          #endif

                    // Calculate Lanczos scaling coefficients describing the effect
                    // of various neighbour texels in a scanline on the current
                    // pixel.
                    float4 coeffs = PI * float4(1.0 + uv_ratio.x, uv_ratio.x, 1.0 - uv_ratio.x, 2.0 - uv_ratio.x);

                    // 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));

                    // 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  = clamp(mul(coeffs, float4x4(
                        TEX2D(xy + float2(-VAR.one.x, 0.0)),
                        TEX2D(xy),
                        TEX2D(xy + float2(VAR.one.x, 0.0)),
                        TEX2D(xy + float2(2.0 * VAR.one.x, 0.0)))),
						0.0, 1.0);
					float4 col2 = clamp(mul(coeffs, float4x4(
                        TEX2D(xy + float2(-VAR.one.x, VAR.one.y)),
                        TEX2D(xy + float2(0.0, VAR.one.y)),
                        TEX2D(xy + VAR.one),
                        TEX2D(xy + float2(2.0 * VAR.one.x, VAR.one.y)))),
						0.0, 1.0);


            #ifndef LINEAR_PROCESSING
                    col  = pow(col , float4(CRTgamma));
                    col2 = pow(col2, float4(CRTgamma));
            #endif

                    // Calculate the influence of the current and next scanlines on
                    // the current pixel.
                    float4 weights  = scanlineWeights(uv_ratio.y, col);
                    float4 weights2 = scanlineWeights(1.0 - uv_ratio.y, col2);
            #ifdef OVERSAMPLE
                    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
                    float3 mul_res  = (col * weights + col2 * weights2).rgb;
			#ifdef MULTIPASS
					mul_res += pow(tex2D(decal, xy2).rgb, float3(monitorgamma))*0.1;
			#endif
					mul_res *= float3(cval);

                    // dot-mask emulation:
                    // Output pixels are alternately tinted green and magenta.
			#ifdef DOTMASK
                    float3 dotMaskWeights = lerp(
                            float3(1.0, 0.7, 1.0),
                            float3(0.7, 1.0, 0.7),
                            floor(fmod(VAR.mod_factor, 2.0))
                        );
            #else
                    float3 dotMaskWeights = lerp(
                            float3(1.0, 1.0, 1.0),
                            float3(1.0, 1.0, 1.0),
                            floor(fmod(VAR.mod_factor, 2.0))
                        );
            #endif
                    

                    mul_res *= dotMaskWeights;

                    // Convert the image gamma for display on our output device.
                    mul_res = pow(mul_res, float3(1.0 / monitorgamma));

                    // Color the texel.
                    return float4(mul_res, 1.0);
    }


EDIT: Changed “Circular Effect” to “Multipass”. Guess it’s what the line does (yes I don’t understand the shader code that much!). Disabling this line makes a great difference when playing vertical games in landscape on a 1080p screen (when the resolution is low to summarize, like 600 pixels wide in that scenario).

@Tripulent

There’s someone here that made some tweaks to play with the scanlines. I’m not sure that’s how it should be done but you can play around with it to see if that can suit you.

It pleases me to see people tweaking my shaders to their liking (although I don’t approve of using low values for the “cornersmooth” parameter to get a vignetting effect — I don’t think that was very common in CRTs, and I think it can also cause the border to be more jagged).

I noticed that on the shmups forum, some screenshots were posted that showed a big Moiré effect (and there was some discussion of the matter). This is bigger than I remember, so I checked here, and the oversampling is not correct:

#ifdef OVERSAMPLE
        //float filter = fwidth(ratio_scale.y);
        float filter = (IN.video_size / (IN.output_size * IN.texture_size) * ratio_scale.y);
#endif

“filter” should definitely not be proportional to ratio_scale.y. The commented line is correct, but (I think) it’s faster and almost as good to use the ratio of the input height to the output height. I don’t have the time right now to get a current version of Retroarch and test it myself.

I’ve also been meaning to replace the sampling with an approximation of an integral (I used an integral in my LCD shader, and the GTU shader does it as well) which could hopefully further reduce Moiré effects.

changing it to

float filter = IN.video_size.y / IN.output_size.y;

indeed reduces the moire. It was changed because fwidth is broken with some drivers (AMD…) but yeah, it was changed to something that didn’t really work.

EDIT: alright, I committed these changes to the common-shaders repo, so everything should be up-to-date there. Let me know if I missed anything.

There’s still an issue with the interlaced switch: try a game like Syvalion or Cruis’n USA which is 400p not interlaced. -with it on it will try to deinterlace which is not needed, no problem here -with it off the image is wrong and out of the scanlines

The old CRT-geom code was working for that. Don’t know how to fix it now.

This one is more of a detail, but you can put cornersmooth to 8000 instead of 1000 for crt-geom-flat. It makes something like a pixel more visible on each side of the screen (you really need to compare both setting side to side to catch this difference).

I found a workaround for the interlaced switch not working. I removed the old switch and made a new one with this code:

#ifdef INTERLACED
     OUT.ilfac = float2(1.0,clamp(floor(IN.video_size.y/200.0),1.0,2.0));
#else
     OUT.ilfac = float2(1.0,clamp(floor(IN.video_size.y/1000.0),1.0,2.0));
#endif

So that means anything under 2000p won’t get de-interlaced I guess. Enough to be a interlaced off trick. It’s working for everything I tried.

So the new code is:

    /* COMPATIBILITY
       - HLSL compilers
       - Cg   compilers
    */

    /*
        CRT-interlaced-halation shader - pass2

        Like the CRT-interlaced shader, but adds a subtle glow around bright areas
        of the screen.

        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."
        )
    */

            // Comment the next line to disable interpolation in linear gamma (and
            // gain speed).
            #define LINEAR_PROCESSING

            // Enable screen curvature.
            #define CURVATURE

            // Enable 3x oversampling of the beam profile
            //#define OVERSAMPLE

            // Use the older, purely gaussian beam profile
            #define USEGAUSSIAN
          
            // Use interlacing detection; may interfere with other shaders if combined
            //#define INTERLACED
			
			// Enable Dot-mask emulation:
            // Output pixels are alternately tinted green and magenta.
			//#define DOTMASK
			
			//Enable if using several shaders. 
			//Disabling it reduces moiré for single pass.
			//#define MULTIPASS
			
            // Macros.
            #define FIX(c) max(abs(c), 1e-5);
            #define PI 3.141592653589

            #ifdef LINEAR_PROCESSING
            #       define TEX2D(c) pow(tex2D(ORIG.texture, (c)), float4(CRTgamma))
            #else
            #       define TEX2D(c) tex2D(ORIG.texture, (c))
            #endif



                    // START of parameters

                    // gamma of simulated CRT
                    static float CRTgamma = 2.4;
                    // gamma of display monitor (typically 2.2 is correct)
                    static float monitorgamma = 2.2;
                    // overscan (e.g. 1.02 for 2% overscan)
                    static float2 overscan = float2(1.0,1.0);
                    // aspect ratio
                    static float2 aspect = float2(1.0, 0.75);
                    // lengths are measured in units of (approximately) the width
                    // of the monitor simulated distance from viewer to monitor
                    static float d = 1.9;
                    // radius of curvature
                    static float R = 2.0;
                    // tilt angle in radians
                    // (behavior might be a bit wrong if both components are
                    // nonzero)
                    const static float2 angle = float2(0.0,-0.15);
                    // size of curved corners
                    static float cornersize = 0.01;
                    // border smoothness parameter
                    // decrease if borders are too aliased
                    static float cornersmooth = 800.0;

                    // END of parameters


            float 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 bkwtrans(float2 xy, float2 sinangle, float2 cosangle)
            {
                    float c = intersect(xy, sinangle, cosangle);
                    float2 point = float2(c)*xy;
                    point -= float2(-R)*sinangle;
                    point /= float2(R);
                    float2 tang = sinangle/cosangle;
                    float2 poc = point/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 = d/R + x*cosangle.x*cosangle.y+dot(uv,sinangle);
                    return d*(uv*cosangle-x*sinangle)/D;
            }

            float3 maxscale(float2 sinangle, float2 cosangle)
            {
                    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));
            }

            // 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));
                    float4 weights = float4(distance / wid);
                    return 0.4 * exp(-weights * weights) / wid;
            #else
                    float4 wid = 2.0 + 2.0 * pow(color, float4(4.0));
                    float4 weights = float4(distance / 0.3);
                    return 1.4 * exp(-pow(weights * rsqrt(0.5 * wid), wid)) / (0.6 + 0.2 * wid);
            #endif
            }

    struct orig
    {
        float2 tex_coord;
        uniform float2 video_size;
        uniform float2 texture_size;
        uniform float2 output_size;
        uniform sampler2D texture;
    };


    struct input
    {
        float2 video_size;
        float2 texture_size;
        float2 output_size;
        float frame_count;
        float frame_direction;
        float frame_rotation;
    };


    struct out_vertex {
        float4 position : POSITION;
        float4 color : COLOR;
        float2 texCoord : TEXCOORD0;
            float2 one;
            float mod_factor;
            float2 ilfac;
            float3 stretch;
            float2 sinangle;
            float2 cosangle;
    };



    /* VERTEX_SHADER */
    out_vertex main_vertex
    (
        float4 position : POSITION,
        float4 color : COLOR,
        float2 texCoord : TEXCOORD0,

        uniform float4x4 modelViewProj,
        orig ORIG,
        uniform input IN
    )
    {

        out_vertex OUT;

        OUT.position = mul(modelViewProj, position);
        OUT.color = color;


                    // Precalculate a bunch of useful values we'll need in the fragment
                    // shader.
                    OUT.sinangle = sin(angle);
                    OUT.cosangle = cos(angle);
                    OUT.stretch = maxscale(OUT.sinangle, OUT.cosangle);
        OUT.texCoord = texCoord;

		#ifdef INTERLACED
				OUT.ilfac = float2(1.0,clamp(floor(IN.video_size.y/200.0),1.0,2.0));
        #else
				OUT.ilfac = float2(1.0,clamp(floor(IN.video_size.y/1000.0),1.0,2.0));
        #endif
                    

                    // The size of one texel, in texture-coordinates.
                    OUT.one = OUT.ilfac / ORIG.texture_size;

                    // Resulting X pixel-coordinate of the pixel we're drawing.
                    OUT.mod_factor = texCoord.x * ORIG.texture_size.x * IN.output_size.x / ORIG.video_size.x;

        return OUT;
    }

	
    /* FRAGMENT SHADER */
    float4 main_fragment(in out_vertex VAR, uniform sampler2D decal : TEXUNIT0, orig ORIG, uniform input IN) : COLOR
    {

    /*        float2 transform(float2 coord)
            {
                    coord *= ORIG.texture_size / ORIG.video_size;
                    coord = (coord-float2(0.5))*aspect*stretch.z+stretch.xy;
                    return (bkwtrans(coord)/overscan/aspect+float2(0.5)) * ORIG.video_size / ORIG.texture_size;
            }

            float corner(float2 coord)
            {
                    coord *= ORIG.texture_size / ORIG.video_size;
                    coord = (coord - float2(0.5)) * overscan + float2(0.5);
                    coord = min(coord, float2(1.0)-coord) * aspect;
                    float2 cdist = float2(cornersize);
                    coord = (cdist - min(coord,cdist));
                    float dist = sqrt(dot(coord,coord));
                    return clamp((cdist.x-dist)*cornersmooth,0.0, 1.0);
            }
    */

                    // 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.
            #ifdef CURVATURE
                    float2 cd = VAR.texCoord;
                    cd *= ORIG.texture_size / ORIG.video_size;
                    cd = (cd-float2(0.5))*aspect*VAR.stretch.z+VAR.stretch.xy;
                    float2 xy =  (bkwtrans(cd, VAR.sinangle, VAR.cosangle)/overscan/aspect+float2(0.5)) * ORIG.video_size / ORIG.texture_size;

            #else
                    float2 xy = VAR.texCoord;
            #endif
                    float2 cd2 = xy;
                    cd2 *= ORIG.texture_size / ORIG.video_size;
                    cd2 = (cd2 - float2(0.5)) * overscan + float2(0.5);
                    cd2 = min(cd2, float2(1.0)-cd2) * aspect;
                    float2 cdist = float2(cornersize);
                    cd2 = (cdist - min(cd2,cdist));
                    float dist = sqrt(dot(cd2,cd2));
                    float cval = clamp((cdist.x-dist)*cornersmooth,0.0, 1.0);

                    float2 xy2 = ((xy*ORIG.texture_size/ORIG.video_size-float2(0.5))*float2(1.0,1.0)+float2(0.5))*IN.video_size/IN.texture_size;
                    // Of all the pixels that are mapped onto the texel we are
                    // currently rendering, which pixel are we currently rendering?
                    float2 ilfloat = float2(0.0,VAR.ilfac.y > 1.5 ? fmod(float(IN.frame_count),2.0) : 0.0);
                    float2 ratio_scale = (xy * IN.texture_size - float2(0.5) + ilfloat)/VAR.ilfac;
          
            #ifdef OVERSAMPLE
                    //float filter = fwidth(ratio_scale.y);
                    float filter = IN.video_size.y / IN.output_size.y;
            #endif
                    float2 uv_ratio = frac(ratio_scale);

                    // Snap to the center of the underlying texel.
                    xy = (floor(ratio_scale)*VAR.ilfac + float2(0.5) - ilfloat) / IN.texture_size;

                    // Calculate Lanczos scaling coefficients describing the effect
                    // of various neighbour texels in a scanline on the current
                    // pixel.
                    float4 coeffs = PI * float4(1.0 + uv_ratio.x, uv_ratio.x, 1.0 - uv_ratio.x, 2.0 - uv_ratio.x);

                    // 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));

                    // 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  = clamp(mul(coeffs, float4x4(
                        TEX2D(xy + float2(-VAR.one.x, 0.0)),
                        TEX2D(xy),
                        TEX2D(xy + float2(VAR.one.x, 0.0)),
                        TEX2D(xy + float2(2.0 * VAR.one.x, 0.0)))),
						0.0, 1.0);
					float4 col2 = clamp(mul(coeffs, float4x4(
                        TEX2D(xy + float2(-VAR.one.x, VAR.one.y)),
                        TEX2D(xy + float2(0.0, VAR.one.y)),
                        TEX2D(xy + VAR.one),
                        TEX2D(xy + float2(2.0 * VAR.one.x, VAR.one.y)))),
						0.0, 1.0);


            #ifndef LINEAR_PROCESSING
                    col  = pow(col , float4(CRTgamma));
                    col2 = pow(col2, float4(CRTgamma));
            #endif

                    // Calculate the influence of the current and next scanlines on
                    // the current pixel.
                    float4 weights  = scanlineWeights(uv_ratio.y, col);
                    float4 weights2 = scanlineWeights(1.0 - uv_ratio.y, col2);
            #ifdef OVERSAMPLE
                    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
                    float3 mul_res  = (col * weights + col2 * weights2).rgb;
			#ifdef MULTIPASS
					mul_res += pow(tex2D(decal, xy2).rgb, float3(monitorgamma))*0.1;
			#endif
					mul_res *= float3(cval);

                    // dot-mask emulation:
                    // Output pixels are alternately tinted green and magenta.
			#ifdef DOTMASK
                    float3 dotMaskWeights = lerp(
                            float3(1.0, 0.7, 1.0),
                            float3(0.7, 1.0, 0.7),
                            floor(fmod(VAR.mod_factor, 2.0))
                        );
            #else
                    float3 dotMaskWeights = lerp(
                            float3(1.0, 1.0, 1.0),
                            float3(1.0, 1.0, 1.0),
                            floor(fmod(VAR.mod_factor, 2.0))
                        );
            #endif
                    

                    mul_res *= dotMaskWeights;

                    // Convert the image gamma for display on our output device.
                    mul_res = pow(mul_res, float3(1.0 / monitorgamma));

                    // Color the texel.
                    return float4(mul_res, 1.0);
    }


Now this CRT shader should be the one working for every scenario.

If you just want to disable the interlacing detection altogether–which is what your change does–you can just comment out the #define INTERLACED line near the top of the shader.