Skip to content

GLSL

  • Made for graphics
  • Running on the GPU
  • C-like syntax
  • Isolated small programs

Structure

cpp
#version 330 core

in type in_name;
out type out_name;
uniform type uniform_name;

int main() {
  ...
}

Data Types

  • scalar: float, double, int, uint, bool
  • container:
    • n = 1/2/3/4
    • vector: vecndvecn, ivecn, uvecn, bvecn
    • matrix: matn, dmatn, mat2x3, mat4x2, ...
  • aggregate: struct, array
  • opaque: samplerXXX, imageXXX, atomic_xxx ...
  • void

Alias

glsl
vec4 v;
v.xyzw; // coord
v.rgba; // color
v.stpq; // texture

Swizzling

glsl
vec2 v;
vec4 v1 = v.xyxx;
vec3 v2 = v1.rgb;

Constructor

glsl
vec2 v = vec2(1.0); // = vec2(1.0, 1.0)
vec3 v1 = vec3(v, 1.0);
vec4 v2 = vec4(v, v);
vec4 v3 = vec4(1.0, v2.rgb);

Ins and Outs

Vertex shader:

glsl
#version 330 core
layout (location = 0) in vec3 position;

out vec4 vertexColor;

void main() {
  gl_Position = vec4(position, 1.0);
  vertexColor = vec4(0.5f, 0.0f, 0.0f, 1.0f);
}

gl_PointSize is a builtin variable.

Fragment shader:

glsl
#version 330 core
in vec4 vertexColor; // Same name as vertex shader

out vec4 color; // Single vec4 as output color

void main()
{
  color = vertexColor;
}

Interface Blocks

glsl
in VS_OUT
{
  vec2 TexCoords;
} fs_in;

Uniform

  • Global data from the main program
  • Read-only in shaders

Example (OpenGL):

glsl
#version 330 core
uniform vec4 ourColor;
cpp
// C++: For each frame:
GLfloat timeValue = glfwGetTime();
GLfloat greenValue = (sin(timeValue) / 2) + 0.5;
GLint location = glGetUniformLocation(shaderProgram, "ourColor");
glUseProgram(shaderProgram);
glUniform4f(location, 0.0f, greenValue, 0.0f, 1.0f);

Uniform Buffer Objects

  • Only the size and alignment of members are important.
  • Allows batch updating and management of uniform data.
  • Reduces API calls.
  • May use std140 layout, which aligns members:
glsl
layout (std140) uniform ExampleBlock
{
    float value;
    ...
};

Builtin Operations

  • Operators: +-*/&|...
  • Functions: sin, length, normalize, inverse, ...
  • Accelerated by hardware.

Other Shading Languages

This section mainly refers to https://alain.xyz/blog/a-review-of-shader-languages#language-features.

Shader → IR → Machine code

AbbreviationLanguageOfficial Graphics APIsTranspiling
HLSLHigh Level S.L.DirectXHLSL → SPIR-V via dxc and glslang
GLSLOpenGL S.L.Vulkan / OpenGLGLSL → SPIR-V → HLSL
MSLMetal S.L.MetalSPIR-V → MSL via SPIR-V Cross
WGSLWebGPU S.L.WebGPUWGSL → SPIR-V via naga or tint
  • All similar to C
  • Sharing most of builtin function names.

HLSL

  • "High level"
  • Namespaces, template generics, operator overloading, #include, ...
  • Semantic labels for struct fields.
  • float4 for vector, float4x4 for matrix.

Compute shader:

hlsl
struct Constants
{
  float time : packoffset(c0);
};

ConstantBuffer<Constants> constants : register(b0);
Texture2D<float4> albedoTex : register(t1);
Texture2D<float4> pbrLutTex : register(t2);
SamplerState gSampler : register(s0);

RWTexture2D<float4> tOutput : register(u0);

groupshared float groupData[4];

namespace random
{
  float foo()
  {
    return 16.0;
  }
}

[numthreads(8, 4, 1)]
void main(uint3 groupThreadID : SV_GroupThreadID,
         uint3 groupID : SV_GroupID,
         uint  groupIndex : SV_GroupIndex,
         uint3 dispatchThreadID: SV_DispatchThreadID)
{
  tOutput[dispatchThreadID.xy] = ...;
}

MSL

  • Clearer input/output model.
  • Vector and matrix name same as HLSL.
  • Fields also have labels.
  • kernel keyword for compute shaders.
metal
#include <metal_stdlib>
#include <simd/simd.h>

using namespace metal;

struct Constants
{
  float time;
};

float foo()
{
  return 16.0;
}

kernel void main(
    uint3 groupThreadID [[ thread_position_in_threadgroup ]],
    uint3 groupID [[threadgroup_position_in_grid]],
    uint groupIndex [[thread_index_in_threadgroup]],
    uint3 dispatchThreadID [[thread_position_in_grid]],
    constant Constants& constants [[buffer(0)]],
    texture2d<float, access::read_write> tOutput [[texture(0)]])
{
  threadgroup float groupData[4];
  tOutput[dispatchThreadID.xy] = ...;
}

WGSL

  • More "verbose" vector and matrix type: mat4x4<f32>
  • @builtin/@location/... as labels
  • Compute shader is similar:
wgsl
struct Constants
{
  time: f32
};

@group(0) @binding(0) var<uniform> constants: Constants;
@group(0) @binding(1) var tOutput: texture_storage_2d<rgba8unorm,write>;

fn foo() -> f32
{
  return 4.0;
}

@compute @workgroup_size(8, 4, 1)
fn main(@builtin(local_invocation_id) localInvocationID: vec3<u32>,
        @builtin(workgroup_id) workgroupID: vec3<u32>,
        @builtin(local_invocation_index) localInvocationIndex: u32,
        @builtin(global_invocation_id) globalInvocationID: vec3<u32>) {

  var color = vec4<f32>(...);

  textureStore(tOutput, vec2<i32>(globalInvocationID.xy), color);
}

OpenCL

  • Compute kernel only.
  • Incredibly similar to both Apple Metal and OpenGL.
cpp
struct Constants
{
  float time;
  uint width;
  uint height;
};

float foo()
{
  return 16.0;
}

__kernel void main
(
  __constant Constants constants,
  __global float4* tOutput
)
{
  uint localId = get_local_id(0);
  uint workgroupId = get_group_id(0);
  uint globalId = get_global_id(0);

  int x = globalId % constants.width;
  int y = globalId / constants.width;

  if (x >= constants.width || y >= constants.height)
  {
    return;
  }
  tOutput[y * constants.width + x] = ...;
}

How to Ship Shaders

  • Offline: compiled to target IR, and stored as bytecode in lib/exe.
  • Online: WGSL or older GLSL. May minify/obfuscate the code.
  • Sort of like AOT vs JIT.

meow

  • DSL can be necessary and nice.
  • I love the idea of field alias and swizzling. I haven't seen these anywhere else before myself.
  • Semantic labels. ATM I love WGSL's way - clear and extensible.
  • IR is useful as always.
  • How C-like syntax is widely accepted.
  • I love the look of input (including uniform buffer) as args and output as return value of main - consistency. But this makes them only invisible to subroutines - less flexible.
  • vec4 or float4 or vec4<f32>?

References