New CRT shader from Guest + CRT Guest Advanced updates

I recently played retroarch with this shader (Mega Bezel) via steamVR. I noticed that the moire effect was replicated perfectly like it was on a real CRT. Moving my head even moved the moire effect like a real TV. I wonder why that was? I’m guessing there’s something about supersampling or the pixels on the HMD not aligning perfectly with what’s on screen. Either way, it was cool and authentic.

2 Likes

@guest.r

I ported/translated the MD Palette and SMS Blue Lift functions from @Dogway’s Grade into crt-guest-advanced-ntsc/pre-shaders-afterglow if you would like to include them:

modified pre-shaders-afterglow.slang
#version 450

/*
   CRT Advanced Afterglow, color altering
   
   Copyright (C) 2019-2022 guest(r) and Dr. Venom
   
   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License
   as published by the Free Software Foundation; either version 2
   of the License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
   
*/  

layout(push_constant) uniform Push
{
   vec4 SourceSize;
   vec4 OriginalSize;
   vec4 OutputSize;
   uint FrameCount;
   float TNTC;
   float LS;
   float LUTLOW, LUTBR;   
   float CP, CS;
   float WP;
   float wp_saturation;
   float AS, sat;
   float BP;
   float vigstr;
   float vigdef;
   float sega_fix;
   float sms_blue;
   float MD_Pal;
   float pre_bb;
   float contr;
} params;

#pragma parameter AS "          Afterglow Strength" 0.20 0.0 0.60 0.01
#define AS params.AS

#pragma parameter sat "          Afterglow saturation" 0.50 0.0 1.0 0.01
#define sat params.sat

#pragma parameter bogus_color "[ COLOR TWEAKS ]:" 0.0 0.0 1.0 1.0

#pragma parameter CS "          Display Gamut: sRGB, Modern, DCI, Adobe, Rec.2020" 0.0 0.0 4.0 1.0 

#pragma parameter CP "          CRT Profile: EBU | P22 | SMPTE-C | Philips | Trin." 0.0 -1.0 5.0 1.0 

#define CP params.CP
#define CS params.CS

#pragma parameter TNTC "          LUT Colors: Trin.1 | Trin.2 | Nec Mult. | NTSC" 0.0 0.0 4.0 1.0
#define TNTC params.TNTC

#pragma parameter LS "          LUT Size" 32.0 16.0 64.0 16.0
#define LS params.LS

#define LUTLOW 5.0  // "Fix LUT Dark - Range" from 0.0 to 50.0 - RGB singletons

#define LUTBR 1.0   // "Fix LUT Brightness" from 0.0 to 1.0

#pragma parameter WP "          Color Temperature %" 0.0 -100.0 100.0 5.0 

#pragma parameter wp_saturation "          Saturation Adjustment" 1.0 0.0 2.0 0.05

#pragma parameter pre_bb "          Brightness Adjustment" 1.0 0.0 2.0 0.01

#pragma parameter contr "          Contrast Adjustment" 0.0 -2.0 2.0 0.05

#pragma parameter sega_fix "          Sega Brightness Fix" 0.0 0.0 1.0 1.0

#pragma parameter sms_blue "          SMS Blue Lift" 0.0 0.0 1.0 1.0

#pragma parameter MD_Pal "          MD Palette" 0.0 0.0 1.0 1.0

#pragma parameter BP "          Raise Black Level" 0.0 -100.0 25.0 1.0

#pragma parameter vigstr "          Vignette Strength" 0.0 0.0 2.0 0.05

#pragma parameter vigdef "          Vignette Size" 1.0 0.5 3.0 0.10

#define WP params.WP
#define wp_saturation params.wp_saturation
#define BP params.BP

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

#pragma stage vertex
layout(location = 0) in vec4 Position;
layout(location = 1) in vec2 TexCoord;
layout(location = 0) out vec2 vTexCoord;

void main()
{
   gl_Position = global.MVP * Position;
   vTexCoord = TexCoord;
}

#pragma stage fragment
layout(location = 0) in vec2 vTexCoord;
layout(location = 0) out vec4 FragColor;
layout(set = 0, binding = 2) uniform sampler2D StockPass;
layout(set = 0, binding = 3) uniform sampler2D AfterglowPass;
layout(set = 0, binding = 4) uniform sampler2D SamplerLUT1;
layout(set = 0, binding = 5) uniform sampler2D SamplerLUT2;
layout(set = 0, binding = 6) uniform sampler2D SamplerLUT3;
layout(set = 0, binding = 7) uniform sampler2D SamplerLUT4;

#define COMPAT_TEXTURE(c,d) texture(c,d)


// Color profile matrices

const mat3 Profile0 = 
mat3(
 0.412391,  0.212639,  0.019331,
 0.357584,  0.715169,  0.119195,
 0.180481,  0.072192,  0.950532
);

const mat3 Profile1 = 
mat3(
 0.430554,  0.222004,  0.020182,
 0.341550,  0.706655,  0.129553,
 0.178352,  0.071341,  0.939322
);

const mat3 Profile2 = 
mat3(
 0.396686,  0.210299,  0.006131,
 0.372504,  0.713766,  0.115356,
 0.181266,  0.075936,  0.967571
);

const mat3 Profile3 = 
mat3(
 0.393521,  0.212376,  0.018739,
 0.365258,  0.701060,  0.111934,
 0.191677,  0.086564,  0.958385
);

const mat3 Profile4 = 
mat3(
 0.392258,  0.209410,  0.016061,
 0.351135,  0.725680,  0.093636,
 0.166603,  0.064910,  0.850324
);

const mat3 Profile5 = 
mat3(
 0.377923,  0.195679,  0.010514,
 0.317366,  0.722319,  0.097826,
 0.207738,  0.082002,  1.076960
);

const mat3 ToSRGB = 
mat3(
 3.240970, -0.969244,  0.055630,
-1.537383,  1.875968, -0.203977,
-0.498611,  0.041555,  1.056972
);

const mat3 ToModern = 
mat3(
 2.791723,	-0.894766,	0.041678,
-1.173165,	 1.815586, -0.130886,
-0.440973,	 0.032000,	1.002034
);

const mat3 ToDCI = 
mat3(
 2.493497,	-0.829489,	0.035846,
-0.931384,	 1.762664, -0.076172,
-0.402711,	 0.023625,	0.956885
);

const mat3 ToAdobe = 
mat3(
 2.041588, -0.969244,  0.013444,
-0.565007,  1.875968, -0.11836,
-0.344731,  0.041555,  1.015175
);

const mat3 ToREC = 
mat3(
 1.716651, -0.666684,  0.017640,
-0.355671,  1.616481, -0.042771,
-0.253366,  0.015769,  0.942103
); 

// Color temperature matrices

const mat3 D65_to_D55 = mat3 (
           0.4850339153,  0.2500956126,  0.0227359648,
           0.3488957224,  0.6977914447,  0.1162985741,
           0.1302823568,  0.0521129427,  0.6861537456);


const mat3 D65_to_D93 = mat3 (
           0.3412754080,  0.1759701322,  0.0159972847,
           0.3646170520,  0.7292341040,  0.1215390173,
           0.2369894093,  0.0947957637,  1.2481442225);


vec3 fix_lut(vec3 lutcolor, vec3 ref)
{
	float r = length(ref);
	float l = length(lutcolor);
	float m = max(max(ref.r,ref.g),ref.b);
	ref = normalize(lutcolor + 0.0000001) * mix(r, l, pow(m,1.25));
	return mix(lutcolor, ref, LUTBR);
}


float vignette(vec2 pos) {
	vec2 b = vec2(params.vigdef, params.vigdef) *  vec2(1.0, params.OriginalSize.x/params.OriginalSize.y) * 0.125;
	pos = clamp(pos, 0.0, 1.0);
	pos = abs(2.0*(pos - 0.5));
	vec2 res = mix(0.0.xx, 1.0.xx, smoothstep(1.0.xx, 1.0.xx-b, sqrt(pos)));
	res = pow(res, 0.70.xx);	
	return max(mix(1.0, sqrt(res.x*res.y), params.vigstr), 0.0);
}


vec3 plant (vec3 tar, float r)
{
	float t = max(max(tar.r,tar.g),tar.b) + 0.00001;
	return tar * r / t;
}

float contrast(float x)
{
	return max(mix(x, smoothstep(0.0, 1.0, x), params.contr),0.0);
}

float contrast_sigmoid_inv(float imgColor, float cont, float pivot)
{
	cont = pow(cont - 1., 3.);
	float knee  = 1. / (1. + exp (cont *  pivot));
	float shldr = 1. / (1. + exp (cont * (pivot - 1.)));
	imgColor = pivot - log(1. / (imgColor * (shldr - knee) + knee) - 1.) / cont;
	return imgColor;
}


void main()
{
   vec4 imgColor = COMPAT_TEXTURE(StockPass, vTexCoord.xy);
   vec4 aftglow = COMPAT_TEXTURE(AfterglowPass, vTexCoord.xy);
   
   float w = 1.0-aftglow.w;

   float l = length(aftglow.rgb);
   aftglow.rgb = AS*w*normalize(pow(aftglow.rgb + 0.01, vec3(sat)))*l;
   float bp = w * BP/255.0;
   
   if (params.sega_fix > 0.5) imgColor.rgb = imgColor.rgb * (255.0 / 239.0);
   
   imgColor.rgb = min(imgColor.rgb, 1.0);
   
   if (params.sms_blue > 0.5) imgColor.r = imgColor.r * (1.00), imgColor.g = imgColor.g * (1.00), imgColor.b = imgColor.b * (1.16);
   
   imgColor.rgb = min(imgColor.rgb, 1.0);

   if (params.MD_Pal > 0.5) imgColor.r = contrast_sigmoid_inv(imgColor.r,2.578419881,0.520674), imgColor.g = contrast_sigmoid_inv(imgColor.g,2.578419881,0.520674), imgColor.b = contrast_sigmoid_inv(imgColor.b,2.578419881,0.520674);
   
   imgColor.rgb = min(imgColor.rgb, 1.0);
   
   vec3 color = imgColor.rgb;
 
   if (int(TNTC) == 0)
   {
      color.rgb = imgColor.rgb;
   }
   else
   {
	  float lutlow = LUTLOW/255.0; float invLS = 1.0/LS;
	  vec3 lut_ref = imgColor.rgb + lutlow*(1.0 - pow(imgColor.rgb, 0.333.xxx));
	  float lutb = lut_ref.b * (1.0-0.5*invLS);	  
	  lut_ref.rg    = lut_ref.rg * (1.0-invLS) + 0.5*invLS; 
	  float tile1 = ceil (lutb * (LS-1.0));
	  float tile0 = max(tile1 - 1.0, 0.0);
	  float f = fract(lutb * (LS-1.0)); if (f == 0.0) f = 1.0;
	  vec2 coord0 = vec2(tile0 + lut_ref.r, lut_ref.g)*vec2(invLS, 1.0);
	  vec2 coord1 = vec2(tile1 + lut_ref.r, lut_ref.g)*vec2(invLS, 1.0);
	  vec4 color1, color2, res;
	  
      if (int(TNTC) == 1)
      {
         color1 = COMPAT_TEXTURE(SamplerLUT1, coord0);
         color2 = COMPAT_TEXTURE(SamplerLUT1, coord1);
         res = mix(color1, color2, f);
      }
      else if (int(TNTC) == 2)
      {
         color1 = COMPAT_TEXTURE(SamplerLUT2, coord0);
         color2 = COMPAT_TEXTURE(SamplerLUT2, coord1);
         res = mix(color1, color2, f);
      }	
      else if (int(TNTC) == 3)
      {
         color1 = COMPAT_TEXTURE(SamplerLUT3, coord0);
         color2 = COMPAT_TEXTURE(SamplerLUT3, coord1);
         res = mix(color1, color2, f);
      }	
      else if (int(TNTC) == 4)
      {
         color1 = COMPAT_TEXTURE(SamplerLUT4, coord0);
         color2 = COMPAT_TEXTURE(SamplerLUT4, coord1);
         res = mix(color1, color2, f);
      }	

      res.rgb = fix_lut (res.rgb, imgColor.rgb);
	  
      color = mix(imgColor.rgb, res.rgb, min(TNTC,1.0));
   }

	vec3 c = clamp(color, 0.0, 1.0);
	
	float p;
	mat3 m_out;
	
	if (CS == 0.0) { p = 2.2; m_out =  ToSRGB;   } else
	if (CS == 1.0) { p = 2.2; m_out =  ToModern; } else	
	if (CS == 2.0) { p = 2.6; m_out =  ToDCI;    } else
	if (CS == 3.0) { p = 2.2; m_out =  ToAdobe;  } else
	if (CS == 4.0) { p = 2.4; m_out =  ToREC;    }
	
	color = pow(c, vec3(p));
	
	mat3 m_in = Profile0;

	if (CP == 0.0) { m_in = Profile0; } else	
	if (CP == 1.0) { m_in = Profile1; } else
	if (CP == 2.0) { m_in = Profile2; } else
	if (CP == 3.0) { m_in = Profile3; } else
	if (CP == 4.0) { m_in = Profile4; } else
	if (CP == 5.0) { m_in = Profile5; }
	
	color = m_in*color;
	color = m_out*color;

	color = clamp(color, 0.0, 1.0);

	color = pow(color, vec3(1.0/p));	
	
	if (CP == -1.0) color = c;
	
	vec3 scolor1 = plant(pow(color, vec3(wp_saturation)), max(max(color.r,color.g),color.b));
	float luma = dot(color, vec3(0.299, 0.587, 0.114));
	vec3 scolor2 = mix(vec3(luma), color, wp_saturation);
	color = (wp_saturation > 1.0) ? scolor1 : scolor2;

	color = plant(color, contrast(max(max(color.r,color.g),color.b)));

	p = 2.2;
	color = clamp(color, 0.0, 1.0);	
	color = pow(color, vec3(p)); 
	
	vec3 warmer = D65_to_D55*color;
	warmer = ToSRGB*warmer;
	
	vec3 cooler = D65_to_D93*color;
	cooler = ToSRGB*cooler;
	
	float m = abs(WP)/100.0;
	
	vec3 comp = (WP < 0.0) ? cooler : warmer;
	
	color = mix(color, comp, m);
	color = pow(max(color, 0.0), vec3(1.0/p));

	if (BP > -0.5) color = color + aftglow.rgb + bp; else
	{ 
		color = max(color + BP/255.0, 0.0) / (1.0 + BP/255.0*step(- BP/255.0, max(max(color.r,color.g),color.b))) + aftglow.rgb;
	}
	
	color = min(color * params.pre_bb, 1.0);
	
	FragColor = vec4(color, vignette(vTexCoord.xy)); 
}
4 Likes

I’m sorry, but what are these used for again? Would you kindly, explain?

1 Like

SMS Blue Lift was Dogway’s attempt at implementing the Master System behavior described in this document, tho upon reviewing the original document and comparing the results, i have noticed that his implementation was flawed. (Only the first grey and blue color bars in SMSTestSuite’s Color bars test should have boosted blue, but instead both the first and second bars are boosted.)

MD Palette is an implementation/approximation of the Mega Drive/Genesis RGB palette discussed here.

4 Likes

Damn that is intense technology knowledge.

tho upon reviewing the original document and comparing the results

Was the flaw every addressed?

Nevermind, I see that you posted on the thread

I implemented the Sega MS Nonlinear Blue behavior as described in Notes & Measures: Nonlinear Blue on Sega Master System 1 & Other Findings, and renamed the Sega settings to better clarify their purpose and what platforms they are intended to be used with.

modified pre-shaders-afterglow.slang
#version 450

/*
   CRT Advanced Afterglow, color altering
   
   Copyright (C) 2019-2022 guest(r) and Dr. Venom
   
   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License
   as published by the Free Software Foundation; either version 2
   of the License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
   
*/  

layout(push_constant) uniform Push
{
   vec4 SourceSize;
   vec4 OriginalSize;
   vec4 OutputSize;
   uint FrameCount;
   float TNTC;
   float LS;
   float LUTLOW, LUTBR;   
   float CP, CS;
   float WP;
   float wp_saturation;
   float AS, sat;
   float BP;
   float vigstr;
   float vigdef;
   float sega_fix;
   float sms_blue;
   float MD_Pal;
   float pre_bb;
   float contr;
} params;

#pragma parameter AS "          Afterglow Strength" 0.20 0.0 0.60 0.01
#define AS params.AS

#pragma parameter sat "          Afterglow saturation" 0.50 0.0 1.0 0.01
#define sat params.sat

#pragma parameter bogus_color "[ COLOR TWEAKS ]:" 0.0 0.0 1.0 1.0

#pragma parameter CS "          Display Gamut: sRGB, Modern, DCI, Adobe, Rec.2020" 0.0 0.0 4.0 1.0 

#pragma parameter CP "          CRT Profile: EBU | P22 | SMPTE-C | Philips | Trin." 0.0 -1.0 5.0 1.0 

#define CP params.CP
#define CS params.CS

#pragma parameter TNTC "          LUT Colors: Trin.1 | Trin.2 | Nec Mult. | NTSC" 0.0 0.0 4.0 1.0
#define TNTC params.TNTC

#pragma parameter LS "          LUT Size" 32.0 16.0 64.0 16.0
#define LS params.LS

#define LUTLOW 5.0  // "Fix LUT Dark - Range" from 0.0 to 50.0 - RGB singletons

#define LUTBR 1.0   // "Fix LUT Brightness" from 0.0 to 1.0

#pragma parameter WP "          Color Temperature %" 0.0 -100.0 100.0 5.0 

#pragma parameter wp_saturation "          Saturation Adjustment" 1.0 0.0 2.0 0.05

#pragma parameter pre_bb "          Brightness Adjustment" 1.0 0.0 2.0 0.01

#pragma parameter contr "          Contrast Adjustment" 0.0 -2.0 2.0 0.05

#pragma parameter sms_blue "          Sega MS Nonlinear Blue Fix" 0.0 0.0 1.0 1.0

#pragma parameter MD_Pal "          Sega MD RGB Palette" 0.0 0.0 1.0 1.0

#pragma parameter sega_fix "          Sega Brightness Fix (MD/G/CD/32X/2D Sat)" 0.0 0.0 1.0 1.0

#pragma parameter BP "          Raise Black Level" 0.0 -100.0 25.0 1.0

#pragma parameter vigstr "          Vignette Strength" 0.0 0.0 2.0 0.05

#pragma parameter vigdef "          Vignette Size" 1.0 0.5 3.0 0.10

#define WP params.WP
#define wp_saturation params.wp_saturation
#define BP params.BP

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

#pragma stage vertex
layout(location = 0) in vec4 Position;
layout(location = 1) in vec2 TexCoord;
layout(location = 0) out vec2 vTexCoord;

void main()
{
   gl_Position = global.MVP * Position;
   vTexCoord = TexCoord;
}

#pragma stage fragment
layout(location = 0) in vec2 vTexCoord;
layout(location = 0) out vec4 FragColor;
layout(set = 0, binding = 2) uniform sampler2D StockPass;
layout(set = 0, binding = 3) uniform sampler2D AfterglowPass;
layout(set = 0, binding = 4) uniform sampler2D SamplerLUT1;
layout(set = 0, binding = 5) uniform sampler2D SamplerLUT2;
layout(set = 0, binding = 6) uniform sampler2D SamplerLUT3;
layout(set = 0, binding = 7) uniform sampler2D SamplerLUT4;

#define COMPAT_TEXTURE(c,d) texture(c,d)


// Color profile matrices

const mat3 Profile0 = 
mat3(
 0.412391,  0.212639,  0.019331,
 0.357584,  0.715169,  0.119195,
 0.180481,  0.072192,  0.950532
);

const mat3 Profile1 = 
mat3(
 0.430554,  0.222004,  0.020182,
 0.341550,  0.706655,  0.129553,
 0.178352,  0.071341,  0.939322
);

const mat3 Profile2 = 
mat3(
 0.396686,  0.210299,  0.006131,
 0.372504,  0.713766,  0.115356,
 0.181266,  0.075936,  0.967571
);

const mat3 Profile3 = 
mat3(
 0.393521,  0.212376,  0.018739,
 0.365258,  0.701060,  0.111934,
 0.191677,  0.086564,  0.958385
);

const mat3 Profile4 = 
mat3(
 0.392258,  0.209410,  0.016061,
 0.351135,  0.725680,  0.093636,
 0.166603,  0.064910,  0.850324
);

const mat3 Profile5 = 
mat3(
 0.377923,  0.195679,  0.010514,
 0.317366,  0.722319,  0.097826,
 0.207738,  0.082002,  1.076960
);

const mat3 ToSRGB = 
mat3(
 3.240970, -0.969244,  0.055630,
-1.537383,  1.875968, -0.203977,
-0.498611,  0.041555,  1.056972
);

const mat3 ToModern = 
mat3(
 2.791723,	-0.894766,	0.041678,
-1.173165,	 1.815586, -0.130886,
-0.440973,	 0.032000,	1.002034
);

const mat3 ToDCI = 
mat3(
 2.493497,	-0.829489,	0.035846,
-0.931384,	 1.762664, -0.076172,
-0.402711,	 0.023625,	0.956885
);

const mat3 ToAdobe = 
mat3(
 2.041588, -0.969244,  0.013444,
-0.565007,  1.875968, -0.11836,
-0.344731,  0.041555,  1.015175
);

const mat3 ToREC = 
mat3(
 1.716651, -0.666684,  0.017640,
-0.355671,  1.616481, -0.042771,
-0.253366,  0.015769,  0.942103
); 

// Color temperature matrices

const mat3 D65_to_D55 = mat3 (
           0.4850339153,  0.2500956126,  0.0227359648,
           0.3488957224,  0.6977914447,  0.1162985741,
           0.1302823568,  0.0521129427,  0.6861537456);


const mat3 D65_to_D93 = mat3 (
           0.3412754080,  0.1759701322,  0.0159972847,
           0.3646170520,  0.7292341040,  0.1215390173,
           0.2369894093,  0.0947957637,  1.2481442225);


vec3 fix_lut(vec3 lutcolor, vec3 ref)
{
	float r = length(ref);
	float l = length(lutcolor);
	float m = max(max(ref.r,ref.g),ref.b);
	ref = normalize(lutcolor + 0.0000001) * mix(r, l, pow(m,1.25));
	return mix(lutcolor, ref, LUTBR);
}


float vignette(vec2 pos) {
	vec2 b = vec2(params.vigdef, params.vigdef) *  vec2(1.0, params.OriginalSize.x/params.OriginalSize.y) * 0.125;
	pos = clamp(pos, 0.0, 1.0);
	pos = abs(2.0*(pos - 0.5));
	vec2 res = mix(0.0.xx, 1.0.xx, smoothstep(1.0.xx, 1.0.xx-b, sqrt(pos)));
	res = pow(res, 0.70.xx);	
	return max(mix(1.0, sqrt(res.x*res.y), params.vigstr), 0.0);
}


vec3 plant (vec3 tar, float r)
{
	float t = max(max(tar.r,tar.g),tar.b) + 0.00001;
	return tar * r / t;
}

float contrast(float x)
{
	return max(mix(x, smoothstep(0.0, 1.0, x), params.contr),0.0);
}

float contrast_sigmoid_inv(float imgColor, float cont, float pivot)
{
	cont = pow(cont - 1., 3.);
	float knee  = 1. / (1. + exp (cont *  pivot));
	float shldr = 1. / (1. + exp (cont * (pivot - 1.)));
	imgColor = pivot - log(1. / (imgColor * (shldr - knee) + knee) - 1.) / cont;
	return imgColor;
}


void main()
{
   vec4 imgColor = COMPAT_TEXTURE(StockPass, vTexCoord.xy);
   vec4 aftglow = COMPAT_TEXTURE(AfterglowPass, vTexCoord.xy);
   
   float w = 1.0-aftglow.w;

   float l = length(aftglow.rgb);
   aftglow.rgb = AS*w*normalize(pow(aftglow.rgb + 0.01, vec3(sat)))*l;
   float bp = w * BP/255.0;
   
   if (params.sms_blue > 0.5)
   {
      if (imgColor.b < 0.5)
      {
	  if (imgColor.b > 0.25)
	  {
	       (imgColor.b = 0.4078431372549019607843137254902);
      	  }
      }
   }
   
   imgColor.rgb = min(imgColor.rgb, 1.0);

   if (params.MD_Pal > 0.5) imgColor.r = contrast_sigmoid_inv(imgColor.r,2.578419881,0.520674), imgColor.g = contrast_sigmoid_inv(imgColor.g,2.578419881,0.520674), imgColor.b = contrast_sigmoid_inv(imgColor.b,2.578419881,0.520674);
   
   imgColor.rgb = min(imgColor.rgb, 1.0);
   
   if (params.sega_fix > 0.5) imgColor.rgb = imgColor.rgb * (255.0 / 239.0);
   
   imgColor.rgb = min(imgColor.rgb, 1.0);
   
   vec3 color = imgColor.rgb;
 
   if (int(TNTC) == 0)
   {
      color.rgb = imgColor.rgb;
   }
   else
   {
	  float lutlow = LUTLOW/255.0; float invLS = 1.0/LS;
	  vec3 lut_ref = imgColor.rgb + lutlow*(1.0 - pow(imgColor.rgb, 0.333.xxx));
	  float lutb = lut_ref.b * (1.0-0.5*invLS);	  
	  lut_ref.rg    = lut_ref.rg * (1.0-invLS) + 0.5*invLS; 
	  float tile1 = ceil (lutb * (LS-1.0));
	  float tile0 = max(tile1 - 1.0, 0.0);
	  float f = fract(lutb * (LS-1.0)); if (f == 0.0) f = 1.0;
	  vec2 coord0 = vec2(tile0 + lut_ref.r, lut_ref.g)*vec2(invLS, 1.0);
	  vec2 coord1 = vec2(tile1 + lut_ref.r, lut_ref.g)*vec2(invLS, 1.0);
	  vec4 color1, color2, res;
	  
      if (int(TNTC) == 1)
      {
         color1 = COMPAT_TEXTURE(SamplerLUT1, coord0);
         color2 = COMPAT_TEXTURE(SamplerLUT1, coord1);
         res = mix(color1, color2, f);
      }
      else if (int(TNTC) == 2)
      {
         color1 = COMPAT_TEXTURE(SamplerLUT2, coord0);
         color2 = COMPAT_TEXTURE(SamplerLUT2, coord1);
         res = mix(color1, color2, f);
      }	
      else if (int(TNTC) == 3)
      {
         color1 = COMPAT_TEXTURE(SamplerLUT3, coord0);
         color2 = COMPAT_TEXTURE(SamplerLUT3, coord1);
         res = mix(color1, color2, f);
      }	
      else if (int(TNTC) == 4)
      {
         color1 = COMPAT_TEXTURE(SamplerLUT4, coord0);
         color2 = COMPAT_TEXTURE(SamplerLUT4, coord1);
         res = mix(color1, color2, f);
      }	

      res.rgb = fix_lut (res.rgb, imgColor.rgb);
	  
      color = mix(imgColor.rgb, res.rgb, min(TNTC,1.0));
   }

	vec3 c = clamp(color, 0.0, 1.0);
	
	float p;
	mat3 m_out;
	
	if (CS == 0.0) { p = 2.2; m_out =  ToSRGB;   } else
	if (CS == 1.0) { p = 2.2; m_out =  ToModern; } else	
	if (CS == 2.0) { p = 2.6; m_out =  ToDCI;    } else
	if (CS == 3.0) { p = 2.2; m_out =  ToAdobe;  } else
	if (CS == 4.0) { p = 2.4; m_out =  ToREC;    }
	
	color = pow(c, vec3(p));
	
	mat3 m_in = Profile0;

	if (CP == 0.0) { m_in = Profile0; } else	
	if (CP == 1.0) { m_in = Profile1; } else
	if (CP == 2.0) { m_in = Profile2; } else
	if (CP == 3.0) { m_in = Profile3; } else
	if (CP == 4.0) { m_in = Profile4; } else
	if (CP == 5.0) { m_in = Profile5; }
	
	color = m_in*color;
	color = m_out*color;

	color = clamp(color, 0.0, 1.0);

	color = pow(color, vec3(1.0/p));	
	
	if (CP == -1.0) color = c;
	
	vec3 scolor1 = plant(pow(color, vec3(wp_saturation)), max(max(color.r,color.g),color.b));
	float luma = dot(color, vec3(0.299, 0.587, 0.114));
	vec3 scolor2 = mix(vec3(luma), color, wp_saturation);
	color = (wp_saturation > 1.0) ? scolor1 : scolor2;

	color = plant(color, contrast(max(max(color.r,color.g),color.b)));

	p = 2.2;
	color = clamp(color, 0.0, 1.0);	
	color = pow(color, vec3(p)); 
	
	vec3 warmer = D65_to_D55*color;
	warmer = ToSRGB*warmer;
	
	vec3 cooler = D65_to_D93*color;
	cooler = ToSRGB*cooler;
	
	float m = abs(WP)/100.0;
	
	vec3 comp = (WP < 0.0) ? cooler : warmer;
	
	color = mix(color, comp, m);
	color = pow(max(color, 0.0), vec3(1.0/p));

	if (BP > -0.5) color = color + aftglow.rgb + bp; else
	{ 
		color = max(color + BP/255.0, 0.0) / (1.0 + BP/255.0*step(- BP/255.0, max(max(color.r,color.g),color.b))) + aftglow.rgb;
	}
	
	color = min(color * params.pre_bb, 1.0);
	
	FragColor = vec4(color, vignette(vTexCoord.xy)); 
}

Edit 20240314: Changed “if (imgColor.b > 0)” to “if (imgColor.b > 0.25)” in the Nonlinear Blue Fix to prevent potential errors.

6 Likes

Proof of concept implementation of automatic resolution-based dynamic NTSC Resolution Scaling for use with games that include pseudo hi-res material:

ntsc-pass1.slang
#version 450

// NTSC-Adaptive
// based on Themaister's NTSC shader


layout(push_constant) uniform Push
{
   vec4 OutputSize;
   vec4 OriginalSize;
   vec4 SourceSize;
   uint FrameCount;
   float quality, ntsc_sat, cust_fringing, cust_artifacting, ntsc_bright, ntsc_scale, ntsc_fields, ntsc_phase, ntsc_gamma, ntsc_rainbow1, ntsc_pseudo, ntsc_pscale;
} params;

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

#pragma parameter ntsc-row1 "------------------------------------------------" 0.0 0.0 0.0 1.0
#pragma parameter quality "INFO --> A&F Values: Svideo = 0.0 | Composite = 1.0 | RF = 2.0" 0.0 0.0 0.0 1.0
#pragma parameter cust_artifacting "NTSC Artifacting Value" 1.0 0.0 5.0 0.1
#pragma parameter cust_fringing "NTSC Fringing Value" 1.0 0.0 5.0 0.1
#pragma parameter ntsc-row2 "------------------------------------------------" 0.0 0.0 0.0 1.0
#pragma parameter ntsc_fields "NTSC Merge Fields: Auto | NO | YES" -1.0 -1.0 1.0 1.0
#pragma parameter ntsc_phase "NTSC Phase: Auto | 2 phase | 3 phase | Mixed" 1.0 1.0 4.0 1.0
#pragma parameter ntsc_scale "NTSC Resolution Scaling" 1.0 0.20 2.5 0.025
#pragma parameter ntsc_pseudo "Downsample Pseudo Hi-Res" 1.0 1.0 3.0 1.0
#pragma parameter ntsc_pscale "Downsample Resolution Scaling" 0.5 0.20 2.5 0.025
#pragma parameter ntsc_sat "NTSC Color Saturation" 1.0 0.0 2.0 0.01
#pragma parameter ntsc_bright "NTSC Brightness" 1.0 0.0 1.5 0.01
#pragma parameter ntsc_gamma "NTSC Filtering Gamma Correction" 1.0 0.25 2.5 0.025
#pragma parameter ntsc_rainbow1 "NTSC Coloring/Rainbow Effect" 0.0 -1.0 1.0 0.05


#define PI 3.14159265

#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 pix_no;
layout(location = 2) out float phase;
layout(location = 3) out float BRIGHTNESS;
layout(location = 4) out float SATURATION;
layout(location = 5) out float FRINGING;
layout(location = 6) out float ARTIFACTING;
layout(location = 7) out float CHROMA_MOD_FREQ;
layout(location = 8) out float MERGE;

void main()
{
	float res = min(params.ntsc_scale, 1.0);
	float OriginalSize = params.OriginalSize.x;
	float SourceSize = params.SourceSize.x;
	float OriginalSizeX = params.OriginalSize.x;
	float OriginalSizeY = params.OriginalSize.y;
	gl_Position = global.MVP * Position;
	vTexCoord = TexCoord;
		
if (params.ntsc_pseudo == 1.0) 
{
	pix_no = TexCoord * params.OriginalSize.xy * res * vec2(4.0,1.0);
}
	
if (params.ntsc_pseudo == 2.0) 
{
	if ((OriginalSizeX / OriginalSizeY) >= 2)
	{
	pix_no = (TexCoord * params.OriginalSize.xy * res * vec2(4.0,1.0)) * (params.ntsc_pscale);
	}
	else if ((OriginalSizeX / OriginalSizeY) < 2)
	{
	pix_no = TexCoord * params.OriginalSize.xy * res * vec2(4.0,1.0);
	}
}

if (params.ntsc_pseudo == 3.0) 
{
	if (SourceSize >= 480)
	{
	pix_no = (TexCoord * params.OriginalSize.xy * res * vec2(4.0,1.0)) * (params.ntsc_pscale);
	}
	else if (SourceSize < 480)
	{
	pix_no = TexCoord * params.OriginalSize.xy * res * vec2(4.0,1.0);
	}
}

if (params.ntsc_pseudo == 1.0) 
{	
	phase = (params.ntsc_phase < 1.5) ? ((OriginalSize > 300.0) ? 2.0 : 3.0) : ((params.ntsc_phase > 2.5) ? 3.0 : 2.0);
}
	
if (params.ntsc_pseudo == 2.0) 
{
	if ((OriginalSizeX / OriginalSizeY) >= 2)
	{
	phase = (params.ntsc_phase < 1.5) ? (((OriginalSize * (params.ntsc_pscale)) > 300.0) ? 2.0 : 3.0) : ((params.ntsc_phase > 2.5) ? 3.0 : 2.0);
	}
	else if ((OriginalSizeX / OriginalSizeY) < 2)
	{
	phase = (params.ntsc_phase < 1.5) ? ((OriginalSize > 300.0) ? 2.0 : 3.0) : ((params.ntsc_phase > 2.5) ? 3.0 : 2.0);
	}
}

if (params.ntsc_pseudo == 3.0) 
{
	if (SourceSize >= 480)
	{
	phase = (params.ntsc_phase < 1.5) ? (((OriginalSize * (params.ntsc_pscale)) > 300.0) ? 2.0 : 3.0) : ((params.ntsc_phase > 2.5) ? 3.0 : 2.0);
	}
	else if (SourceSize < 480)
	{
	phase = (params.ntsc_phase < 1.5) ? ((OriginalSize > 300.0) ? 2.0 : 3.0) : ((params.ntsc_phase > 2.5) ? 3.0 : 2.0);
	}
}

	if (params.ntsc_phase == 4.0) phase = 3.0;
	
	CHROMA_MOD_FREQ = (phase < 2.5) ? (4.0 * PI / 15.0) : (PI / 3.0);
	ARTIFACTING = params.cust_artifacting;
	FRINGING = params.cust_fringing;
	SATURATION = params.ntsc_sat;
	BRIGHTNESS = params.ntsc_bright;
	MERGE = 0.0;
	if (params.ntsc_fields == -1.0 && phase == 3.0) MERGE = 1.0;
	else if (params.ntsc_fields ==  0.0) MERGE = 0.0;
	else if (params.ntsc_fields ==  1.0) MERGE = 1.0;	
}

#pragma stage fragment
layout(location = 0) in vec2 vTexCoord;
layout(location = 1) in vec2 pix_no;
layout(location = 2) in float phase;
layout(location = 3) in float BRIGHTNESS;
layout(location = 4) in float SATURATION;
layout(location = 5) in float FRINGING;
layout(location = 6) in float ARTIFACTING;
layout(location = 7) in float CHROMA_MOD_FREQ;
layout(location = 8) in float MERGE;
layout(location = 0) out vec4 FragColor;
layout(set = 0, binding = 2) uniform sampler2D Source;

#define mix_mat  mat3(BRIGHTNESS, FRINGING, FRINGING, ARTIFACTING, 2.0 * SATURATION, 0.0, ARTIFACTING, 0.0, 2.0 * SATURATION)

const mat3 yiq2rgb_mat = mat3(
   1.0, 0.956, 0.6210,
   1.0, -0.2720, -0.6474,
   1.0, -1.1060, 1.7046);

vec3 yiq2rgb(vec3 yiq)
{
   return yiq * yiq2rgb_mat;
}

const mat3 yiq_mat = mat3(
      0.2989, 0.5870, 0.1140,
      0.5959, -0.2744, -0.3216,
      0.2115, -0.5229, 0.3114
);

vec3 rgb2yiq(vec3 col)
{
   return col * yiq_mat;
}

float get_luma(vec3 c)
{
	return dot(c, vec3(0.2989, 0.5870, 0.1140));
}


void main()
{
   vec3 col = texture(Source, vTexCoord).rgb;
	float OriginalSize = params.OriginalSize.x;
	float SourceSize = params.SourceSize.x;
	float OriginalSizeX = params.OriginalSize.x;
	float OriginalSizeY = params.OriginalSize.y;
	float res = 1;

if (params.ntsc_pseudo == 1.0) 
{
   res = params.ntsc_scale;
}

if (params.ntsc_pseudo == 2.0) 
{
	if ((OriginalSizeX / OriginalSizeY) >= 2)
	{
   res = params.ntsc_scale * params.ntsc_pscale;
	}
	else if ((OriginalSizeX / OriginalSizeY) < 2)
	{
   res = params.ntsc_scale;
	}
}

if (params.ntsc_pseudo == 3.0) 
{
	if (SourceSize >= 480)
	{
   res = params.ntsc_scale * params.ntsc_pscale;
	}
	else if (SourceSize < 480)
	{
   res = params.ntsc_scale;
	}
}
 
   vec3 yiq = rgb2yiq(col); float c3 = yiq.x;
   yiq.x = pow(yiq.x, params.ntsc_gamma);
   float lum = yiq.x;

   float mix02 = 0.0;
   float mix03 = 0.0;   
   vec2 dx = vec2(params.OriginalSize.z, 0.0);
   vec3 c1 = texture(Source, vTexCoord - dx).rgb;
   vec3 c2 = texture(Source, vTexCoord + dx).rgb;
   
if(abs(params.ntsc_rainbow1) > 0.025)
{ 
   vec2 dy = vec2(0.0, params.OriginalSize.w);
   vec3 c4 = texture(Source, vTexCoord + dy).rgb;  
   vec3 c5 = texture(Source, vTexCoord + dx +dy).rgb;
   vec3 c6 = texture(Source, vTexCoord + dx +dx).rgb;
   vec3 c7 = texture(Source, vTexCoord + 3.0*dx).rgb;
   
   c1.x = get_luma(c1);
   c2.x = get_luma(c2);
   c4.x = get_luma(c4);
   c5.x = get_luma(c5);
   c6.x = get_luma(c6);
   c7.x = get_luma(c7);
   
   float mix00 = min(5.0*min(min(abs(c3-c1.x),abs(c3-c2.x)),min(abs(c2.x-c6.x),abs(c6.x-c7.x))),1.0);
   float bar1  = 1.0 - min(7.0*min(max(max(c3,c4.x)-0.15,0.0), max(max(c2.x,c5.x)-0.15,0.0)),1.0);
   float bar2  = step(abs(c1.x-c2.x)+abs(c3-c6.x)+abs(c2.x-c7.x), 0.325);
   mix02 = bar1 * bar2 * mix00 * (1.0 - min(10.0*min(abs(c3-c4.x), abs(c2.x-c5.x)),1.0));
   mix02 = mix02*params.ntsc_rainbow1;
}
   
if (params.ntsc_phase == 4.0) 
{
   float mix01 = min(5.0*abs(c1.x-c2.x),1.0);
   c1.x = pow(c1.x, params.ntsc_gamma);
   c2.x = pow(c2.x, params.ntsc_gamma);
   yiq.x = mix(min(0.5*(yiq.x + max(c1.x,c2.x)), max(yiq.x , min(c1.x,c2.x))), yiq.x, mix01);
}   
   vec3 yiq2 = yiq;	
   vec3 yiqs = yiq;
   vec3 yiqz = yiq;
   
   float mod1 = 2.0;
   float mod2 = 3.0; 

if (MERGE > 0.5)
{
   float chroma_phase2 = (phase < 2.5) ? PI * (mod(pix_no.y, mod1) + mod(params.FrameCount+1, 2.)) : 0.6667 * PI * (mod(pix_no.y, mod2) + mod(params.FrameCount+1, 2.));
   float mod_phase2 = chroma_phase2 * (1.0-mix02) + pix_no.x * CHROMA_MOD_FREQ;
   float i_mod2 = cos(mod_phase2);
   float q_mod2 = sin(mod_phase2);
   yiq2.yz *= vec2(i_mod2, q_mod2); // Modulate.
   yiq2 *= mix_mat; // Cross-talk.
   yiq2.yz *= vec2(i_mod2, q_mod2); // Demodulate.   

   if (res > 1.025)
   {
      mod_phase2 = chroma_phase2 * (1.0-mix02) + pix_no.x * CHROMA_MOD_FREQ * res;
      i_mod2 = cos(mod_phase2);
      q_mod2 = sin(mod_phase2);
      yiqs.yz *= vec2(i_mod2, q_mod2); // Modulate.
      yiq2.x = dot(yiqs, mix_mat[0]);  // Cross-talk.
   }
}
   
   float chroma_phase = (phase < 2.5) ? PI * (mod(pix_no.y, mod1) + mod(params.FrameCount, 2.)) : 0.6667 * PI * (mod(pix_no.y, mod2) + mod(params.FrameCount, 2.));
   float mod_phase = chroma_phase * (1.0-mix02) + pix_no.x * CHROMA_MOD_FREQ;

   float i_mod = cos(mod_phase);
   float q_mod = sin(mod_phase);

   yiq.yz *= vec2(i_mod, q_mod); // Modulate.
   yiq *= mix_mat; // Cross-talk.
   yiq.yz *= vec2(i_mod, q_mod); // Demodulate.
   
    if (res > 1.025)
   {
      mod_phase = chroma_phase * (1.0-mix02) + pix_no.x * CHROMA_MOD_FREQ * res;
      i_mod = cos(mod_phase); 
      q_mod = sin(mod_phase);
      yiqz.yz *= vec2(i_mod, q_mod); // Modulate.
      yiq.x = dot(yiqz, mix_mat[0]); // Cross-talk.
   }
      
if (params.ntsc_phase == 4.0)
{
	yiq.x = lum; yiq2.x = lum;
}

   yiq = (MERGE < 0.5) ? yiq : 0.5*(yiq+yiq2);
   
   FragColor = vec4(yiq, lum);
} 
ntsc-pass2.slang
#version 450

// NTSC-Adaptive
// based on Themaister's NTSC shader


layout(push_constant) uniform Push
{
   vec4 OutputSize;
   vec4 OriginalSize;
   vec4 SourceSize;
   float ntsc_scale;
   float ntsc_phase;
   float auto_res;
   float ntsc_ring;
   float ntsc_cscale;
   float ntsc_cscale1;   
   float ntsc_taps;
   float ntsc_falloff;
   float ntsc_pseudo;
   float ntsc_pscale;
} params;

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


#pragma parameter ntsc_scale   "NTSC Resolution Scaling" 1.0 0.20 2.5 0.025
#pragma parameter ntsc_pseudo "Downsample Pseudo Hi-Res" 1.0 1.0 3.0 1.0
#pragma parameter ntsc_pscale "Downsample Resolution Scaling" 0.5 0.20 2.5 0.025
#pragma parameter ntsc_phase   "NTSC Phase: Auto | 2 phase | 3 phase | Mixed" 1.0 1.0 4.0 1.0
#pragma parameter ntsc-row3 "------------------------------------------------" 0.0 0.0 0.0 1.0
#pragma parameter ntsc_taps    "NTSC # of Taps (Filter Width)" 32.0 6.0 32.0 1.0
#pragma parameter ntsc_cscale  "NTSC Chroma Scaling / Bleeding (2-phase)" 1.0 1.00 4.00 0.05
#pragma parameter ntsc_cscale1 "NTSC Chroma Scaling / Bleeding (3-phase)" 1.0 0.20 2.25 0.05
#pragma parameter ntsc_falloff "NTSC Chroma Taps Fall-Off (2-phase)" 0.0 0.0 1.0 0.05
#pragma parameter ntsc-row4 "------------------------------------------------" 0.0 0.0 0.0 1.0
#pragma parameter ntsc_ring    "NTSC Anti-Ringing" 0.5 0.0 1.0 0.10

#pragma stage vertex
layout(location = 0) in vec4 Position;
layout(location = 1) in vec2 TexCoord;
layout(location = 0) out vec2 vTexCoord;

void main()
{
   gl_Position = global.MVP * Position;
   vTexCoord = TexCoord - vec2(0.25 * params.OriginalSize.z/4.0, 0.0); // Compensate for decimate-by-2.
}

#pragma stage fragment
layout(location = 0) in vec2 vTexCoord;
layout(location = 0) out vec4 FragColor;
layout(set = 0, binding = 2) uniform sampler2D Source;
layout(set = 0, binding = 3) uniform sampler2D PrePass0;


vec3 fetch_offset2(vec2 dx)
{
   return texture(Source, vTexCoord + dx).xyz + texture(Source, vTexCoord - dx).xyz;
}

vec3 fetch_offset3(vec3 dx)
{
   return vec3(texture(Source, vTexCoord + dx.xz).x + texture(Source, vTexCoord - dx.xz).x, texture(Source, vTexCoord + dx.yz).yz + texture(Source, vTexCoord - dx.yz).yz);
}

const mat3 yiq2rgb_mat = mat3(
   1.0, 0.956, 0.6210,
   1.0, -0.2720, -0.6474,
   1.0, -1.1060, 1.7046);

vec3 yiq2rgb(vec3 yiq)
{
   return yiq * yiq2rgb_mat;
}

const mat3 yiq_mat = mat3(
      0.2989, 0.5870, 0.1140,
      0.5959, -0.2744, -0.3216,
      0.2115, -0.5229, 0.3114
);

vec3 rgb2yiq(vec3 col)
{
   return col * yiq_mat;
}


const int TAPS_2_phase = 32;
const float luma_filter_2_phase[33] = float[33](
  -0.000174844,
  -0.000205844,
  -0.000149453,
  -0.000051693,
   0.000000000,
  -0.000066171,
  -0.000245058,
  -0.000432928,
  -0.000472644,
  -0.000252236,
   0.000198929,
   0.000687058,
   0.000944112,
   0.000803467,
   0.000363199,
   0.000013422,
   0.000253402,
   0.001339461,
   0.002932972,
   0.003983485,
   0.003026683,
  -0.001102056,
  -0.008373026,
  -0.016897700,
  -0.022914480,
  -0.021642347,
  -0.028863273,
   0.027271957,
   0.054921920,
   0.098342579,
   0.139044281,
   0.168055832,
   0.178571429);


const int TAPS_3_phase = 24;

const float chroma_filter_3_phase[25] = float[25](
  -0.000118847,
  -0.000271306,
  -0.000502642,
  -0.000930833,
  -0.001451013,
  -0.002064744,
  -0.002700432,
  -0.003241276,
  -0.003524948,
  -0.003350284,
  -0.002491729,
  -0.000721149,
   0.002164659,
   0.006313635,
   0.011789103,
   0.018545660,
   0.026414396,
   0.035100710,
   0.044196567,
   0.053207202,
   0.061590275,
   0.068803602,
   0.074356193,
   0.077856564,
   0.079052396);

const float luma_filter_4_phase[25] = float[25](
  -0.000472644,
  -0.000252236,
   0.000198929,
   0.000687058,
   0.000944112,
   0.000803467,
   0.000363199,
   0.000013422,
   0.000253402,
   0.001339461,
   0.002932972,
   0.003983485,
   0.003026683,
  -0.001102056,
  -0.008373026,
  -0.016897700,
  -0.022914480,
  -0.021642347,
  -0.028863273,
   0.027271957,
   0.054921920,
   0.098342579,
   0.139044281,
   0.168055832,
   0.178571429);

void main()
{

float luma_filter_3_phase[25] = float[25](
  -0.000012020,
  -0.000022146,
  -0.000013155,
  -0.000012020,
  -0.000049979,
  -0.000113940,
  -0.000122150,
  -0.000005612,
   0.000170516,
   0.000237199,
   0.000169640,
   0.000285688,
   0.000984574,
   0.002018683,
   0.002002275,
  -0.005909882,
  -0.012049081,
  -0.018222860,
  -0.022606931,
   0.002460860,
   0.035868225,
   0.084016453,
   0.135563500,
   0.175261268,
   0.220176552);

   float res = params.ntsc_scale;
   float OriginalSize = params.OriginalSize.x; 
   float SourceSize = params.SourceSize.x;
   float OriginalSizeX = params.OriginalSize.x;
   float OriginalSizeY = params.OriginalSize.y;
   float resdiv = 1;
	
if (params.ntsc_pseudo == 1.0) 
{
   if (OriginalSize >= 480) { resdiv = 1; }
   else if (OriginalSize < 480) { resdiv = 1; }
}
   
if (params.ntsc_pseudo == 2.0) 
{
   if ((OriginalSizeX / OriginalSizeY) >= 2) { resdiv = (params.ntsc_pscale); }
   else if ((OriginalSizeX / OriginalSizeY) < 2) { resdiv = 1; }
}
  
if (params.ntsc_pseudo == 3.0) 
{
   if (OriginalSize >= 480 { resdiv = (params.ntsc_pscale); }
   else if (OriginalSize < 480) { resdiv = 1; }
}

   vec2 one_x = 0.25*params.OriginalSize.zz / (res * resdiv);
   
   vec3 signal = vec3(0.0);
   float phase = (params.ntsc_phase < 1.5) ? (((OriginalSize * resdiv) > 300.0) ? 2.0 : 3.0) : ((params.ntsc_phase > 2.5) ? 3.0 : 2.0);
   if (params.ntsc_phase == 4.0) { phase = 3.0; luma_filter_3_phase = luma_filter_4_phase; }

   float offset = 0.0; float tf = 0.0; int i = 0; float j = 0.0;
   vec3 wsum = 0.0.xxx;
   vec3 sums = wsum;
   vec3 tmp  = wsum;  

   if(phase < 2.5)
   {
	  vec2 dx = vec2(one_x.x, 0.0); vec2 dx1 = dx;
	  int loopstart = int(TAPS_2_phase - params.ntsc_taps);
	  
	  float ltap = params.ntsc_taps + 1.0;
	  float cs_sub = params.ntsc_taps - params.ntsc_taps / params.ntsc_cscale;
	  float taps_fo = 0.0;
	  
      for (i = loopstart; i < 32; i++)
      {
         offset = float(i-loopstart); j = offset + 1.0; dx1 = (offset - params.ntsc_taps)*dx;
         sums = fetch_offset2(dx1); taps_fo = max(j-cs_sub, 0.0);
		 taps_fo = mix(taps_fo, taps_fo*taps_fo, params.ntsc_falloff);
         tmp = vec3(luma_filter_2_phase[i], taps_fo.xx);
         wsum = wsum + tmp;
         signal += sums * tmp;
      }
      taps_fo = ltap - cs_sub; taps_fo = mix(taps_fo, taps_fo*taps_fo, params.ntsc_falloff);
	  tmp = vec3(luma_filter_2_phase[TAPS_2_phase], taps_fo.xx);
      wsum = wsum + wsum + tmp;
      signal += texture(Source, vTexCoord).xyz * tmp;
      signal = signal / wsum;
   }
   else
   {
      one_x.y = one_x.y/params.ntsc_cscale1;
	  vec3 dx = vec3(one_x.x, one_x.y, 0.0); vec3 dx1 = dx;
	  
	  float iloop = min(params.ntsc_taps, TAPS_3_phase);
      int loopstart = int(24.0 - iloop);
	  
      for (i = loopstart; i < 24; i++)
      {
         offset = float(i-loopstart); j = offset + 1.0; dx1.xy = (offset - iloop)*dx.xy;
         sums = fetch_offset3(dx1);
         tmp = vec3(luma_filter_3_phase[i], chroma_filter_3_phase[i].xx);
         wsum = wsum + tmp;
         signal += sums * tmp;
      }
      tmp = vec3(luma_filter_3_phase[TAPS_3_phase], chroma_filter_3_phase[TAPS_3_phase], chroma_filter_3_phase[TAPS_3_phase]);
      wsum = wsum + wsum + tmp;
      signal += texture(Source, vTexCoord).xyz * tmp;
      signal = signal / wsum;
   }

   if (params.ntsc_ring > 0.05)
   {
      vec2 dx = vec2(params.OriginalSize.z / min((res * resdiv), 1.0), 0.0);
      float a = texture(Source, vTexCoord - 1.5*dx).a;
      float b = texture(Source, vTexCoord - 0.5*dx).a;
      float c = texture(Source, vTexCoord + 1.5*dx).a;
      float d = texture(Source, vTexCoord + 0.5*dx).a;
      float e = texture(Source, vTexCoord         ).a;	  
      signal.x = mix(signal.x, clamp(signal.x, min(min(min(a,b),min(c,d)),e), max(max(max(a,b),max(c,d)),e)), params.ntsc_ring);
   }
   
   vec3 orig = rgb2yiq(texture(PrePass0, vTexCoord).rgb);
   
   signal.x = clamp(signal.x, -1.0, 1.0);
   vec3 rgb = signal;
   
   FragColor = vec4(rgb, orig.x);
}  

Image Comparisons

Downsample Pseudo Hi-Res parameter options:

1: Off

2: Downsample is activated when the horizontal resolution is at least double the vertical resolution.

3: Downsample is activated when the horizontal resolution is 480 or greater. Needed for cores that double the vertical resolution of pseudo hi-res modes, such as Mesen-S. Doesn’t play nice with games that include interlaced modes or other non-pseudo hi-res material.

When the downsample is active, Auto NTSC Phase is derived from the final downsampled horizontal resolution.

This method isn’t quite perfect, and cannot be perfect so long as ntsc-pass1.slang and ntsc-pass2.slang utilize preset-level scale_type_x/scale_x settings (unless i was misinformed, and there is a way to modify preset scale_type_x/scale_x settings using shader code.)

In the meantime, some additional modifications to crt-guest-advanced-ntsc-pass1.slang, the old scale_type_x absolute/scale_x 1024 preset settings can work in tandem with this Downsample Pseudo Hi-Res parameter to perfect the downsample for Super Famicom games.

crt-guest-advanced-ntsc-pass1.slang
#version 450

/*
   CRT - Guest - NTSC - Pass1
   
   Copyright (C) 2018-2023 guest(r) - [email protected]

   Incorporates many good ideas and suggestions from Dr. Venom.
   I would also like give thanks to many Libretro forums members for continuous feedback, suggestions and caring about the shader.
   
   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License
   as published by the Free Software Foundation; either version 2
   of the License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
   
*/

layout(push_constant) uniform Push
{
	vec4 SourceSize;
	vec4 OriginalSize;
	vec4 OutputSize;
	uint FrameCount;
	float SIGMA_HOR;
	float HSHARPNESS;
	float S_SHARP;
	float HARNG;
	float HSHARP; 
	float spike;
	float internal_res;
	float MAXS;
} params;

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


#pragma parameter bogus_filtering "[ FILTERING OPTIONS ]: " 0.0 0.0 1.0 1.0

#pragma parameter HSHARPNESS "          Horizontal Filter Range" 1.60 1.0 8.0 0.05
#define HSHARPNESS params.HSHARPNESS

#pragma parameter SIGMA_HOR "          Horizontal Blur Sigma" 0.80 0.1 7.0 0.025
#define SIGMA_HOR params.SIGMA_HOR

#pragma parameter S_SHARP "          Substractive Sharpness" 1.20 0.0 3.0 0.05
#define S_SHARP params.S_SHARP

#pragma parameter HSHARP "          Sharpness Definition" 1.20 0.0 2.0 0.10
#define HSHARP params.HSHARP

#pragma parameter MAXS "          Maximum Sharpness" 0.18 0.0 0.30 0.01
#define MAXS params.MAXS 

#pragma parameter HARNG "          Substractive Sharpness Ringing" 0.30 0.0 4.0 0.05
#define HARNG params.HARNG 

#pragma parameter spike "          Scanline Spike Removal" 1.0 0.0 2.0 0.10
#define spike params.spike

#define COMPAT_TEXTURE(c,d) texture(c,d)
#define TEX0 vTexCoord

#define OutputSize params.OutputSize
#define gl_FragCoord (vTexCoord * OutputSize.xy)

#pragma stage vertex
layout(location = 0) in vec4 Position;
layout(location = 1) in vec2 TexCoord;
layout(location = 0) out vec2 vTexCoord;

void main()
{
   gl_Position = global.MVP * Position;
   vTexCoord = TexCoord * 1.00001;
}

#pragma stage fragment
layout(location = 0) in vec2 vTexCoord;
layout(location = 0) out vec4 FragColor;
layout(set = 0, binding = 2) uniform sampler2D LinearizePass;

float gaussian(float x)
{
	float invsqrsigma = 1.0/(2.0*SIGMA_HOR*SIGMA_HOR);
	return exp(-x*x*invsqrsigma);
}

void main()
{	
	vec2 prescalex = vec2(textureSize(LinearizePass, 0)) / params.OriginalSize.xy;

	vec4 SourceSize = params.OriginalSize * vec4(prescalex.x, prescalex.y, 1.0/prescalex.x, 1.0/prescalex.y);

	float f = fract(SourceSize.x * vTexCoord.x);
	f = 0.5 - f;
	vec2 tex = floor(SourceSize.xy * vTexCoord)*SourceSize.zw + 0.5*SourceSize.zw;
	vec3 color = 0.0.xxx;
	float scolor = 0.0;
	vec2 dx  = vec2(SourceSize.z, 0.0);

	float w = 0.0;
	float swsum = 0.0;
	float wsum = 0.0;
	vec3 pixel;
	
	float hsharpness = HSHARPNESS;
	vec3 cmax = 0.0.xxx;
	vec3 cmin = 1.0.xxx;
	float sharp = gaussian(hsharpness) * S_SHARP;
	float maxsharp = MAXS;
	float FPR = hsharpness;
	float fpx = 0.0;
	float sp = 0.0;
	float sw = 0.0;

	float ts = 0.025;
	vec3 luma = vec3(0.2126, 0.7152, 0.0722); 

	float LOOPSIZE = ceil(2.0*FPR);
	float CLAMPSIZE = round(2.0*LOOPSIZE/3.0);
	
	float n = -LOOPSIZE;
	
	do
	{
		pixel  = COMPAT_TEXTURE(LinearizePass, tex + n*dx).rgb;
		sp = max(max(pixel.r,pixel.g),pixel.b);
		
		w = gaussian(n+f) - sharp;
		fpx = abs(n+f-sign(n)*FPR)/FPR;
		if (abs(n) <= CLAMPSIZE) { cmax = max(cmax, pixel); cmin = min(cmin, pixel); }
		if (w < 0.0) w = clamp(w, mix(-maxsharp, 0.0, pow(clamp(fpx,0.0,1.0), HSHARP)), 0.0);
	
		color = color + w * pixel;
		wsum  = wsum + w;

		sw = max(w, 0.0) * (dot(pixel,luma) + ts); 
		scolor = scolor + sw * sp;
		swsum = swsum + sw;
		
		n = n + 1.0;
			
	} while (n <= LOOPSIZE);

	color = color / wsum;
	scolor = scolor / swsum;
	
	color = clamp(mix(clamp(color, cmin, cmax), color, HARNG), 0.0, 1.0); 
	
	scolor = clamp(mix(max(max(color.r, color.g),color.b), scolor, spike), 0.0, 1.0);
	
	FragColor = vec4(color, scolor);
}
4 Likes

Have you been able to take advantage of the local dimming on this yet when using it with CRT Shaders?

2 Likes

Unfortunately the local dimming with shaders is kind of a bummer, just dims the entire image too much and causes some unwanted changes to the colors. It’s awesome with modern games, though.

2 Likes

New Release Version (2024-03-26-r1):

Notable changes:

  • Changes are relative to the 2024-02-29-r1 version.
  • Improved halation+, now also complies with mask gamma feature.
  • Preserve mask strength feature re-implemented, seems quite more useful now.
  • Ntsc taps feature improvements with lower tap count, much better coloring and chroma scaling compatibility. Rainbowing (2-phase) works differently with 15 or lower tap count (info).
  • Ntsc taps fall-off feature removed, it’s no longer needed due to the tap count improvements.
  • “Silly” Magic Glow bugfix. :slight_smile:
  • Feedback is welcome.
  • Edit: nice update to halation+

Download link:

https://mega.nz/file/1kogSL5Z#XpnDLAEJdx_yPApXPklk6MinukdhGW4LumEgr5-EqUI

20 Likes

Oh man, this update is loaded with goodies. Been waiting on this, gonna take this straight to the lab and have a look at all of this.

3 Likes

thanks

but when I did update to it I got image

edit: after update the slang shaders in retroarch and then update to 2024-03-26-r1 it work now

2 Likes

There is a constant “struggle” in coding, namely to keep the code simple and clean or to add some voodoo. But sometimes it’s imperative to make it a bit heavier. :wink:

I updated the latest release above, improving the halation part a bit (better contrast…), in fact it’s getting a good compromise.

4 Likes

I did a little playing around with halation+ in the first release from yesterday, still not sure which direction I’ll be taking things yet but the first thing I noticed tweaking it on on my curvature presets is that turning it up to about 0.12 or above makes moire patterns more pronounced? I already know the moire is there to stay no matter what and that using non interger scaling doesn’t make it any better.

In comparison to halation- which in my presets I have it at -.20 moire patterns aren’t as pronounced. Just something I noticed, I still haven’t really digged into this release yet.

1 Like

It’s quite different compared with negative halation, allowing some new looks. Yes, it’s not really about like replacing it regardless, but more for presets with emphasis on the “new feature”. Previously both halations looked similar, much more presets used negative halation, so I made the positive fork work in some other directions. It’s also mask gamma sensitive, which can contribute to a more coarse look.

2 Likes

Hey, Guest, do I have to delete your old files to update it or can I replace them?

1 Like

Currently it’s fine with “replace”, no obsolete files or changed file names since a while.

1 Like

What shader parameter values should I change for a (7 inch) 1080p screen? I know 1080p is quite low for nice shader results, but that’s what I have on my Rog Ally. Btw, what screen resolution are the default shader parameter values targeted at? 4k? Tks!!!

1 Like

Hey! 1080p is already a nice resolution, allowing nice setups. If not going for integer scaling, i would advise milder scanlines.

Masks which go nice with 1080p are:

  • 0.0, 1.0, 2.0, 3.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0 (shadowmask setup).

Slotmask is always nice, but it looks better with quite mild scanlines with it or minimum slotmask height.

4 Likes

New Release Version (2024-04-08-r1):

Notable changes:

  • halation+ improvements, now it works more nicely as a standalone mask mitigation technique
  • ntsc taps behavior changed for taps below 16
  • UPDATE: fixed horizontal downsampling with standard and fast versions

Download link:

https://mega.nz/file/J4x2XBAK#9AbJrGBN7DXieBid0xM0qZTfKNGobnpkQddvQIE-aRY

15 Likes