Yeah IMO gaussian is much better for vertical detail, but did you make some changes to the sinc version that I missed (I’ve been away)?
Last time I tested it I got some undesirable results.
Yeah IMO gaussian is much better for vertical detail, but did you make some changes to the sinc version that I missed (I’ve been away)?
Last time I tested it I got some undesirable results.
You should test again. Maybe it’s fixed.
Hey, @hunterk, Do you have that subpixel mask include for glsl shaders?
I’m looking for it to properly port crt-hyllian with latest evolutions. Besides, to use mask with glow I need to put it as a last pass, otherwise some weird scaling artifacts appear.
There’s an old port of it here: but it’s missing a few of the later masks (and they might be in a different order). Let me know if you need me to work on it.
The main issue with GLSL is that it doesn’t support 2D arrays until relatively late in the game, so for compatibility’s sake, I’ve switched to xBR-style nested boolean comparisons.
EDIT: I went ahead and updated it to match the version in the slang repo (full shader included for context; notice that it requires #version 120 or higher):
#version 120
#pragma parameter mask_type "mask_type" 1.0 0.0 20.0 1.0
#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 TexCoord;
COMPAT_VARYING vec4 TEX0;
// out variables go here as COMPAT_VARYING whatever
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;
#define vTexCoord TEX0.xy
#define SourceSize vec4(TextureSize, 1.0 / TextureSize)
#define OutSize vec4(OutputSize, 1.0 / OutputSize)
void main()
{
gl_Position = MVPMatrix * VertexCoord;
TEX0.xy = TexCoord.xy;
}
#elif defined(FRAGMENT)
#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
#if __VERSION__ >= 130
#define COMPAT_VARYING in
#define COMPAT_TEXTURE texture
out COMPAT_PRECISION vec4 FragColor;
#else
#define COMPAT_VARYING varying
#define FragColor gl_FragColor
#define COMPAT_TEXTURE texture2D
#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 mask_type;
#else
#define mask_type 0.0
#endif
/*
A collection of CRT mask effects that work with LCD subpixel structures for
small details
author: hunterk
license: public domain
How to use it:
Multiply your image by the vec3 output:
FragColor.rgb *= mask_weights(gl_FragCoord.xy, 1.0, 1);
The function needs to be tiled across the screen using the physical pixels, e.g.
gl_FragCoord (the "vec2 coord" input). In the case of slang shaders, we use
(vTexCoord.st * OutputSize.xy).
The "mask_intensity" (float value between 0.0 and 1.0) is how strong the mask
effect should be. Full-strength red, green and blue subpixels on a white pixel
are the ideal, and are achieved with an intensity of 1.0, though this darkens
the image significantly and may not always be desirable.
The "phosphor_layout" (int value between 0 and 19) determines which phophor
layout to apply. 0 is no mask/passthru.
Many of these mask arrays are adapted from cgwg's crt-geom-deluxe LUTs, and
those have their filenames included for easy identification
*/
vec3 mask_weights(vec2 coord, float mask_intensity, int phosphor_layout){
vec3 weights = vec3(1.,1.,1.);
float on = 1.;
float off = 1.-mask_intensity;
vec3 red = vec3(on, off, off);
vec3 green = vec3(off, on, off);
vec3 blue = vec3(off, off, on );
vec3 magenta = vec3(on, off, on );
vec3 yellow = vec3(on, on, off);
vec3 cyan = vec3(off, on, on );
vec3 black = vec3(off, off, off);
vec3 white = vec3(on, on, on );
int w, z = 0;
// This pattern is used by a few layouts, so we'll define it here
vec3 aperture_weights = mix(magenta, green, floor(mod(coord.x, 2.0)));
if(phosphor_layout == 0) return weights;
else if(phosphor_layout == 1){
// classic aperture for RGB panels; good for 1080p, too small for 4K+
// aka aperture_1_2_bgr
weights = aperture_weights;
return weights;
}
else if(phosphor_layout == 2){
// 2x2 shadow mask for RGB panels; good for 1080p, too small for 4K+
// aka delta_1_2x1_bgr
vec3 inverse_aperture = mix(green, magenta, floor(mod(coord.x, 2.0)));
weights = mix(aperture_weights, inverse_aperture, floor(mod(coord.y, 2.0)));
return weights;
}
else if(phosphor_layout == 3){
// slot mask for RGB panels; looks okay at 1080p, looks better at 4K
// {magenta, green, black, black},
// {magenta, green, magenta, green},
// {black, black, magenta, green}
// GLSL can't do 2D arrays until version 430, so do this stupid thing instead for compatibility's sake:
// First lay out the horizontal pixels in arrays
vec3 slotmask_x1[4] = vec3[](magenta, green, black, black);
vec3 slotmask_x2[4] = vec3[](magenta, green, magenta, green);
vec3 slotmask_x3[4] = vec3[](black, black, magenta, green);
// find the vertical index
w = int(floor(mod(coord.y, 3.0)));
// find the horizontal index
z = int(floor(mod(coord.x, 4.0)));
// do a big, dumb comparison in place of a 2D array
weights = (w == 1) ? slotmask_x1[z] : (w == 2) ? slotmask_x2[z] : slotmask_x3[z];
}
if(phosphor_layout == 4){
// classic aperture for RBG panels; good for 1080p, too small for 4K+
weights = mix(yellow, blue, floor(mod(coord.x, 2.0)));
return weights;
}
else if(phosphor_layout == 5){
// 2x2 shadow mask for RBG panels; good for 1080p, too small for 4K+
vec3 inverse_aperture = mix(blue, yellow, floor(mod(coord.x, 2.0)));
weights = mix(mix(yellow, blue, floor(mod(coord.x, 2.0))), inverse_aperture, floor(mod(coord.y, 2.0)));
return weights;
}
else if(phosphor_layout == 6){
// aperture_1_4_rgb; good for simulating lower
vec3 ap4[4] = vec3[](red, green, blue, black);
z = int(floor(mod(coord.x, 4.0)));
weights = ap4[z];
return weights;
}
else if(phosphor_layout == 7){
// aperture_2_5_bgr
vec3 ap3[5] = vec3[](red, magenta, blue, green, green);
z = int(floor(mod(coord.x, 5.0)));
weights = ap3[z];
return weights;
}
else if(phosphor_layout == 8){
// aperture_3_6_rgb
vec3 big_ap[7] = vec3[](red, red, yellow, green, cyan, blue, blue);
w = int(floor(mod(coord.x, 7.)));
weights = big_ap[w];
return weights;
}
else if(phosphor_layout == 9){
// reduced TVL aperture for RGB panels
// aperture_2_4_rgb
vec3 big_ap_rgb[4] = vec3[](red, yellow, cyan, blue);
w = int(floor(mod(coord.x, 4.)));
weights = big_ap_rgb[w];
return weights;
}
else if(phosphor_layout == 10){
// reduced TVL aperture for RBG panels
vec3 big_ap_rbg[4] = vec3[](red, magenta, cyan, green);
w = int(floor(mod(coord.x, 4.)));
weights = big_ap_rbg[w];
return weights;
}
else if(phosphor_layout == 11){
// delta_1_4x1_rgb; dunno why this is called 4x1 when it's obviously 4x2 /shrug
vec3 delta_1_1[4] = vec3[](red, green, blue, black);
vec3 delta_1_2[4] = vec3[](blue, black, red, green);
w = int(floor(mod(coord.y, 2.0)));
z = int(floor(mod(coord.x, 4.0)));
weights = (w == 1) ? delta_1_1[z] : delta_1_2[z];
return weights;
}
else if(phosphor_layout == 12){
// delta_2_4x1_rgb
vec3 delta_2_1[4] = vec3[](red, yellow, cyan, blue);
vec3 delta_2_2[4] = vec3[](cyan, blue, red, yellow);
z = int(floor(mod(coord.x, 4.0)));
weights = (w == 1) ? delta_2_1[z] : delta_2_2[z];
return weights;
}
else if(phosphor_layout == 13){
// delta_2_4x2_rgb
vec3 delta_1[4] = vec3[](red, yellow, cyan, blue);
vec3 delta_2[4] = vec3[](red, yellow, cyan, blue);
vec3 delta_3[4] = vec3[](cyan, blue, red, yellow);
vec3 delta_4[4] = vec3[](cyan, blue, red, yellow);
w = int(floor(mod(coord.y, 4.0)));
z = int(floor(mod(coord.x, 4.0)));
weights = (w == 1) ? delta_1[z] : (w == 2) ? delta_2[z] : (w == 3) ? delta_3[z] : delta_4[z];
return weights;
}
else if(phosphor_layout == 14){
// slot mask for RGB panels; too low-pitch for 1080p, looks okay at 4K, but wants 8K+
// {magenta, green, black, black, black, black},
// {magenta, green, black, magenta, green, black},
// {black, black, black, magenta, green, black}
vec3 slot2_1[6] = vec3[](magenta, green, black, black, black, black);
vec3 slot2_2[6] = vec3[](magenta, green, black, magenta, green, black);
vec3 slot2_3[6] = vec3[](black, black, black, magenta, green, black);
w = int(floor(mod(coord.y, 3.0)));
z = int(floor(mod(coord.x, 6.0)));
weights = (w == 1) ? slot2_1[z] : (w == 2) ? slot2_2[z] : slot2_3[z];
return weights;
}
else if(phosphor_layout == 15){
// slot_2_4x4_rgb
// {red, yellow, cyan, blue, red, yellow, cyan, blue },
// {red, yellow, cyan, blue, black, black, black, black},
// {red, yellow, cyan, blue, red, yellow, cyan, blue },
// {black, black, black, black, red, yellow, cyan, blue }
vec3 slotmask_RBG_x1[8] = vec3[](red, yellow, cyan, blue, red, yellow, cyan, blue );
vec3 slotmask_RBG_x2[8] = vec3[](red, yellow, cyan, blue, black, black, black, black);
vec3 slotmask_RBG_x3[8] = vec3[](red, yellow, cyan, blue, red, yellow, cyan, blue );
vec3 slotmask_RBG_x4[8] = vec3[](black, black, black, black, red, yellow, cyan, blue );
// find the vertical index
w = int(floor(mod(coord.y, 4.0)));
// find the horizontal index
z = int(floor(mod(coord.x, 8.0)));
weights = (w == 1) ? slotmask_RBG_x1[z] : (w == 2) ? slotmask_RBG_x2[z] : (w == 3) ? slotmask_RBG_x3[z] : slotmask_RBG_x4[z];
return weights;
}
else if(phosphor_layout == 16){
// slot mask for RBG panels; too low-pitch for 1080p, looks okay at 4K, but wants 8K+
// {yellow, blue, black, black},
// {yellow, blue, yellow, blue},
// {black, black, yellow, blue}
vec3 slot2_1[4] = vec3[](yellow, blue, black, black);
vec3 slot2_2[4] = vec3[](yellow, blue, yellow, blue);
vec3 slot2_3[4] = vec3[](black, black, yellow, blue);
w = int(floor(mod(coord.y, 3.0)));
z = int(floor(mod(coord.x, 4.0)));
weights = (w == 1) ? slot2_1[z] : (w == 2) ? slot2_2[z] : slot2_3[z];
return weights;
}
else if(phosphor_layout == 17){
// slot_2_5x4_bgr
// {red, magenta, blue, green, green, red, magenta, blue, green, green},
// {black, blue, blue, green, green, red, red, black, black, black},
// {red, magenta, blue, green, green, red, magenta, blue, green, green},
// {red, red, black, black, black, black, blue, blue, green, green}
vec3 slot_1[10] = vec3[](red, magenta, blue, green, green, red, magenta, blue, green, green);
vec3 slot_2[10] = vec3[](black, blue, blue, green, green, red, red, black, black, black);
vec3 slot_3[10] = vec3[](red, magenta, blue, green, green, red, magenta, blue, green, green);
vec3 slot_4[10] = vec3[](red, red, black, black, black, black, blue, blue, green, green);
w = int(floor(mod(coord.y, 4.0)));
z = int(floor(mod(coord.x, 10.0)));
weights = (w == 1) ? slot_1[z] : (w == 2) ? slot_2[z] : (w == 3) ? slot_3[z] : slot_4[z];
return weights;
}
else if(phosphor_layout == 18){
// same as above but for RBG panels
// {red, yellow, green, blue, blue, red, yellow, green, blue, blue },
// {black, green, green, blue, blue, red, red, black, black, black},
// {red, yellow, green, blue, blue, red, yellow, green, blue, blue },
// {red, red, black, black, black, black, green, green, blue, blue }
vec3 slot_1[10] = vec3[](red, yellow, green, blue, blue, red, yellow, green, blue, blue );
vec3 slot_2[10] = vec3[](black, green, green, blue, blue, red, red, black, black, black);
vec3 slot_3[10] = vec3[](red, yellow, green, blue, blue, red, yellow, green, blue, blue );
vec3 slot_4[10] = vec3[](red, red, black, black, black, black, green, green, blue, blue );
w = int(floor(mod(coord.y, 4.0)));
z = int(floor(mod(coord.x, 10.0)));
weights = (w == 1) ? slot_1[z] : (w == 2) ? slot_2[z] : (w == 3) ? slot_3[z] : slot_4[z];
return weights;
}
else if(phosphor_layout == 19){
// slot_3_7x6_rgb
// {red, red, yellow, green, cyan, blue, blue, red, red, yellow, green, cyan, blue, blue},
// {red, red, yellow, green, cyan, blue, blue, red, red, yellow, green, cyan, blue, blue},
// {red, red, yellow, green, cyan, blue, blue, black, black, black, black, black, black, black},
// {red, red, yellow, green, cyan, blue, blue, red, red, yellow, green, cyan, blue, blue},
// {red, red, yellow, green, cyan, blue, blue, red, red, yellow, green, cyan, blue, blue},
// {black, black, black, black, black, black, black, black, red, red, yellow, green, cyan, blue}
vec3 slot_1[14] = vec3[](red, red, yellow, green, cyan, blue, blue, red, red, yellow, green, cyan, blue, blue);
vec3 slot_2[14] = vec3[](red, red, yellow, green, cyan, blue, blue, red, red, yellow, green, cyan, blue, blue);
vec3 slot_3[14] = vec3[](red, red, yellow, green, cyan, blue, blue, black, black, black, black, black, black, black);
vec3 slot_4[14] = vec3[](red, red, yellow, green, cyan, blue, blue, red, red, yellow, green, cyan, blue, blue);
vec3 slot_5[14] = vec3[](red, red, yellow, green, cyan, blue, blue, red, red, yellow, green, cyan, blue, blue);
vec3 slot_6[14] = vec3[](black, black, black, black, black, black, black, black, red, red, yellow, green, cyan, blue);
w = int(floor(mod(coord.y, 6.0)));
z = int(floor(mod(coord.x, 14.0)));
weights = (w == 1) ? slot_1[z] : (w == 2) ? slot_2[z] : (w == 3) ? slot_3[z] : (w == 4) ? slot_4[z] : (w == 5) ? slot_5[z] : slot_6[z];
return weights;
}
else return weights;
}
void main()
{
FragColor = COMPAT_TEXTURE(Source, vTexCoord);
FragColor.rgb *= mask_weights(gl_FragCoord.xy, 1.0, int(mask_type));
}
#endif
Thanks! I’ll use it asap!
BTW, I’ve updated crt-hyllian and variants in slang:
Unfortunately, using masks with glow in glsl generates some weird artifacts, instead the slang ones. So glow won’t use masks in glsl.
Here the glsl port of most shaders: crt-hyllian updated (glsl)
Unfortunately, the masks can’t work with glow without artifacts. In slang they can coexist.
Can you post a screenshot of the artifacts? AFAIK, there shouldn’t be anything wildly different between the two.
crt-hyllian-glow (no mask):
crt-hyllian-glow (mask 4):
Look at vertical stripes. These artifacts happen with any mask.
Ah, no worries. Just change line 564:
vec2 mask_coords = gl_FragCoord.xy; //texCoord.xy * OutputSize.xy;
This also mostly works in slang, but it can apparently be unreliable sometimes(?), according to maister’s spec. In Cg/GLSL, I think the texcoord*OutputSize method is wonky due to NPOT textures. (FAKEDIT: yeah, that’s the problem; you could also resolve it like this: vec2 mask_coords = (texCoord.xy * OutputSize.xy) * TextureSize.xy / InputSize.xy;
)
You’ll also want to add #version 120 at the top of the shaders with the mask code in them or it’ll fail on GPU/drivers that fall back to implicit #version 100 if no #version is set explicitly (I’m looking at YOU, Intel ಠ_ಠ)
Unfortunately, it doesn’t solve the vertical stripes. Tested with both options.
I think the problem is the way glow shaders work by scaling up and down. It destroys the masks.
It mitigates a bit the ntsc version. Though the only-glow version still presents the vertical stripes.
Mask7 is the most affected. Test using this mask with mask_intensity=1.0
Yeah, any scaling that happens afterward can screw it up. I don’t know why the slang version wouldn’t suffer from the same effect, though…
I would recommend putting the mask into the resolve pass in that case.
Correct, the problem isn’t with the masks. Using curvature+masks without glow fixes it. The problem is the way glow shader chain works.
Ok, managed to change resolve.glsl to include mask code. Now those artifacts are gone and masks are used on glow and ntsc versions.
@hunterk, I just found out that with imageviewer, internal core, curvature works as expected. But then, when using emulator cores like (snes9x or gengxplus) curvature doesn’t work. This is in glsl. The same curvature code works in slang. I just copied the code as I didn’t see anything I should change.
(glsl is very picky…)
Can you post a screenshot of the wonky curvature?
I can’t post now, but it only curves the upper left part of screen.
Ah, yeah, that’s the stupid NPOT texcoord issue. I think it’s fixable by doing the texturesize/inputsize thing.
EDIT: for reference, here’s the texcoord hokey-pokey used for lottes’ curvature: https://github.com/libretro/glsl-shaders/blob/master/crt/shaders/fakelottes.glsl#L245