Add render throttling to decrease CPU/GPU usage while screen is idle

This commit is contained in:
Ivan Danyliuk 2018-10-22 14:02:59 +02:00
parent 19ed6ded1e
commit b50c31811d
No known key found for this signature in database
GPG Key ID: 97ED33CE024E1DBF
4 changed files with 93 additions and 3 deletions

View File

@ -5,11 +5,12 @@ import (
"time"
"github.com/gopherjs/gopherjs/js"
"github.com/gopherjs/vecty"
"github.com/status-im/simulation/propagation"
)
// TODO(divan): move this as variables to the frontend
const (
// TODO(divan): move this as variables to the frontend
BlinkDecay = 100 * time.Millisecond // time for highlighted node/link to be active
AnimationSlowdown = 1 // slowdown factor for propagation animation
FPS = 30 // default FPS
@ -42,7 +43,18 @@ func (w *WebGLScene) animate() {
w.updatePositions()
}
w.renderer.Render(w.scene, w.camera)
// some render throttling magic to prevent wasting CPU/GPU while idle
// if auto rotation or other effects are active, render always
var needRendering bool = w.wobble || w.autoRotate
if !needRendering {
// else, consult render throttler
needRendering = w.rt.NeedRendering()
}
if needRendering {
w.renderer.Render(w.scene, w.camera)
w.rt.ReenableIfNeeded()
}
}
// ToggleAutoRotation switches auto rotation option.
@ -75,6 +87,7 @@ func (w *WebGLScene) BlinkEdge(id int) {
// AnimatePropagation visualizes propagation of message based on plog.
func (w *WebGLScene) AnimatePropagation(plog *propagation.Log) {
fmt.Println("Animating plog")
w.rt.Disable()
for i, ts := range plog.Timestamps {
duration := time.Duration(time.Duration(ts) * time.Millisecond)
duration = duration * AnimationSlowdown
@ -94,3 +107,17 @@ func (w *WebGLScene) AnimatePropagation(plog *propagation.Log) {
time.AfterFunc(duration, fn)
}
}
// MouseMoveListener implements listener for mousemove events.
// We use it for disabling render throttling, as mousemove events
// correlates with user moving inside of the WebGL canvas. We
// may switch to use mousedown or drag events, but let's see how
// mousemove works.
// This is sort of a hack, as the proper approach would be to get
// data from controls code (w.controls.Update), but it's currently
// a JS code, so it's easier use this hack.
func (p *Page) MouseMoveListener(e *vecty.Event) {
if !p.webgl.rt.NeedRendering() {
p.webgl.rt.Disable()
}
}

View File

@ -105,6 +105,7 @@ func (p *Page) Render() vecty.ComponentOrHTML {
),
vecty.Markup(
event.KeyDown(p.KeyListener),
event.MouseMove(p.MouseMoveListener),
event.VisibilityChange(p.VisibilityListener),
),
)

58
render_throttler.go Normal file
View File

@ -0,0 +1,58 @@
package main
import (
"fmt"
"time"
)
// DefaultRenderThrottleDecay defines a decay period after which throttling should reenabled.
const DefaultRenderThrottleDecay = 5 // sec
// RenderThrottler is a helper to enable rendering only when it's really need to.
// Rendering can be enabled/requested externally, but will be turned off/slowed down
// after some period of time.
// TODO(divan): disable/enable throttling is inverse of enable/disable rendering, and may be confusing. Rename maybe?
// TODO(divan): also, instead of disabling, might be a good idea to just decrease FPS. Explore this.
type RenderThrottler struct {
needRendering bool
lastUpdate int64
decay int64
}
// NewRenderThrottler returns a new render throttler.
func NewRenderThrottler() *RenderThrottler {
r := &RenderThrottler{
needRendering: true,
lastUpdate: time.Now().Unix(),
decay: DefaultRenderThrottleDecay,
}
return r
}
// Disable disables render throttling.
func (r *RenderThrottler) Disable() {
fmt.Printf("Switching ON rendering")
r.needRendering = true
r.lastUpdate = time.Now().Unix()
}
// Enable enables render throttling.
func (r *RenderThrottler) Enable() {
fmt.Printf("Switching off rendering")
r.needRendering = false
}
// ReenableIfNeeded checks if sufficient time has passed since throttling has been
// disabled, and enables throttling back if so.
func (r *RenderThrottler) ReenableIfNeeded() {
now := time.Now().Unix()
if r.lastUpdate+r.decay < now {
r.Enable()
}
}
// NeedRendering returns true if next frame should be rendered.
func (r *RenderThrottler) NeedRendering() bool {
return r.needRendering
}

View File

@ -31,11 +31,15 @@ type WebGLScene struct {
// TODO(divan): as soon as three.js wrappers allow us to access children, get rid of it here
nodes []*Mesh
lines []*Line
rt *RenderThrottler // used as a helper to reduce rendering calls when animation is not needed (experimental)
}
// NewWebGLScene inits and returns new WebGL scene and canvas.
func NewWebGLScene() *WebGLScene {
w := &WebGLScene{}
w := &WebGLScene{
rt: NewRenderThrottler(),
}
w.WebGLRenderer = vthree.NewWebGLRenderer(vthree.WebGLOptions{
Init: w.init,
Shutdown: w.shutdown,