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 = selfView on GitHub (pinned to c507b35645)
Solutions
- 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.
- 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.
- 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.
- 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
- Probe navigator.gpu + requestAdapter() at boot and remember the result; branch UI on it.
- Keep the wgpu/gpu cargo features consistent with the browsers you deploy to.
- Never construct PlatformEditorApi with a Default application_io in embedders.
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
- ApplicationIo not available
- GPU executor not available
- GPU executor not available
- GPU executor not available
- Buffer mapping communication failed
AI-assisted analysis of GraphiteEditor/Graphite@c507b35645 (2026-08-16).
Data as JSON: /api/errors/bc371d799cf44ee9.
Report an issue: GitHub.