From GLSL to WGSL: the future of shaders on the Web

How does the new and shiny WebGPU Shading Language compares to the seasoned GLSL

WGSL full screen triangle example from DGEL

A companion language for the new WebGPU API

History

What it means for shaders in the future

WGSL and GLSL: comparing the syntaxes’ basics

Scalar and matrix types

| WGSL  | GLSL     |
| :---- | :------- |
| `i32` | `int` |
| `u32` | `uint` |
| `f32` | `float` |
| `N/A` | `double` |
| WGSL          | GLSL     |
| :------------ | :------- |
| `mat2x2<f32>` | `mat2` |
| `mat3x2<f32>` | `mat3x2` |
| `mat4x2<f32>` | `mat4x2` |
| `mat2x3<f32>` | `mat2x3` |
| `mat3x3<f32>` | `mat3` |
| `mat4x3<f32>` | `mat4x3` |
| `mat2x4<f32>` | `mat2x4` |
| `mat3x4<f32>` | `mat3x4` |
| `mat4x4<f32>` | `mat4` |

Structs

[[block]] struct Light {
position: vec3<f32>;
color: vec4<f32>;
attenuation: f32;
direction: vec3<f32>;
innerAngle: f32;
angle: f32;
range: f32;
};
struct Light {
vec3 position;
vec4 color;
float attenuation;
vec3 direction;
float innerAngle;
float angle;
float range;
};

Uniform buffer object

[[block]] struct SystemUniform {
projectionMatrix: mat4x4<f32>;
viewMatrix: mat4x4<f32>;
inverseViewMatrix: mat4x4<f32>;
cameraPosition: vec3<f32>;
time: f32;
};
[[group(0), binding(0)]] var<uniform> system: SystemUniform;
layout(set = 0, binding = 0) uniform SystemUniform {
mat4 projectionMatrix;
mat4 viewMatrix;
mat4 inverseViewMatrix;
vec3 cameraPosition;
float time;
} system;

Functions declarations

fn saturate(x: f32) -> f32 {
return clamp(x, 0.0, 1.0);
}
float saturate(float x) {
return clamp(x, 0.0, 1.0);
}
// or
#define saturate(x) clamp(x, 0.0, 1.0)
// but more on that later

Built-in

|                   WGSL |   Stage | IO  | GLSL                    |
| ---------------------: | ------: | :-- | :---------------------- |
| vertex_index | vertex | in | gl_VertexID |
| instance_index | vertex | in | gl_InstanceID |
| position | vertex | out | gl_Position |
| position | frag | in | gl_FragCoord |
| front_facing | frag | in | gl_FrontFacing |
| frag_depth | frag | out | gl_FragDepth |
| local_invocation_id | compute | in | gl_LocalInvocationID |
| local_invocation_index | compute | in | gl_LocalInvocationIndex |
| global_invocation_id | compute | in | gl_GlobalInvocationID |
| workgroup_id | compute | in | gl_WorkGroupID |
| num_workgroups | compute | in | gl_NumWorkGroups |
| sample_index | frag | in | gl_SampleID |
| sample_mask | frag | in | gl_SampleMask |
| sample_mask | frag | out | gl_SampleMask |
// UBOs
[[block]] struct SystemUniform {
projectionMatrix: mat4x4<f32>;
viewMatrix: mat4x4<f32>;
};
[[group(0), binding(0)]] var<uniform> system: SystemUniform;
[[block]] struct MeshUniform {
modelMatrix: array<mat4x4<f32>, 256>;
};
[[group(1), binding(0)]] var<uniform> mesh: MeshUniform;
// Output
[[block]] struct Output {
[[builtin(position)]] position: vec4<f32>;
};
[[stage(vertex)]]
fn main(
[[builtin(instance_index)]] instance_index: u32,
[[location(0)]] position: vec3<f32>
) -> Output {
var output: Output;
let modelMatrix = mesh.modelMatrix[instance_index]; output.position = system.projectionMatrix * system.viewMatrix * modelMatrix * vec4<f32>(position, 1.0); return output;
}

A quick guide on how not to hate rewriting all your shaders

var/let

let GAMMA: f32 = 2.2;

No preprocessor (#define/#ifdef/#if defined())

<f32> everywhere

Arithmetic and assignments operators, l-values swizzling: where are you

vec4 color = vec4(1.0);
color.xyz = vec3(0.1, 0.2, 0.3);
var color = vec4<f32>(1.0);
color = vec4<f32>(0.1, 0.2, 0.3, color.a);

Branching: elseif vs else if? no ternary? no optional braces?

if (diff <= 0.0) return vec3(0.0);
if (diff <= 0.0) {
return vec3<f32>(0.0);
}

functions overload

float toLinear(float v) {
return pow(v, GAMMA);
}
vec2 toLinear(vec2 v) {
return pow(v, vec2(GAMMA));
}
vec3 toLinear(vec3 v) {
return pow(v, vec3(GAMMA));
}
vec4 toLinear(vec4 v) {
return vec4(toLinear(v.rgb), v.a);
}
// WILL THROW
fn toLinear(v: f32) -> f32 {
return pow(v, GAMMA);
}
fn toLinear(v: vec2<f32>) -> vec2<f32> {
return pow(v, vec2<f32>(GAMMA));
}
fn toLinear(v: vec3<f32>) -> vec3<f32> {
return pow(v, vec3<f32>(GAMMA));
}
fn toLinear(v: vec4<f32>) -> vec4<f32> {
return vec4<f32>(toLinear(v.rgb), v.a);
}

Conclusion

Tech Lead/Creative Developer @variable_io , Computational Designer, Generative Artisan, Cheese Enthusiast.