$npx -y skills add ZhangHanDong/makepad-skills --skill makepad-2.0-shadersCRITICAL: Use for Makepad 2.0 shader system. Triggers on: makepad shader, Sdf2d, pixel shader, draw_bg, draw_text, draw_quad, makepad gpu, shader function, pixel fn, vertex fn, instance, uniform, shader variable, sdf, premultiply, Pal.premul, GaussShadow, makepad graphics, custom
| 1 | # Makepad 2.0 Shader Skill |
| 2 | |
| 3 | > **Version:** makepad-widgets (dev branch) | **Last Updated:** 2026-03-03 |
| 4 | |
| 5 | ## Overview |
| 6 | |
| 7 | Makepad uses a custom GPU shader system integrated into the widget property tree. Shaders are defined inline using `pixel: fn() { ... }` and `vertex: fn() { ... }` blocks within `draw_bg`, `draw_text`, or custom draw objects. |
| 8 | |
| 9 | ## Documentation |
| 10 | |
| 11 | Refer to the local files for detailed documentation: |
| 12 | - `./references/shader-reference.md` - Shader syntax, variables, built-ins, custom functions |
| 13 | - `./references/sdf2d-reference.md` - SDF2D primitives, combinators, drawing operations |
| 14 | |
| 15 | --- |
| 16 | |
| 17 | ## Shader Basics |
| 18 | |
| 19 | ### Pixel Shader Structure |
| 20 | |
| 21 | ``` |
| 22 | draw_bg +: { |
| 23 | // Declare variables |
| 24 | instance hover: 0.0 // Animatable per-instance |
| 25 | uniform accent: #4488ff // Shared across all instances |
| 26 | |
| 27 | pixel: fn() { |
| 28 | let sdf = Sdf2d.viewport(self.pos * self.rect_size) |
| 29 | // ... SDF operations ... |
| 30 | return sdf.result |
| 31 | } |
| 32 | } |
| 33 | ``` |
| 34 | |
| 35 | ### Variable Types |
| 36 | |
| 37 | | Type | Declaration | Animatable | Scope | |
| 38 | |------|-------------|-----------|-------| |
| 39 | | `instance` | `instance hover: 0.0` | Yes (via Animator) | Per-widget instance | |
| 40 | | `uniform` | `uniform color: #fff` | No | Shared across instances | |
| 41 | | `texture_2d` | `texture_2d tex: none` | No | Texture sampler | |
| 42 | | `varying` | `varying uv: vec2` | No | Vertex → fragment | |
| 43 | |
| 44 | ### Built-in Variables |
| 45 | |
| 46 | | Variable | Type | Description | |
| 47 | |----------|------|-------------| |
| 48 | | `self.pos` | `vec2` | Normalized position (0.0 to 1.0) | |
| 49 | | `self.rect_size` | `vec2` | Widget size in pixels | |
| 50 | | `self.dpi_factor` | `float` | Screen DPI factor | |
| 51 | | `self.draw_pass.time` | `float` | Time in seconds | |
| 52 | |
| 53 | --- |
| 54 | |
| 55 | ## CRITICAL: Premultiply Alpha |
| 56 | |
| 57 | **Every pixel shader MUST return premultiplied alpha color!** |
| 58 | |
| 59 | ``` |
| 60 | // WRONG - non-premultiplied |
| 61 | pixel: fn() { |
| 62 | return vec4(1.0, 0.0, 0.0, 0.5) |
| 63 | } |
| 64 | |
| 65 | // CORRECT - use Pal.premul() |
| 66 | pixel: fn() { |
| 67 | return Pal.premul(vec4(1.0, 0.0, 0.0, 0.5)) |
| 68 | } |
| 69 | |
| 70 | // ALSO CORRECT - sdf.result is already premultiplied |
| 71 | pixel: fn() { |
| 72 | let sdf = Sdf2d.viewport(self.pos * self.rect_size) |
| 73 | sdf.circle(cx, cy, r) |
| 74 | sdf.fill(#f00) |
| 75 | return sdf.result |
| 76 | } |
| 77 | ``` |
| 78 | |
| 79 | --- |
| 80 | |
| 81 | ## SDF2D Quick Reference |
| 82 | |
| 83 | ### Setup |
| 84 | ``` |
| 85 | let sdf = Sdf2d.viewport(self.pos * self.rect_size) |
| 86 | ``` |
| 87 | |
| 88 | ### Primitives |
| 89 | ``` |
| 90 | sdf.circle(cx, cy, radius) |
| 91 | sdf.rect(x, y, w, h) |
| 92 | sdf.box(x, y, w, h, border_radius) |
| 93 | sdf.hexagon(cx, cy, radius) |
| 94 | sdf.arc(cx, cy, radius, start_angle, end_angle, thickness) |
| 95 | sdf.move_to(x, y) |
| 96 | sdf.line_to(x, y) |
| 97 | sdf.close_path() |
| 98 | ``` |
| 99 | |
| 100 | ### Drawing |
| 101 | ``` |
| 102 | sdf.fill(color) // Filled shape |
| 103 | sdf.stroke(color, width) // Outlined shape |
| 104 | sdf.glow(color, amount) // Glow effect |
| 105 | sdf.clear(color) // Clear with color |
| 106 | ``` |
| 107 | |
| 108 | ### Combinators |
| 109 | ``` |
| 110 | sdf.union() // Add shapes together |
| 111 | sdf.intersect() // Keep overlap only |
| 112 | sdf.subtract() // Remove second from first |
| 113 | sdf.gloop(radius) // Smooth union |
| 114 | sdf.blend(amount) // Linear blend |
| 115 | ``` |
| 116 | |
| 117 | ### Transforms |
| 118 | ``` |
| 119 | sdf.translate(x, y) |
| 120 | sdf.rotate(angle, cx, cy) |
| 121 | sdf.scale(factor, cx, cy) |
| 122 | ``` |
| 123 | |
| 124 | --- |
| 125 | |
| 126 | ## Color Operations |
| 127 | |
| 128 | ``` |
| 129 | // Mix two colors |
| 130 | mix(#f00, #00f, 0.5) // 50% blend |
| 131 | |
| 132 | // Premultiply alpha |
| 133 | Pal.premul(vec4(r, g, b, a)) |
| 134 | |
| 135 | // HSV conversions |
| 136 | Pal.hsv2rgb(vec4(h, s, v, 1.0)) |
| 137 | Pal.rgb2hsv(color) |
| 138 | |
| 139 | // Random |
| 140 | Math.random_2d(vec2(x, y)) |
| 141 | ``` |
| 142 | |
| 143 | --- |
| 144 | |
| 145 | ## Common Shader Patterns |
| 146 | |
| 147 | ### Gradient Background |
| 148 | ``` |
| 149 | draw_bg +: { |
| 150 | pixel: fn() { |
| 151 | let grad = mix(#1a1a2e, #16213e, self.pos.y) |
| 152 | return Pal.premul(vec4(grad.xyz, 1.0)) |
| 153 | } |
| 154 | } |
| 155 | ``` |
| 156 | |
| 157 | ### Hover Color Change |
| 158 | ``` |
| 159 | draw_bg +: { |
| 160 | instance hover: 0.0 |
| 161 | color: #333 |
| 162 | pixel: fn() { |
| 163 | return Pal.premul(mix(self.color, self.color * 1.3, self.hover)) |
| 164 | } |
| 165 | } |
| 166 | ``` |
| 167 | |
| 168 | ### Box Shadow |
| 169 | ``` |
| 170 | draw_bg +: { |
| 171 | pixel: fn() { |
| 172 | let sdf = Sdf2d.viewport(self.pos * self.rect_size) |
| 173 | // Shadow |
| 174 | sdf.box(2.0, 2.0, self.rect_size.x - 4.0, self.rect_size.y - 4.0, 8.0) |
| 175 | sdf.fill(GaussShadow.box_shadow(sdf, 4.0, #0005)) |
| 176 | // Card |
| 177 | sdf.box(0.0, 0.0, self.rect_size.x - 2.0, self.rect_size.y - 2.0, 8.0) |
| 178 | sdf.fill(#2a2a3d) |
| 179 | return sdf.result |
| 180 | } |
| 181 | } |
| 182 | ``` |
| 183 | |
| 184 | ### Rounded Button with States |
| 185 | ``` |
| 186 | draw_bg +: { |
| 187 | instance hover: 0.0 |
| 188 | instance down: 0.0 |
| 189 | uniform color_bg: #4488ff |
| 190 | uniform color_hover: #5599ff |
| 191 | uniform color_down: #3377ee |
| 192 | |
| 193 | pixel: fn() { |
| 194 | let sdf = Sdf2d.viewport(self.pos * self.rect_size) |
| 195 | sdf.box(0.0, 0.0, self.rect_size.x, self.rect_size.y, 6.0) |
| 196 | let color = mix(self.color_bg, self.color_hover, self.hover) |
| 197 | let color = mix(color, self.color_down, self.down) |
| 198 | sdf.fill(color) |
| 199 | return sdf.result |
| 200 | } |
| 201 | } |
| 202 | ``` |
| 203 | |
| 204 | --- |
| 205 | |
| 206 | ## Custom |