This change adds a CPU fallback for devices that don't support the old
renderer nor have GPU support for compute programs.
Most of the hard work is implemented in the gioui.org/cpu module. It
uses the SwiftShader project with light modification to output
statically compiled CPU .o files for each compute program.
The CPU fallback only covers Linux and Android on arm, arm64, amd64
architectures. There is no fundamental reason support can't be extended
to other platforms:
- macOS and iOS are probably easy, but it's likely that virtually every
device has GPU support for compute shaders.
- Windows needs a Cgo-less port, or a build constraint to require a C
compiler (Gio core doesn't).
- FreeBSD and OpenBSD are probably also easy to do because they're so
similar to Linux.
- The 386 binaries didn't work properly in my tests, so fixes to
SwiftShader is probably needed. However, I expect virtually every
Intel device can run amd64 binaries.
Updates gio#49
Fixes gio#228
Signed-off-by: Elias Naur <mail@eliasnaur.com>
The hash of the clipping paths that affect drawing operations are computed
and used to quickly determine that two operations are not equal, the
most likely outcome of a comparison.
However, for paths that are constructed once and cached computing the
hash at every frame is wasteful. This is especially true for text, which
is both cached and also among the largest paths in a frame.
This change moves the hashing to op/clip.Path construction time, and
stores the hash in the ops list so it won't be re-computed at every use.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
To re-use previously cached layers, the compute renderer must know
whether two drawing operations are equal. In the case two operations are
not equal, a fast hash comparison will most likely fail. In the case two
equal operations with complicated clipping paths, the comparison of the
path data is expensive.
This change adds support for fast ops.Key comparisons, where two paths
are equal if their ops.Key are. This is an optimization that kicks in
for text rendering, where glyph clipping shapes are re-used across
frames.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
To re-use drawing operations common to two layers, every operation must
exactly match, including their transformations. However, layers that
differ only by an integer offset can be re-used because rendering does
not depend on the absolute integer offset. This is important in the very
common case of scrolling otherwise static UI content.
This change separate the integer offset from drawing operations and
relaxes the layer cache to match layers that differ only in integer
offsets.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
The compute renderer is more expensive to run than the old renderer on
low-end GPUs, and even more so on CPUs. To ensure good performance
regardless of the end-user device, this change implements automatic
re-use of content rendered in the frame before the current.
The basic idea is that every drawing operation (PaintOp), along with its
transform and clipping, can be hashed and efficiently looked up. A naïve
caching approach is then to rasterize every operation to separate
sections of several large texture atlases, turning a cache hit into a
very cheap texture copy.
However, for scenes with lots of overlapping operations, the resulting
texture memory from separating the operations would be much larger than
the memory for just the window framebuffer.
So instead of caching individual operations, this change caches layers,
which are sequences of drawing operations. It starts by putting all
operations into a single layer. Then, if the subsequent frame re-uses a
sub-sequence of that larger layer, it is split.
For example, consider a UI similar to the kitchen sample:
Hello, Gio
<Editor>
<Line Editor>
<Button> <Button> <Button>
<ProgressBar>
<Checkbox> <Toggle>
In the first frame, all of the drawing operations comprising the UI will
be stored and cached in a single layer. In the second frame the
progress bar will have moved and the renderer splits the UI into three
layers: layer A for everything up to (but not including) the progress
bar, layer B with just the progress bar, and layer C for the rest. Note
that nothing has been re-used yet. In the third frame, the progress bar
moves again, and this time layer A and C can be copied from the cache
only the progress bar needs redrawing through the compute programs.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
The performance difference is negligible, but is useful when the compute
pipeline can skip rendering to empty tiles.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
Until now, the two renderers have shared structures and code for
decoding drawing ops and convert them to GPU-friendly structures.
However, the decoder is tailored to the old renderer and use
structures that poorly fits the new compute renderer.
This change copies the decoder and specializes the copy for the compute
renderer, avoiding a round-trip through the old renderer decoder.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
Programs such as gio-example/glfw rely on Gio drawing blending with
the framebuffer background. This change makes it so when sRGB emulation
is active.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
Collect is for converting ops to GPU commands, Frame is for actual
rendering. There's little practical difference, but makes profiling
easier to distinguish between conversion and rendering.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
GPU operations logically belong in the Frame method, and it's probably
best to keep them inside BeginFrame/EndFrame as well.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
Before this change, the two renderers both had special case code for
approximating strokes they don't support natively. This change moves
that conversion to clip.Op.Add, for several reasons:
- The compute renderer no longer need fallback logic and caches for
strokes it doesn't support.
- The approximation logic is slow. Moving it to clip.Op.Add will not
speed it up, but will make the cost easier to spot in profiles. Until all
strokes are supported natively, users can use macros to cache
expensive strokes.
- Reduced garbage: Op.Add takes an op.Ops anyway, and can use that for
storing the approximated stroke outline.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
To avoid an import cycle in a future change, internal/stroke can no
longer import op/clip. Move required op/clip functionality to
internal/stroke and duplicate the remaining types.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
Complex strokes are not yet supported in either of the current renderers,
so they are converted to filled outlines in package gpu.
We're about to move that complexity up to the op/clip package, so we're
going to need the converter available from outside package gpu. This
change extracts the conversion code and related types to the separate,
internal package stroke.
No functional changes; a follow-up moves the stroke conversion.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
In the old renderer, all strokes are converted to filled paths. The new
renderer can draw simple strokes natively. Do that, and avoid the costly
conversions.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
The fill mode is now controlled by a SetFillMode command, not by flags
on each path segment and fill command.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
Otherwise the padding we leave around rendered materials may contain
content from reclaimed materials.
Fixes icon "shimmering" when the kitchen example is transforming.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
Since clip.Path now encodes paths in the format expected by
elements.comp, use that data directly instead of a roundtrip through
drawOps.buildVerts.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
We're about to encode clip.Paths with the format compatible with the
compute renderer. This change extracts the encoding to a re-usable
package.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
All functions left in the old package unsafe were provided byte slice
views of other types. Rename the package accordingly and avoid a name
clash with the standard library package unsafe.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
This change tracks materials so that only the updated materials needs to
be rendered.
Materials are likely cheap to render each frame, at least compared to
the rest of the compute pipeline. However, the CPU fallback must
transfer all changed materials to CPU memory, and a cache is a great
improvement over fetching all materials every frame.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
We're about to cache the transformed materials. It's easier to do when
quads can be constructed before determining their atlas position.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
There are no longer any importers of package backend outside of
gioui.org/gpu. Move it internally, and rename it to the slightly more
specific "driver" while we're at it.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
Return the output framebuffer from BeginFrame, to make it clear that
it may change between frames. Delete CurrentFramebuffer which is no
longer needed.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
We're about to change the last stage of the compute pipeline to only
accept images, not sampled textures. This change prepares materials
for pixel-aligned image copying by pre-rendering images to a texture,
applying transforms.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
Gio UI may be overlaid on top of custom graphics such as in the glfw example.
That will only work if Gio doesn't clear the screen (to white).
Signed-off-by: Elias Naur <mail@eliasnaur.com>
The old renderer is still the default, so the new compute renderer will only be
used in the rare case the old renderer is not supported but the new is. That
happens on the Samsung J2 Prime and Moto C Android phones. Or set the
GIORENDERER environment variable to "forcecompute" to disable the old renderer:
$ GIORENDERER=forcecompute go run ...
Missing features:
- Gradients are not supported yet, and render as a solid color.
- Draw timers are not added, and profile.Events are not emitted.
- Stroked paths may in some cases appear corrupted because their clip
outlines are not continuous when generated by Gio. Sebastien is
working on a fix.
- The new renderer shares most CPU-side logic with the old renderer,
resulting in several inefficient conversion steps between the old
operations representation and the new. This is slower, but minimizes
divergence in features and bugs between the two renderers.
Roadmap:
- The compute renderer supports features that Gio does not yet
exploit: stroked paths with round caps, transformations, lines,
cubic beziér curves.
- More stroke styles and maybe dashed strokes natively in shaders.
- Metal and Direct3D ports.
The most important feature is porting the renderer to run on the CPU. A
CPU renderer will both support Gio on devices with insufficient GPU
support, and allow us to remove the old renderer. Two renderers is twice
the maintenance but the feature set of the weakest implementation.
Signed-off-by: Elias Naur <mail@eliasnaur.com>