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

  1. Run the evaluation in an environment that supplies a GPU executor: working adapter/driver, WebGPU enabled, or a software adapter fallback
  2. Check startup logs for why the try_wgpu_executor slot is None (adapter request failure, ApplicationIo without GPU executor)
  3. Replace the .expect with a graceful node error or a CPU rasterization fallback instead of a panic
  4. 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

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


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