optimizing immediate mode UI power usage?

I'm doing an animated UI with a little immediate mode UI library I've written on top of nanovg.

Currently it redraws everything each frame (and typically most of the frame is identical to the previous frame). Frame rate is good enough, but it burns CPU, and thus battery.

Because the UI is animated, I can't simply redraw the UI after user input.

Is there a convenient approach to reducing power usage?

Edited by Taylor on
You can enable vsync. No reason to render more frames than monitor can show. This will make your code to pause from time to time and will reduce CPU usage. Unfortunately this comes with price of more input lag.
I'm already doing that. It renders at 60fps.
You could do dirty rectangles, where you just update what has changed.
So if you are limiting rendering at 60Hz, are you sure you need to reduce it more? The CPU usage should be pretty close to 0 if you are vsync'ing and not doing anything complex in background threads.
this is the reason why retained mode GUIs are a thing, they cache things so stuff doesn't need to be redrawn every frame.

I think the approach I'm going to try is to separate my UI into two layers: static and dynamic.

Static is redrawn only when there are changes to the data model, or there is user input.

Dynamic is redrawn at 60fps, for the things that are always animated (it's basically lights and displays on a control panel).

My little library uses separate passes for drawing and events, so I should be able to do it.

Edited by Taylor on
Yes I'm quite sure. Xcode reports "High" energy use. Profiler reveals lots of layout and path tessellation (20% of cpu time just spent tessellating bezier paths).
This sounds like a nanovg problem (generating rendering data) and not a UI redraw problem (actual draw call submission / GPU rendering)
Yeah, you need to optimize code that does your tesselation and bezier curves. There's not magic switch that will make that faster. You need to check what you do there, do you really want that, maybe don't do curves, just flat rectangles, and so on... The more fancy UI will look like the more CPU power it will require.
I've found it difficult to improve nanovg's tessellation. In the past, I've tried using Adaptive Forward Differencing, with mixed results (mostly, I had trouble coming up with a good flatness test in the AFD domain, and you loose the performance advantage if you transform back to the bezier domain). Implementing Loop-Blinn would be better, though that requires some initial tessellation that isn't ideal for dynamic geometry. It's also reasonably complex for cubic curves. There's another technique based on Loop-Blinn which uses the stencil buffer to avoid that tessellation. It might be better, though I'm not sure how that would interact with AA. Suffice it to say, it's a lot of work.

I might be able to get a modest gain by eliminating the recursion in the bezier subdivision (to free up the optimizer a bit). I've had some pretty good perf gains lately by eliminating recursion when building octrees.

There is of course time spent computing layout as well.

So in light of all that, I think the two-layer approach, which eliminates the vast majority of drawing and layout for most frames, is the way to go.

> "most of the frame is identical to the previous frame"
> "Because the UI is animated, I can't simply redraw the UI after user input. "

Maybe you ought to clarify what is animated and what isn't?
You want to display animated contents (at say, 60 fps) and somehow the cost of doing that is problematic for you, precisely what part of that problem has something to do with the use of an immediate mode UI? (honest question)

Some ideas of the thing I'm experimenting on or will experiment on with dear imgui:

- You can consider using interleaved/variable refresh rate on a per-window basis. e.g. In dear imgui each window maintain their output (vertex+index buffer), which means it is possible to have some windows not refresh at all and not require any code running. For example we may implement a policy where only specific windows are updated (e.g the focused window) and other would update at lower frequency e.g. 1/N frames, and we can distribute load balance of the workload of those windows over those N frames. The sky's the limit as to what kind of refresh policy we can implement on the CPU side.
- Very specific case of animation known by the core UI system (e.g. blinking cursor) could be rewritten by the core system to avoid any work CPU-side. In many desktop-app case when the only animated thing is a blinking cursor we can perfectly poke into the vertices, and rerender over a small viewport surrounding the cursor only. This way there's near zero CPU-cost and GPU pixel shader cost are bound to a minuscule region, most of the work left for the GPU is clipping.
- In the typical case of displaying a game render inside a window, since the game is rendered into a texture it can be updated as a higher frequency and not require any CPU-side recreation of the interface.

Edited by Omar on
The UI is mostly static, unless the user zooms or pans. Then the whole thing has to be redrawn. Other than that there are some animated data readouts about what's happening on the DSP side. The app is http://audulus.com

omar
precisely what part of that problem has something to do with the use of an immediate mode UI? (honest question)


It has to do primarily with my immediate mode UI library, which until yesterday didn't cache anything. The dual-layer approach I mentioned above is working quite well. It's similar to your first suggestion about variable refresh rates.

I think though, in general, this issue of power usage will keep immediate mode UIs from being adopted for more general purpose UIs (especially on mobile). I'd guess it's easy to come up with a test where a system like CoreAnimation/UIKit would be more power efficient than say Nuklear or Dear IMGUI.

Edited by Taylor on