Skip to content

Repository files navigation

Grafo

Grafo crate Grafo documentation Build and test

Grafo is a GPU-accelerated vector graphics library for Rust.

Features

  • Path rendering with cached tessellation
  • Hierarchical path clipping
  • Per-instance 3D and perspective transforms
  • Solid fills and linear, radial, and conic gradients
  • Custom WGSL shader effects on shape masks, groups, and backdrops
  • Geometry-based antialiasing and MSAA

Install Grafo from crates.io or read the API documentation.

Getting started

Add the following to your Cargo.toml:

[dependencies]
grafo = "0.20"
winit = "0.30"
futures = "0.3"
env_logger = "0.11"

Basic usage

Create a shape, set its fill and transform, then render it. For a complete window, run cargo run --example basic. The same example appears in the crate documentation.

use grafo::{Color, Shape, ShapeDrawCommandOptions};

// Set the fill when queueing the shape.
let rect = Shape::rect([(0.0, 0.0), (200.0, 100.0)]);
renderer
    .add_shape(
        rect,
        None,
        None,
        ShapeDrawCommandOptions::new()
            .color(Color::rgb(0, 128, 255))
            .transform(grafo::TransformInstance::translation(100.0, 100.0)),
    )
    .unwrap();

// Call this on RedrawRequested in a winit event loop
renderer.render(&mut surface).unwrap();
renderer.clear_draw_queue();

Render targets

Create a surface and renderer from the same context:

use grafo::{Renderer, RendererContext, Surface};

let context = RendererContext::new().await;
let mut surface = Surface::new(&context, platform_target, size, true, false)?;
let mut renderer = Renderer::new_with_context(context, size, scale_factor, 1);
renderer.render(&mut surface)?;

Pass a window or an object that provides native display and surface handles to Surface::new. Use surface.resize(size) to resize it and surface.set_vsync(enabled) to change vsync. render borrows the target and uses its dimensions.

For memory output, Pixmap owns the buffer and PixmapMut borrows a slice:

use grafo::{PixelFormat, PixelLayout, Pixmap, PixmapMut};

let size = (640, 480);
let mut image = Pixmap::new(size, PixelFormat::Bgra8)?;
renderer.render(&mut image)?;
let bytes = image.pixels();

let mut pixels = vec![0_u32; 640 * 480];
renderer.render(PixmapMut::argb32(&mut pixels, size)?)?;

// RGBA rows with 32 bytes of padding.
let layout = PixelLayout::new(size, PixelFormat::Rgba8, 640 * 4 + 32)?;
let mut storage = vec![0; layout.byte_len()];
let mut borrowed = PixmapMut::new(&mut storage, layout)?;
renderer.render(&mut borrowed)?;

Pixmap::resize reuses buffer capacity when it can. A borrowed slice must fit its layout. Rendering leaves row padding and trailing bytes untouched.

When rendering to memory, WGPU waits for readback. Errors leave the buffer unchanged. Rendering to a surface submits and presents the frame.

BGRA8 and RGBA8 store channels in that byte order. ARGB32 stores native-endian 0xAARRGGBB words. RGB is premultiplied in linear space, then encoded as sRGB. Alpha stays linear.

Multiple independent windows

Reuse a RendererContext to share the WGPU device, queue, and textures across windows:

use futures::executor::block_on;
use grafo::{Renderer, RendererContext, Surface};

let context = block_on(RendererContext::new());

let first_surface = Surface::new(&context, first_window, first_size, true, false)?;
let second_surface = Surface::new(&context, second_window, second_size, true, false)?;
let first_renderer = Renderer::new_with_context(
    context.clone(), first_size, first_scale_factor, 1,
);
let second_renderer = Renderer::new_with_context(
    context, second_size, second_scale_factor, 1,
);

Create a renderer when you open a window and drop it when you close the window. Draw calls added to one renderer never appear in another renderer's draw queue.

Loaded shapes are also shared by the context. A cache_key passed to load_shape is scoped to the RendererContext, so another renderer can reuse that shape through add_cached_shape. Use a content-derived key when the same geometry should be shared; loading a different shape with the same key replaces the shared entry, and remove_shape removes it for every renderer using the context.

Shape hierarchy and overflow

The second argument to add_shape and add_clipping_rect is the optional parent_shape_id. A child is drawn inside that parent in the draw tree. By default, parents clip their children:

use grafo::{Color, Shape, ShapeDrawCommandOptions};

let clipping_parent_id = renderer
    .add_shape(
        Shape::rect([(0.0, 0.0), (120.0, 80.0)]),
        None,
        None,
        ShapeDrawCommandOptions::new().color(Color::rgb(220, 220, 220)),
    )
    .unwrap();

renderer
    .add_shape(
        Shape::rect([(80.0, 20.0), (160.0, 60.0)]),
        Some(clipping_parent_id),
        None,
        ShapeDrawCommandOptions::new().color(Color::rgb(220, 80, 80)),
    )
    .unwrap();

// Let children render outside `overflow_parent_id`, while still respecting any ancestor clip.
let overflow_parent_id = renderer
    .add_shape(
        Shape::rect([(0.0, 0.0), (120.0, 80.0)]),
        None,
        None,
        ShapeDrawCommandOptions::new()
            .color(Color::rgb(220, 220, 220))
            .clips_children(false),
    )
    .unwrap();

renderer
    .add_shape(
        Shape::rect([(80.0, 20.0), (160.0, 60.0)]),
        Some(overflow_parent_id),
        None,
        ShapeDrawCommandOptions::new().color(Color::rgb(220, 80, 80)),
    )
    .unwrap();

// The same call works for ids returned by `add_clipping_rect`, so clip rectangles
// can also be used as non-clipping containers.

Examples

  • basic.rs draws shapes using winit 0.30's ApplicationHandler.
  • transforms.rs covers instance transforms, color, perspective, and hit-testing.
  • benches/visual_regression.rs benchmarks the visual regression scene with Criterion.

Run the visual-regression benchmark in release mode:

cargo bench --bench visual_regression

The examples directory includes hierarchical clipping, texture layers, transforms, and shader effects.

Background and foreground textures

Shapes composite up to two texture layers over the instance color using premultiplied alpha.

Set the layers with ShapeDrawCommandOptions::background_texture and ShapeDrawCommandOptions::foreground_texture. Each accepts ShapeTextureOptions with a texture ID and fit mode. Use background_texture_id and foreground_texture_id to set just the IDs.

Composition from bottom to top:

final = foreground + (background + color * (1 - background.a)) * (1 - foreground.a)

API:

use grafo::{Color, Renderer, Shape, ShapeDrawCommandOptions};

// After allocating textures via renderer.texture_manager()
renderer
    .add_shape(
        Shape::rect([(0.0, 0.0), (300.0, 200.0)]),
        None,
        None,
        ShapeDrawCommandOptions::new()
            .color(Color::rgb(40, 40, 40))
            .background_texture_id(bg_tex_id)
            .foreground_texture_id(fg_tex_id),
    )
    .unwrap();

// Transparent parts of the background texture reveal the white fill.
renderer
    .add_shape(
        Shape::rect([(0.0, 0.0), (300.0, 200.0)]),
        None,
        None,
        ShapeDrawCommandOptions::new()
            .color(Color::WHITE)
            .background_texture_id(bg_tex_id),
    )
    .unwrap();

See examples/multi_texture.rs for procedural background and foreground textures.

Positioning shapes

Use per-shape transforms to position shapes. Common helpers:

  • Translate: TransformInstance::translation(tx, ty)
  • Scale: TransformInstance::scale(sx, sy)
  • Rotate around Z: TransformInstance::rotation_z_deg(deg)
  • Compose: a.multiply(&b) and a.then(&b) apply a first, then b.

Example:

use grafo::ShapeDrawCommandOptions;

let r = grafo::TransformInstance::rotation_z_deg(15.0);
let t = grafo::TransformInstance::translation(150.0, 80.0);
// Rotate first, then translate
renderer
    .add_shape(
        my_shape,
        None,
        None,
        ShapeDrawCommandOptions::new().transform(r.then(&t)),
    )
    .unwrap();

Documentation

Documentation is available on docs.rs.

Contributing

Read CONTRIBUTING.md for coding conventions and required checks.

Authors

License

This project is licensed under the MIT License - see the LICENSE.md file for details

About

GPU-accelerated rendering library for Rust

Topics

Resources

Contributing

Stars

8 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages