commit | fe3273ab5418e33a5471227ce845a39d60d6768b | [log] [tgz] |
---|---|---|
author | Raph Levien <raph.levien@gmail.com> | Wed Mar 13 14:54:44 2024 -0700 |
committer | GitHub <noreply@github.com> | Wed Mar 13 21:54:44 2024 +0000 |
tree | 9a5d2166350423e5063a65da0e33576ff8166f63 | |
parent | f55f82f23c0233df1baea76fef9252b871c737f7 [diff] |
Checkpoint of Euler spiral based stroke expansion (#496) * Checkpoint euler patch This just imports Arman's patch and tries to update it for other changes. * Checkpoint numerical robustness work Apply Euler spiral numerical robustness to CPU shader. There are a number of minor tweaks, and some important changes: * Cubic segments are computed with points and derivatives, not subdivision * Those derivatives are adjusted if they are near-zero * The ESPC integral is robust to small k1 and dist Note that dealing with small dist is essential for doing ES flattening for fills as well as strokes. * Checkpoint This pipes through the start/end points (always using them as endpoints) and always uses Euler flattening. It seems to basically work for fills, but has lots of artifacts still for strokes. * Don't special case lines Drop zero-length lines. This doesn't get rid of all the code, but it shouldn't be needed for correctness. In addition, zero length lines are dropped. We need to come back to this, they do have some semantics for strokes. * Force style for each encoded glyph * Better continuity of flattening Reuse endpoints of Euler segments. In theory, this should ensure watertight meshes, but we're definitely not seeing it. * Fix end normals Calculation of end normals for stroke offset was mistaken. At this point, it's starting to look fairly good. Some of the tricky strokes need investigation, and handling of zero-length lines is still a bit dodgy. * Checkpoint WGSL work Euler flattening seems to be almost working as long as subdivision is disabled, at least for fills. This commit has that disabled. * Fix bugs Simple typo in subdivision logic (and re-enable subdivision, which was disabled in previous commit). Sign wrong in inverse integral. Typo in calculation of start tangent (repeated p3 twice instead of p2, p3). Note: also enables GPU-side stroking * Small fixes and cleanups Make handling of small tangent consistent. Remove cubic flattening. Make subdivision limiting logic of CPU shader consistent with GPU version. Get rid of unused is_line predicate on CPU side. * Clippy fixes * Fix copyright headers (and I also added this to my local pre-push git hook) * Review suggestions Mostly doc improvements, but also improvements to robustness for short chords. * Tune tolerance value I have validated that the tolerance value for both arcs and Bézier segments is calibrated correctly. Thus, it makes sense to set both to a value of 0.25, so that caps are consistent. This comes quite close to matching the amount of subdivision in current main, and the line count is also consistent between CPU and GPU stroke expansion. * Make GPU tolerance consistent with CPU Oops, I made the change only on the CPU side. This brings GPU into agreement. * Address review comments A bit more comment, but also a more robust protection against a divide by zero, which happens in the colinear double-cusp case, as exercised by tricky-strokes.
An experimental GPU compute-centric 2D renderer
Vello is an experimental 2D graphics rendering engine written in Rust, with a focus on GPU compute. It can draw large 2D scenes with interactive or near-interactive performance, using wgpu
for GPU access.
Quickstart to run an example program:
cargo run -p with_winit
It is used as the rendering backend for Xilem, a Rust GUI toolkit.
[!WARNING] Vello can currently be considered in an alpha state. In particular, we're still working on the following:
Vello is meant to fill the same place in the graphics stack as other vector graphics renderers like Skia, Cairo, and its predecessor project Piet. On a basic level, that means it provides tools to render shapes, images, gradients, text, etc, using a PostScript-inspired API, the same that powers SVG files and the browser <canvas>
element.
Vello's selling point is that it gets better performance than other renderers by better leveraging the GPU. In traditional PostScript-style renderers, some steps of the render process like sorting and clipping either need to be handled in the CPU or done through the use of intermediary textures. Vello avoids this by using prefix-sum algorithms to parallelize work that usually needs to happen in sequence, so that work can be offloaded to the GPU with minimal use of temporary buffers.
This means that Vello needs a GPU with support for compute shaders to run.
Vello is meant to be integrated deep in UI render stacks. While drawing in a Vello scene is easy, actually rendering that scene to a surface requires setting up a wgpu context, which is a non-trivial task.
To use Vello as the renderer for your PDF reader / GUI toolkit / etc, your code will have to look roughly like this:
// Initialize wgpu and get handles let (width, height) = ...; let device: wgpu::Device = ...; let queue: wgpu::Queue = ...; let surface: wgpu::Surface<'_> = ...; let texture_format: wgpu::TextureFormat = ...; let mut renderer = Renderer::new( &device, RendererOptions { surface_format: Some(texture_format), use_cpu: false, antialiasing_support: vello::AaSupport::all(), num_init_threads: NonZeroUsize::new(1), }, ).expect("Failed to create renderer"); // Create scene and draw stuff in it let mut scene = vello::Scene::new(); scene.fill( vello::peniko::Fill::NonZero, vello::Affine::IDENTITY, vello::Color::rgb8(242, 140, 168), None, &vello::Circle::new((420.0, 200.0), 120.0), ); // Draw more stuff scene.push_layer(...); scene.fill(...); scene.stroke(...); scene.pop_layer(...); // Render to your window/buffer/etc. let surface_texture = surface.get_current_texture() .expect("failed to get surface texture"); renderer .render_to_surface( &device, &queue, &scene, &surface_texture, &vello::RenderParams { base_color: Color::BLACK, // Background color width, height, antialiasing_method: AaConfig::Msaa16, }, ) .expect("Failed to render to surface"); surface_texture.present();
See the examples/
folder to see how that code integrates with frameworks like winit and bevy.
We've observed 177 fps for the paris-30k test scene on an M1 Max, at a resolution of 1600 pixels square, which is excellent performance and represents something of a best case for the engine.
More formal benchmarks are on their way.
This repository also includes vello_svg
, which supports converting a usvg
Tree
into a Vello scene.
This is currently incomplete; see its crate level documentation for more information.
This is used in the winit example for the SVG rendering.
A separate integration for playing Lottie animations is available through the velato
crate.
Our examples are provided in separate packages in the examples
folder. This allows them to have independent dependencies and faster builds. Examples must be selected using the --package
(or -p
) Cargo flag.
Our winit example (examples/with_winit) demonstrates rendering to a winit window. By default, this renders the GhostScript Tiger as well as all SVG files you add in the examples/assets/downloads/ directory using vello_svg
. A custom list of SVG file paths (and directories to render all SVG files from) can be provided as arguments instead. It also includes a collection of test scenes showing the capabilities of vello
, which can be shown with --test-scenes
.
cargo run -p with_winit
Some default test scenes can be downloaded from Wikimedia Commons using the download
subcommand. This also supports downloading from user-provided URLS.
cargo run -p with_winit -- download
The Bevy example (examples/with_bevy) demonstrates using Vello within a Bevy application. This currently draws to a wgpu
Texture
using vello
, then uses that texture as the faces of a cube.
cargo run -p with_bevy
There is also a separate community integration for rendering lottie and SVG files through bevy_vello
.
We aim to target all environments which can support WebGPU with the default limits. We defer to wgpu
for this support. Other platforms are more tricky, and may require special building/running procedures.
Because Vello relies heavily on compute shaders, we rely on the emerging WebGPU standard to run on the web. Until browser support becomes widespread, it will probably be necessary to use development browser versions (e.g. Chrome Canary) and explicitly enable WebGPU.
The following command builds and runs a web version of the winit demo. This uses cargo-run-wasm
to build the example for web, and host a local server for it
# Make sure the Rust toolchain supports the wasm32 target rustup target add wasm32-unknown-unknown # The binary name must also be explicitly provided as it differs from the package name cargo run_wasm -p with_winit --bin with_winit_bin
There is also a web demo available here on supporting web browsers.
[!WARNING] The web is not currently a primary target for Vello, and WebGPU implementations are incomplete, so you might run into issues running this example.
The with_winit
example supports running on Android, using cargo apk.
cargo apk run -p with_winit
[!TIP]
cargo apk doesn't support running in release mode without configuration. See their crates page docs (aroundpackage.metadata.android.signing.<profile>
).See also cargo-apk#16. To run in release mode, you must add the following to
examples/with_winit/Cargo.toml
(changing$HOME
to your home directory):
[package.metadata.android.signing.release] path = "$HOME/.android/debug.keystore" keystore_password = "android"
[!NOTE]
Ascargo apk
does not allow passing command line arguments or environment variables to the app when ran, these can be embedded into the program at compile time (currently for Android only)with_winit
currently supports the environment variables:
VELLO_STATIC_LOG
, which is equivalent toRUST_LOG
VELLO_STATIC_ARGS
, which is equivalent to passing in command line arguments
For example (with unix shell environment variable syntax):
VELLO_STATIC_LOG="vello=trace" VELLO_STATIC_ARGS="--test-scenes" cargo apk run -p with_winit --lib
Discussion of Vello development happens in the Linebender Zulip, specifically the #gpu stream. All public content can be read without logging in.
Contributions are welcome by pull request. The Rust code of conduct applies.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be licensed as noted in the “License” section, without any additional terms or conditions.
Vello was previously known as piet-gpu
. This prior incarnation used a custom cross-API hardware abstraction layer, called piet-gpu-hal
, instead of wgpu
.
An archive of this version can be found in the branches custom-hal-archive-with-shaders
and custom-hal-archive
. This succeeded the previous prototype, piet-metal, and included work adapted from piet-dx12.
The decision to lay down piet-gpu-hal
in favor of WebGPU is discussed in detail in the blog post Requiem for piet-gpu-hal.
A vision document dated December 2020 explained the longer-term goals of the project, and how we might get there. Many of these items are out-of-date or completed, but it still may provide some useful background.
Vello takes inspiration from many other rendering projects, including:
Licensed under either of
at your option.
In addition, all files in the shader
and src/cpu_shader
directories and subdirectories thereof are alternatively licensed under the Unlicense (shader/UNLICENSE or http://unlicense.org/). For clarity, these files are also licensed under either of the above licenses. The intent is for this research to be used in as broad a context as possible.
The files in subdirectories of the examples/assets
directory are licensed solely under their respective licenses, available in the LICENSE
file in their directories.