7 ms·
As much as I like Dear ImGui, the immediate mode paradigm doesn't work everywhere. The problem of "page tearing" (not to be confused with vsync) means you eithe
by exDM69 6y ago
As much as I like Dear ImGui, the immediate mode paradigm doesn't work everywhere. The problem of "page tearing" (not to be confused with vsync) means you either need to render frames continuously, or at least 2 frames for every time you get input or need to implement some kind of "retained mode state tracking" on top of Imgui. It's good for games, but for applications where you're not rendering new frames constantly, the paradigm doesn't seem a very good fit.
I wish there was a retained mode GUI library that would be as easy to integrate in your game and graphics apps as ImGui (or nuklear) is.
On my spare time, I've been working on a retained mode GUI layout and rendering framework (not a complete GUI toolkit) that uses a DOM-like tree, CSS style flexbox layouts and produces a vertex buffer for rendering like ImGui does.
Unfortunately life gets in the way, and all I have to show for my project is a small prototype in C and another one in Rust (can't decide which I like more) that draws some text and rectangles with a fancy layout. Code is not public at the moment but I'm willing to share it with anyone (on GitHub) who responds with a thoughtful comment or a good question.
I have been following Raph Levien's fantastic work on Druid and Rust GUIs which has been very inspirational, this video in particular [0].
Where Raph is focused on native GUI rendering and high fidelity text output, I've only focused on rendering in a GPU application using shaders and only very basic text layout with simple kerning (like Dear ImGui).
As Raph points out in his later talks on the topic, layout and rendering are only half of the GUI puzzle. The other half is maintaining the state of the application, usually through some kind of widget tree.
Leave a comment if you're interested, I'd be happy to chat even though it doesn't seem like my projects are going anywhere anytime soon.
[0] https://www.youtube.com/watch?v=4YTfxresvS8 https://www.youtube.com/watch?v=4YTfxresvS8 Data Oriented GUI in Rust by Raph Levien
- flohofwoe 6y agoWouldn't it be possible to separate the public API from the internal implementation so much that the library appears "immediate mode" on the outside, but is completely "retained mode" on the inside? With a persistent widget tree which is only manipulated by diffing it against the current frame's UI description provided by the user code? The important feature of immediate mode UIs is that there's only one sequential piece of code for UI creation, updating and input handling. All I'm saying as the framework user is basically "this is what I want the UI to look like in the current frame". E.g. if the UI that's described in the current frame by user code is completely identical to the last frame's UI, exactly "nothing" would happen, the internal widget tree wouldn't change, and nothing would be rendered.
- HelloNurse 6y agoAvoiding the burden of a "widget tree" that duplicates application state in complicated, inefficient and error-prone ways is more or less the whole point of reconstructing the whole GUI every frame from data structures that deserve to exist.
- flohofwoe 6y agoAgreed, but in traditional UI toolkits this burden is on the user-side. If the widget tree is hidden away inside the library it becomes just an internal implementation detail, a caching structure which might help make some things more efficient. Proof is in the pudding of course.
- ratww 6y ago> Wouldn't it be possible to separate the public API from the internal implementation so much that the library appears "immediate mode" on the outside, but is completely "retained mode" on the inside? It is possible but you would need some severe API changes... you could either ask the users to use unique names for each widget, or something similar like storing references between frames. Alternatively you'd have to force "same order between frames" (similar to React does with Hooks).
- flohofwoe 6y agoDear ImGui already uses such "implicit" widget ids, by default it hashes the widget's label string, window title or name, with a convention to add an invisible 'identifier string' to the label string separated by a "##" (e.g. "Label##id") - for the case that the visible label string needs to change or isn't unique within the current scope. There are also separate PushID/PopID functions to manage such widget ids in "more elaborate ways".
- ratww 6y agoInteresting, I didn't knew that. I helped write a couple immediate-mode GUI some time ago, but I'm not deeply familiar with ImGui. If this is the case, then it seems entirely possible to have an "internal retained mode", and might even help with some extra stuff like focus management and accessibility.
- formerly_proven 6y ago> As much as I like Dear ImGui, the immediate mode paradigm doesn't work everywhere. The problem of "page tearing" (not to be confused with vsync) means you either need to render frames continuously, or at least 2 frames for every time you get input or need to implement some kind of "retained mode state tracking" on top of Imgui. It's good for games, but for applications where you're not rendering new frames constantly, the paradigm doesn't seem a very good fit. Writing a desktop app using ImGui I had the same problems. In many instances it can be resolved by re-ordering operations such that in a frame you first update the state and then render it, but this isn't always possible or easy. For those cases I eventually resorted to having a requestFrame() function (which posts an empty event to the input queue at the end of the frame, causing another frame to be rendered immediately).
- malkia 6y agoIt's exactly these cases, where I'm not really comfortable moving away to imgui, say from Qt (not that I like it a lot), but there has been pressure at work to do so...
- milansuk 6y agoHi, I'm making SkyAlt[0]. It's IDE and language for building applications. Probably I did something right in the beginning, because It doesn't have problems with "page tearing", but it's true that It does "state tracking" in the background. Feel free to check my blog and send me an email if you are interested. I have work in progress version for Windows and Linux, but no documentation yet. It will be released(free and open-source) by end of this year. [0] https://skyalt.com/ https://skyalt.com/
- exDM69 6y agoYou will always get page tearing with ImGui (this library and any others). There are mitigations and workarounds but the issue is fundamental to the paradigm. Consider this trivial counterexample: int counter = 0; label("count: %d\n", counter); if(button("increase")) { counter += 1; } label("count: %d\n", counter); This is, of course, a silly little example but the same issue will arise in many practical use cases and would be much more complicated. Most applications "solve" this by rendering new frames continuously, so the inconsistent state is only shown for one frame and will quickly disappear. This, however, has terrible power consumption implications if nothing of interest is occuring on the screen.
- milansuk 6y agoSorry, I thought that "page tearing" is something else. For example that It has to decide if the scroll bar will be drawn or not. And it has to make that decision before it starts drawing layout items because there can be an item which expands from left-side to right-side(dynamic layout), so the finalLayoutWidth = originalLayoutWidth - scollBarWidth. Then it can compute item's sizes and draw them. My solution is to save maximum layout size from previous frame. SkyAlt would "solve" your example by quickly redrawing the frame. The power consumption is good because it's redrawing only when the key is pressed or the mouse is moving, otherwise one frame every two seconds, so CPU is most of the time under 1% and my 3.6GHz CPU is running on 1.4GHz.
- CountHackulus 6y agoThat looks more like coupling the state with the UI. You could try refactoring your code in an MVC/MVVM style like react such that the state isn't being modified during a UI draw. That sort of out-of-sync issue can happen even with retained mode UIs. I wouldn't say it's fundamental to the paradigm that you intersperse your business logic into your UI. Granted at large scales basically any UI framework falls apart.
- tgb 6y agoCan you explain what "page tearing" is?
- dthul 6y agoI think it refers to the fact that in an immediate mode GUI you adjust the application state while you render the page, meaning that the part of the screen which you rendered before the state was updated shows the old state, while the part of the screen which you rendered after the state was updated shows the new state. The screen is basically "torn" between two or more states and you need to draw another complete frame to bring it back in sync. Not a problem in a game where you redraw everything at 60fps anyway, but not so good for a classical GUI toolkit. Edit: I guess you could partially solve this problem by double-buffering the application state, so during the first draw you update a state copy, and during the second draw you use the updated state. You won't get any "tearing" but you still need to draw twice.
- Torkel 6y agoI use ImGui for gui in a user-facing app that is "large-ish" (~500k LOC maybe). When removing items from lists etc I used to keep drawing non-removed items in the list so the user would not see a "glitch". But I realized it is not necessary. Just cut it short. The user can only take one action per frame from mouse click or button press etc. It's totally fine to render one frame a bit "strange" when someone deletes or adds to a list. There is no problem in redrawing everything from state every frame. There is no problems in having "half frame be old state" - even at 30 fps it is not noticeable. We use a separate update() and gui() path though. So all entities will get an update opportunity even if the gui is hidden. And state is kept in a config that is beautiful to use... Maybe the issues here comes from not having those parts.
- ska 6y agoI think their point was that you can't do this without committing to redrawing all the time at a reasonably high rate, which has its own set of problems.
- boomlinde 6y ago> The problem of "page tearing" (not to be confused with vsync) means you either need to render frames continuously, or at least 2 frames for every time you get input or need to implement some kind of "retained mode state tracking" on top of Imgui. Suppose that the widget functions apply one of two different strategies depending on the call context: 1. Render the widgets and 2. Evaluate the layout and process input accordingly. You invoke the GUI code in the second context on every input, and after that invoke it in the first context only if the calls in the second context potentially resulted in a change. This minimizes re-rendering and eliminates page tearing while allowing you to re-render reactively to events. You will of course still need some kind of basic state tracking to make the GUI re-render to reflect changes that are not triggered by input. In general, I agree, though. The right tool for the right job.
- deleted 6y ago[deleted]
- FraKtus 6y agoMaybe it is still VBL related, in the DX11 sample you will see this when presenting the final frame: g_pSwapChain->Present(1, 0); // Present with vsync //g_pSwapChain->Present(0, 0); // Present without vsync So you can control this by setting the first argument to 1 or 0.
- Rusky 6y agoIt's not related. The "page tearing" problem is caused by the way imgui handles events. You describe the UI by making a bunch of function calls, each of which immediately renders a piece of the UI and returns any relevant events (e.g. render a button, return whether it has been clicked). But this means that any event that should change how the UI is rendered may have missed its chance- what if the changed part of the UI has already had its function called that frame? The "ideal" dataflow, as far as this problem is concerned, would be to fully process all the events in a frame (from raw input, to widget-level events like button presses, to application-specific stuff) before rendering anything.
- userbinator 6y agoIt's good for games, but for applications where you're not rendering new frames constantly, the paradigm doesn't seem a very good fit. It's also very inefficient to continuously redraw, which is not a concern for games, but not in general.
- robinei 6y agoI'm not convinced that redrawing on input events is especially inefficient. No need to redraw continuously.
- laserbeam 6y agoIndeed, I've got dear imgui working like that and it's the performance is miles above any retained gui. Dear imgui however it is not designed with this in mind. You run into a some issues with timing, handling release events and a few things which normally take 1-3 frames to fully resolve. Things like key repeats are off. It feels more desirable to fork the lib or design an alternative imgui to support long periods of idle time.
- deleted 6y ago[deleted]
- mrmonkeyman 6y agoVideocards redraw every frame anyway.
- forrestthewoods 6y agoI’m not entirely sure this is true in practice. Games can render huge, vibrant, dynamic 3D worlds at 60fps and increasingly at >120Hz rates. Web browsers lag if a piece of dust falls on the wrong spot. I agree with you in theory. But in practice retained mode is a mountain range or complexity and doesn’t actually perform better.
- malkia 6y agoGames also require said GPU computing power. Lots of UI may have to conserve power/energy - think about some IoT device that may not even have GPU, and need to use as little as possible energy.
- raphlinus 6y agoThanks for the kind words. My thinking has evolved since that video; my blog at https://raphlinus.github.io/ https://raphlinus.github.io/ is a good place to check. As flohofwoe suggests in a sibling, combining an imgui-like API with an actual retained widget tree would be something of the best of both worlds. That is indeed pretty close to what the Crochet prototype does. We're not trying to exactly match the imgui but it's pretty close. In particular, getting actions back from widgets (like button presses) is the return value from calling the widget-creating function. That's a notable difference from, say, Jetpack Compose, which is fairly imgui-like on widget creation but pretty much uses callbacks for those actions. Crochet does solve the page tearing problem (though the current implementation is hacky, don't look too closely under the hood). Another note - the Dear ImGui implementation has become a lot more sophisticated over the years, and is no longer a simplistic implementation of the immediate mode GUI concept. It has unique id's for widgets, its text processing has become better, etc.
- lenkite 6y agoHow about this one: https://github.com/cycfi/elements https://github.com/cycfi/elements ?
- DonHopkins 6y agoYou can't implement a user interface editor, or user editable interfaces, with immediate mode guis. You can't load and save user interfaces out to resource files, or dynamically generate user interface resources from other programs. You can't iterate quickly on user interface development, because the user interface specification is a program that you have to recompile, not data that you can reload.
- athrowaway3z 6y agoWhen you say we can't, it might be prudent to specify that "It's not a mainstream/simple feature" instead of "It's impossible"
- adwn 6y agoWell, of course it's possible: The widgets can be stored in a tree-like data structure, which is constructed and modified by the UI editor, can be loaded and stored in a file, and is interpreted by a runtime which calls the corresponding widget functions ooops we've just reinvented retained-mode GUIs.
- ocornut 6y agoThat's not true. One of the benefit of IMGUI-style is to simplify user-data binding and avoid user-data sync/duplication, but that's entirely decorelated with layout consideration. You could perfectly have an editor manipulating widgets properties or their layout info (e.g. pos/size/anchor) somewhere and have the function fetch and use those infos. If a widget exist in an user interface editor, regardless of the UI paradigm, some code will interact with that widget (read/write the data or bind actions to some callback). So what are you describing and what exists everywhere are merely a way to edit properties and layout, which can be done in IMGUI style, and actual binding requires code either way. > or dynamically generate user interface resources from other programs. Err... of course you can, and you are free to express them in whichever format is more adequate and convenient from your application. Generating user interface resources doesn't mean "generating code", that would be silly because the interesting part of that code is the data/function binding, and that's the one that makes less sense to magically generate. > You can't iterate quickly on user interface development, because the user interface specification is a program that you have to recompile, not data that you can reload. Then I'm sorry for you if recompiling and reloading code is a slow process in your workflow today. Many languages including C++ have hot-reloading techniques, and it is the standard for any so-called "scripting" languages like Python, Lua, Javascript to be easy to reload. _Because_ IMGUI have less state and less spread editing UI touches less code, less data structures, less layers and therefore it is easy to have that dynamic editing. With Dear ImGui I can add temporary tooling UI _inside a random function deep into the callstack_, reload the code while running, then remove that code when I'm done.
- t0astbread 6y agoSorry if this comment sounds a lot like "why don't you just"-ing, I'm just trying to understand what I'm missing and I don't expect to be right. But is rendering twice really that much of a problem if you only have to do it on a state-changing event (even if that includes things like hover events or live data)? Even games that run with 30-60 FPS aren't really a problem anymore on weak hardware so why would a GUI that updates "once in a blue moon" (relatively speaking) be? Apart from that, I don't know how Dear ImGui or other immediate mode GUI frameworks do it but if a framework handles page tearing with double rendering it could skip the actual paint (i.e. pixel drawing) in the first render. In case the user wants to avoid some needless work (like rendering labels or graphs that never affect state) in the first render the framework could also pass the calling context (update or paint) to the render function.