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

  • WGSL:
struct Light {
position: vec3<f32>,
color: vec4<f32>,
attenuation: f32,
direction: vec3<f32>,
innerAngle: f32,
angle: f32,
range: f32,
};
  • GLSL:
struct Light {
vec3 position;
vec4 color;
float attenuation;
vec3 direction;
float innerAngle;
float angle;
float range;
};

Uniform buffer object

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

Functions declarations

  • WGSL:
fn saturate(x: f32) -> f32 {
return clamp(x, 0.0, 1.0);
}
  • GLSL:
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
struct SystemUniform {
projectionMatrix: mat4x4<f32>,
viewMatrix: mat4x4<f32>,
};
@group(0) @binding(0) var<uniform> system: SystemUniform;
struct MeshUniform {
modelMatrix: array<mat4x4<f32>, 256>,
};
@group(1) @binding(0) var<uniform> mesh: MeshUniform;
// Output
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.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Damien Seguin

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