It's easy to skip drawing frames when no state changes, and for frames where state does change, you can use techniques like rxi's cached software rendering to avoid redrawing the entire screen: https://rxi.github.io/cached_software_rendering.html
How does it handle scrolling? This is typically very fast and smooth (performed by bitblt and redrawing only the newly exposed part on every frame), but in immediate mode you'd have to redraw the entire screen on every frame.
I think this whole efficiency thing is a common misconception. Only the public API appears "immediate mode", the internal implementation doesn't need to be, and usually isn't (e.g. it keeps mutating state between frames instead of building everything from scratch again).
The user-side code basically describes what the UI should look like in the current frame, those "instructions" are recorded, and this recording is reasonably cheap.
The UI backend can then figure out how to "diff" the new instruction stream against the current internal state and render this with the least changes to the screen.
However some immediate mode UI systems came to the conclusion that it might actually be cheaper to just render most things from scratch instead of spending lots of processing resources to figure out what needs to be updated.
In conclusion: "Immediate Mode UI" doesn't say anything how the UI is actually rendered or generally how the internals are implemented, it only describes how the public API works.
Perhaps I should give a different example: a listbox filled with 1 million items, with a scrollbar.
If the public API requires you to give a new paint command on every frame (everytime the scrollbar is dragged), then regardless of whether the underlying rendering engine performs each of these paint commands, you still have to run through every item of the list (and so does the diff'ing code), making this a O(N) operation on every frame.
From what I've seen (in Dear ImGui), this is solved by not giving the 1 million items to the API, instead you can query what range of the list is currently visible, and only describe those to the API (in Dear ImGui this is called a ListClipper).
But I guess different UI frameworks have different solution for this. Creating and updating a 1 million item list wouldn't be a cheap operation in a traditional UI system either.
Ok. Yes my point is not that you can't find efficient ways around the limitations, but my fear is that you end up with a system that is less ergonomic in the end.
These aren't just workarounds. It's often the natural immediate mode api. A lot of immediate mode "scroll view" systems just have a child lambda passed with an index for which element to render, and it only calls the lambda for what's actually in view. (eg: the Gio API in Go)
Concrete examples of code that is not immediate mode api, vs. code that is immediate mode api, both implementing the same thing, can help discuss / reflect on that fear. IME immediate mode is great for mapping tree-ish data to tree-ish UI; when things have a lot of nontree linkages or reordering (eg. when you do drag and drop) it gets trickier. React's JSX / createElement API also feels somewhat immediate mode, tbh; the updates are just scheduled to fire on state changes.
> A lot of immediate mode "scroll view" systems just have a child lambda passed with an index for which element to render
This sounds a lot like paintEvent() in traditional OO-style GUI systems; i.e. event-driven.
So my understanding now is that with immediate-mode callbacks happen within the scope of a function-call rather than from some event-loop. I probably have to look into it deeper to get a good understanding. It is still unclear where state is stored (e.g. for the position of a scroll-view), and if state is passed through the function call tree on every frame.
Yeah the in depth look helps before forming conclusions like "you have to render everything every frame." Key thing is immediate mode can often mean just the sense of the API, not necessarily the draw scheduling.
re: widget-local state -- React is one of the most popular models for that. Basically if widgets are identified over time by the sequence of ids (includes array indices like React keys) on the path to them from the root, state is coherent across that identity, and mount / unmount events exist at the boundary of the identity. Callstack / push-pop based APIs like ImGUI maintain this sequence either impicitly or explicitly in the stack. Then there is some API to read / write to the state store (like React hooks or ImGUI generic state storage) with optional update triggering in async APIs like React's.
It's not difficult to implement a 'virtual' list which renders only the items which are visible. It's a little trickier if your list items are not all the same height, but not impossible.
Modern renderers typically draw scrollable content into a separate texture (or collection of smaller textures that tile the scrollable region) and use the GPU to scroll rather than bitblting on the CPU. You can use the same technique to render the scrollable part of the UI in an immediate mode system.
What if you have your ‘immediate mode’ produce not a direct rendering of the GUI, but a virtual object model describing what the structure of the GUI should be at that moment? Then you diff that object model against the actual object model of the GUI and apply the changes....
That’s how react works. It’s effectively an ‘immediate mode’ abstraction above a component based UI.
That avoids the component connecting and wiring problems, and creates the simple determinism that makes immediate mode systems easier to reason about.