[TEST] crt-interlaced-halation with adjustable settings

I was bored, trying to get the hang of slang shaders and inspired by @hunterk’s recent updates to geom shader family. Thought I’d try to add parameters to the settings in crt-interlaced-halation.

Everything seems to be working fine. Haven’t been able to test the Interlace toggle/detection. Scanline strength only works if GUASSIAN is commented out. Halation has no strength setting because of how its implemented in this shader (at least I didn’t see where to add it.)

Use this instead of the current crt-interlaced-halation-pass2.slang:

#version 450

layout(push_constant) uniform Push
	vec4 SourceSize;
	vec4 OriginalSize;
	vec4 OutputSize;
	uint FrameCount;
	float CRTgamma;
	float monitorgamma;
	float d;
	float R;
	float cornersize;
	float cornersmooth;
	float x_tilt;
	float y_tilt;
	float overscan_x;
	float overscan_y;
	float DOTMASK;
	float SHARPER;
	float scanline_weight;
	float interlace_detect;
   float lum;
} params;

layout(std140, set = 0, binding = 0) uniform UBO
	mat4 MVP;
} global;

#pragma parameter CRTgamma "CRTGeom Target Gamma" 2.4 0.1 5.0 0.1
#pragma parameter monitorgamma "CRTGeom Monitor Gamma" 2.2 0.1 5.0 0.1
#pragma parameter d "CRTGeom Distance" 1.5 0.1 3.0 0.1
#pragma parameter CURVATURE "CRTGeom Curvature Toggle" 1.0 0.0 1.0 1.0
#pragma parameter R "CRTGeom Curvature Radius" 2.0 0.1 10.0 0.1
#pragma parameter cornersize "CRTGeom Corner Size" 0.03 0.001 1.0 0.005
#pragma parameter cornersmooth "CRTGeom Corner Smoothness" 1000.0 80.0 2000.0 100.0
#pragma parameter x_tilt "CRTGeom Horizontal Tilt" 0.0 -0.5 0.5 0.05
#pragma parameter y_tilt "CRTGeom Vertical Tilt" 0.0 -0.5 0.5 0.05
#pragma parameter overscan_x "CRTGeom Horiz. Overscan %" 100.0 -125.0 125.0 1.0
#pragma parameter overscan_y "CRTGeom Vert. Overscan %" 100.0 -125.0 125.0 1.0
#pragma parameter DOTMASK "CRTGeom Dot Mask Strength" 0.3 0.0 1.0 0.05
#pragma parameter SHARPER "CRTGeom Sharpness" 1.0 1.0 3.0 1.0
#pragma parameter scanline_weight "CRTGeom Scanline Weight" 0.3 0.1 0.5 0.05
#pragma parameter lum "CRTGeom Luminance" 0.0 0.0 1.0 0.01
#pragma parameter interlace_detect "CRTGeom Interlacing Simulation" 1.0 0.0 1.0 1.0

float CRTgamma = params.CRTgamma;
float monitorgamma = params.monitorgamma;
float d = params.d;
float R = params.R;
float cornersize = params.cornersize;
float cornersmooth = params.cornersmooth;
float x_tilt = params.x_tilt;
float y_tilt = params.y_tilt;
float overscan_x = params.overscan_x;
float overscan_y = params.overscan_y;
float DOTMASK = params.DOTMASK;
float SHARPER = params.SHARPER;
float scanline_weight = params.scanline_weight;
float interlace_detect = params.interlace_detect;
float lum = params.lum;

    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:


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

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

// Use the older, purely gaussian beam profile
//Enable if using several shaders. 
//Disabling it reduces moire for single pass.
// Macros.
#define FIX(c) max(abs(c), 1e-5);
#define PI 3.141592653589

#       define TEX2D(c) pow(texture(Original, (c)), vec4(CRTgamma))
#       define TEX2D(c) texture(Original, (c))
                    vec2 aspect = vec2(1.0, 0.75);

            float intersect(vec2 xy, vec2 sinangle, vec2 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);

            vec2 bkwtrans(vec2 xy, vec2 sinangle, vec2 cosangle)
                    float c = intersect(xy, sinangle, cosangle);
                    vec2 point = vec2(c)*xy;
                    point -= vec2(-R)*sinangle;
                    point /= vec2(R);
                    vec2 tang = sinangle/cosangle;
                    vec2 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);
                    vec2 uv = (point-a*sinangle)/cosangle;
                    float r = FIX(R*acos(a));
                    return uv*r/sin(r/R);

            vec2 fwtrans(vec2 uv, vec2 sinangle, vec2 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;

            vec3 maxscale(vec2 sinangle, vec2 cosangle)
                    vec2 c = bkwtrans(-R * sinangle / (1.0 + R/d*cosangle.x*cosangle.y), sinangle, cosangle);
                    vec2 a = vec2(0.5,0.5)*aspect;
                    vec2 lo = vec2(fwtrans(vec2(-a.x,c.y), sinangle, cosangle).x,
                                 fwtrans(vec2(c.x,-a.y), sinangle, cosangle).y)/aspect;
                    vec2 hi = vec2(fwtrans(vec2(+a.x,c.y), sinangle, cosangle).x,
                                 fwtrans(vec2(c.x,+a.y), sinangle, cosangle).y)/aspect;
                    return vec3((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.
            vec4 scanlineWeights(float distance, vec4 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
                    vec4 wid = 0.3 + 0.1 * pow(color, vec4(3.0));
                    vec4 weights = vec4(distance / wid);
                    return (lum + 0.4) * exp(-weights * weights) / wid;
                    vec4 wid = 2.0 + 2.0 * pow(color, vec4(4.0));
                    vec4 weights = vec4(distance / scanline_weight);
                    return (lum + 1.4) * exp(-pow(weights * inversesqrt(0.5 * wid), wid)) / (0.6 + 0.2 * wid);

#pragma stage vertex
layout(location = 0) in vec4 Position;
layout(location = 1) in vec2 TexCoord;
layout(location = 0) out vec2 vTexCoord;
layout(location = 1) out vec2 one;
layout(location = 2) out float mod_factor;
layout(location = 3) out vec2 ilfac;
layout(location = 4) out vec3 stretch;
layout(location = 5) out vec2 sinangle;
layout(location = 6) out vec2 cosangle;
layout(location = 7) out vec2 TextureSize;

void main()
   gl_Position = global.MVP * Position;
   vTexCoord = TexCoord;
                       // Precalculate a bunch of useful values we'll need in the fragment
                    // shader.
                    sinangle = sin(vec2(x_tilt, y_tilt));
                    cosangle = cos(vec2(x_tilt, y_tilt));
                    stretch = maxscale(sinangle, cosangle);
                    TextureSize = vec2(SHARPER * params.SourceSize.x, params.SourceSize.y);

                    ilfac = vec2(1.0,clamp(floor(params.SourceSize.y/200.0),1.0,2.0));

                    // The size of one texel, in texture-coordinates.
                    one = ilfac / TextureSize;

                    // Resulting X pixel-coordinate of the pixel we're drawing.
                    mod_factor = vTexCoord.x * params.SourceSize.x * params.OutputSize.x / params.SourceSize.x;

#pragma stage fragment
layout(location = 0) in vec2 vTexCoord;
layout(location = 1) in vec2 one;
layout(location = 2) in float mod_factor;
layout(location = 3) in vec2 ilfac;
layout(location = 4) in vec3 stretch;
layout(location = 5) in vec2 sinangle;
layout(location = 6) in vec2 cosangle;
layout(location = 7) in vec2 TextureSize;
layout(location = 0) out vec4 FragColor;
layout(set = 0, binding = 2) uniform sampler2D Source;
layout(set = 0, binding = 3) uniform sampler2D Original;

#define mul(a, b) (b * a)

void main()
                    // 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.
	                vec2 xy, cd;
	                if (CURVATURE > 0.5)
						cd = vTexCoord;
						cd = (cd-vec2(0.5))*aspect*stretch.z+stretch.xy;
						xy =  (bkwtrans(cd, sinangle, cosangle)/vec2(overscan_x / 100.0, overscan_y / 100.0)/aspect+vec2(0.5,0.5));// * ORIG.video_size / ORIG.texture_size;
                    } else
					xy = vTexCoord;
                    vec2 cd2 = xy;
                    //cd2 *= ORIG.texture_size / ORIG.video_size;
                    cd2 = (cd2 - vec2(0.5)) * vec2(overscan_x / 100.0, overscan_y / 100.0) + vec2(0.5, 0.5);
                    cd2 = min(cd2, vec2(1.0)-cd2) * aspect;
                    vec2 cdist = vec2(cornersize);
                    cd2 = (cdist - min(cd2,cdist));
                    float dist = sqrt(dot(cd2,cd2));
                    float cval = clamp((cdist.x-dist)*cornersmooth,0.0, 1.0);

                    vec2 xy2 = ((xy-vec2(0.5))*vec2(1.0,1.0)+vec2(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?
                    vec2 ilfloat = vec2(0.0,ilfac.y > 1.5 ? mod(vec2(params.FrameCount,params.FrameCount).x,2.0) : 0.0);

                    vec2 ratio_scale = (xy * TextureSize - vec2(0.5, 0.5) + ilfloat)/ilfac;
            #ifdef OVERSAMPLE
                    //float filter = fwidth(ratio_scale.y);
                    float os_filter = params.SourceSize.y / params.OutputSize.y;
                    vec2 uv_ratio = fract(ratio_scale);

                    // Snap to the center of the underlying texel.
                    xy = (floor(ratio_scale)*ilfac + vec2(0.5, 0.5) - ilfloat) / TextureSize;

                    // Calculate Lanczos scaling coefficients describing the effect
                    // of various neighbour texels in a scanline on the current
                    // pixel.
                    vec4 coeffs = PI * vec4(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, vec4(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.
               vec4 col  = clamp(mul(coeffs, mat4x4(
                        TEX2D(xy + vec2(-one.x, 0.0)),
                        TEX2D(xy + vec2(one.x, 0.0)),
                        TEX2D(xy + vec2(2.0 * one.x, 0.0)))),
                  0.0, 1.0);
               vec4 col2 = clamp(mul(coeffs, mat4x4(
                        TEX2D(xy + vec2(-one.x, one.y)),
                        TEX2D(xy + vec2(0.0, one.y)),
                        TEX2D(xy + one),
                        TEX2D(xy + vec2(2.0 * one.x, one.y)))),
                  0.0, 1.0);

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

                    // Calculate the influence of the current and next scanlines on
                    // the current pixel.
                    vec4 weights  = scanlineWeights(uv_ratio.y, col);
                    vec4 weights2 = scanlineWeights(1.0 - uv_ratio.y, col2);
            #ifdef OVERSAMPLE
                    uv_ratio.y =uv_ratio.y+1.0/3.0*os_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*os_filter;
                    weights=weights+scanlineWeights(abs(uv_ratio.y), col)/3.0;
                    weights2=weights2+scanlineWeights(abs(1.0-uv_ratio.y), col2)/3.0;
                    vec3 mul_res  = (col * weights + col2 * weights2).rgb;
         #ifdef MULTIPASS
               mul_res += pow(texture(Source, xy2).rgb, vec3(monitorgamma))*0.1;
               mul_res *= vec3(cval);

                    // dot-mask emulation:
                    // Output pixels are alternately tinted green and magenta.
                    vec3 dotMaskWeights = mix(
                            vec3(1.0, 1.0 - DOTMASK, 1.0),
                            vec3(1.0 - DOTMASK, 1.0, 1.0 - DOTMASK),
                            floor(mod(mod_factor, 2.0))

                    mul_res *= dotMaskWeights;

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

                    // Color the texel.
   FragColor = vec4(mul_res, 1.0);

Not that anyone is paying attention to this, but UPDATE:

Interlacing support is functioning, thanks @ProfessorBraun for confirmation.

I’m still not sure about the sharpness setting situation, @hunterk said that it isn’t working as intended in geom-deluxe (I think?) but I’m literally getting the same looking results between the slang versions of this shader and standard geom when applying sharpness changes (feel free to correct me with some images).

Maybe the sharpness issue is a slang issue for this code? Maybe I don’t know what I’m talking about… Yeah probably the second one, lol.

Have fun kids!

1 Like

Any screenshots of how it looks ?

It should look the same as default, I’ll have to check the original values and compare. That’s why I didn’t think of posting any screenshots.

Honestly it’s just crt-interlaced-halation but you can adjust the settings while in retroarch with this version. All I did was make the settings adjustable instead of being static (non-adjustable).

1 Like


Did you ever figure out was going on with the geom-family of shaders sharpness setting? Mainly I’m asking because I’d like to get this finished up and possibly updated in the repo (if possible).

Quick question, are the first two passes of crt-interlaced-halation basically linearizing gamma and x/y blur (respectively)? Because it looks they’re both setting gamma and doing blur (that’s what I’m thinking from looking at it.) If so I’m going to try and update this to also add halation control (similar to geom-deluxe.)

No, never figured out why it works sometimes but not others.

yeah, they don’t use any special framebuffer formats, so they have to be linearized and delinearized in each pass.

1 Like

Could I use the same method geom-deluxe uses for blur width and halation strength? Mainly asking about the width, as I don’t see any issues with the halation method.

EDIT: Nevermind, I looked it over with something more then a glance and realized I’d have to do more then that to get those working properly.

Any suggestions for adding a halation strength control to this? I’m not super worried about the blur width control, mainly the halation adjustment.

It looks like the only place it’s adding in the previous glow passes is right here:

which is a pretty naive way of combining. You could replace it with a mix() or something.

1 Like

I still don’t really understand the mix function that well TBH, could you help me out?

The highlighted line samples the previous texture, linearizes it and then multiplies it by 0.1, and then it adds that to the “mul_res” value that holds the current image.

So, instead of adding it to mul_res, store it some other vec3 of its own and then do:

mul_res = mix(mul_res, glow, glow_strength);

The first 2 elements need to be the same size (that is, vec2 or vec3 or whatever) and the third element is a float between 0.0 and 1.0, with 0.0 meaning “all the first element” and 1.0 meaning “all the second element”. 0.5 means halfway between the first element and the second element.

1 Like

Alright so do the highlighted line but instead of doing this:

mul_res +=

I need to make a new vec3, or add it to an existing vec3 that’s not mul_res?

Then do this:

mul_res = mix(mul_res, glow, glow_strength);

Is the glow vec part of the mix line supposed to be the new version of the highlighted line(I understand the glow_strength is supposed to be the user adjustment)?

If not, when and how do I reintroduce the highlighted portion I changed?

Sorry I’m trying to follow what you’re explaining, at least I understand the mix function now.

Make a new one, and yes, glow is the new thing you make to hold the highlighted line.

1 Like

I’ll do the update sometime tomorrow most likely, after I update it in this thread could you possibly push it the repo? (Don’t have GitHub myself.)

Yeah, I’ll check it out and if it’s ready I’ll push it up.

1 Like

Thanks, I’ll (tag you?) in the post tomorrow for it.

Would you like me to remove the sharpness adjustment or leave it in?

Doesn’t matter to me. If it works, leave it in, I guess /shrug

1 Like

It was working on my AMD card, I didn’t see any difference compared to the other two geom shaders in how it was working (in game.)

Just thought I’d ask, lol.

Thanks for your time.


Hey this most likely won’t be done until sometime over the weekend.

I got the glow strength added and functional, had to make a slight tweak to get it working correctly.

I’m planning on trying to update the blur passes to be more inline with geom-deluxe’s blur passes for the addition of blur width control.

I’m probally going to move all the settings into a .“inc” file like deluxe, and I’m going to add in the aspect_x/y settings from deluxe as well.

The main reason I’m wanting to add the blur controls and update the blur passes is because right now I’m getting a very blocky effect with the glow halo’s (hoping that doing these things fix it).

Feel free to give me your thoughts on the matter, if you don’t want me mucking about with all of those things, I can use my original build that doesn’t allow glow strength control as everything functions as intended (at least on my end all of the settings seemed to be working as intended.)

Sorry for the delay!

no hurry. take your time :slight_smile:

This shader is a port of a port of a very old shader, so a lot of things are sub-optimal with it. If you can improve stuff, great.

1 Like

I’m debating on whether if it would just be easier to gut the phosphor pass references from deluxe for this update, lol.