Android/GoogleTV compatible shaders - Nitpicky

Borrowed some ideas, check this out

#version 110

/* 
  work by DariusG 2023, some ideas borrowed from Dogway's zfast_crt_geo
*/

#pragma parameter curv "Curvature"  1.0 0.0 1.0 1.0
#pragma parameter blur "Interpolation: 0.0 nearest,0.25 bilinear,1.0 sharp" 0.12 0.0 1.0 0.01
#pragma parameter msize "Mask Size" 1.0 1.0 2.0 1.0
#pragma parameter slotx "Slot Size x" 3.0 1.0 4.0 1.0
#pragma parameter sloty "Slot Size Y" 3.0 1.0 4.0 1.0
#pragma parameter mask "Mask Strength" 0.3 0.0 1.0 0.05
#pragma parameter ssize "Scanline Size" 1.0 1.0 2.0 1.0
#pragma parameter scan "Scanline Strength" 0.4 0.0 1.0 0.05
#pragma parameter thresh "Effect Threshold on brights" 0.2 0.0 0.33 0.01
#pragma parameter colors "Colors: 0.0 RGB, 1.0 NTSC, 2.0:P22 D93" 0.0 0.0 2.0 1.0
#pragma parameter sat "Saturation" 1.0 0.0 2.0 0.01
#pragma parameter wp "White Point" 0.0 -0.2 0.2 0.01

#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;

// compatibility #defines
#define vTexCoord TEX0.xy
#define SourceSize vec4(TextureSize, 1.0 / TextureSize) //either TextureSize or InputSize
#define OutSize vec4(OutputSize, 1.0 / OutputSize)

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

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;

// compatibility #defines
#define Source Texture
#define vTexCoord TEX0.xy

#define SourceSize vec4(TextureSize, 1.0 / TextureSize) //either TextureSize or InputSize
#define OutSize vec4(OutputSize, 1.0 / OutputSize)

#ifdef PARAMETER_UNIFORM
uniform COMPAT_PRECISION float blur;
uniform COMPAT_PRECISION float msize;
uniform COMPAT_PRECISION float sloty;
uniform COMPAT_PRECISION float slotx;
uniform COMPAT_PRECISION float mask;
uniform COMPAT_PRECISION float ssize;
uniform COMPAT_PRECISION float scan;
uniform COMPAT_PRECISION float curv;
uniform COMPAT_PRECISION float thresh;
uniform COMPAT_PRECISION float colors;
uniform COMPAT_PRECISION float sat;
uniform COMPAT_PRECISION float wp;

#else
#define blur 1.0
#define msize 1.0
#define sloty 1.0
#define slotx 1.0
#define mask 0.5
#define ssize 1.0
#define scan 0.4
#define curv 1.0
#define thresh 0.2
#define colors 0.0
#define sat 1.0
#define wp 0.0
#endif

#define pi 3.1415926    
#define pwr vec3(0.05/((1.0 - 0.05*scan)*(1.0 - 0.15*mask)))

vec2 Warp(vec2 pos)
{
    pos  = pos*2.0-1.0;
    pos *= vec2(1.0 + (pos.y*pos.y)*0.0276, 1.0 + (pos.x*pos.x)*0.0414);
    return pos*0.5 + 0.5;
}

const mat3 P22D93 = mat3(
     1.00000, 0.00000, -0.06173,
     0.07111, 0.96887, -0.01136,
     0.00000, 0.08197,  1.07280);

// NTSC to sRGB matrix, used in linear space
const mat3 NTSC = mat3(1.5073,  -0.3725, -0.0832, 
                    -0.0275, 0.9350,  0.0670,
                     -0.0272, -0.0401, 1.1677);

// Returns gamma corrected output, compensated for scanline+mask embedded gamma
vec3 inv_gamma(vec3 col, vec3 power)
{
    vec3 cir  = col-1.0;
         cir *= cir;
         col  = mix(sqrt(col),sqrt(1.0-cir),power);
    return col;
}

void main()
{
    vec2 scale = SourceSize.xy/InputSize.xy; vec2 pos,corn;
    if (curv == 1.0) { 
        pos = Warp(vTexCoord*scale); 
        corn   = min(pos,1.0-pos); // This is used to mask the rounded
        corn.x = 0.0001/corn.x;  // corners later on
        pos /= scale;
        } else pos = vTexCoord;

    vec2 textureCoords = pos*SourceSize.xy;
    vec2 screenCoords = vTexCoord*OutputSize.xy;
    float scalex = OutputSize.x/InputSize.x*blur;
    vec2 tex_pos = fract(textureCoords);

 //Interpolation: https://colececil.io/blog/2017/scaling-pixel-art-without-destroying-it/
 //If blur is 0.5 on an axis, then the color will not be interpolated on that axis. 
 //If it’s 0 or 1, then it will have maximum interpolation, with 0 being on one side of the texel 
 //and 1 being on the other. Anything between those values will cause interpolation somewhere 
 //between the minimum and maximum. 

    vec2 blur = clamp(tex_pos / scalex, 0.0,0.5) + clamp((tex_pos - 1.0) / scalex + 0.5, 0.0, 0.5);

//At this point, our texture coordinates are still in the range [0, texture size], 
//but we need to get them back into the range [0, 1] before we can use them to grab the color. 
//To do so, we simply divide by the texture width and height

    vec2 coords = (floor(textureCoords) + blur) * SourceSize.zw;

    vec4 res = COMPAT_TEXTURE(Source, coords) ;
    res *= res;
    res *= mix(((1.0-mask+1.0-scan)/2.0 + 
                    mask*sin(screenCoords.x/msize*SourceSize.x/InputSize.x*2.0/slotx*pi) +
                    mask*sin((screenCoords.y/msize+sloty*mod(screenCoords.x,2.0))*2.0*pi) +
                    scan*sin(textureCoords.y/ssize*2.0*pi)),1.0,dot(res.rgb,vec3(thresh))) ;
    
    if (colors == 1.0) res.rgb *= NTSC; else 
    if (colors == 2.0) res.rgb *= P22D93; else
    res.rgb;
 
    //CHEAP TEMPERATURE CONTROL     
       if (wp != 0.0) { res.rgb *= vec3(1.0+wp,1.0,1.0-wp);}
    

    res = clamp(res,0.0,1.0);
    vec3 lumweight = vec3(0.29,0.6,0.11);
    vec3 grays = vec3(dot(lumweight, res.rgb));
    res.rgb = mix(grays, res.rgb, sat);
    if (corn.y <= corn.x && curv == 1.0 || corn.x < 0.0001 && curv ==1.0 )
    res = vec4(0.0);
    res.rgb = inv_gamma(res.rgb,pwr);

    FragColor = res;
}

#endif
1 Like

Looks fine to me. I guess the pwr fit is adapted to your scanlines and mask implementation, I’m not sure what the 0.05/ means there if it’s part of the fit because it will modify the slope so brightness won’t be the same when no scanlines+mask is applied, probably a better alternative is to apply an offset at the end as I did with -1.2 (find yours as appropriate).

vec3(0.05/((1.0 - 0.05*scan)*(1.0 - 0.15*mask))-offset)

Everything else looks fine from a quick glance except I still don’t know the phosphor for the NTSC matrix. In grade I listed a few them.

2 Likes

It looks fine that way with and without scanlines & mask. Brightness is kept at the same level without mask/scanlines.

That corner function creates some rough edges, isn’t there a way to smooth things out?

Improved that slot mask a lot too.

#version 110

/* 
  work by DariusG 2023, some ideas borrowed from Dogway's zfast_crt_geo
*/

#pragma parameter curv "Curvature"  1.0 0.0 1.0 1.0
#pragma parameter blur "Interpolation: 0.0 nearest,0.25 bilinear,1.0 sharp" 0.12 0.0 1.0 0.01
#pragma parameter msize "Mask Size" 1.0 1.0 2.0 1.0
#pragma parameter slotx "Slot Size x" 3.0 2.0 3.0 1.0
#pragma parameter width "Mask Width 3.0/2.0 " 0.67 0.67 1.0 0.333
#pragma parameter mask "Mask Strength" 0.3 0.0 1.0 0.05
#pragma parameter ssize "Scanline Size" 1.0 1.0 2.0 1.0
#pragma parameter scan "Scanline Strength" 0.4 0.0 1.0 0.05
#pragma parameter thresh "Effect Threshold on brights" 0.2 0.0 0.33 0.01
#pragma parameter colors "Colors: 0.0 P22D93, 1.0 RGB, 2.0:NTSC" 1.0 0.0 2.0 1.0
#pragma parameter sat "Saturation" 1.0 0.0 2.0 0.01
#pragma parameter wp "White Point" 0.0 -0.2 0.2 0.01

#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;

// compatibility #defines
#define vTexCoord TEX0.xy
#define SourceSize vec4(TextureSize, 1.0 / TextureSize) //either TextureSize or InputSize
#define OutSize vec4(OutputSize, 1.0 / OutputSize)

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

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;

// compatibility #defines
#define Source Texture
#define vTexCoord TEX0.xy

#define SourceSize vec4(TextureSize, 1.0 / TextureSize) //either TextureSize or InputSize
#define OutSize vec4(OutputSize, 1.0 / OutputSize)

#ifdef PARAMETER_UNIFORM
uniform COMPAT_PRECISION float blur;
uniform COMPAT_PRECISION float msize;
uniform COMPAT_PRECISION float width;
uniform COMPAT_PRECISION float slotx;
uniform COMPAT_PRECISION float mask;
uniform COMPAT_PRECISION float ssize;
uniform COMPAT_PRECISION float scan;
uniform COMPAT_PRECISION float curv;
uniform COMPAT_PRECISION float thresh;
uniform COMPAT_PRECISION float colors;
uniform COMPAT_PRECISION float sat;
uniform COMPAT_PRECISION float wp;

#else
#define blur 1.0
#define msize 1.0
#define width 1.0
#define slotx 1.0
#define mask 0.5
#define ssize 1.0
#define scan 0.4
#define curv 1.0
#define thresh 0.2
#define colors 0.0
#define sat 1.0
#define wp 0.0
#endif

#define pi 3.1415926    
#define pwr vec3(0.25/((1.0 - 0.05*scan)*(1.0 - 0.15*mask)))

vec2 Warp(vec2 pos)
{
    pos  = pos*2.0-1.0;
    pos *= vec2(1.0 + (pos.y*pos.y)*0.0276, 1.0 + (pos.x*pos.x)*0.0414);
    return pos*0.5 + 0.5;
}

const mat3 P22D93 = mat3(
     1.00000, 0.00000, -0.06173,
     0.07111, 0.96887, -0.01136,
     0.00000, 0.08197,  1.07280);

// NTSC to sRGB matrix, used in linear space
const mat3 NTSC = mat3(1.5073,  -0.3725, -0.0832, 
                    -0.0275, 0.9350,  0.0670,
                     -0.0272, -0.0401, 1.1677);

// Returns gamma corrected output, compensated for scanline+mask embedded gamma
vec3 inv_gamma(vec3 col, vec3 power)
{
    vec3 cir  = col-1.0;
         cir *= cir;
         col  = mix(sqrt(col),sqrt(1.0-cir),power);
    return col;
}

void main()
{
    vec2 scale = SourceSize.xy/InputSize.xy; vec2 pos,corn;
    if (curv == 1.0) { 
        pos = Warp(vTexCoord*scale); 
        corn   = min(pos,1.0-pos); // This is used to mask the rounded
        corn.x = 0.0001/corn.x;  // corners later on
        pos /= scale;
        } else pos = vTexCoord;

    vec2 textureCoords = pos*SourceSize.xy;
    vec2 screenCoords = vTexCoord*OutputSize.xy*SourceSize.xy/InputSize.xy;
    float scalex = OutputSize.x/InputSize.x*blur;
    vec2 tex_pos = fract(textureCoords);

 //Interpolation: https://colececil.io/blog/2017/scaling-pixel-art-without-destroying-it/
 //If blur is 0.5 on an axis, then the color will not be interpolated on that axis. 
 //If it’s 0 or 1, then it will have maximum interpolation, with 0 being on one side of the texel 
 //and 1 being on the other. Anything between those values will cause interpolation somewhere 
 //between the minimum and maximum. 

    vec2 blur = clamp(tex_pos / scalex, 0.0,0.5) + clamp((tex_pos - 1.0) / scalex + 0.5, 0.0, 0.5);

//At this point, our texture coordinates are still in the range [0, texture size], 
//but we need to get them back into the range [0, 1] before we can use them to grab the color. 
//To do so, we simply divide by the texture width and height

    vec2 coords = (floor(textureCoords) + blur) * SourceSize.zw;

    vec4 res = COMPAT_TEXTURE(Source, coords) ;
    res *= res;

    // similar mask to Lottes 1, done with sins, option for wide 2.0
    float oddx = mod(screenCoords.x,slotx*2.0) < slotx ? 1.0 : 0.0;
    res *= mix(((1.0-mask+1.0-scan)/2.0  
                    + mask*sin(screenCoords.x*width/msize*pi) 
                    + mask*sin(((screenCoords.y+oddx)/msize)*pi) 
                    + scan*sin(textureCoords.y/ssize*2.0*pi)),1.0,dot(res.rgb,vec3(thresh)));
    
    if (colors == 2.0) res.rgb *= NTSC; else 
    if (colors == 0.0) res.rgb *= P22D93; else
    res.rgb;
 
    //CHEAP TEMPERATURE CONTROL     
       if (wp != 0.0) { res.rgb *= vec3(1.0+wp,1.0,1.0-wp);}
    

    res = clamp(res,0.0,1.0);
    vec3 lumweight = vec3(0.29,0.6,0.11);
    vec3 grays = vec3(dot(lumweight, res.rgb));
    res.rgb = mix(grays, res.rgb, sat);
    if (corn.y <= corn.x && curv == 1.0 || corn.x < 0.0001 && curv ==1.0 )
    res = vec4(0.0);
    res.rgb = inv_gamma(res.rgb,pwr);

    FragColor = res;
}

#endif
2 Likes

You must change the mask line to

COMPAT_PRECISION float whichmask = floor(vTexCoord.x*OutputSize.x*TextureSize.x/InputSize.x)*-MSCL;

Instead of “4.0”

Or else funky stuff on android screen like screen splitting in 2 where right part has no mask

What’s the resolution of the android screen? 4.0 is the TVL designed for 1080p resolution.

That’s 1080p. If I change to source/input it’s fixed.

2 Likes

The terminal is 1920x1080 right?

That effect is strange because it was supposed to be fixed in the next line:

TEX0.xy = TexCoord.xy*1.00001;

Before for the MSCL definition instead of *-0.5 I had 0.499999 and I was told it was unnecesary since I already had the above line and worked for me in Chromecast.

Try to change it back to 0.499999 but keeping the 4.0*

Probably some cores handle Sourcesize, InputSize differently I guess that’s why 4.0 is not working.

Here on another phone

2 Likes

What core are you testing on? just for reference.

Try as I said to keep the 4.0 multiplier but tweak the MSCL values to minus epsilon. If I remove 4.0 I don’t get any mask at all on my (UHD) display.

I don’t have retroarch on my phone or tablet, neither is 1080p so I can’t test either.

It’s Pico drive, at least it doesn’t look correct on some Androids there. Being a fast shader it would be good to run properly on cellphones, I guess that is where it would be used mostly. I have some ideas for fast shaders but I don’t have a really weak device, the slowest is htc one m7 that reports 97 gflops (it can run crt-geom-mini, crt-sines very well, 65 and 85 fps respectively at 1080p and looks brilliant). I have a netbook around 10 gflops but I guess that would be useless, not much to do there that would look different than something like fake-crt-geom-potato.

2 Likes