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)