GraphiteEditor/Graphite · error
GPU executor not available
Error message
GPU executor not available
What it means
In the render node's Vello branch, the executor parameter is declared as Item<Option<&WgpuExecutor>> injected from the try_wgpu_executor scope slot (render_node.rs:81). Into the texture-output path, .expect("GPU executor not available") panics when that slot delivered None, i.e. this environment cannot supply a WgpuExecutor (no adapter, GPU init failed, headless host) even though the node is asked to rasterize a Vello scene into a GPU texture.
Source
Thrown at node-graph/nodes/gstd/src/render_node.rs:137
// We now replace all transforms which are supposed to be infinite with a transform which covers the entire viewport.
// See <https://xi.zulipchat.com/#narrow/channel/197075-vello/topic/Full.20screen.20color.2Fgradients/near/538435044> for more detail.
//
// `!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(|| {View on GitHub (pinned to c507b35645)
Solutions
- Run the evaluation in an environment that supplies a GPU executor: working adapter/driver, WebGPU enabled, or a software adapter fallback
- Check startup logs for why the try_wgpu_executor slot is None (adapter request failure, ApplicationIo without GPU executor)
- Replace the .expect with a graceful node error or a CPU rasterization fallback instead of a panic
- For hosts, detect the missing executor up front (try_wgpu_executor returns Option) and skip/warn before evaluating render-to-texture graphs
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 Some(executor) = executor.into_element() else {
// surface a node-level error instead of panicking
return node_error("render requires a GPU executor, none available in this environment");
};
let texture = executor.render_vello_scene(&transformed_scene, footprint.resolution, context, None).await?; Defensive patterns
Strategy: fallback
Validate before calling
// The render node's executor comes from the try_wgpu_executor scope slot; check it before evaluating
let executor: Option<&WgpuExecutor> = try_wgpu_executor_result.into_element();
if executor.is_none() {
// skip render-to-texture evaluation or substitute a CPU rasterization path
} Type guard
fn can_gpu_render(executor_slot: Option<&WgpuExecutor>) -> bool {
executor_slot.is_some()
} Try / catch
let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| render_node.evaluate(ctx).await));
if outcome.is_err() {
// no GPU executor (or render failed): report a node error and fall back to CPU/SVG rendering
} Prevention
- Check GPU availability once at startup and gate render-to-texture graphs on it
- Keep an SVG/CPU output path available for documents that must render without a GPU
- Sandbox graph evaluation so environment-dependent panics become node-level errors
When it happens
Trigger: A render node evaluated with RenderOutputTypeRequest::Vello in an environment where the try_wgpu_executor scope dependency produced None: no GPU adapter/driver, WebGPU disabled, or a host that never attached a GPU executor to ApplicationIo.
Common situations: Documents with render nodes opened on GPU-less machines; exporting renders from headless CLI tools; CI screenshot pipelines; browsers/OSes with WebGPU unavailable; after a device loss that reset the executor slot.
Related errors
- ApplicationIo not available
- GPU executor not available
- GPU executor not available
- GPU executor should be available when we receive a texture
- ApplicationIo must be available when using resources
AI-assisted analysis of GraphiteEditor/Graphite@c507b35645 (2026-08-16).
Data as JSON: /api/errors/1ceb343b8b382249.
Report an issue: GitHub.