NTSC shader discussion

This post is prompted by Syh’s question in the CRT showoff thread:

@hunterk Could you please explain the difference between the ntsc 2-phase and 3-phase composite/svideo shaders, like what the visual difference is (could it be a quality difference)? Or is the difference something else?

For our uses, the phase has to do with the resolution. 3-phase is for 256px width images, 2-phase is for 320px. More on this in a bit.

background:

The analog color TV standards were built to be backward-compatible with the black and white standard–which is just luminance (aka luma) data–by stacking color data on top. The signal is separated into the YIQ (NTSC) or YUV (PAL) colorspaces, with Y representing the luma and IQ/UV representing the color data channels (aka chroma).

relevant bit:

Svideo is often referred to as Y/IQ because it runs the Y (luma) signal on a separate line from the IQ (chroma) signal, while composite is a … wait for it… composite of the 2 signals running over the same line.

The luma and chroma carrier channels are mostly separate/separable (the “phase” has to do with how the chroma is encoded), but they overlap slightly, and in a composite signal, the luma can bleed into the chroma signal through a process known as “chroma/luma crosstalk”, which is what makes the characteristic color rainbows and “artifact colors”. (H and V sync pulses are in the signal, as well, but they don’t cause any visible issues)

There are also some other artifacts, like the sawtooth and dot crawl effects that are common with NTSC composite video, that should be lessened or gone entirely with svideo.

4 Likes

Thanks so much for this answer, I’ve been googling this for the past two days and couldn’t figure what the hell was going, lol.

So would the phase function be similar to signal bandwidth? Or completely different things?

It’s a different thing. Phase is the offset of the wave while bandwidth is the magnitude of the min/max amplitude of the wave.

1 Like

Hmm, alright.

I’m asking about the signal bandwidth because I was thinking about trying to add a signal bandwidth function to ntsc-adaptive. (If it’s possible, then we could theoretically at least from what I was reading anyway, have a composite, svideo, RGB ntsc shader.)

Dunno if it will work though.

AFAIK, RGB signals never actually pass through the NTSC encoders, so RGB should be basically no modification at all, but that’s more or less what GTU does. That is, you can adjust the signal bandwidth for the Y/I/Q channels.

1 Like

Yeah, I was thinking of basically using the signal bandwidth section of GTU code, and using an if statement for things so it would be a pseudo-passthrough mode that only did signal bandwidth (for RGB) and the rest would just function as normal so 0-1 would composite/s-video respectively.

I mean I don’t really no how likely this is to work but I sounded good in my head, lol.

yeah, you could do that, I suppose. Just take the GTU shader and put a big if statement on each pass. The performance impact shouldn’t be terrible, but I would personally just load the shader I want in that case.

1 Like

First off, sorry about derailing the thread again…

Yeah I think I know how I’m going to do it after looking over GTU, at least for the first pass of ntsc-adaptive, not sure how to do the pass through for second pass of adaptive.

For the second pass could I just make an empty vec3 in an if statement and just run it into fragColor in the if statement for the pass through?

I’m weird I’d rather have a couple of larger shaders that are swiss army knifes so I don’t have to have a bunch of presets, and have to flip through them.

To me it’s simpler to just open up the shader settings and change a couple of things to get what I want. Instead of loading a different preset then still opening up the shader settings to change things anyway. (I know I can make presets on a per core/game basis. I’m just not super interested in it.)

I’m not saying one way is better than the other, it’s completely a preference thing. The per w/e presets are certainly simpler and faster in alot of ways though.

@hunterk , I observed that ntsc-adaptive has brightness set to 0.95 due to clipping, I’m not sure if that’s clipping prevention from the ntsc pass itself or from following shaders like scanlines (which with bloom, halation and such could clip values). What I see is a decline in overall image brightness. Could be it ok to go and update also the 2-phase and 3-phase shader versions or is that something you were experimenting on?

2 Likes

I dropped it based on my own screen with the 240p test suite. My laptop LCD is a piece of crap, though, so if you have a calibrated screen and can vouch for 100%, I’ll take your word for it :slight_smile:

And yeah, I didn’t want to apply it to the others without verifying elsewhere, since they’re the “official”/real reference implementation.

3 Likes

@hunterk @Dogway Hey good news !

1 Like

EDIT: Removed code as I just realised that tvout-tweaks-pass-1.glsl can be used for processing signal-bandwidth only (RGB signal).

1 Like

Could you elaborate some? (I know it’s related to what I was talking about. But more context would be nice and appreciated, lol.)

EDIT: Did you post code here and then remove it? I just checked the repos, glsl, slang, yours and nothing new has been updated.

I was reading on how to emulate signal bandwidth for Arcade systems so I tried to merge GTU pass2 and pass3, almost got it but as I posted I read here, tested and that’s it, only signal-bandwidth. Pass 1 only, Pass 0 is for TV levels, you only need to edit the last pow() function of Pass 1 and then adjust the correct full signal resolutions I guess.

  • NeoGeo 264
  • CPS2 262
  • CPS3 264
1 Like

How would I need to alter the last pow function?

The only knowledge I have about shaders really is from talking to people here on the forums and just hacking away at things until the work/seem to work.

So I’d need to run tvout-tweaks-pass1.glsl, I imagine before any ntsc passes, or is this not what we’re talking about?

Actually I neither know anything about coding shaders but I have some background on programming and image processing. I stripped out all composite related code from pass1 of tvout-tweaks.

signal-bandwidth.glsl

///////////////
//	TV-out tweaks Linearized Multipass - Pass1
//	Author: aliaspider and RiskyJumps
//	License: GPLv3
////////////////////////////////////////////////////////


// this shader is meant to be used when running
// an emulator on a real CRT-TV @240p or @480i
////////////////////////////////////////////////////////
// Basic settings:

// signal resolution
// higher = sharper
#pragma parameter TVOUT_RESOLUTION "TVOut Signal Resolution" 256.0 0.0 1024.0 1.0 // default, minimum, maximum, optional step

// simulate a composite connection instead of RGB
//#pragma parameter TVOUT_COMPOSITE_CONNECTION "TVOut Composite Enable" 0.0 0.0 1.0 1.0

//// use TV video color range (16-235)
//// instead of PC full range (0-255)
//#pragma parameter TVOUT_TV_COLOR_LEVELS "TVOut TV Color Levels Enable" 0.0 0.0 1.0 1.0
////////////////////////////////////////////////////////

////////////////////////////////////////////////////////
// Advanced settings:
//
// these values will be used instead
// if COMPOSITE_CONNECTION is defined
// to simulate different signal resolutions(bandwidth)
// for luma (Y) and chroma ( I and Q )
// this is just an approximation
// and will only simulate the low bandwidth anspect of
// composite signal, not the crosstalk between luma and chroma
// Y = 4MHz I=1.3MHz Q=0.4MHz
//#pragma parameter TVOUT_RESOLUTION_Y "TVOut Luma (Y) Resolution" 256.0 0.0 1024.0 32.0
//#pragma parameter TVOUT_RESOLUTION_I "TVOut Chroma (I) Resolution" 83.2 0.0 256.0 8.0
//#pragma parameter TVOUT_RESOLUTION_Q "TVOut Chroma (Q) Resolution" 25.6 0.0 256.0 8.0

// formula is MHz=resolution*15750Hz
// 15750Hz being the horizontal Frequency of NTSC
// (=262.5*60Hz)
////////////////////////////////////////////////////////

#if defined(VERTEX)

#if __VERSION__ >= 130
#define COMPAT_VARYING out
#define COMPAT_ATTRIBUTE in
#define COMPAT_TEXTURE texture
#else
#define COMPAT_VARYING varying
#define COMPAT_ATTRIBUTE attribute
#define COMPAT_TEXTURE texture2D
#endif

#ifdef GL_ES
#define COMPAT_PRECISION mediump
#else
#define COMPAT_PRECISION
#endif

COMPAT_ATTRIBUTE vec4 VertexCoord;
COMPAT_ATTRIBUTE vec4 COLOR;
COMPAT_ATTRIBUTE vec4 TexCoord;
COMPAT_VARYING vec4 COL0;
COMPAT_VARYING vec4 TEX0;

vec4 _oPosition1;
uniform mat4 MVPMatrix;
uniform COMPAT_PRECISION int FrameDirection;
uniform COMPAT_PRECISION int FrameCount;
uniform COMPAT_PRECISION vec2 OutputSize;
uniform COMPAT_PRECISION vec2 TextureSize;
uniform COMPAT_PRECISION vec2 InputSize;

void main()
{
    gl_Position = MVPMatrix * VertexCoord;
    TEX0.xy = TexCoord.xy;
}

#elif defined(FRAGMENT)

#if __VERSION__ >= 130
#define COMPAT_VARYING in
#define COMPAT_TEXTURE texture
out vec4 FragColor;
#else
#define COMPAT_VARYING varying
#define FragColor gl_FragColor
#define COMPAT_TEXTURE texture2D
#endif

#ifdef GL_ES
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
#define COMPAT_PRECISION mediump
#else
#define COMPAT_PRECISION
#endif

#ifdef PARAMETER_UNIFORM // If the shader implementation understands #pragma parameters, this is defined.
uniform COMPAT_PRECISION float TVOUT_RESOLUTION;
#else
// Fallbacks if parameters are not supported.
#define TVOUT_RESOLUTION 256.0 // Default
#endif

#define pi			3.14159265358
#define a(x) abs(x)
#define d(x,b) (pi*b*min(a(x)+0.5,1.0/b))
#define e(x,b) (pi*b*min(max(a(x)-0.5,-1.0/b),1.0/b))
#define STU(x,b) ((d(x,b)+sin(d(x,b))-e(x,b)-sin(e(x,b)))/(2.0*pi))

#define GETC(c) \
    c = ((COMPAT_TEXTURE(Texture, vec2(TEX0.x - X*oneT,TEX0.y)).xyz))

#define VAL(tempColor) \
    tempColor += (c*STU(X,(TVOUT_RESOLUTION*oneI)))


uniform COMPAT_PRECISION int FrameDirection;
uniform COMPAT_PRECISION int FrameCount;
uniform COMPAT_PRECISION vec2 OutputSize;
uniform COMPAT_PRECISION vec2 TextureSize;
uniform COMPAT_PRECISION vec2 InputSize;
uniform sampler2D Texture;
COMPAT_VARYING vec4 TEX0;

void main()
{

   vec3 tempColor = vec3(0.0,0.0,0.0);
   float	offset	= fract((TEX0.x * TextureSize.x) - 0.5);
   float oneT = 1.0 / TextureSize.x;
   float oneI = 1.0 / InputSize.x;

   float X;
   vec3 c;

   X = (offset-(-1.0));//X(-1.0);
   GETC(c);
   VAL(tempColor);

   X = (offset-(0.0));//X(0.0);
   GETC(c);
   VAL(tempColor);

   X = (offset-(1.0));//X(1.0);
   GETC(c);
   VAL(tempColor);

   X = (offset-(2.0));//X(2.0);
   GETC(c);
   VAL(tempColor);

   FragColor = vec4(tempColor, 1.0);
}
#endif
2 Likes

The extent of my coding knowledge before this was doing MySpace layouts (Jesus I feel old.) And working on EmulationStation themes.

I am making progress though, I know way more about what I’m doing then when I started a year or two ago.

So I imagine I can shoehorn this code into ntsc-adaptive? At least for what I was discussing.

EDIT: This actually is super helpful, as now I don’t have to try and rip this code from GTU, should be able to do some if statements in both passes of ntsc adaptive. Adding this to the first pass and making a passthrough in the second pass if RGB mode is enabled.

Also thanks for taking the time for the assistance! Wish I could give more than a heart on the comment, lol.

1 Like

Yes I guess this code is a nice start for your purpose. I’m actually not worried to implement RGB into the ntsc shader, but I might have a look in the next few days. I guess it’s a matter of copying the code and evaluate if ntsc > 2.0 (3.0 would be RGB). ntsc-adaptive doesn’t do signal-bandwidth you say?

1 Like

Not from my understanding, at least not in a manner that you can adjust it.

I may be wrong and maybe it’s built into the video modes. (I really can’t say with certainty, as I don’t really understand the ntsc code.)

I’d check the first couple posts of this thread they may help you out figuring out if/how the ntsc shaders handle bandwidth.

Here’s a possible relevant quote from Hunter. 3rd post in the thread.

Phase is the offset of the wave while bandwidth is the magnitude of the min/max amplitude of the wave.

1 Like

So I was looking over the ntsc shaders, and I’m trying to figure out where it’s applying the scale in shader, is it the first line after void main ()

vec3 col = COMPAT_TEXTURE(Source, vTexCoord).rgb

Or is it the pix_no value that applying it?

#define SourceSize vec4(TextureSize, 1.0 / TextureSize)
#define outsize vec4(OutputSize, 1.0 / OutputSize)

pix_no = vTexCoord * SourceSize.xy * (outsize.xy / InputSize.xy);

float mod_phase = chroma_phase + pix_no.x * CHROMA_MOD_FREQ;

Or is it applied outside the shader via the glslp, or slangp?

Mainly I’m trying to figure out where and how it’s applying the horizontal resolution to the shader.