mcpbeat Sign in

Makepad 2 0 Shaders Skill for Cursor

| 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 draw, DrawQuad, DrawVector, 着色器, 像素, 渲染, 自定义绘制, 距离场

6k tokens
context cost
the whole folder, loaded on every use
3
files
instructions only
0
copies elsewhere
how many repositories repackaged it
745
stars on the repo
on the repository, not the skill itself

Install

one command, takes just this skill from the repository
npx skills add https://github.com/ZhangHanDong/makepad-skills --skill makepad-2.0-shaders

What comes with it

8 875 bytes besides the instruction
references/sdf2d-reference.md
references/shader-reference.md

The instruction itself

36 sections, as written by the author

Makepad 2.0 Shader Skill

> Version: makepad-widgets (dev branch) | Last Updated: 2026-03-03

Overview

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.

Documentation

Refer to the local files for detailed documentation:

  • ./references/shader-reference.md - Shader syntax, variables, built-ins, custom functions
  • ./references/sdf2d-reference.md - SDF2D primitives, combinators, drawing operations

Shader Basics

Pixel Shader Structure

draw_bg +: {
    // Declare variables
    instance hover: 0.0          // Animatable per-instance
    uniform accent: #4488ff      // Shared across all instances

    pixel: fn() {
        let sdf = Sdf2d.viewport(self.pos * self.rect_size)
        // ... SDF operations ...
        return sdf.result
    }
}

Variable Types

| Type | Declaration | Animatable | Scope |

|------|-------------|-----------|-------|

| instance | instance hover: 0.0 | Yes (via Animator) | Per-widget instance |

| uniform | uniform color: #fff | No | Shared across instances |

| texture_2d | texture_2d tex: none | No | Texture sampler |

| varying | varying uv: vec2 | No | Vertex → fragment |

Built-in Variables

| Variable | Type | Description |

|----------|------|-------------|

| self.pos | vec2 | Normalized position (0.0 to 1.0) |

| self.rect_size | vec2 | Widget size in pixels |

| self.dpi_factor | float | Screen DPI factor |

| self.draw_pass.time | float | Time in seconds |


CRITICAL: Premultiply Alpha

Every pixel shader MUST return premultiplied alpha color!

// WRONG - non-premultiplied
pixel: fn() {
    return vec4(1.0, 0.0, 0.0, 0.5)
}

// CORRECT - use Pal.premul()
pixel: fn() {
    return Pal.premul(vec4(1.0, 0.0, 0.0, 0.5))
}

// ALSO CORRECT - sdf.result is already premultiplied
pixel: fn() {
    let sdf = Sdf2d.viewport(self.pos * self.rect_size)
    sdf.circle(cx, cy, r)
    sdf.fill(#f00)
    return sdf.result
}

SDF2D Quick Reference

Setup

let sdf = Sdf2d.viewport(self.pos * self.rect_size)

Primitives

sdf.circle(cx, cy, radius)
sdf.rect(x, y, w, h)
sdf.box(x, y, w, h, border_radius)
sdf.hexagon(cx, cy, radius)
sdf.arc(cx, cy, radius, start_angle, end_angle, thickness)
sdf.move_to(x, y)
sdf.line_to(x, y)
sdf.close_path()

Drawing

sdf.fill(color)         // Filled shape
sdf.stroke(color, width) // Outlined shape
sdf.glow(color, amount)  // Glow effect
sdf.clear(color)         // Clear with color

Combinators

sdf.union()       // Add shapes together
sdf.intersect()   // Keep overlap only
sdf.subtract()    // Remove second from first
sdf.gloop(radius) // Smooth union
sdf.blend(amount) // Linear blend

Transforms

sdf.translate(x, y)
sdf.rotate(angle, cx, cy)
sdf.scale(factor, cx, cy)

Color Operations

// Mix two colors
mix(#f00, #00f, 0.5)           // 50% blend

// Premultiply alpha
Pal.premul(vec4(r, g, b, a))

// HSV conversions
Pal.hsv2rgb(vec4(h, s, v, 1.0))
Pal.rgb2hsv(color)

// Random
Math.random_2d(vec2(x, y))

Common Shader Patterns

Gradient Background

draw_bg +: {
    pixel: fn() {
        let grad = mix(#1a1a2e, #16213e, self.pos.y)
        return Pal.premul(vec4(grad.xyz, 1.0))
    }
}

Hover Color Change

draw_bg +: {
    instance hover: 0.0
    color: #333
    pixel: fn() {
        return Pal.premul(mix(self.color, self.color * 1.3, self.hover))
    }
}

Box Shadow

draw_bg +: {
    pixel: fn() {
        let sdf = Sdf2d.viewport(self.pos * self.rect_size)
        // Shadow
        sdf.box(2.0, 2.0, self.rect_size.x - 4.0, self.rect_size.y - 4.0, 8.0)
        sdf.fill(GaussShadow.box_shadow(sdf, 4.0, #0005))
        // Card
        sdf.box(0.0, 0.0, self.rect_size.x - 2.0, self.rect_size.y - 2.0, 8.0)
        sdf.fill(#2a2a3d)
        return sdf.result
    }
}

Rounded Button with States

draw_bg +: {
    instance hover: 0.0
    instance down: 0.0
    uniform color_bg: #4488ff
    uniform color_hover: #5599ff
    uniform color_down: #3377ee

    pixel: fn() {
        let sdf = Sdf2d.viewport(self.pos * self.rect_size)
        sdf.box(0.0, 0.0, self.rect_size.x, self.rect_size.y, 6.0)
        let color = mix(self.color_bg, self.color_hover, self.hover)
        let color = mix(color, self.color_down, self.down)
        sdf.fill(color)
        return sdf.result
    }
}

Custom Shader Functions

draw_bg +: {
    fn wave(pos: vec2, time: float) -> float {
        return sin(pos.x * 10.0 + time * 3.0) * 0.1
    }

    pixel: fn() {
        let w = self.wave(self.pos, self.draw_pass.time)
        let color = mix(#1a1a2e, #4488ff, self.pos.y + w)
        return Pal.premul(vec4(color.xyz, 1.0))
    }
}

Splash Shader Capability Boundary

Splash CAN:

  • Override pixel: fn(), vertex: fn(), get_color: fn() on existing draw types via +:
  • Define helper shader functions within +: blocks
  • Set instance/uniform variables on existing draw types
  • Use all SDF2D, color, math built-ins in shader functions

Splash CANNOT:

  • Create new DrawQuad/DrawText/DrawSvg types (must define in Rust)
  • Add new instance fields to existing shaders (GPU layout is compile-time)

Rule: Rust defines the draw type struct + registers it; Splash overrides how it draws.

See ./references/shader-reference.md "Splash Shader Capabilities & Boundaries" for the full pattern.


Custom Fullscreen Shader Pattern (learned 2026-03-26)

For standalone shader-driven widgets (e.g. particle fields, visualizers), follow the examples/shader pattern:

1. Custom Draw Type (Rust)

#[derive(Script, ScriptHook)]
#[repr(C)]  // CRITICAL: must be repr(C) for GPU layout
pub struct DrawMyShader {
    #[deref] draw_super: DrawQuad,  // inherits from DrawQuad
    #[live] my_param: f32,           // maps to shader variable
}

2. Register + Define Shader (script_mod!)

set_type_default() do #(DrawMyShader::script_shader(vm)){
    ..mod.draw.DrawQuad        // inherit DrawQuad defaults
    my_param: 0.5              // default value

    // Custom functions: property-style syntax, NOT fn name(self, ...)
    my_helper: fn(a: float, b: float) -> vec2 {
        return vec2(a * 2.0, b * 0.5)
    }

    pixel: fn() {
        let result = self.my_helper(self.pos.x, self.pos.y)
        return Pal.premul(vec4(result.x, result.y, 0.0, 1.0))
    }
}

3. Widget with Turtle Layout

fn draw_walk(&mut self, cx: &mut Cx2d, _: &mut Scope, walk: Walk) -> DrawStep {
    cx.begin_turtle(walk, self.layout);
    let rect = cx.turtle().rect();
    self.draw_bg.draw_abs(cx, rect);      // single fullscreen quad
    cx.end_turtle_with_area(&mut self.area);
    DrawStep::done()
}

4. Updating Shader Variables from Rust

// Direct field access (when draw type has #[live] fields):
self.draw_bg.my_param = 0.75;
self.area.redraw(cx);

// Via NextFrame for animation:
if let Event::NextFrame(ne) = event {
    if ne.set.contains(&self.next_frame) {
        self.draw_bg.my_param += 0.01;
        self.area.redraw(cx);
        self.next_frame = cx.new_next_frame();
    }
}

Instanced Particle Rendering (learned 2026-03-26)

For drawing thousands of independent particles (dots, stars, etc.):

Draw Shader

#[derive(Script, ScriptHook)]
#[repr(C)]
pub struct DrawDot {
    #[deref] draw_super: DrawQuad,
    #[live] dot_color: Vec3,  // per-instance color
}
// Shader: each instance is a small circle
pixel: fn() {
    let d = length(self.pos - vec2(0.5, 0.5))
    let alpha = 1.0 - smoothstep(0.35, 0.5, d)
    return Pal.premul(vec4(self.dot_color * alpha, alpha))
}

Rendering Loop

self.draw_dot.begin_many_instances(cx);  // start batch

for i in 0..particles.len() {
    let (x, y) = particles[i];
    self.draw_dot.dot_color = vec3(r, g, b);  // set per-instance data
    self.draw_dot.draw_abs(cx, Rect {
        pos: dvec2(x - radius, y - radius),
        size: dvec2(radius * 2.0, radius * 2.0),
    });
}

self.draw_dot.end_many_instances(cx);  // submit batch as one draw call

Physics Pattern (spring-back displacement)

// Per particle: store persistent displacement
displacements: Vec<(f64, f64)>,

// Each frame:
for i in 0..dots.len() {
    let (mut dx, mut dy) = displacements[i];

    // 1. Decay (spring back, 0.94 = ~2-3 sec return)
    dx *= 0.94;
    dy *= 0.94;

    // 2. Apply forces (cursor push, ripples, etc.)
    let dist = distance(dot_pos, mouse_pos);
    let t = (1.0 - dist / radius).max(0.0);
    let push = t * t * t * strength;  // cubic falloff
    dx += direction.x * push;
    dy += direction.y * push;

    displacements[i] = (dx, dy);
    // Draw at original_pos + displacement
}

Performance Notes

  • 10,000 particles at 60fps: OK on macOS Metal (one draw call via instancing)
  • CPU physics loop: 10K × 17 distance checks = ~170K ops/frame, negligible
  • Key: begin_many_instances / end_many_instances batches into single GPU draw call

Shader Syntax Pitfalls (learned 2026-03-26)

| Pitfall | Error | Fix |

|---------|-------|-----|

| let x = 1.0; x = 2.0 | cannot assign to let binding | Use different names: let x2 = ... |

| fn push(self, ...) -> vec2 | method not found on self | Use property syntax: push: fn(...) -> vec2 { } |

| return vec4(r, g, b, a) without premul | Incorrect alpha blending | return Pal.premul(vec4(r, g, b, a)) |

| Custom shader in Splash eval | Silent blank render | Must use compiled script_mod! path |

| Missing #[repr(C)] on draw struct | GPU layout mismatch | Always add #[repr(C)] |

| fn calc(self, x: float) syntax | cannot push to frozen vec | Use calc: fn(x: float) -> float { } |


Best Practices

  • Always premultiply - Use Pal.premul() or return sdf.result
  • Use instance for animation - Only instance variables work with Animator
  • Use uniform for shared values - Colors, sizes shared across instances
  • Use +: merge operator - Extend default shaders: draw_bg +: { ... }
  • Keep shaders simple - Complex shaders impact rendering performance
  • Use SDF for shapes - Much cleaner than manual math
  • Test with new_batch: true - Required when mixing shaders with text
  • Property-style functions - name: fn(args) -> type { }, call via self.name(args)
  • Immutable let - Shader let cannot be reassigned; use unique names per step

10. #[repr(C)] on draw structs - Required for GPU memory layout alignment


SDF Capsule (Pill) Shape Pattern (learned 2026-03-31)

sdf.box() with large border_radius breaks when radius approaches half the dimension — the formula size.xy - vec2(2*r, 2*r) goes negative, producing diamond/spiky shapes. Use this standard capsule SDF instead:

draw_bg +: {
    pixel: fn() {
        let w = self.rect_size.x
        let h = self.rect_size.y
        let r = h * 0.5
        let px = self.pos.x * w
        let py = self.pos.y * h
        // Standard capsule: clamp x to center segment, then circle distance
        let cx = clamp(px, r, max(r, w - r))
        let cy = h * 0.5
        let d = length(vec2(px - cx, py - cy)) - r
        let alpha = 1.0 - smoothstep(-1.0, 1.0, d)
        return Pal.premul(vec4(0.1, 0.1, 0.18, alpha * 0.82))
    }
}

Key points:

  • clamp(px, r, w-r) constrains x to the center line segment between the two end circles
  • max(r, w-r) prevents clamp range inversion when widget is very narrow
  • Smoothstep (-1.0, 1.0, d) provides 2px anti-aliasing
  • Works correctly at any width — dynamically adapts as width: Fit content changes
  • No three-part union (circles + rect) needed — single formula, no seam artifacts

Pulsing Dot in Background Shader (learned 2026-03-31)

Embed animation directly in the background shader to avoid z-order issues with child widgets (LoadingSpinner/other widgets can cause bleed-through at capsule edges):

draw_bg +: {
    pixel: fn() {
        let w = self.rect_size.x
        let h = self.rect_size.y
        let r = h * 0.5
        let px = self.pos.x * w
        let py = self.pos.y * h

        // Capsule background
        let cx_bg = clamp(px, r, max(r, w - r))
        let cy = h * 0.5
        let d_bg = length(vec2(px - cx_bg, py - cy)) - r
        let bg_alpha = 1.0 - smoothstep(-1.0, 1.0, d_bg)
        let bg = vec4(0.1, 0.1, 0.18, bg_alpha * 0.82)

        // Pulsing dot (driven by draw_pass.time)
        let t = self.draw_pass.time
        let pulse = 0.5 + 0.5 * sin(t * 4.0)
        let dot_r = 4.0 + pulse * 3.0
        let dot_cx = r + 2.0
        let d_dot = length(vec2(px - dot_cx, py - cy)) - dot_r
        let dot_alpha = (1.0 - smoothstep(-1.0, 1.0, d_dot)) * bg_alpha
        let dot_color = mix(vec3(0.3, 0.6, 1.0), vec3(0.2, 0.9, 0.5), pulse)

        // Composite
        let final_rgb = mix(bg.xyz, dot_color, dot_alpha * 0.8)
        let final_a = bg.w + dot_alpha * 0.6 * (1.0 - bg.w)
        return Pal.premul(vec4(final_rgb, final_a))
    }
}

IMPORTANT: Must call self.ui.widget(cx, ids!(my_window)).redraw(cx) from handle_next_frame to keep draw_pass.time advancing. Without continuous redraw, time-based animation freezes.

Other skills for the same job

different authors, same section of the catalogue
MCP Builder
by anthropics
vendor ×13

Guide for creating high-quality MCP (Model Context Protocol) servers that enable LLMs to interact with external services through well-designed tools. Use when building MCP servers to integrate external APIs or services, whether in Python (FastMCP) or Node/TypeScript (MCP SDK).

30k tokens scripts
Changelog Generator
by frostant
×9

Automatically creates user-facing changelogs from git commits by analyzing commit history, categorizing changes, and transforming technical commits into clear, customer-friendly release notes. Turns hours of manual changelog writing into minutes of automated generation.

774 tokens
Finishing A Development Branch
by ZhanlinCui
×7

Use when implementation is complete, all tests pass, and you need to decide how to integrate the work - guides completion of development work by presenting structured options for merge, PR, or cleanup

1k tokens
MCP Builder
by JayZeeDesign
×7

Guide for creating high-quality MCP (Model Context Protocol) servers that enable LLMs to interact with external services through well-designed tools. Use when building MCP servers to integrate external APIs or services, whether in Python (FastMCP) or Node/TypeScript (MCP SDK).

37k tokens scripts
Vercel React Native Skills
by vercel-labs
vendor ×6

React Native and Expo best practices for building performant mobile apps. Use when building React Native components, optimizing list performance, implementing animations, or working with native modules. Triggers on tasks involving React Native, Expo, mobile performance, or native platform APIs.

39k tokens
Vercel React Best Practices
by ratacat
×5

React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.

34k tokens
Next Best Practices
by vercel-labs
vendor ×4

Next.js best practices - file conventions, RSC boundaries, data patterns, async APIs, metadata, error handling, route handlers, image/font optimization, bundling

20k tokens
Using Git Worktrees
by ZhanlinCui
×4

Use when starting feature work that needs isolation from current workspace or before executing implementation plans - creates isolated git worktrees with smart directory selection and safety verification

1k tokens

How to use it

Copy the folder

Take zhanghandong/makepad-2.0-shaders from the repository into ~/.claude/skills for personal use, or into .claude/skills inside a project.

Check the name does not clash

The agent identifies a skill by the name field in its header. Two skills with the same name cannot sit side by side — one of them will be ignored.