From 380938c6023ab6db464751c108b7928a02f35b42 Mon Sep 17 00:00:00 2001 From: Viktor Date: Sat, 20 Jun 2020 23:29:54 +0200 Subject: [PATCH] gpu: cache quad splitting and transform Cache also CPU operations by moving pathCache into drawOps and use it in collectOps to avoid splitting and transformation of quads if in cache. In order to support this use a concrete type in opCache instead of interface. Signed-off-by: Viktor --- gpu/caches.go | 25 +++++++++++++++---------- gpu/gpu.go | 44 +++++++++++++++++++++++++++----------------- 2 files changed, 42 insertions(+), 27 deletions(-) diff --git a/gpu/caches.go b/gpu/caches.go index 14eb8f74..4217b040 100644 --- a/gpu/caches.go +++ b/gpu/caches.go @@ -5,6 +5,7 @@ package gpu import ( "fmt" + "gioui.org/f32" "gioui.org/internal/ops" ) @@ -13,11 +14,15 @@ type resourceCache struct { newRes map[interface{}]resource } -// opCache is like a resourceCache using the concrete Key -// key type to avoid allocations. +// opCache is like a resourceCache but using concrete types. type opCache struct { - res map[ops.Key]resource - newRes map[ops.Key]resource + res map[ops.Key]opCacheValue + newRes map[ops.Key]opCacheValue +} + +type opCacheValue struct { + data *pathData + bounds f32.Rectangle } func newResourceCache() *resourceCache { @@ -66,12 +71,12 @@ func (r *resourceCache) release() { func newOpCache() *opCache { return &opCache{ - res: make(map[ops.Key]resource), - newRes: make(map[ops.Key]resource), + res: make(map[ops.Key]opCacheValue), + newRes: make(map[ops.Key]opCacheValue), } } -func (r *opCache) get(key ops.Key) (resource, bool) { +func (r *opCache) get(key ops.Key) (opCacheValue, bool) { v, exists := r.res[key] if exists { r.newRes[key] = v @@ -79,7 +84,7 @@ func (r *opCache) get(key ops.Key) (resource, bool) { return v, exists } -func (r *opCache) put(key ops.Key, val resource) { +func (r *opCache) put(key ops.Key, val opCacheValue) { if _, exists := r.newRes[key]; exists { panic(fmt.Errorf("key exists, %#v", key)) } @@ -91,7 +96,7 @@ func (r *opCache) frame() { for k, v := range r.res { if _, exists := r.newRes[k]; !exists { delete(r.res, k) - v.release() + v.data.release() } } for k, v := range r.newRes { @@ -102,7 +107,7 @@ func (r *opCache) frame() { func (r *opCache) release() { for _, v := range r.newRes { - v.release() + v.data.release() } r.newRes = nil r.res = nil diff --git a/gpu/gpu.go b/gpu/gpu.go index 245e8f6e..b7d2388c 100644 --- a/gpu/gpu.go +++ b/gpu/gpu.go @@ -29,8 +29,7 @@ import ( ) type GPU struct { - pathCache *opCache - cache *resourceCache + cache *resourceCache defFBO backend.Framebuffer profile string @@ -65,6 +64,7 @@ type drawOps struct { pathOps []*pathOp pathOpCache []pathOp qs quadSplitter + pathCache *opCache uniqueKeyCounter int } @@ -86,6 +86,7 @@ type pathOp struct { // clip is the union of all // later clip rectangles. clip image.Rectangle + bounds f32.Rectangle pathKey ops.Key path bool pathVerts []byte @@ -277,10 +278,10 @@ const ( func New(ctx backend.Device) (*GPU, error) { defFBO := ctx.CurrentFramebuffer() g := &GPU{ - defFBO: defFBO, - pathCache: newOpCache(), - cache: newResourceCache(), + defFBO: defFBO, + cache: newResourceCache(), } + g.drawOps.pathCache = newOpCache() if err := g.init(ctx); err != nil { return nil, err } @@ -295,7 +296,7 @@ func (g *GPU) init(ctx backend.Device) error { func (g *GPU) Release() { g.renderer.release() - g.pathCache.release() + g.drawOps.pathCache.release() g.cache.release() if g.timers != nil { g.timers.release() @@ -316,9 +317,12 @@ func (g *GPU) Collect(viewport image.Point, frameOps *op.Ops) { g.cleanupTimer = g.timers.newTimer() } for _, p := range g.drawOps.pathOps { - if _, exists := g.pathCache.get(p.pathKey); !exists { + if _, exists := g.drawOps.pathCache.get(p.pathKey); !exists { data := buildPath(g.ctx, p.pathVerts) - g.pathCache.put(p.pathKey, data) + g.drawOps.pathCache.put(p.pathKey, opCacheValue{ + data: data, + bounds: p.bounds, + }) } p.pathVerts = nil } @@ -344,7 +348,7 @@ func (g *GPU) BeginFrame() { g.stencilTimer.begin() g.ctx.SetBlend(true) g.renderer.packStencils(&g.drawOps.pathOps) - g.renderer.stencilClips(g.pathCache, g.drawOps.pathOps) + g.renderer.stencilClips(g.drawOps.pathCache, g.drawOps.pathOps) g.renderer.packIntersections(g.drawOps.imageOps) g.renderer.intersect(g.drawOps.imageOps) g.stencilTimer.end() @@ -361,7 +365,7 @@ func (g *GPU) BeginFrame() { func (g *GPU) EndFrame() { g.cleanupTimer.begin() g.cache.frame() - g.pathCache.frame() + g.drawOps.pathCache.frame() g.cleanupTimer.end() if g.drawOps.profile && g.timers.ready() { zt, st, covt, cleant := g.zopsTimer.Elapsed, g.stencilTimer.Elapsed, g.coverTimer.Elapsed, g.cleanupTimer.Elapsed @@ -505,8 +509,8 @@ func (r *renderer) stencilClips(pathCache *opCache, ops []*pathOp) { r.ctx.BindFramebuffer(f.fbo) r.ctx.Clear(0.0, 0.0, 0.0, 0.0) } - data, _ := pathCache.get(p.pathKey) - r.pather.stencilPath(p.clip, p.place.Pos, data.(*pathData)) + v, _ := pathCache.get(p.pathKey) + r.pather.stencilPath(p.clip, p.place.Pos, v.data) } } @@ -681,10 +685,11 @@ func (d *drawOps) newPathOp() *pathOp { return &d.pathOpCache[len(d.pathOpCache)-1] } -func (d *drawOps) addClipPath(state *drawState, aux []byte, auxKey ops.Key) { +func (d *drawOps) addClipPath(state *drawState, aux []byte, auxKey ops.Key, bounds f32.Rectangle) { npath := d.newPathOp() *npath = pathOp{ parent: state.cpath, + bounds: bounds, } state.cpath = npath if len(aux) > 0 { @@ -724,9 +729,14 @@ loop: if len(aux) > 0 { // There is a clipping path, build the gpu data and update the // cache key such that it will be equal only if the transform is the - // same also. - aux, op.bounds = d.buildVerts(aux, state.t) + // same also. Use cached data if we have it. auxKey = auxKey.SetTransform(state.t) + if v, ok := d.pathCache.get(auxKey); ok { + // Since the GPU data exists in the cache aux will not be used. + op.bounds = v.bounds + } else { + aux, op.bounds = d.buildVerts(aux, state.t) + } } else { aux, op.bounds, _ = d.boundsForTransformedRect(bounds, state.t) auxKey = d.noCacheKey() @@ -736,7 +746,7 @@ loop: continue } - d.addClipPath(&state, aux, auxKey) + d.addClipPath(&state, aux, auxKey, op.bounds) aux = nil auxKey = ops.Key{} case opconst.TypeColor: @@ -760,7 +770,7 @@ loop: if clipData != nil { // The paint operation is sheared or rotated, add a clip path representing // this transformed rectangle. - d.addClipPath(&state, clipData, d.noCacheKey()) + d.addClipPath(&state, clipData, d.noCacheKey(), bnd) } bounds := boundRectF(clip)