{"record":{"id":"e8bdf8c4403dfff4","repo":"GraphiteEditor/Graphite","slug":"gpu-executor-not-available-e8bdf8","errorCode":null,"errorMessage":"GPU executor not available","messagePattern":"GPU executor not available","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"node-graph/nodes/gstd/src/render_cache.rs","lineNumber":393,"sourceCode":"\tfor missing_region in &cache_query.missing_regions {\n\t\tif missing_region.tiles.is_empty() {\n\t\t\tcontinue;\n\t\t}\n\t\tlet region = render_missing_region(missing_region, |ctx| data.eval(ctx), ctx.clone(), render_params, &footprint.transform, &device_origin_offset).await;\n\t\tnew_regions.push(region);\n\t}\n\n\ttile_cache.store_regions(new_regions.clone());\n\n\tlet all_regions: Vec<_> = cache_query.cached_regions.into_iter().chain(new_regions).collect();\n\n\t// If no regions, fall back to direct render\n\tif all_regions.is_empty() {\n\t\tlet context = OwnedContextImpl::from(ctx.clone()).with_footprint(*footprint).with_vararg(Box::new(render_params.clone()));\n\t\treturn data.eval(context.into_context()).await;\n\t}\n\n\tlet executor = executor.into_element().expect(\"GPU executor not available\");\n\tlet output_texture = executor.request_texture(physical_resolution).await;\n\n\tlet combined_metadata = composite_cached_regions(&all_regions, &output_texture, &device_origin_offset, &footprint.transform, executor);\n\n\tItem::new_from_element(RenderOutput {\n\t\tdata: RenderOutputType::Texture(output_texture),\n\t\tmetadata: combined_metadata,\n\t})\n}\n\nasync fn render_missing_region<F, Fut>(\n\tregion: &RenderRegion,\n\trender_fn: F,\n\tctx: impl Ctx + ExtractAll + CloneVarArgs,\n\trender_params: &RenderParams,\n\tviewport_transform: &DAffine2,\n\tviewport_origin_offset: &DVec2,\n) -> CachedRegion","sourceCodeStart":375,"sourceCodeEnd":411,"githubUrl":"https://github.com/GraphiteEditor/Graphite/blob/c507b356453361e31638b8bff8f6d46b6da2961e/node-graph/nodes/gstd/src/render_cache.rs#L375-L411","documentation":"The tiled render-cache node composites cached/new render regions into a GPU output texture via executor.request_texture(...) and composite_cached_regions(...); executor.into_element().expect(\"GPU executor not available\") panics when the scope-provided executor slot (an Option, same as the render node) is None. Notably the function already has a direct-render fallback for the no-regions and zero-size cases, but the tiled compositing path unconditionally requires the GPU, so a missing executor aborts evaluation as soon as there is anything to composite.","triggerScenarios":"Tiled/cached rendering with at least one region to composite in an environment whose executor scope slot is None (no GPU adapter, GPU initialization failed, headless host); reaching line 393 after render_missing_region() and tile_cache.store_regions() have produced regions.","commonSituations":"Documents with render caching enabled opened on GPU-less or driver-broken machines; CI screenshot tests of cached documents; sessions where the wgpu device was lost and the executor slot became None; exports after the platform degraded to CPU.","solutions":["Reuse the existing direct-render fallback when the executor is absent instead of unwrapping it (see exampleFix)","Ensure the environment supplies a GPU executor: working adapter/driver, WebGPU enabled, or a software adapter","Detect absence up front with the try_wgpu_executor Option and route the whole render through the non-cached path before evaluating","Propagate an evaluation error instead of .expect so hosts can degrade to CPU rendering"],"exampleFix":"// before\nlet executor = executor.into_element().expect(\"GPU executor not available\");\nlet output_texture = executor.request_texture(physical_resolution).await;\n\n// after: reuse the existing direct-render fallback when no GPU executor is in scope\nlet Some(executor) = executor.into_element() else {\n    let context = OwnedContextImpl::from(ctx.clone()).with_footprint(*footprint).with_vararg(Box::new(render_params.clone()));\n    return data.eval(context.into_context()).await;\n};\nlet output_texture = executor.request_texture(physical_resolution).await;","handlingStrategy":"fallback","validationCode":"// The cache node's executor is the same Optional scope slot; check before evaluating cached renders\nlet executor: Option<&WgpuExecutor> = executor_slot.into_element();\nif executor.is_none() {\n    // evaluate through the direct (non-cached) render path instead\n}","typeGuard":"fn can_composite_gpu(executor_slot: Option<&WgpuExecutor>) -> bool {\n    executor_slot.is_some()\n}","tryCatchPattern":"let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| render_output_cache.evaluate(ctx).await));\nif outcome.is_err() {\n    // no GPU executor for compositing: re-evaluate through the direct render path and report the degraded mode\n}","preventionTips":["When GPU availability is uncertain, evaluate renders through the direct path rather than the tiled cache path","Detect a missing executor up front via try_wgpu_executor and skip cache compositing entirely","After any GPU device loss, reset cached render state so cached paths do not run without an executor","Sandbox evaluation so this panic becomes a node error with a CPU fallback"],"tags":["rust","graphite","node-graph","wgpu","render-cache","panic"],"backgroundTag":"gpu-adapter-unavailable","analyzedSha":"c507b356453361e31638b8bff8f6d46b6da2961e","analyzedAt":"2026-08-16T21:57:18.596Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}