grafo 0.15.0

A GPU-accelerated rendering library for Rust
Documentation
// TODO: This is needed to avoid false positives generated by the repr(C) expansion in compiler 1.89
#![allow(unused)]
/// Example: Separable Gaussian blur effect
///
/// Demonstrates a multi-pass effect: a two-pass separable Gaussian blur.
/// Pass 1 blurs horizontally, Pass 2 blurs vertically.
/// Both passes share the same params uniform (radius + texture size).
///
/// The scene has:
/// - A background shape (no effect)
/// - A group of overlapping colored rectangles with a Gaussian blur applied
use futures::executor::block_on;
use grafo::Shape;
use grafo::{Color, Stroke};
use std::sync::Arc;
use winit::application::ApplicationHandler;
use winit::event::WindowEvent;
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::window::{Window, WindowId};

const BLUR_EFFECT: u64 = 1;

/// Parameters shared by both blur passes.
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
#[allow(dead_code)]
struct BlurParams {
    radius: f32,
    _pad: f32,
    tex_size: [f32; 2],
}

const HORIZONTAL_BLUR_WGSL: &str = r#"
const DIRECTION: vec2<f32> = vec2<f32>(1.0, 0.0);

struct Params {
    radius: f32,
    _pad: f32,
    tex_size: vec2<f32>,
}
@group(1) @binding(0) var<uniform> params: Params;

@fragment
fn effect_main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
    let pixel = DIRECTION / params.tex_size;
    let sigma = max(params.radius / 3.0, 0.001);
    var color = vec4<f32>(0.0);
    var total_weight = 0.0;
    let r = i32(ceil(params.radius));
    for (var i = -r; i <= r; i++) {
        let offset = f32(i);
        let weight = exp(-(offset * offset) / (2.0 * sigma * sigma));
        color += textureSample(t_input, s_input, uv + pixel * offset) * weight;
        total_weight += weight;
    }
    return color / total_weight;
}
"#;

const VERTICAL_BLUR_WGSL: &str = r#"
const DIRECTION: vec2<f32> = vec2<f32>(0.0, 1.0);

struct Params {
    radius: f32,
    _pad: f32,
    tex_size: vec2<f32>,
}
@group(1) @binding(0) var<uniform> params: Params;

@fragment
fn effect_main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
    let pixel = DIRECTION / params.tex_size;
    let sigma = max(params.radius / 3.0, 0.001);
    var color = vec4<f32>(0.0);
    var total_weight = 0.0;
    let r = i32(ceil(params.radius));
    for (var i = -r; i <= r; i++) {
        let offset = f32(i);
        let weight = exp(-(offset * offset) / (2.0 * sigma * sigma));
        color += textureSample(t_input, s_input, uv + pixel * offset) * weight;
        total_weight += weight;
    }
    return color / total_weight;
}
"#;

#[derive(Default)]
struct App<'a> {
    window: Option<Arc<Window>>,
    renderer: Option<grafo::Renderer<'a>>,
}

impl<'a> ApplicationHandler for App<'a> {
    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
        let window = Arc::new(
            event_loop
                .create_window(
                    Window::default_attributes()
                        .with_title("Grafo – Gaussian Blur (Multi-Pass Effect)"),
                )
                .unwrap(),
        );

        let window_size = window.inner_size();
        let scale_factor = window.scale_factor();
        let physical_size = (window_size.width, window_size.height);

        let mut renderer = block_on(grafo::Renderer::new(
            window.clone(),
            physical_size,
            scale_factor,
            true,
            false,
            1,
        ));

        // Load the two-pass Gaussian blur effect
        renderer
            .load_effect(BLUR_EFFECT, &[HORIZONTAL_BLUR_WGSL, VERTICAL_BLUR_WGSL])
            .expect("Failed to compile blur effect");

        self.window = Some(window);
        self.renderer = Some(renderer);
    }

    fn window_event(
        &mut self,
        event_loop: &ActiveEventLoop,
        window_id: WindowId,
        event: WindowEvent,
    ) {
        let Some(window) = &self.window else { return };
        let Some(renderer) = &mut self.renderer else {
            return;
        };

        if window_id != window.id() {
            return;
        }

        match event {
            WindowEvent::CloseRequested => event_loop.exit(),
            WindowEvent::Resized(physical_size) => {
                let new_size = (physical_size.width, physical_size.height);
                renderer.resize(new_size);
                window.request_redraw();
            }
            WindowEvent::RedrawRequested => {
                let (pw, ph) = renderer.size();

                // ── Background (no effect) ───────────────────────────────
                let bg = Shape::rect(
                    [(30.0, 30.0), (770.0, 570.0)],
                    Stroke::new(2.0, Color::BLACK),
                );
                let bg_id = renderer.add_shape(bg, None, None);
                renderer.set_shape_color(bg_id, Some(Color::rgb(240, 240, 245)));

                // ── Label text (just a thin rectangle as a visual marker) ─
                let label = Shape::rect(
                    [(310.0, 200.0), (750.0, 230.0)],
                    Stroke::new(1.0, Color::BLACK),
                );
                let l = renderer.add_shape(label, Some(bg_id), None);
                renderer.set_shape_color(l, Some(Color::rgb(200, 200, 255)));

                // ── Blurred group ────────────────────────────────────────
                // Parent shape defines the group boundary
                let group_bg = Shape::rect(
                    [(80.0, 80.0), (500.0, 400.0)],
                    Stroke::new(0.0, Color::TRANSPARENT),
                );
                let group = renderer.add_shape(group_bg, None, None);
                renderer.set_shape_color(group, Some(Color::rgba(255, 100, 100, 100)));

                // Child shapes inside the group
                let child1 = Shape::rect(
                    [(100.0, 100.0), (300.0, 280.0)],
                    Stroke::new(3.0, Color::rgb(0, 0, 0)),
                );
                let c1 = renderer.add_shape(child1, Some(group), None);
                renderer.set_shape_color(c1, Some(Color::rgb(50, 120, 255)));

                let child2 = Shape::rect(
                    [(200.0, 180.0), (450.0, 360.0)],
                    Stroke::new(3.0, Color::rgb(0, 0, 0)),
                );
                let c2 = renderer.add_shape(child2, Some(group), None);
                renderer.set_shape_color(c2, Some(Color::rgb(50, 220, 80)));

                // Attach the blur effect with radius = 8 pixels
                let blur_params = BlurParams {
                    radius: 8.0,
                    _pad: 0.0,
                    tex_size: [pw as f32, ph as f32],
                };
                renderer
                    .set_group_effect(group, BLUR_EFFECT, bytemuck::bytes_of(&blur_params))
                    .expect("Failed to set blur effect");

                // ── Sharp group for comparison ───────────────────────────
                let sharp_bg = Shape::rect(
                    [(80.0, 420.0), (500.0, 560.0)],
                    Stroke::new(0.0, Color::TRANSPARENT),
                );
                let sharp = renderer.add_shape(sharp_bg, None, None);
                renderer.set_shape_color(sharp, Some(Color::rgb(255, 100, 100)));

                let sc1 = Shape::rect(
                    [(100.0, 430.0), (300.0, 550.0)],
                    Stroke::new(3.0, Color::rgb(0, 0, 0)),
                );
                let s1 = renderer.add_shape(sc1, Some(sharp), None);
                renderer.set_shape_color(s1, Some(Color::rgb(50, 120, 255)));

                let sc2 = Shape::rect(
                    [(200.0, 440.0), (450.0, 550.0)],
                    Stroke::new(3.0, Color::rgb(0, 0, 0)),
                );
                let s2 = renderer.add_shape(sc2, Some(sharp), None);
                renderer.set_shape_color(s2, Some(Color::rgb(50, 220, 80)));

                // ── Render ───────────────────────────────────────────────
                match renderer.render() {
                    Ok(_) => {
                        renderer.clear_draw_queue();
                    }
                    Err(wgpu::SurfaceError::Lost) => renderer.resize(renderer.size()),
                    Err(wgpu::SurfaceError::OutOfMemory) => event_loop.exit(),
                    Err(e) => eprintln!("{e:?}"),
                }
            }
            _ => {}
        }
    }
}

pub fn main() {
    env_logger::init();
    let event_loop = EventLoop::new().expect("To create the event loop");

    let mut app = App::default();
    let _ = event_loop.run_app(&mut app);
}