Conversation
FillPath, StrokePath, FillShape and StrokeShape queued a draw command without establishing that a device could be had. Context.doFill and doStroke read the nil return as "the GPU has this" and skip the software rasterizer, so the failure only surfaced at Flush, where ensureGPU runs — by which time the pending draws had nowhere to go and were dropped. On a machine with no adapter the result was silent geometry loss: every filled and stroked path vanished while text still rendered, because DrawText and DrawGlyphMaskText make this check before they queue. What came out was a valid image with nothing in it but the labels, and no error anywhere: Stroke had already returned nil. The four queueing entry points now make the same check. It asks for deviceReady rather than gpuReady, because under strategyRasterAtlas the shape pipelines are deliberately absent and Flush dispatches the queue on the CPU — a queued draw still reaches the buffer there. It sits after the compute-mode delegation, which routes to VelloAccelerator and answers for its own readiness. The test is the property the gpu package documents — with the accelerator registered, a chart still renders — asserted on a stroked path. It fails against the old code on any machine without an adapter, and passes on hardware, where the draw goes to the GPU and comes back. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qry8YtUUPEPY5fiaAK5QWu
This was referenced Sep 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
With the GPU accelerator registered and no usable adapter present, every filled and stroked path is silently dropped. Text still renders, so the output is a valid image with the labels in it and none of the geometry — and no error is returned anywhere.
FillPath,StrokePath,FillShapeandStrokeShapequeue a draw command without establishing that a device can be had.Context.doFillanddoStrokeread thenilreturn as "the GPU has this" and skip the software rasterizer. The failure only surfaces atFlush, whereensureGPU()runs, fails withrequest adapter: no adapters available, logs a warning and returnsErrFallbackToCPU— by which point the pending draws have nowhere to go.DrawTextandDrawGlyphMaskTextalready make this check before they queue (gpu_render_context.go:546,:583), which is why text is unaffected and why the symptom looks like a rasterizer artefact rather than a missing device.Minimal reproduction, no external dependencies:
Without the
gpuimport, or aftergg.CloseAccelerator(), the same code draws the line.Changes
internal/gpu:FillPath,StrokePath,FillShapeandStrokeShapecheck for a device before queueing, returningErrFallbackToCPUwhen there is none, so the caller's CPU path runs.internal/gpu: newdeviceUsable()helper — the checkDrawTextmakes inline, hoisted.deviceReady, notgpuReady. UnderstrategyRasterAtlasthe shape pipelines are deliberately absent while a device exists andFlushdispatches the queue on the CPU, so a queued draw does reach the buffer there. Guarding ongpuReadybreaksTestFlushCPU_TempPixmapDimensionsandTestFlushCPUToView_WithNoop.VelloAcceleratorand answers for its own readiness throughCanCompute(). Guarding before it breaksTestStrokeRouting_ComputeModeUsesVello.gpu/fallback_test.go: regression test.No public API change. No behaviour change on a machine with a working adapter:
deviceReadyis already true there, so the check is a field read.Testing
The test asserts the property the
gpupackage documents — "If GPU initialization fails, the registration is silently skipped and rendering falls back to CPU" — on a stroked path: with the accelerator registered, the stroke has to be in the buffer. It holds on hardware too, where the draw goes to the GPU and comes back.Against the current code on a machine with no adapter:
With the fix, and across the repository:
Found while rendering charts through a plotting library that offers this package as an opt-in tier; every chart came out with its axis labels and no axes.
Environment: Windows 11 x64, Go 1.27.0, branched off
mainat bd563f4.Checklist
go test -race ./...)golangci-lint run) — could not run locally: golangci-lint v2.12.2 is built with Go 1.25 and panics on this tree withfile requires newer Go version go1.27. Left to CI.go fmt ./...— changed files only; a repository-widego fmtrewrites every file here because of CRLF checkout, so that was reverted)gpu/gpu.go's package comment already promises