Using ImGui with modern C++ and STL for creating game dev tools – Part 2
eliasdaler.github.io
eliasdaler.github.io
Check out what people have been doing with Dear Imgui:
But for people already using STL and integrating ImGui with their codebase, using STL with ImGui may be somewhat difficult and that's why I wrote the article about things that worked for me. :)
In Unity3d game engine in particular, IMGUI was originally used, and it was a huge performance drain. Now all the "real" UIs have been moved to a new, object-based system (with a LOT of caching and optimization), but IMGUI is still used for quick prototyping and debug UIs.
The vast majority of games use simple GUIs where it won't matter. But if you have a complex or large GUI, you will be eating up significantly more CPU cycles.
Also, the optimization bottleneck nowadays is mostly programmer/brain time, so having a ten-fold productivity improvements means you can actually create more/better tooling and that has very direct consequence on your software performance. The reality with toolkits like QT is that people write very little amount of tooling with it, because it is so tedious. The cost of that are lost opportunity in improving a game engine and its performances.
Once you have such a system it doesn't matter whether the higher level parts follow the immediate-mode or non-immediate-mode philosophy, or if the scene graph itself is rebuilt each frame or not, just the render state changes and draw-call overhead will kill performance as soon as you have a few hundred UI elements on screen.
Dear Imgui builds flat vertex buffers and a very simple command list where each command is basically 'please render this range of vertices with this scissor rect', and as I wrote above, generates about 1 draw call per window. Only a new clip-rect (e.g. a scrolling text frame) requires a new draw call. As an example, in my 8-bit emulator, the entire debugging UI, with all windows open (12 windows with a lot of scrollable text, buttons, lists etc...) costs 16 draw calls.
Just to take "simple" text rendering... When you have just ASCII text rendered with monospace font then it probably OK to do its full layout on each render run - that's really simple. But if you need to render, say, HN page then you'd better compute once and cache glyph positions. And other objects too that need calculations: gradients, tesselated shapes, etc.
Check this: http://sciter.com/sciter-and-directx/ That's relatively simple UI rendered directly into DirectX as a batch of precomputed meshes.
Ideally you should be able to take best of two worlds: immediate mode graphics and declarative one.
It's funny that you mention this because I remember trying to utilize DirectX RM (retained mode) for a game in late 90s. Even without taking the performance into account (one could say that the implementation was not very optimal) it was a major pain to do anything. One thing I remember vividly: our explosions were static meshes containing all frames of animation and were animated through toggling visibility of different nodes. Luckily that game had been killed by publisher and I did not have to suffer through optimizing its performance.
Having said that, while ImGui is pretty simple, I still think there's a lot of implementation details that leak out when you're coding (the stuff with the double hashes for instance). I still prefer Qt for regular UI work, but ImGui is deliciously lightweight though.
That would very likely be done at a loss, the processing/clipping of ImGui is sufficiently fast.
> I still think there's a lot of implementation details that leak out when you're coding (the stuff with the double hashes for instance)
Yes the id-stack system is an artefact of the entire architecture but it's pretty trivial, transparent and a cost that is immediately amortized once you understand the UI architecture. Double-hash is a feature, would you prefer an UI system where every single widget would requires an explicit ID? Or worse, an explicit storage (like most RMGUI). Every piece of software involve implementation details. The details of QT can get pretty opaque if you ask me.
I am bit annoyed with people just theorizing on performance issues that they haven't experienced themselves. I wish people where spending a tenth of the time looking at the cost of naively using std::string or containers.
On average, Dear Imgui seems to generate one draw call per window, with just changing the scissor rect inbetween draw calls. It will be very hard to come up with a complex enough UI that this becomes a performance problem even on low-end GPUs.
I think the arguments that "well, games redraw the HUD every frame and it's fine, so why is recreating the UI every frame a problem?" are misunderstanding the problem. HUDs usually don't have much layout to do: in CSS terms, everything is absolutely positioned. Designers usually create HUDs visually using Flash or similar, so the layout ends up being trivial since, in effect, a human has already manually calculated the positions of every widget. For a programmatically-created GUI, though, there is actually layout to perform, usually via a flexbox-like model but sometimes via a constraint solving system (which makes the time complexity go way up). For this to be efficient you need a way to cache the results of layout so that you only recompute them from frame to frame when needed. This caching is precisely what a retained mode UI is.
For those who think that layout is always fast enough to never be a performance problem, try writing a flexbox UI in your browser, then open the dev tools, resize the window (which will trigger a full relayout if you don't have any explicit widths in there) and see how long the layout takes. It often eats up the time budgets of multiple frames (and this is not just browsers being slow--the complexity is inherent).
This is not to say IMGUI is bad, mind you! It's a great solution for debug overlays, which is what it was originally designed for, because of how simple it is. Just don't overstate its benefits: retained mode UIs exist for a reason.
One thing you have to keep in mind is that the more dynamic the scene graph, and the larger its memory footprint, the more frequent you'll miss the cache while traversing it.
It's possible to manually write optimized flex-box-like layout code, where the overall structure, if not exact position, is mostly static.
For example, if you always have one resizable panel to the side of two viewports, of equal width (as is the case in the editor I use atm), you can easily do `viewport_width = (total_width - panel_width) / 2` and be done.
Those operations, if done with integers, should be faster than a read from cache, or if done with floating-point numbers, (much) faster than going to DRAM in the even of a cache miss.
However, doing all of that by hand would be a pain, and it's hopeless in the face of user customization. SIMD is another resource that's completely unusable in the face of truly dynamic data, and a time sink to do manually.
What we're missing is automation of mostly-static UIs, generating specialized code and optimizing it into computations that run under a microsecond. For HTML, the hope would be JITs, but highly dynamic DOMs are still all over the place, so you're not going to have as many gains as a dedicated UI system, and it wouldn't be free for the user.
I think you're referring to applying optimizations to static UI with constant data structures, but that can be done with retained mode too, and I doubt it actually is going to help.
Also, layout is typically memory-bound. The arithmetic intensity is sadly fairly low.
I do agree with you, though. Sadly very few Web developers and designers care about performance of layout; all everyone seems to want is new features without any regard for performance...
The only drawback i see is that the linked ocornut imgui is pretty minimalist in design and layout, not easily bendable and not well suited for an elaborate pixelised artistic game interface. But since it got textured buttons already in, it might not so much work at all to wrap something up. In the current state it's super easy painless and quick to implement for small tools, and to get values on screen updated in realtime.