Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Shaders

WaterUI supports custom GPU shaders via waterui::graphics::ShaderSurface. You can write WGSL fragment shaders that render directly to the view’s surface.

Using ShaderSurface

The easiest way to use shaders is with the shader! macro, which loads a WGSL file at compile time:

#![allow(unused)]
fn main() {
use waterui::prelude::*;
use waterui::graphics::shader;

fn flame_effect() -> impl View {
    shader!("flame.wgsl")
        .width(400.0).height(500.0)
}
}

Or define the shader inline:

use waterui::graphics::ShaderSurface;

fn gradient() -> impl View {
    ShaderSurface::new(r#"
        @fragment
        fn main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
            return vec4<f32>(uv.x, uv.y, 0.5, 1.0);
        }
    "#)
}

Built-in Uniforms

ShaderSurface automatically prepends the following uniform definition (and a full-screen vertex shader) to your code. Do not redefine this struct or variable in your WGSL file.

// Auto-generated prelude (available to your shader):
struct Uniforms {
    time: f32,           // Elapsed time in seconds
    resolution: vec2<f32>, // Surface size in pixels
    _padding: f32,
}
@group(0) @binding(0) var<uniform> uniforms: Uniforms;

Your shader code should strictly define the fragment entry point:

@fragment
fn main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
    let t = uniforms.time;
    // ...
}

uv coordinates are normalized (0.0 to 1.0).

Advanced GPU Rendering

ShaderSurface is limited to single-pass fragment shaders. For full control over the rendering pipeline (custom vertex shaders, multiple passes, compute shaders), implement the GpuRenderer trait and use GpuSurface.

#![allow(unused)]
fn main() {
use waterui::graphics::{GpuRenderer, GpuSurface, GpuContext, GpuFrame};

struct MyRenderer;

impl GpuRenderer for MyRenderer {
    fn setup(&mut self, ctx: &GpuContext) {
        // Create pipelines, buffers, bind groups...
    }

    fn render(&mut self, frame: &GpuFrame) {
        // Create encoder, render passes, submit to queue...
    }
}

fn custom_render() -> impl View {
    GpuSurface::new(MyRenderer)
}
}

See the flame example for a complete implementation of a multi-pass HDR renderer using GpuSurface and wgpu.