Big crt mask function for copypaste

if you have anything other than 4 (such as a parameter) in there for the second number, wrap it in int(), like this int(mask_picking_parameter)

1 Like

So this?

color *= mask_weights(gl_FragCoord.xy, 1.0, int(mask_picking_parameter));

Yeahhh, that got it going!

2 Likes

While we’re on the subject of masks, is there anyway possible to get good slot mask emulation in 1080p? I updated RA today and went through all the new CRT shader all afternoon…Not one looks right on my Panny Plasma.

I reverted to using CRT Aperture but I’m trying to replicate my consumer TV’s I grew up with and all had slotmasks including the one in my den right now.

Either the ones a few posts up or the ones from lottes’ shaders are probably your best bet. There are some from the blend_overlay shader that look pretty nice, if not “accurate”, too.

2 Likes

I know that on my plasma, I can’t get any masks to look right because the subpixels on a plasma work differently than on an LCD.

Unfortunately, you need 8K resolution to do a convincing slot mask emulation, but there are various compromise solutions available for lower resoutions.

I’m thinking of breaking this pack up so it’s easier to use for some people (not everyone is going to want 17 masks).

Is it possible that you could do two of lottes masks like this?

Mainly I want mask 3 the stretched VGA shadow mask, it’d also be cool to get mask 1 very compressed TV shadow mask but my main request is mask 3.

There’s not really any harm in having all of them, but most of them won’t be of use to most people (e.g., the RBG masks). I did set it up in such a way that it’s easy to isolate/add/remove them.

As for Lottes’ masks, they function a bit differently and need to be applied in linear gamma, so they’re probably better off staying on their own.

1 Like

About the lottes masks, I’m not really saying I need that exact code. I’m asking if we could get masks that look like those but done in this mask pack style.

Also I wasn’t complaining about all of the masks I was saying I was thinking about splitting them into smaller packs to make peoples lives easier, lol.

Many of the Samsung QLEDs use RBG pixels, so I’m definitely glad that these options are included! :slight_smile:

I feel like there’s a lot of good info here and in the threads “ten years of CRT shaders” and “new crt shader from Guest,” but it’s all kind of scattered. Was thinking of organizing this stuff into a user’s guide when I get some time.

1 Like

That’s a good idea. I’m not sure what all it does, but you can make posts into “wikis”, if that helps.

1 Like

Is there anyway to speed up masks 13-14?

I really like them, but whenever I run them, my system starts to chug (pretty much all of the other masks run at full speed though, except masks (3, 13-17). 11 out of 17 being fast is pretty good though lol.

Not that I know of, other than making a LUT texture and tiling that across the screen instead of using a 2D array lookup.

1 Like

Could you help me out with that? This is way outta my kiddie pool of knowledge, lol.

Would those masks be any faster in slang?

EDIT: What does this do? float mask_intensity

In this:

vec3 mask_weights(vec2 coord, float mask_intensity, int phosphor_layout)

Should I be using that parameter, instead of a new parameter for this;

color *= mask_weights(gl_FragCoord.xy, new_parameter, int(mask_picking_parameter));

When I should be using this instead?

color *= mask_weights(gl_FragCoord.xy, mask_intensity, int(mask_picking_parameter));

@hunterk I have a quick question, I’m currently in the process of switching over to slang and was wondering how to use subpixel-masks.h.

I’m wanting to pipe it into guest-dr-venom replacing the original mask setup. I don’t really want to copy paste all of the mask code into guest-dr-venom. So I was wanting to learn how to do this instead.

My main issue is how I go about applying the mask in guest when it’s in the .h file (I understand I have to do the ‘#include-stuff’ masks.h in guest-dr-venom) and if there’s any little things I have to do to accommodate this way of doing it via code.

Hope to hear from you, and thank you for your time!

1 Like

The header file is just a single function:

vec3 mask_weights(vec2 coord, float mask_intensity, int phosphor_layout)

The function outputs a vec3 that you multiply the unmasked image output by. It has 3 components required to calculate the mask: the screen coordinates (in slang, it’s usually ‘vTexCoord * params.OutputSize.xy’), the strength (on a scale of 0.0 to 1.0; most shaders have something for this already) and the mask selector (it expects an int while parameters are floats, so you’ll need to wrap the raw parameter in int() at some point).

here’s crt-geom-deluxe.slang with the changes made:

#version 450

/*  CRT shader
 *
 *  Copyright (C) 2010-2016 cgwg, Themaister and DOLLS
 *
 *  This program is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU General Public License as published by the Free
 *  Software Foundation; either version 2 of the License, or (at your option)
 *  any later version.
 */
 
#include "geom-deluxe-params.inc"

#include "../../../include/subpixel_masks.h"

#define u_tex_size0 global.SourceSize
#define u_tex_size1 global.internal1Size
#define u_quad_dims global.OutputSize

// Comment the next line to disable interpolation in linear gamma (and gain speed).
//#define LINEAR_PROCESSING

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

// Use the older, purely gaussian beam profile
#define USEGAUSSIAN

// Macros.
#define FIX(c) max(abs(c), 1e-5)
#define PI 3.141592653589

#pragma stage vertex
layout(location = 0) in vec4 Position;
layout(location = 1) in vec2 TexCoord;
layout(location = 0) out vec2 v_texCoord;
layout(location = 1) out vec2 v_sinangle;
layout(location = 2) out vec2 v_cosangle;
layout(location = 3) out vec3 v_stretch;
layout(location = 4) out vec2 v_one;

float intersect(vec2 xy , vec2 sinangle, vec2 cosangle)
{
  float A = dot(xy,xy)+d.x*d.x;
  float B = 2.0*(params.R.x*(dot(xy,sinangle)-d.x*cosangle.x*cosangle.y)-d.x*d.x);
  float C = d.x*d.x + 2.0*params.R.x*d.x*cosangle.x*cosangle.y;
  return (-B-sqrt(B*B-4.0*A*C))/(2.0*A);
}

vec2 bkwtrans(vec2 xy, vec2 sinangle, vec2 cosangle)
{
  float c = intersect(xy, sinangle, cosangle);
  vec2 pt = vec2(c)*xy;
  pt -= vec2(-params.R.x)*sinangle;
  pt /= vec2(params.R.x);
  vec2 tang = sinangle/cosangle;
  vec2 poc = pt/cosangle;
  float A = dot(tang,tang)+1.0;
  float B = -2.0*dot(poc,tang);
  float C = dot(poc,poc)-1.0;
  float a = (-B+sqrt(B*B-4.0*A*C))/(2.0*A);
  vec2 uv = (pt-a*sinangle)/cosangle;
  float r = FIX(params.R.x*acos(a));
  return uv*r/sin(r/params.R.x);
}

vec2 fwtrans(vec2 uv, vec2 sinangle, vec2 cosangle)
{
  float r = FIX(sqrt(dot(uv,uv)));
  uv *= sin(r/params.R.x)/r;
  float x = 1.0-cos(r/params.R.x);
  float D = d.x/params.R.x + x*cosangle.x*cosangle.y+dot(uv,sinangle);
  return d.x*(uv*cosangle-x*sinangle)/D;
}

vec3 maxscale(vec2 sinangle, vec2 cosangle)
{
  vec2 c = bkwtrans(-params.R.x * sinangle / (1.0 + params.R.x/d.x*cosangle.x*cosangle.y), sinangle, cosangle);
  vec2 a = vec2(0.5,0.5)*aspect.xy;
  vec2 lo = vec2(fwtrans(vec2(-a.x,c.y), sinangle, cosangle).x,
		 fwtrans(vec2(c.x,-a.y), sinangle, cosangle).y)/aspect.xy;
  vec2 hi = vec2(fwtrans(vec2(+a.x,c.y), sinangle, cosangle).x,
		 fwtrans(vec2(c.x,+a.y), sinangle, cosangle).y)/aspect.xy;
  return vec3((hi+lo)*aspect.xy*0.5,max(hi.x-lo.x,hi.y-lo.y));
}

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

  // Precalculate a bunch of useful values we'll need in the fragment
  // shader.
  vec2 ang;
  // if (u_rotation_type.x < 0.5)
  //   ang = vec2(0.0,angle.x);
  // else if (u_rotation_type.x < 1.5)
  //   ang = vec2(angle.x,0.0);
  // else if (u_rotation_type.x < 2.5)
  //   ang = vec2(0.0,-angle.x);
  // else
  //   ang = vec2(-angle.x,0.0);
  ang = angle.xy;
  v_sinangle = sin(ang);
  v_cosangle = cos(ang);
  v_stretch = maxscale(v_sinangle, v_cosangle);

  // The size of one texel, in texture-coordinates.
  v_one = 1.0 / u_tex_size0.xy;
}

#pragma stage fragment
layout(location = 0) in vec2 v_texCoord;
layout(location = 1) in vec2 v_sinangle;
layout(location = 2) in vec2 v_cosangle;
layout(location = 3) in vec3 v_stretch;
layout(location = 4) in vec2 v_one;
layout(location = 0) out vec4 FragColor;
layout(set = 0, binding = 2) uniform sampler2D blur;
layout(set = 0, binding = 3) uniform sampler2D internal1;
layout(set = 0, binding = 4) uniform sampler2D aperture;
layout(set = 0, binding = 5) uniform sampler2D slot;
layout(set = 0, binding = 6) uniform sampler2D delta;

#define blur_texture blur

#ifdef LINEAR_PROCESSING
#       define TEX2D(c) pow(texture(internal1, (c)), vec4(CRTgamma))
#else
#       define TEX2D(c) texture(internal1, (c))
#endif

float intersect(vec2 xy , vec2 sinangle, vec2 cosangle)
{
  float A = dot(xy,xy)+d.x*d.x;
  float B = 2.0*(params.R.x*(dot(xy,sinangle)-d.x*cosangle.x*cosangle.y)-d.x*d.x);
  float C = d.x*d.x + 2.0*params.R.x*d.x*cosangle.x*cosangle.y;
  return (-B-sqrt(B*B-4.0*A*C))/(2.0*A);
}

vec2 bkwtrans(vec2 xy, vec2 sinangle, vec2 cosangle)
{
  float c = intersect(xy, sinangle, cosangle);
  vec2 pt = vec2(c)*xy;
  pt -= vec2(-params.R.x)*sinangle;
  pt /= vec2(params.R.x);
  vec2 tang = sinangle/cosangle;
  vec2 poc = pt/cosangle;
  float A = dot(tang,tang)+1.0;
  float B = -2.0*dot(poc,tang);
  float C = dot(poc,poc)-1.0;
  float a = (-B+sqrt(B*B-4.0*A*C))/(2.0*A);
  vec2 uv = (pt-a*sinangle)/cosangle;
  float r = FIX(params.R.x*acos(a));
  return uv*r/sin(r/params.R.x);
}

vec2 transform(vec2 coord, vec3 stretch, vec2 sinangle, vec2 cosangle)
{
  coord = (coord-vec2(0.5))*aspect.xy*stretch.z+stretch.xy;
  return (bkwtrans(coord, sinangle, cosangle)/overscan.xy/aspect.xy+vec2(0.5));
}

float corner(vec2 coord)
{
  coord = (coord - vec2(0.5)) * overscan.xy + vec2(0.5);
  coord = min(coord, vec2(1.0)-coord) * aspect.xy;
  vec2 cdist = vec2(cornersize.x);
  coord = (cdist - min(coord,cdist));
  float dist = sqrt(dot(coord,coord));
  return clamp((max(cdist.x,1e-3)-dist)*cornersmooth.x,0.0, 1.0);
}

// Calculate the influence of a scanline on the current pixel.
//
// 'distance' is the distance in texture coordinates from the current
// pixel to the scanline in question.
// 'color' is the colour of the scanline at the horizontal location of
// the current pixel.
vec4 scanlineWeights(float distance, vec4 color)
{
  // "wid" controls the width of the scanline beam, for each RGB channel
  // The "weights" lines basically specify the formula that gives
  // you the profile of the beam, i.e. the intensity as
  // a function of distance from the vertical center of the
  // scanline. In this case, it is gaussian if width=2, and
  // becomes nongaussian for larger widths. Ideally this should
  // be normalized so that the integral across the beam is
  // independent of its width. That is, for a narrower beam
  // "weights" should have a higher peak at the center of the
  // scanline than for a wider beam.
#ifdef USEGAUSSIAN
  vec4 wid = 0.3 + 0.1 * pow(color, vec4(3.0));
  vec4 weights = vec4(distance / wid);
  return 0.4 * exp(-weights * weights) / wid;
#else
  vec4 wid = 2.0 + 2.0 * pow(color, vec4(4.0));
  vec4 weights = vec4(distance / 0.3);
  return 1.4 * exp(-pow(weights * inversesqrt(0.5 * wid), wid)) / (0.6 + 0.2 * wid);
#endif
}

void main()
{
  // Here's a helpful diagram to keep in mind while trying to
  // understand the code:
  //
  //  |      |      |      |      |
  // -------------------------------
  //  |      |      |      |      |
  //  |  01  |  11  |  21  |  31  | <-- current scanline
  //  |      | @    |      |      |
  // -------------------------------
  //  |      |      |      |      |
  //  |  02  |  12  |  22  |  32  | <-- next scanline
  //  |      |      |      |      |
  // -------------------------------
  //  |      |      |      |      |
  //
  // Each character-cell represents a pixel on the output
  // surface, "@" represents the current pixel (always somewhere
  // in the bottom half of the current scan-line, or the top-half
  // of the next scanline). The grid of lines represents the
  // edges of the texels of the underlying texture.

  // Texture coordinates of the texel containing the active pixel.
  vec2 xy;
  if (curvature.x > 0.5)
    xy = transform(v_texCoord, v_stretch, v_sinangle, v_cosangle);
  else
    xy = (v_texCoord-vec2(0.5))/overscan.xy+vec2(0.5);
  vec2 xy0 = xy;
  float cval = corner(xy);

  // Of all the pixels that are mapped onto the texel we are
  // currently rendering, which pixel are we currently rendering?
  vec2 ratio_scale = xy * u_tex_size0.xy - vec2(0.5);
  
#ifdef OVERSAMPLE
  float oversample_filter = fwidth(ratio_scale.y);
#endif
  vec2 uv_ratio = fract(ratio_scale);

  // Snap to the center of the underlying texel.
  xy = (floor(ratio_scale) + vec2(0.5)) / u_tex_size0.xy;

  // Calculate Lanczos scaling coefficients describing the effect
  // of various neighbour texels in a scanline on the current
  // pixel.
  vec4 coeffs = PI * vec4(1.0 + uv_ratio.x, uv_ratio.x, 1.0 - uv_ratio.x, 2.0 - uv_ratio.x);

  // Prevent division by zero.
  coeffs = FIX(coeffs);
  
  // Lanczos2 kernel.
  coeffs = 2.0 * sin(coeffs) * sin(coeffs / 2.0) / (coeffs * coeffs);

  // Normalize.
  coeffs /= dot(coeffs, vec4(1.0));

  // Calculate the effective colour of the current and next
  // scanlines at the horizontal location of the current pixel,
  // using the Lanczos coefficients above.
  vec4 col = clamp(TEX2D(xy + vec2(-v_one.x, 0.0))*coeffs.x +
                   TEX2D(xy)*coeffs.y +
		   TEX2D(xy +vec2(v_one.x, 0.0))*coeffs.z +
		   TEX2D(xy + vec2(2.0 * v_one.x, 0.0))*coeffs.w , 0.0, 1.0);
			 
  vec4 col2 = clamp(TEX2D(xy + vec2(-v_one.x, v_one.y))*coeffs.x +
		    TEX2D(xy + vec2(0.0, v_one.y))*coeffs.y +
		    TEX2D(xy + v_one)*coeffs.z +
		    TEX2D(xy + vec2(2.0 * v_one.x, v_one.y))*coeffs.w , 0.0, 1.0);


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

  // Calculate the influence of the current and next scanlines on
  // the current pixel.
  vec4 weights  = scanlineWeights(uv_ratio.y, col);
  vec4 weights2 = scanlineWeights(1.0 - uv_ratio.y, col2);
#ifdef OVERSAMPLE
  uv_ratio.y =uv_ratio.y+1.0/3.0*oversample_filter;
  weights = (weights+scanlineWeights(uv_ratio.y, col))/3.0;
  weights2=(weights2+scanlineWeights(abs(1.0-uv_ratio.y), col2))/3.0;
  uv_ratio.y =uv_ratio.y-2.0/3.0*oversample_filter;
  weights=weights+scanlineWeights(abs(uv_ratio.y), col)/3.0;
  weights2=weights2+scanlineWeights(abs(1.0-uv_ratio.y), col2)/3.0;
#endif
  vec3 mul_res  = (col * weights + col2 * weights2).rgb;

  // halation and corners
  vec3 blur = pow(texture(blur_texture,xy0).rgb, vec3(CRTgamma.x));
  mul_res = mix(mul_res, blur, halation.x) * vec3(cval);
  
  // Convert the image gamma for display on our output device.
  mul_res = pow(mul_res, vec3(1.0 / monitorgamma.x));

  // Shadow mask
  // original code; just makes a giant phosphor here
  // xy = v_texCoord.xy * u_quad_dims.xy / u_tex_size1.xy;
  
  // gl_FragCoord; tied to physical pixel size
  xy = v_texCoord.xy * global.OutputSize.xy;

  vec3 mask = mask_weights(xy, aperture_strength.x, mask_picker);
  
  FragColor = vec4(mul_res*mask, col.a);
}
2 Likes

Could you show an example and explain this last bit?

The mask selector (it expects an int while parameters are floats, so you’ll need to wrap the raw parameter in int() at some point).

Thanks for the example and everything!

if the parameter is, say, params.mask_selector, you would do:

int(params.mask_selector)

1 Like

Sorry is this what you’re meaning?

vec3 mask = mask_weights(xy, aperture_strength.x, int(params.mask_selector));

Then you put the actual parameter in geom-deluxe-params.inc for this example?

I apologise, sometimes I struggle with written explanations, I appreciate you taking the time to help!

Yes, but I didn’t have to wrap it in this case because mask_picker was already an integer. The parameters are always floats, so if you’re using a parameter directly, it needs to be cast (fancy name for ‘converted’) to an integer.

The parameters for that shader are indeed in geom-deluxe-params.inc file, so that’s where you would change, e.g., mask_picker’s range from 0.0-3.0 to 1.0-17.0 or whatever.

1 Like