app,app/internal/gpu: process frame in parallel

A frame ops buffers is tracersed twice: once for GPU commands,
and once for everything else. There is already a GPU goroutine for
concurrentl issuing of GPU commands; move the frame ops processing
to the goroutine as well. Both frame ops passed can then proceed
in parallel.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2019-11-08 13:50:49 +01:00
parent 1678f922e7
commit 1eaa5dd15e
2 changed files with 33 additions and 25 deletions
+30 -24
View File
@@ -37,13 +37,12 @@ type GPU struct {
ack chan struct{} ack chan struct{}
stop chan struct{} stop chan struct{}
stopped chan struct{} stopped chan struct{}
ops drawOps
} }
type frame struct { type frame struct {
collectStats bool collectStats bool
viewport image.Point viewport image.Point
ops drawOps ops *op.Ops
} }
type frameResult struct { type frameResult struct {
@@ -246,11 +245,13 @@ func NewGPU(ctx gl.Context) (*GPU, error) {
results: make(chan frameResult), results: make(chan frameResult),
refresh: make(chan struct{}), refresh: make(chan struct{}),
refreshErr: make(chan error), refreshErr: make(chan error),
ack: make(chan struct{}), // Ack is buffered so GPU commands can be issued after
stop: make(chan struct{}), // ack'ing the frame.
stopped: make(chan struct{}), ack: make(chan struct{}, 1),
pathCache: newOpCache(), stop: make(chan struct{}),
cache: newResourceCache(), stopped: make(chan struct{}),
pathCache: newOpCache(),
cache: newResourceCache(),
} }
if err := g.renderLoop(ctx); err != nil { if err := g.renderLoop(ctx); err != nil {
return nil, err return nil, err
@@ -284,12 +285,15 @@ func (g *GPU) renderLoop(glctx gl.Context) error {
var timers *timers var timers *timers
var zopsTimer, stencilTimer, coverTimer, cleanupTimer *timer var zopsTimer, stencilTimer, coverTimer, cleanupTimer *timer
initErr <- nil initErr <- nil
var drawOps drawOps
loop: loop:
for { for {
select { select {
case <-g.refresh: case <-g.refresh:
g.refreshErr <- glctx.MakeCurrent() g.refreshErr <- glctx.MakeCurrent()
case frame := <-g.frames: case frame := <-g.frames:
drawOps.reset(g.cache, frame.viewport)
drawOps.collect(g.cache, frame.ops, frame.viewport)
glctx.Lock() glctx.Lock()
frameStart := time.Now() frameStart := time.Now()
if frame.collectStats && timers == nil && ctx.caps.EXT_disjoint_timer_query { if frame.collectStats && timers == nil && ctx.caps.EXT_disjoint_timer_query {
@@ -300,41 +304,42 @@ func (g *GPU) renderLoop(glctx gl.Context) error {
cleanupTimer = timers.newTimer() cleanupTimer = timers.newTimer()
defer timers.release() defer timers.release()
} }
ops := frame.ops // Upload path data to GPU before ack'ing the frame
// Upload path data to GPU before ack'ing the frame data for re-use. // ops for re-use.
for _, p := range ops.pathOps { for _, p := range drawOps.pathOps {
if _, exists := g.pathCache.get(p.pathKey); !exists { if _, exists := g.pathCache.get(p.pathKey); !exists {
data := buildPath(r.ctx, p.pathVerts) data := buildPath(r.ctx, p.pathVerts)
g.pathCache.put(p.pathKey, data) g.pathCache.put(p.pathKey, data)
} }
p.pathVerts = nil p.pathVerts = nil
} }
// Signal that we're done with the frame ops.
g.ack <- struct{}{} g.ack <- struct{}{}
r.blitter.viewport = frame.viewport r.blitter.viewport = frame.viewport
r.pather.viewport = frame.viewport r.pather.viewport = frame.viewport
for _, img := range ops.imageOps { for _, img := range drawOps.imageOps {
expandPathOp(img.path, img.clip) expandPathOp(img.path, img.clip)
} }
if frame.collectStats { if frame.collectStats {
zopsTimer.begin() zopsTimer.begin()
} }
ctx.DepthFunc(gl.GREATER) ctx.DepthFunc(gl.GREATER)
ctx.ClearColor(ops.clearColor[0], ops.clearColor[1], ops.clearColor[2], 1.0) ctx.ClearColor(drawOps.clearColor[0], drawOps.clearColor[1], drawOps.clearColor[2], 1.0)
ctx.ClearDepthf(0.0) ctx.ClearDepthf(0.0)
ctx.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) ctx.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
ctx.Viewport(0, 0, frame.viewport.X, frame.viewport.Y) ctx.Viewport(0, 0, frame.viewport.X, frame.viewport.Y)
r.drawZOps(ops.zimageOps) r.drawZOps(drawOps.zimageOps)
zopsTimer.end() zopsTimer.end()
stencilTimer.begin() stencilTimer.begin()
ctx.Enable(gl.BLEND) ctx.Enable(gl.BLEND)
r.packStencils(&ops.pathOps) r.packStencils(&drawOps.pathOps)
r.stencilClips(g.pathCache, ops.pathOps) r.stencilClips(g.pathCache, drawOps.pathOps)
r.packIntersections(ops.imageOps) r.packIntersections(drawOps.imageOps)
r.intersect(ops.imageOps) r.intersect(drawOps.imageOps)
stencilTimer.end() stencilTimer.end()
coverTimer.begin() coverTimer.begin()
ctx.Viewport(0, 0, frame.viewport.X, frame.viewport.Y) ctx.Viewport(0, 0, frame.viewport.X, frame.viewport.Y)
r.drawOps(ops.imageOps) r.drawOps(drawOps.imageOps)
ctx.Disable(gl.BLEND) ctx.Disable(gl.BLEND)
r.pather.stenciler.invalidateFBO() r.pather.stenciler.invalidateFBO()
coverTimer.end() coverTimer.end()
@@ -398,16 +403,17 @@ func (g *GPU) Refresh() {
g.setErr(<-g.refreshErr) g.setErr(<-g.refreshErr)
} }
func (g *GPU) Draw(profile bool, viewport image.Point, root *op.Ops) { // Draw initiates a draw of a frame. It returns a channel
// than signals when the frame is no longer being accessed.
func (g *GPU) Draw(profile bool, viewport image.Point, frameOps *op.Ops) <-chan struct{} {
if g.err != nil { if g.err != nil {
return g.ack <- struct{}{}
return g.ack
} }
g.Flush() g.Flush()
g.ops.reset(g.cache, viewport) g.frames <- frame{profile, viewport, frameOps}
g.ops.collect(g.cache, root, viewport)
g.frames <- frame{profile, viewport, g.ops}
<-g.ack
g.drawing = true g.drawing = true
return g.ack
} }
func (g *GPU) setErr(err error) { func (g *GPU) setErr(err error) {
+3 -1
View File
@@ -122,7 +122,7 @@ func (w *Window) update(frame *op.Ops) {
} }
func (w *Window) draw(frameStart time.Time, size image.Point, frame *op.Ops) { func (w *Window) draw(frameStart time.Time, size image.Point, frame *op.Ops) {
w.gpu.Draw(w.queue.q.Profiling(), size, frame) sync := w.gpu.Draw(w.queue.q.Profiling(), size, frame)
w.queue.q.Frame(frame) w.queue.q.Frame(frame)
switch w.queue.q.TextInputState() { switch w.queue.q.TextInputState() {
case input.TextInputOpen: case input.TextInputOpen:
@@ -141,6 +141,8 @@ func (w *Window) draw(frameStart time.Time, size image.Point, frame *op.Ops) {
w.setNextFrame(t) w.setNextFrame(t)
} }
w.updateAnimation() w.updateAnimation()
// Wait for the GPU goroutine to finish processing frame.
<-sync
} }
// Invalidate the window such that a FrameEvent will be generated // Invalidate the window such that a FrameEvent will be generated