ocornut / imgui

Dear ImGui: Bloat-free Graphical User interface for C++ with minimal dependencies
MIT License
61.14k stars 10.3k forks source link

Render caching layer? #2948

Closed traverseda closed 4 years ago

traverseda commented 4 years ago

I've been playing with the python wrappers for a bit, and unfortunately it just doesn't seem to be performant enough to be practical. This isn't surprising, python being what it is, but it did get me thinking about how that might be solvable for slower languages.

What I imagined would constitute a major architecture change, so I'm definitely not expecting it to become part of imgui, but it's a slow day for me and I figured it would be worth getting the opinion of people who are more experienced with this UI paradigm.

What if imgui calls were (mostly) side-effect free, and returned a cachable data structure?

In python, that could look something like this:

@lru_cache(maxsize=32)
def myCustomWidget(imgui,text):
    imgui = imgui.text_colored(text, 0.8, 0, 0)
    return imgui

Where all the "imgui" methods return immutable/hash-able objects. You can use a very similar technique for calls that retain UI state, like checkboxes, with a bit of work.

My thought is that there are all kinds of performance tweaks you could do using that kind of caching, it could provide a generically faster system than retained-mode UIs, while not being much more difficult of an API than imgui already has.

Any thoughts on that general approach?

ocornut commented 4 years ago

What if imgui calls were (mostly) side-effect free

I don't see how that you would remotely possible with this library. Someone would need to go back to the drawing board and do some R&D for it. But then, I don't really understand the snipped of code you posted so I may be missing something with your idea.

One thing I can see happening more easily would be alter the refresh rate of windows dynamically (e.g. unfocused windows could be marked to refresh only every 4 frames, and that work could be interleaved). That would however to variable perf on a per-frame basis.

(Otherwise I'd like to suggest not using slow languages, but hey, that's not super constructive)

traverseda commented 4 years ago

I don't see how that you would remotely possible with this library. Dear ImGui outputs vertex buffers and a small list of draw calls batches.

Keeping in mind that I don't actually know what I'm talking about, and definitely don't have a good understanding of ImGui's internals, what if imgui calls returned those vertex buffers and draw calls? How I'm imagining it works right now is those calls add a bunch of data to an internal state object, and that object gets passed to the backend.

Theoretically, could the imgui calls return those vertex buffers and draw calls, instead of adding them to a global data structure? Then the backend re-assembled the return values into something that looks a lot like the internal state object?

It definitely wouldn't be compatible with the current API, which is why I presume it's a non-starter, major architecture changes and all that...


windows could be marked to refresh only every 4 frames

How about a way to mark a window as "dirty" and in need of a re-draw? That would be easier to integrate with slower languages. Very similar to the caching example, just at a different layer. Of course you'd need to signal that window are both expanded/opened, but still shouldn't be redrawn.

(Otherwise I'd like to suggest not using slow languages, but hey, that's not super constructive)

I mean you're not wrong. I think a lot of it in this case is that we are copying data around instead of just referencing it. Each call into C is way more expensive then it would be natively. It's less about python's speed, and more about the inefficiency of c foreign-functions in higher-level languages.

Which is unfortunate, because ImGui is a lot more pleasant then any of the standard GUI toolkits for prototyping, and python is pretty good for prototyping as well.

ocornut commented 4 years ago

How I'm imagining it works right now is those calls add a bunch of data to an internal state object, and that object gets passed to the backend.

It's reading/writing hundreds of possible different things in those calls, the calls are dependent on each others (for positioning obviously, but also for lots of other subtle yet important things) and the data binding with your data is done in those calls so the suggestion doesn't make much sense to me right now.

How about a way to mark a window as "dirty" and in need of a re-draw?

That's similar to what I said, depending of we express those refresh rate policy you could set a 0 refresh rate, change it for a short time or have function to forcefully trigger a single or more update. It would all try on you testing the return value of Begin() which is currently not mandatory (and inconsistency with all other BeginXXX api, so we will fix it eventually).

python is pretty good for prototyping as well.

Not wanting to turn it into a lets-change-all-your-habits conversation, but consider using live-reloaded C++ (edit & continue, Live++, RuntimeCompiledCPlusPlus).

I'm not sure how further I can help other than eventually implementing the update rate policy system (no ETA for it tho).

traverseda commented 4 years ago

your data is done in those calls so the suggestion doesn't make much sense to me right now.

Yeah, that makes sense. Thanks for taking the time to explain it to me, it would have taken forever for me to figure that out on my own.

than eventually implementing the update rate policy system (no ETA for it tho).

That solution will work nicely for slower languages.

but consider using live-reloaded C++ (edit & continue, Live++, RuntimeCompiledCPlusPlus).

As a python zealot... I've got time for a holy war :-p

It's pretty hard to beat python's libraries, and there are significant language improvements happening all the type, like the type-hinting. There's even work on using type-hinting to speed up python code, and cython demonstrates that can be as fast as C code.

Still, I admit the main reason I'm not using cpp is aesthetics.

Programmers should be free to pick their own programming style, and that style should be fully supported by C++.

Is pretty much the opposite of python's

There should be one-- and preferably only one --obvious way to do it.

I am pretty interested in zig though. I think the readability and libraries that python has more than makes up for it's comparative slowness.

traverseda commented 4 years ago

Every increase in expressiveness brings an increased burden on all who care to understand the message.

See also the rule of least power and this article "we need less powerful languages".

traverseda commented 4 years ago

I'm not seeing an issue for variable refresh-rate windows, would you like me to open one? Is that related to #2268 ?

ocornut commented 4 years ago

It's in the TODO list and I have this in mind but first we need to start transitioning to new API for Begin() (we'll do it progressively with a config flag to enable new behavior, and a helper feature to help testing for the transition). No need to create a new issue for it, but it's unlikely to happen in the next few months probably later. Thanks for your patience.

marcj commented 3 months ago

I've written a bit code to allow me to cache expensive widgets (for example I have one that doesn't need to update every frame and draws hundreds of thousands of points, like a world map).

https://gist.github.com/marcj/b3c9a054e29b0f55e8f8891655b5f043


int main() {

  OffscreenRender offscreenRender;

  // imgui main loop
  while (running) {
    //handle events

    ImGui::NewFrame();

    ImGui::Begin("window");

    static int i = 0;
    ImGui::Text("Onscreen renderer %d", i++);

    static int j = 0;
    offscreenRender.render("offscreen", [&]() {
      ImGui::Text("Offscreen renderer %d", j++);
    }).every(1000); //every 1s

    ImGui::End();
    ImGui::Render();

    //this creates the actual textures once frame was fully rendered, so next frame uses textures
    offscreenRender.createTextures();
  }
}

https://github.com/user-attachments/assets/4f15efcc-6ca7-4d95-874f-30955722092a

it uses a second ImGuiContext and for each render its own framebuffer. this is written for OpenGL but could probably easily adjusted for other engines (doRender needs to be modified).