Shader Basic Concepts

Makepad Shader is a custom MPSL shader language. MPSL can be compiled into shader languages like GLSL.

Recommended prerequisite learning: GLSL Shader Introduction Tutorial https://thebookofshaders.com/

What is a Shader

Let's begin understanding shaders with a simple analogy. Imagine you're a painter facing an enormous canvas divided into millions of tiny squares (pixels).

You have two tasks:

  1. Determine the position and size of each shape (Vertex Shader)
  2. Decide what color to fill in each square (Fragment/Pixel Shader)
1// A basic Makepad shader
2MyFirstShader = {{MyFirstShader}} {
3    // 1. Vertex shader - handles position
4    fn vertex(self) -> vec4 {
5        // Place the shape in the correct position
6        let position = self.geom_pos * self.rect_size + self.rect_pos;
7        return self.camera_projection * vec4(position, 0.0, 1.0);
8    }
9
10    // 2. Fragment shader - handles color
11    fn pixel(self) -> vec4 {
12        // Color each pixel
13        return vec4(1.0, 0.0, 0.0, 1.0); // Red
14    }
15}

Let's visualize this process:

1Vertex Shader Stage:          Pixel Shader Stage:
2
3   P1●─────●P2                 ░░░░░░░░
4     │     │                   ░██████░
5     │     │         =>        ░██████░
6     │     │                   ░██████░
7   P3●─────●P4                 ░░░░░░░░
8
9(Determine shape position)    (Fill each pixel)

GPU vs CPU Rendering Differences

Why use GPU rendering? Let's understand through an example:

Imagine rendering a 1000×1000 pixel area:

1CPU Rendering:
2- Process each pixel sequentially
3- 1 million pixels processed one at a time
4- Similar to filling with a single brush
5
6GPU Rendering:
7- Process many pixels in parallel
8- 1 million pixels processed simultaneously
9- Similar to covering with a spray gun

Visual representation::

1CPU Rendering Progress:      GPU Rendering Progress:
2█░░░░░░░░░  10%              ▒▒▒▒▒▒▒▒▒▒  100%
3██░░░░░░░░  20%              (Processed simultaneously)
4███░░░░░░░  30%
5...

Coordinate Systems in Detail

In Makepad, we need to work with three main coordinate systems:

Normalized Device Coordinates (NDC)

Range [-1,1], provides device-independent standardized space, ensuring consistent rendering results across different resolutions.

  • Use cases: When you need device-independent rendering or handling 3D transformations.
  • Examples: 3D rendering, view clipping, perspective projection, cross-device consistent rendering.

Pixel Coordinates

Actual screen pixels, used for precise positioning on screen, directly corresponding to physical display devices.

  • Use cases: When you need precise control over rendering target positions on screen.
  • Examples: Precise UI element positioning, pixel border drawing, text rendering alignment.

UV Coordinates

Range [0,1] texture coordinates, used for texture mapping and parametric surfaces, providing resolution-independent relative positioning.

  • Use cases: When you need to handle texture mapping or create parametric visual effects.
  • Examples: Texture mapping, gradient effects, UV animation, procedural texture generation, parametric shapes.

Code and diagrams to understand coordinate conversions

1fn vertex(self) -> vec4 {
2    // 1. Mapping from geometric coordinates to pixel coordinates
3    let pixel_pos = self.geom_pos * self.rect_size + self.rect_pos;
4
5    // 2. Mapping from pixel coordinates to UV coordinates
6    self.uv = (pixel_pos - self.rect_pos) / self.rect_size;
7
8    // 3. Mapping from pixel coordinates to NDC
9    let ndc = self.camera_projection * vec4(pixel_pos, 0.0, 1.0);
10
11    return ndc;
12}

Coordinate system visualization:

1Normalized Device         Pixel               UV
2Coordinates (NDC)      Coordinates         Coordinates
3     (-1,1)            (0,0)              (0,0)
4        ┃               ┃                   ┃
5        ┃               ┃                   ┃
6  ━━━━━━●━━━━━━   ━━━━━●━━━━━   ━━━━━━━●━━━━━━━
7        ┃               ┃                   ┃
8        ┃               ┃                   ┃
9     (1,-1)         (width,            (1,1)
10                    height)
11
12Coordinate mapping flowchart:
13
14Pixel Coords (100, 100)         UV Coords (0.5, 0.5)         NDC (0.0, 0.0)
15    ┌──────────┐               ┌──────────┐               ┌──────────┐
16    │(0,0)     │     ÷size     │(0,0)     │    *2-1       │(-1,1)    │
17    │          │ ────────────► │          │ ────────────► │          │
18    │   ●      │               │   ●      │               │   ●      │
19    │          │               │          │               │          │
20    │     (w,h)│               │     (1,1)│               │    (1,-1)│
21    └──────────┘               └──────────┘               └──────────┘
22
23Requirements Decision:
24┌────────────────────┐
25│Need pixel precision?│
26└─────────┬──────────┘
2728    ┌─────┴─────┐
29    │   Yes     │     No
30    ▼           ▼
31Pixel      ┌──────────────┐
32Coords     │Need texture  │
33           │or gradients? │
34           └──────┬───────┘
3536             ┌────┴────┐
37             │  Yes    │     No
38             ▼         ▼
39          UV Coords  NDC Coords

Colors and Pixels

In Makepad, colors are represented using vec4, containing four RGBA channels:

1fn pixel(self) -> vec4 {
2    //---------- Red  Green Blue Alpha
3    return vec4(1.0, 0.0,  0.0,  1.0);
4}

Color blending illustration:

1Base Colors:          Alpha Blending:
2Red   (1,0,0)        ██ Opaque    (alpha = 1.0)
3Green (0,1,0)  +     ▒▒ Semi-transparent (alpha = 0.5)
4Blue  (0,0,1)        ░░ Transparent (alpha = 0.0)

The concept of Premultiplied Alpha:

1// Regular RGBA
2vec4(1.0, 0.0, 0.0, 0.5) // Semi-transparent red
3
4// Premultiplied Alpha (recommended)
5vec4(0.5, 0.0, 0.0, 0.5) // RGB channels already multiplied by alpha