GraphiteEditor/Graphite · critical

GPU executor should be available when we receive a texture

Error message

GPU executor should be available when we receive a texture

What it means

Graphite's node graph runtime asserts an invariant when an export render (`render_config.for_export`) returns a GPU-backed `RenderOutputType::Texture`: the texture must be read back to CPU via `Raster::new_gpu(texture).convert(.., executor)`, which needs the `WgpuExecutor` held by `PlatformApplicationIo`. The `.expect` panics when a texture was produced but `gpu_executor()` returned `None`. The runtime already downgrades raster exports to the SVG pipeline when `gpu_executor().is_none()` (runtime.rs:234-238), so hitting this expect means that guard was bypassed or the graph emitted a texture anyway.

Source

Thrown at editor/src/node_graph_executor/runtime.rs:262

						self.process_monitor_nodes(&mut responses, self.update_thumbnails);
					}
					self.update_thumbnails = false;

					// Resolve the result from the inspection by accessing the monitor node
					let inspect_result = self.inspect_state.as_ref().and_then(|state| state.access(&self.executor));

					let (result, texture) = match result {
						Ok(TaggedValue::RenderOutput(RenderOutput {
							data: RenderOutputType::Texture(texture),
							metadata,
						})) if render_config.for_export => {
							let executor = self
								.editor_api
								.application_io
								.as_ref()
								.unwrap()
								.gpu_executor()
								.expect("GPU executor should be available when we receive a texture");

							let raster_cpu = Raster::new_gpu(texture).convert(Footprint::BOUNDLESS, executor).await;

							let (data, width, height) = raster_cpu.to_flat_u8();

							(
								Ok(TaggedValue::RenderOutput(RenderOutput {
									data: RenderOutputType::Buffer { data, width, height },
									metadata,
								})),
								None,
							)
						}
						Ok(TaggedValue::RenderOutput(RenderOutput {
							data: RenderOutputType::Texture(texture),
							metadata: _,
						})) if render_config.for_eyedropper => {
							let executor = self

View on GitHub (pinned to c507b35645)

Solutions

  1. Verify WebGPU is actually available before requesting a raster export: in the embedding page check `navigator.gpu` and `await navigator.gpu.requestAdapter() !== null`; if absent, keep the SVG fallback path (RenderMode::SvgPreview) that runtime.rs:234 already selects.
  2. If you build the wrapper yourself, confirm the `wgpu` (and on wasm, `gpu`) cargo features are enabled for every crate in the workspace so `PlatformApplicationIo` actually stores an executor.
  3. If you control the runtime, replace the `.expect` with a `let ... else` that logs and returns an error `ExecutionResponse` so the editor reports a failed export instead of panicking.
  4. Ensure `PlatformEditorApi.application_io` is always the instance created by `PlatformApplicationIo::new().await`, never `Default::default()`.

Example fix

// before
let executor = self
	.editor_api
	.application_io
	.as_ref()
	.unwrap()
	.gpu_executor()
	.expect("GPU executor should be available when we receive a texture");
let raster_cpu = Raster::new_gpu(texture).convert(Footprint::BOUNDLESS, executor).await;

// after
let Some(executor) = self.editor_api.application_io.as_ref().unwrap().gpu_executor() else {
  error!("Export produced a texture but no GPU executor exists; failing export gracefully");
  self.sender.send_execution_response(ExecutionResponse {
    execution_id,
    result: Err("GPU unavailable for texture export".to_string()),
    responses: VecDeque::new(),
    vector_modify: Default::default(),
    inspect_result: None,
  });
  continue;
};
let raster_cpu = Raster::new_gpu(texture).convert(Footprint::BOUNDLESS, executor).await;
Defensive patterns

Strategy: validation

Validate before calling

// TS: gate GPU-dependent export before invoking the wrapper
export async function webGpuAvailable(): Promise<boolean> {
  if (!('gpu' in navigator)) return false;
  try {
    const adapter = await navigator.gpu.requestAdapter();
    return adapter !== null;
  } catch {
    return false;
  }
}
// Enable raster export only when webGpuAvailable() is true; otherwise keep the SVG fallback path.

Try / catch

Wasm panics cannot be caught from JS. After any export attempt, check `await editor.hasCrashed()`; if true, surface an error dialog and reinitialize the editor rather than issuing further commands.

Prevention

When it happens

Trigger: Sending an `ExecutionRequest` with `for_export: true` and `ExportFormat::Raster` on a build where `PlatformApplicationIo::new()` failed to create a `WgpuExecutor` (`WgpuExecutor::new()` returns None when `navigator.gpu` is missing, adapter/device request fails, or the `wgpu` cargo feature is compiled out, in which case `PlatformApplicationIo::default()` has no executor at all); or a graph that returns `RenderOutputType::Texture` from a cached/stubbed texture without live GPU execution.

Common situations: Running the wasm editor in a browser without WebGPU (older Firefox/Safari), on a GPU blocklisted by wgpu, or inside a Web Worker where `navigator.gpu` is unavailable; embedding the wrapper and constructing `PlatformEditorApi` yourself with `PlatformApplicationIo::default()`; mismatched feature flags where the `wgpu` feature is off but texture-producing nodes still execute.

Related errors


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