Convert GLSL Shader to SLANG (CRT 240p Scale Shader)

Good day fellow Retroarch/Libretro users.

I have found an interesting shader by Tim C. Schröder (blitzcode), that helps to scale content (CRT 240p Scale Shader) here:

https://github.com/blitzcode/crt-240p-scale-shader

I wanted to ask if there is any way to convert that GLSL Shader to a SLANG shader, because my system does not support OPENGL, thus I cannot use GLSL Shaders.

But I can use SLANG shaders (I use Direct3D 11 driver on RetroArch).

I already asked Tim C. Schröder if he can do the conversion, but he told me that he doesn’t know anything about SLANG.

If any expert user or programmer could convert that shader to SLANG I would be very grateful.

Thank you very much!

2 Likes

Try this:

#version 450

// CRT 240p Scale Shader
// by Tim C. Schroeder (visit www.blitzcode.net to learn more)

// published under MIT License
// adapted to slang format by hunterk as retrieved from github.com/blitzcode/crt-240p-scale-shader
// on 5/26/2022

layout(push_constant) uniform Push
{
	vec4 SourceSize;
	vec4 OriginalSize;
	vec4 OutputSize;
	uint FrameCount;
} params;

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

#define InputSize params.SourceSize.xy
#define TextureSize params.SourceSize.xy
#define OutputSize params.OutputSize.xy

// Screen rotation in RA is implemented by rotating the output geometry, detect this here
bool is_vertical() { return global.MVP[0].y != 0.0; }

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

void main()
{
    // Output position in clip space [-1, 1]
    vec4 clip_space_pos = global.MVP * vec4(Position.x, Position.y, 0.0, 1.0);

    bool is_vertical = is_vertical();

    // Scale factor for the vertical axis. Do nothing if input / output resolution
    // matches, center if input is lower than output, shrink to output if input is higher
    // than output. Keep in mind that our geometry might be rotated
    float vert_center = min(1.0, (is_vertical ? InputSize.x : InputSize.y) / OutputSize.y);
    clip_space_pos.y *= vert_center;

    // Handheld consoles get centered on the screen and have their correct aspect ratio
    bool is_handheld = true;
    float handheld_ar = 1.0;
         if (InputSize.x == 160.0 && InputSize.y == 144.0) handheld_ar = 1.11; // GB(C) / GG *
    else if (InputSize.x == 160.0 && InputSize.y == 152.0) handheld_ar = 1.05; // NGP(C)
    else if (InputSize.x == 224.0 && InputSize.y == 144.0) handheld_ar = 1.55; // WonderSwan
    else if (InputSize.x == 160.0 && InputSize.y == 102.0) handheld_ar = 1.57; // Atari Lynx
    else if (InputSize.x == 102.0 && InputSize.y == 160.0) handheld_ar = 0.64; // Atari Lynx Vertical **
    else if (InputSize.x == 240.0 && InputSize.y == 160.0) handheld_ar = 1.50; // GBA
    else
        is_handheld = false;
    // *  We unfortunately can't distinguish between the Game Gear and the Nintendo
    //    handhelds, causing the former to have the wrong aspect ratio as it uses
    //    non-square pixels. Feel free to change the aspect to 1.33 to reverse the
    //    situation in favor of Sega's system
    // ** This is a weird special case. Lynx seems to be the only system where vertical
    //    mode does not rotate the image in post, but the emulator actually outputs a
    //    different resolution. So it's not treated as a vertical system, is_vertical ==
    //    false and we simply treat it as a horizontal system having a tall aspect ratio

    // Fix for 2 & 3 screen wide Darius games. This is not going to look terribly good but
    // at least they're playable (sort of)
    if (InputSize.x == 640.0 && InputSize.y == 224.0)
        clip_space_pos.y /= 2.0;
    else if (InputSize.x == 864.0 && InputSize.y == 224.0)
        clip_space_pos.y /= 3.0;

    if (is_handheld)
    {
        clip_space_pos.x = clip_space_pos.x
            // This gets us to a square display
            * vert_center * (3.0 / 4.0)
            // Now it has the same AR as the physical screen of the device
            * handheld_ar
            // Aspect ratio correction when in vertical orientation
            * (is_vertical ? (1.0 / handheld_ar) * (1.0 / handheld_ar) : 1.0);
    }
    else if (is_vertical)
    {
#if 0
        // Overscan adjustment for TATE games. Most of these games don't seem to have any
        // consideration for the typical overscan present on consumer CRTs and place
        // critical elements like score and bomb counters right at the margin. Here we
        // shrink the image a little bit so it's not cut off on CRTs calibrated for
        // typical home consoles
        float overscan_adj = 1.045;
        clip_space_pos.x /= overscan_adj;
        clip_space_pos.y /= overscan_adj;
#endif

        // Correct the aspect ratio for 3:4 image in 4:3 frame
        clip_space_pos.x *= (3.0 / 4.0) * (3.0 / 4.0);
    }

    // Setup texture filtering offsets for downscaling. We do this here so we don't have
    // to repeat these calculations per-pixel
    {
        // If we want to properly filter the input texture we need to know the radius
        // which one screen pixel (pixel radius / number of output pixels) represents in
        // the normalized texture coordinates of the input texture. It's important to keep
        // in mind that the input texture might not be fully used, so we have to adjust
        // with the input-res-to-texture-size ratio to prevent an oversized filter kernel
        //
        // Why do we not simply use the automatic derivative functions dFdx() / dFdy() to
        // figure out the proper offsets? Because RPi 3B's GPU doesn't support those
        float pixel_r = 0.7;
        float support = (pixel_r / OutputSize.y) *
                        (is_vertical ? InputSize.x / TextureSize.x : InputSize.y / TextureSize.y);

        // Make sure we super-sample along the correct axis of the input texture and use
        // the filter support we just computed
        filter_offs = is_vertical ? vec2(support, 0.0) : vec2(0.0, support);
    }

    // Output

   gl_Position = clip_space_pos;
   vTexCoord = TexCoord;
}

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

void main()
{
    bool is_vertical = is_vertical();

    // Disable texture filtering on the horizontal axis by sampling at the texel center.
    // The super-resolution output in combination with the softness of the CRT already
    // takes care of all filtering, additional bilinear lookups just introduce blurring
    // and a loss of brightness in slim features. Filtering on the vertical is fine since
    // we either match input texture to screen lines perfectly and it has no effect or
    // we're downscaling and want filtering.
    //
    // In vertical mode, disable filtering on the vertical input texture axis as it runs along
    // the horizontal screen axis due to the rotated output geometry
    vec2 tex_coord_center = vTexCoord;
    if (is_vertical)
        tex_coord_center.y = (floor(tex_coord_center.y * TextureSize.y) + 0.5) / TextureSize.y;
    else
        tex_coord_center.x = (floor(tex_coord_center.x * TextureSize.x) + 0.5) / TextureSize.x;

    // Do we have to downscale on the vertical axis (high-res arcade games, TATE games)?
    //
    // Vertical games have the horizontal input texture axis run along the vertical screen axis,
    // swap as with the filtering adjustment code above
    if (is_vertical ? OutputSize.y < InputSize.x : OutputSize.y < InputSize.y)
    {
        // Super-sampling with a tent filter and a bit of sharpening. This can never look
        // perfect and our filter is rather simplistic, but the result already looks
        // significantly better than default RA scaling and strikes a good balance between
        // shimmering and blurriness
        float sharpen = 0.7;
        FragColor =
            ( texture(Source, tex_coord_center - filter_offs * 1.5) * -sharpen +
              texture(Source, tex_coord_center - filter_offs      ) * 0.5      +
              texture(Source, tex_coord_center - filter_offs * 0.5)            +
              texture(Source, tex_coord_center)                     * 1.5      +
              texture(Source, tex_coord_center + filter_offs * 0.5)            +
              texture(Source, tex_coord_center + filter_offs      ) * 0.5      +
              texture(Source, tex_coord_center + filter_offs * 1.5) * -sharpen
            ) * (1.0 / (4.5 - 2.0 * sharpen));
    }
    else
        FragColor = texture(Source, tex_coord_center);
}
2 Likes

Thank you hunterk for the quick response! I will try it tonight when I come home from work.

By the way, do I need to edit the GLSLP file? Or can i use it as is?

1 Like

You would need to modify it to point to the slang shader instead of the glsl one, and you’d need to save it as *.slangp instead of *.glslp. Other than that, they should be identical.

1 Like

Thank you very much hunterk, it worked fine! I recommend this shader to those who want to play vertical games on Retroarch (SHMUPS mostly) on a CRT TV @ 240p, but can’t physically rotate the TV. Have a nice day! :smiley:

2 Likes