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 <viktor.ogeman@gmail.com>
This commit is contained in:
Viktor
2020-06-20 23:29:54 +02:00
committed by Elias Naur
parent e3bb94ebb0
commit 380938c602
2 changed files with 42 additions and 27 deletions
+15 -10
View File
@@ -5,6 +5,7 @@ package gpu
import ( import (
"fmt" "fmt"
"gioui.org/f32"
"gioui.org/internal/ops" "gioui.org/internal/ops"
) )
@@ -13,11 +14,15 @@ type resourceCache struct {
newRes map[interface{}]resource newRes map[interface{}]resource
} }
// opCache is like a resourceCache using the concrete Key // opCache is like a resourceCache but using concrete types.
// key type to avoid allocations.
type opCache struct { type opCache struct {
res map[ops.Key]resource res map[ops.Key]opCacheValue
newRes map[ops.Key]resource newRes map[ops.Key]opCacheValue
}
type opCacheValue struct {
data *pathData
bounds f32.Rectangle
} }
func newResourceCache() *resourceCache { func newResourceCache() *resourceCache {
@@ -66,12 +71,12 @@ func (r *resourceCache) release() {
func newOpCache() *opCache { func newOpCache() *opCache {
return &opCache{ return &opCache{
res: make(map[ops.Key]resource), res: make(map[ops.Key]opCacheValue),
newRes: make(map[ops.Key]resource), 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] v, exists := r.res[key]
if exists { if exists {
r.newRes[key] = v r.newRes[key] = v
@@ -79,7 +84,7 @@ func (r *opCache) get(key ops.Key) (resource, bool) {
return v, exists 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 { if _, exists := r.newRes[key]; exists {
panic(fmt.Errorf("key exists, %#v", key)) panic(fmt.Errorf("key exists, %#v", key))
} }
@@ -91,7 +96,7 @@ func (r *opCache) frame() {
for k, v := range r.res { for k, v := range r.res {
if _, exists := r.newRes[k]; !exists { if _, exists := r.newRes[k]; !exists {
delete(r.res, k) delete(r.res, k)
v.release() v.data.release()
} }
} }
for k, v := range r.newRes { for k, v := range r.newRes {
@@ -102,7 +107,7 @@ func (r *opCache) frame() {
func (r *opCache) release() { func (r *opCache) release() {
for _, v := range r.newRes { for _, v := range r.newRes {
v.release() v.data.release()
} }
r.newRes = nil r.newRes = nil
r.res = nil r.res = nil
+24 -14
View File
@@ -29,7 +29,6 @@ import (
) )
type GPU struct { type GPU struct {
pathCache *opCache
cache *resourceCache cache *resourceCache
defFBO backend.Framebuffer defFBO backend.Framebuffer
@@ -65,6 +64,7 @@ type drawOps struct {
pathOps []*pathOp pathOps []*pathOp
pathOpCache []pathOp pathOpCache []pathOp
qs quadSplitter qs quadSplitter
pathCache *opCache
uniqueKeyCounter int uniqueKeyCounter int
} }
@@ -86,6 +86,7 @@ type pathOp struct {
// clip is the union of all // clip is the union of all
// later clip rectangles. // later clip rectangles.
clip image.Rectangle clip image.Rectangle
bounds f32.Rectangle
pathKey ops.Key pathKey ops.Key
path bool path bool
pathVerts []byte pathVerts []byte
@@ -278,9 +279,9 @@ func New(ctx backend.Device) (*GPU, error) {
defFBO := ctx.CurrentFramebuffer() defFBO := ctx.CurrentFramebuffer()
g := &GPU{ g := &GPU{
defFBO: defFBO, defFBO: defFBO,
pathCache: newOpCache(),
cache: newResourceCache(), cache: newResourceCache(),
} }
g.drawOps.pathCache = newOpCache()
if err := g.init(ctx); err != nil { if err := g.init(ctx); err != nil {
return nil, err return nil, err
} }
@@ -295,7 +296,7 @@ func (g *GPU) init(ctx backend.Device) error {
func (g *GPU) Release() { func (g *GPU) Release() {
g.renderer.release() g.renderer.release()
g.pathCache.release() g.drawOps.pathCache.release()
g.cache.release() g.cache.release()
if g.timers != nil { if g.timers != nil {
g.timers.release() g.timers.release()
@@ -316,9 +317,12 @@ func (g *GPU) Collect(viewport image.Point, frameOps *op.Ops) {
g.cleanupTimer = g.timers.newTimer() g.cleanupTimer = g.timers.newTimer()
} }
for _, p := range g.drawOps.pathOps { 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) 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 p.pathVerts = nil
} }
@@ -344,7 +348,7 @@ func (g *GPU) BeginFrame() {
g.stencilTimer.begin() g.stencilTimer.begin()
g.ctx.SetBlend(true) g.ctx.SetBlend(true)
g.renderer.packStencils(&g.drawOps.pathOps) 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.packIntersections(g.drawOps.imageOps)
g.renderer.intersect(g.drawOps.imageOps) g.renderer.intersect(g.drawOps.imageOps)
g.stencilTimer.end() g.stencilTimer.end()
@@ -361,7 +365,7 @@ func (g *GPU) BeginFrame() {
func (g *GPU) EndFrame() { func (g *GPU) EndFrame() {
g.cleanupTimer.begin() g.cleanupTimer.begin()
g.cache.frame() g.cache.frame()
g.pathCache.frame() g.drawOps.pathCache.frame()
g.cleanupTimer.end() g.cleanupTimer.end()
if g.drawOps.profile && g.timers.ready() { if g.drawOps.profile && g.timers.ready() {
zt, st, covt, cleant := g.zopsTimer.Elapsed, g.stencilTimer.Elapsed, g.coverTimer.Elapsed, g.cleanupTimer.Elapsed 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.BindFramebuffer(f.fbo)
r.ctx.Clear(0.0, 0.0, 0.0, 0.0) r.ctx.Clear(0.0, 0.0, 0.0, 0.0)
} }
data, _ := pathCache.get(p.pathKey) v, _ := pathCache.get(p.pathKey)
r.pather.stencilPath(p.clip, p.place.Pos, data.(*pathData)) 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] 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 := d.newPathOp()
*npath = pathOp{ *npath = pathOp{
parent: state.cpath, parent: state.cpath,
bounds: bounds,
} }
state.cpath = npath state.cpath = npath
if len(aux) > 0 { if len(aux) > 0 {
@@ -724,9 +729,14 @@ loop:
if len(aux) > 0 { if len(aux) > 0 {
// There is a clipping path, build the gpu data and update the // 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 // cache key such that it will be equal only if the transform is the
// same also. // same also. Use cached data if we have it.
aux, op.bounds = d.buildVerts(aux, state.t)
auxKey = auxKey.SetTransform(state.t) 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 { } else {
aux, op.bounds, _ = d.boundsForTransformedRect(bounds, state.t) aux, op.bounds, _ = d.boundsForTransformedRect(bounds, state.t)
auxKey = d.noCacheKey() auxKey = d.noCacheKey()
@@ -736,7 +746,7 @@ loop:
continue continue
} }
d.addClipPath(&state, aux, auxKey) d.addClipPath(&state, aux, auxKey, op.bounds)
aux = nil aux = nil
auxKey = ops.Key{} auxKey = ops.Key{}
case opconst.TypeColor: case opconst.TypeColor:
@@ -760,7 +770,7 @@ loop:
if clipData != nil { if clipData != nil {
// The paint operation is sheared or rotated, add a clip path representing // The paint operation is sheared or rotated, add a clip path representing
// this transformed rectangle. // this transformed rectangle.
d.addClipPath(&state, clipData, d.noCacheKey()) d.addClipPath(&state, clipData, d.noCacheKey(), bnd)
} }
bounds := boundRectF(clip) bounds := boundRectF(clip)