From 43865ddabdf8aac0e87a3c6c6d0b4061d950e794 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Wed, 13 Apr 2022 15:59:25 +0200 Subject: [PATCH] app: don't delay FrameEvent.Frame by v-sync latency We should return as soon as possible from FrameEvent.Frame to allow the main goroutine to continue processing other tasks. Whereas GPU.Frame may touch the frame ops, GPU.Present will not, so this change moves Present to after returning from FrameEvent.Frame. This is a variant of 38ff78df5dbeb8fcb13fa743a891237afd9ff573 that works on OpenGL where context locking is required. Signed-off-by: Elias Naur --- app/window.go | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/app/window.go b/app/window.go index f71375f0..1240ec65 100644 --- a/app/window.go +++ b/app/window.go @@ -174,7 +174,7 @@ func (w *Window) update(frame *op.Ops) { <-w.frameAck } -func (w *Window) validateAndProcess(d driver, size image.Point, sync bool, frame *op.Ops) error { +func (w *Window) validateAndProcess(d driver, size image.Point, sync bool, frame *op.Ops, signal chan<- struct{}) error { for { if w.gpu == nil && !w.nocontext { var err error @@ -214,7 +214,7 @@ func (w *Window) validateAndProcess(d driver, size image.Point, sync bool, frame w.gpu = gpu } if w.gpu != nil { - if err := w.render(frame, size); err != nil { + if err := w.frame(frame, size, signal); err != nil { if errors.Is(err, errOutOfDate) { // GPU surface needs refreshing. sync = true @@ -227,12 +227,11 @@ func (w *Window) validateAndProcess(d driver, size image.Point, sync bool, frame return err } } - w.queue.q.Frame(frame) return nil } } -func (w *Window) render(frame *op.Ops, viewport image.Point) error { +func (w *Window) frame(frame *op.Ops, viewport image.Point, signal chan<- struct{}) error { if err := w.ctx.Lock(); err != nil { return err } @@ -251,6 +250,11 @@ func (w *Window) render(frame *op.Ops, viewport image.Point) error { if err := w.gpu.Frame(frame, target, viewport); err != nil { return err } + w.queue.q.Frame(frame) + // We're done with frame, let the client continue. + if signal != nil { + signal <- struct{}{} + } return w.ctx.Present() } @@ -672,18 +676,18 @@ func (w *Window) destroyGPU() { // waitFrame waits for the client to either call FrameEvent.Frame // or to continue event handling. It returns whether the client // called Frame or not. -func (w *Window) waitFrame(d driver) (*op.Ops, bool) { +func (w *Window) waitFrame(d driver) (*op.Ops, chan<- struct{}) { for { select { case f := <-w.driverFuncs: f(d) case frame := <-w.frames: // The client called FrameEvent.Frame. - return frame, true + return frame, w.frameAck case w.out <- ackEvent: // The client ignored FrameEvent and continued processing // events. - return nil, false + return nil, nil case <-w.immediateRedraws: // Invalidate was called during frame processing. w.setNextFrame(time.Time{}) @@ -799,15 +803,11 @@ func (w *Window) processEvent(d driver, e event.Event) { size := e2.Size // save the initial window size as the decorations will change it. e2.FrameEvent.Size = w.decorate(d, e2.FrameEvent, wrapper) w.out <- e2.FrameEvent - frame, gotFrame := w.waitFrame(d) + frame, signal := w.waitFrame(d) cl := clip.Rect(image.Rectangle{Max: e2.FrameEvent.Size}).Push(wrapper) ops.AddCall(&wrapper.Internal, &frame.Internal, ops.PC{}, ops.PCFor(&frame.Internal)) cl.Pop() - err := w.validateAndProcess(d, size, e2.Sync, wrapper) - if gotFrame { - // We're done with frame, let the client continue. - w.frameAck <- struct{}{} - } + err := w.validateAndProcess(d, size, e2.Sync, wrapper, signal) if err != nil { w.destroyGPU() w.out <- system.DestroyEvent{Err: err}