CRT-Interlaced-Halation Shader Black Pixel!

I love using the crt-interlaced-halation shader for that old school crt look, but have noticed that in the center of the screen there appears to be a few pixels that are black, and whilst isnt massively distracting how ive seen it i cant unsee it. Can it be removed at all??

Heres a pic showing the black pixel…

I’m not seeing any problems here, though I can see what you’re talking about in your screenshot. Does it happen with both OpenGL and D3D drivers? What about in the non-halated CRT shader(s)?

I noticed it as well on my 1080p screen (opengl). It can be removed by tweaking some values in crt-interlaced-halation-pass2.cg.

I don’t remember which one of those two it is, but I changed them like that and the issue is gone:

            // 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 = 1.6;

I’ll give that a go, thanks!!

That seems to work for some game but not other, for instance it works for outrun but for Alien Vs Predator the Black pixel is still there?

Also i have noticed for some later arcade games, like Ridge Racer this shader has some strange effects every few seconds like the screen judders and the scanlines disappear or merge into each other, i think it might be to do with the screen being 480p resolution, Strider 2’s title screen also does it and some Playstation games also, like G-Police title screen and the menu screens in Ridge Racer 4 . Im guess its because these screens are progressive and not interlaced???

Same here on AVP. Guess it’s something about some pixels getting mixed together because of the geometry effect. I’m not using this shader too much though, haven’t test much about it.

Did you try what Hunterk asked you? (d3d/gl and such) I’m on an nvidia card btw.

Yes, the interlacing detection will kick in on anything with a vertical resolution >400, so true 480p+ games (and putting other shaders that increase the resolution before it) will trip it up. You can try replacing crt-interlaced-halation-pass2.cg with this version, which has the interlacing detection #ifdefed (you can enable/disable it by uncommenting/commenting line 44, ‘#define INTERLACED’):

/* 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(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.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 = 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,floor(IN.video_size.y/200.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 / (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(decal, 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);
}

Im on an AMD/ATi card, how do u change from d3d or gl, i think this shader is only opengl isnt it? I dont have any non-halated crt shaders, where can i get them from please?

Yes, the interlacing detection will kick in on anything with a vertical resolution >400, so true 480p+ games (and putting other shaders that increase the resolution before it) will trip it up. You can try replacing crt-interlaced-halation-pass2.cg with this version, which has the interlacing detection #ifdefed (you can enable/disable it by uncommenting/commenting line 44, ‘#define INTERLACED’): [/quote]

That does indeed sort the judder issue out, but the letter appears to be out of lineament/foucs…or maybe the scanline over lap??..

That looks like it’s actually interlaced, so you would want interlacing detection to be on. It will indeed introduce the judder you mentioned but that’s how those things actually worked (i.e., double resolution but at half the framerate by showing half of the alternating fields each frame), though it wasn’t as noticeable on real CRTs because of the persistence.

As for OGL/D3D, one of the advantages of Cg shaders is that you can use either driver (you’ll have to specify it in your cfg file) because Cg can compile to native HLSL or GLSL on the fly. The old XML shaders were already GLSL and thus OpenGL-specific.

Well this is the thing, the screen isnt judder at all its rock solid, just the letter looks out of focus

Ah noticed when you have the mame core loaded and go into the Drivers setting (under settings) you can select GL or D3D9, but if i select D3D9 then when i load a game the game screen is small and position in the lower left corner of my monitor??

Is CRT-Interlaced-Halation a Cg shader then? Im new to retroarch how do i specify this in the cfg file?

Using the version I posted earlier? If so, that’s because I commented/disabled the interlacing detection (by putting ‘//’ in front of the line that says ‘#define INTERLACED’). Those lines that look out of focus are called ‘combing’ and are a tell-tale sign of interlacing. For interlaced content (like that Strider screen), you’ll want to keep the interlacing detection enabled, either by uncommenting that line or using the original version that’s in the common-shaders repo. Playstation games were pretty notorious for switching back and forth from interlaced to non-interlaced (aka, “240p”) modes frequently, often using interlacing for menus and non-interlacing for actual gameplay.

Weird. I don’t have a driver setting on the version I’m using. Are you using the official “mega pack” release or one of the unofficial nightlies from lordashram? Either way, if you go into your RetroArch directory, you should see a file named ‘retroarch.cfg’. Open it in a text editor (notepad++ works well) and change the line that says ‘video_driver = “gl”’ to say "video_driver = “d3d9”’ instead. If that still gives you trouble, I guess OpenGL is the way to go :stuck_out_tongue:

The version that comes with RetroArch is (you can tell because the files end in .cg/.cgp; also the first couple of lines of the Cg shaders usually say ‘COMPATIBILITY - HLSL compilers - Cg compilers’). cgwg originally wrote it in the GLSL/XML format that was previously compatible with both RetroArch and bsnes, but when RetroArch went with Cg instead, we converted it (and many others) to the new format. Now, bsnes/higan has a different shader format, as well, so Hyllian, aliaspider and I converted a bunch of shaders to that format, too. Thankfully, the conversion among the myriad formats isn’t usually too difficult, so we (that is, the people writing shaders) just write it in whichever format we’re most comfortable with and then port it everywhere else as needed.

Yeah im using the unofficial nightlies from lordashram.

I have tried to video the problem but its appears not to show up, its just every now and again the screen will jump ever so slightly, well it looks like the scanline jump. But with your modded crt-interlaced-halation-pass2.cg file i dont get this jumping effect at all just the combing on the text

I ran across this problem (the black dot). For me, it is caused by the end of the bkwtrans function. Try replacing the second-last line,

float r = R*acos(a);

with

float r = FIX(R*acos(a));

in the fragment shader. If r==0, then the return line has 0/0, which causes the problem.

Indeed, that fixes the problem on my side too.

And a BIG thanks for your work on those shaders Cgwg. Can’t say how much I enjoy them, they make me go again through a lot of games, like I discover them once again. :slight_smile:

Could someone make that change on Github?

I made it an open issue here but it seems it went unnoticed.

Alright, fixed in git. I have commit access to that repo but I don’t get any emails or anything for issues.

Thanks Hunterk.