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

  1. Reduce the render resolution or render in smaller tiles/regions (the render cache already tiles) so texture allocation fits GPU memory
  2. Enable wgpu logging / set an uncaptured-error handler to capture the underlying GPU error before the panic
  3. Update GPU drivers or switch to a software adapter (lavapipe/LLVMpipe) where Vello shaders fail to compile or run
  4. 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

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


AI-assisted analysis of GraphiteEditor/Graphite@c507b35645 (2026-08-16). Data as JSON: /api/errors/adef77191e529bce. Report an issue: GitHub.