GraphiteEditor/Graphite · critical
Failed to render Vello scene
Error message
Failed to render Vello scene
What it means
After the render node sanitizes non-finite transforms in the Vello scene encoding, it calls executor.render_vello_scene(...) and .expect("Failed to render Vello scene") panics when the awaited render returns Err. Failures originate inside the wgpu/Vello pipeline: device lost mid-render, out-of-memory allocating the render target texture at the requested resolution, compute shaders/pipelines failing on old or software drivers, or invalid scene data surviving the transform guard.
Source
Thrown at node-graph/nodes/gstd/src/render_node.rs:140
//
// `!is_finite()` rather than `== f32::INFINITY`: `scene.append` composes the child's `Affine::scale(INFINITY)` with
// the viewport rotation, leaving `matrix[0] = cos(θ) * INFINITY`. In the (90°, 270°) tilt range cos is negative so
// the result is `-INFINITY`, which the old equality check missed; Vello then rasterized a unit rect with non-finite
// vertices, dropping the gradient and tanking performance. `!is_finite()` also covers NaN as a guard against future
// code paths where `matrix[0]` could land on `0 * INFINITY`.
let scaled_infinite_transform = vello::kurbo::Affine::scale_non_uniform(footprint.resolution.x as f64, footprint.resolution.y as f64);
for transform in transformed_scene.encoding_mut().transforms.iter_mut() {
if !transform.matrix[0].is_finite() {
*transform = vello_encoding::Transform::from_kurbo(&scaled_infinite_transform);
}
}
let texture = executor
.into_element()
.expect("GPU executor not available")
.render_vello_scene(&transformed_scene, footprint.resolution, context, None)
.await
.expect("Failed to render Vello scene");
RenderOutputType::Texture(texture)
}
_ => unreachable!("Render node did not receive its requested data type"),
};
Item::new_from_element(RenderOutput { data, metadata })
}
#[node_macro::node(category(""))]
async fn create_context<'a: 'n>(
// The executor boundary supplies the render config as the sole vararg (see `wrap_network_in_scope()`)
ctx: impl Ctx + ExtractAll + CloneVarArgs + Sync,
data: impl Node<Context<'static>, Output = Item<RenderOutput>>,
) -> Item<RenderOutput> {
let render_config = ctx.vararg(0).ok().and_then(|config| config.downcast_ref::<RenderConfig>()).copied().unwrap_or_else(|| {
log::error!("The boundary context is missing its render config vararg");
RenderConfig::default()
});View on GitHub (pinned to c507b35645)
Solutions
- Reduce the render resolution or render in smaller tiles/regions (the render cache already tiles) so texture allocation fits GPU memory
- Enable wgpu logging / set an uncaptured-error handler to capture the underlying GPU error before the panic
- Update GPU drivers or switch to a software adapter (lavapipe/LLVMpipe) where Vello shaders fail to compile or run
- Propagate the error instead of .expect so the render node can surface a user-visible failure and optionally retry at reduced resolution
Example fix
// before
let texture = executor.into_element().expect("GPU executor not available")
.render_vello_scene(&transformed_scene, footprint.resolution, context, None).await
.expect("Failed to render Vello scene");
// after
let texture = executor.render_vello_scene(&transformed_scene, footprint.resolution, context, None).await
.map_err(|e| {
log::error!("Vello render failed at resolution {:?}: {e}", footprint.resolution);
RenderError::VelloRenderFailed(e)
})?; // caller may retry at reduced resolution on OOM Defensive patterns
Strategy: retry
Validate before calling
// Pre-check the requested render target against device limits before rendering
let max_dim = executor.context().device.limits().max_texture_dimension_2d;
if footprint.resolution.x > max_dim || footprint.resolution.y > max_dim {
// clamp or tile the resolution before calling render_vello_scene
} Try / catch
let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
executor.render_vello_scene(&scene, resolution, context, None)
}));
match outcome.and_then(|f| futures::executor::block_on(f).map_err(std::panic::panic_any)) {
_ => { /* on failure: halve the resolution and retry, or re-create the executor if the device was lost */ }
} Prevention
- Cap export resolution and render large outputs in tiles (the render cache already does this)
- Register device.on_uncaptured_error and log wgpu validation/OOM errors so the real cause is captured before the panic
- Keep GPU drivers updated; test on the software adapter to distinguish driver issues from scene-data issues
- Sanitize scene data (finite transforms, valid geometry) before handing it to Vello
When it happens
Trigger: Rendering a Vello scene at a resolution whose render target exceeds GPU memory; device/driver loss during the render; a driver or browser without the features Vello's compute pipelines need; degenerate scene content that the !is_finite() transform patch above did not neutralize.
Common situations: Large exports (poster-size artboards at high scale) exhausting VRAM; GPU driver crashes/resets; VMs and remote desktops without proper GPU support; WebGPU implementations with incomplete compute-shader support; long editor sessions after a device loss.
Related errors
- GPU executor not available
- Failed to create WgpuExecutor
- GPU executor should be available when we receive a texture
- Buffer mapping communication failed
- Failed to download texture data
AI-assisted analysis of GraphiteEditor/Graphite@c507b35645 (2026-08-16).
Data as JSON: /api/errors/adef77191e529bce.
Report an issue: GitHub.