Conversation
The GPU tiers paint every draw in one colour, read from the brush at (0, 0), so a linear gradient handed to them rendered as a solid block of its first stop: a colour bar came out as one colour. tryGPUFill and tryGPUStroke now return ErrFallbackToCPU when the paint side they draw is not a single colour. The CPU samples the brush per pixel, and doFill/doStroke flush pending GPU work before it draws, so draw order is kept. Solid draws still go to the GPU.
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.
Problem
With a GPU accelerator registered, a gradient fill comes out as one flat colour: the gradient's first stop.
The GPU tiers paint every draw in a single colour.
getFillColorFromPaint/getStrokeColorFromPaint(ininternal/gpu/sdf_gpu.go) take the inline solid colour, and for any other brush they fall back toColorAt(0, 0). So aLinearGradientBrushhanded toFillShape,FillPath,StrokeShapeorStrokePathis painted as one solid block. Nothing intryGPUFillortryGPUStrokechecks the brush before the accelerator gets it.Fix
tryGPUFillandtryGPUStrokenow returnErrFallbackToCPUwhen the paint side they draw is not a single colour. The CPU rasterizer samples the brush per pixel.doFillanddoStrokealready flush pending GPU work before a CPU draw, so draw order is kept.Solid draws are unaffected. That covers an inline solid colour, a
SolidBrush, and the default brush.Minimal example
In the wild: figure
We hit this in figure, a grammar-of-graphics plotting library for Go that renders through gg. On the GPU tier, its colour bars are drawn as gradient fills and came out as a solid block of the ramp's first colour.
The chart below is a heatmap of a device's gain with isolines, rendered with figure's GPU tier (
github.com/timzifer/figure/backend/gg/gpu):figure builds against our fork (timzifer/gg, tag
v0.52.6-figure.3) until this lands upstream.Tests
New
gradient_gpu_fallback_test.goregisters an accelerator that claims every operation, counts what reaches it and paints nothing:TestGradientFillsAreLeftToTheCPU: a gradient fill must not reach the accelerator, and the painted pixels must actually go from red to blue. Without the fix, the fill reaches the accelerator and nothing is painted.TestGradientStrokesAreLeftToTheCPU: the same rule for strokes.TestSolidFillsStillReachTheGPU: solid fills still go to the accelerator.go test -race ./...andgolangci-lint run --timeout=5mpass (Windows amd64, Go 1.25.3).Possible follow-up
Real gradient support in the GPU tiers (per-vertex or per-fragment colour) would keep these draws on the GPU. This PR only makes them correct.