Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: Proposal for a BeginRender / EndRender "widget" for custom, backend based rendering #4139

Closed
datenwolf opened this issue May 14, 2021 · 5 comments

Comments

@datenwolf
Copy link

The currently recommended methods to embed direct rendering into ImGui windows is to either

  • draw to an offscreen texture and "widget" that with AddImage
  • use ImDrawList::AddCallback

Each methods has some drawbacks with regard to "ease of use". Among the things that make ImGui so outstanding is, the ease with which UI elements are created: Just call the widget function, passing a pointer to the variable and you're done.

Unfortunately when it comes to adding things rendered directly with the backend, there's some substancial overhead involved. In case of using ann offscreen texture, that texture, together with a framebuffer configuration must be created and managed; in case of UIs created in-situ based on program state this adds the dificulty of texture (and framebuffer) lifetime management, since actually drawing those elements is deferred until ImGui::Render. In effect one ends up lobbing around some extra state, usually in some global variable.

The use of the callback mechanism sidesteps the proxy object creation, but its usage ties it to internals of ImGui thereby creating additional work in case of internal API changes.

I'm proposing the introduction of a BeginRender / EndRender widget, that offers an abstraction for the canonical draw-to-texture-and-AddImage method, including proxy object lifetime management (delete proxy objects created in a previos frame, if they're not touched between implementation new frame and ImGui::Render (or simply if they're not touched between two subsequent calls of ImGui::Render).

Before I actually go ahead implementing such a feature I'd like to inquiry comments and hints for this. My current thinking is to have a pair of functions

bool ImGui::BeginRender(char const *label_id, ImRenderCtx *rc, ImVec2 dim = ImVec2(0,0));
void ImGui::EndRender();

with ImRenderCtx being a backend dependent type. The semantics would be, that for stateful APIs, like OpenGL "everything" has been set up to match the destination image (viewport, framebuffer object, etc.) and drawing may commence right away (i.e. in the case of OpenGL-2 or a compatibility profile glBegin/glEnd might be done right away). For buffer based APIs (i.e. Vulkan) that context would offer appropriately prepared render pass, synchronization and means to store pipeline and command buffers for eventual cleanup).

Usage then would follow the usual steps of if( Begin ){ ... End; }

ImRenderCtx rc = nullptr;
if( ImGui::BeginRender("##some_rendering", &rc ) && rc ){
    // assuming OpenGL
    glUseProgram(...);
    glDrawElements(...);
ImGui::EnRender(); }

And thoughts on that?

@Xiliusha
Copy link

I don't think your idea is feasible.
Dear-ImGui is generate render commands and vertex data to rendering. Adding user callback is easy to insert a render command to render data.
If you want BeginRender/EndRender, Dear-ImGui must rendering immediately in order to allow user call functions immediately.

@ocornut
Copy link
Owner

ocornut commented May 16, 2021

I'm not sure I fully understand your proposal, but it appears to be part of what would constitute a game/graphics engine and is out of the scope of dear imgui. dear imgui doesn't aim to replace features that would be present in the host engine. "draw to texture" is such a very large and open ended topic (merely having to consider texture storage memory and texture formats gets us into a very deep pit that's largely out of our scope).

However, as part of #3761 and other changes, it is possible that we may end exposing a concept of textures managed and delete by the backend (primarily for the purpose of managing dynamic font atlas and multiple in-flight font atlas). Those may be end up having other convenient uses tangential to your.

@dgm3333
Copy link

dgm3333 commented May 17, 2021

This was one of my questions in another query yesterday (which I was sleeping on before posting).
The following works vaguely OK, but since looking back though the posts a large number of simple tasks are considered "out of scope" so this probably is too (since the "normal" way of doing things in a modern graphical system is to separate the rendering and operational code into separate threads and use a mutex to communicate between them - which is a pain if you have a simple process requiring a linear program flow.)

What would be ideal is the ability to enter a function and set an "instant_render" flag, from which point any imgui outputs were rendered, buffered and swapped as they were called while leaving the remainder of the frame intact.
Then the flag could be cleared at the end of the function and normal operation resumed.

Looking at the library code it might be feasible to either create an ImGui::Render() alternative which only rendered the last call, or a function which then took the imgui call as a passthrough, and triggered the buffering and swap functions but as I only found imgui 2 days ago and I seem to had to write a lot of basic code to get it to do simple tasks (probably because I don't understand it's full scope yet) I haven't got under the hood sufficiently to be sure.

My Issue/Question:

I want to be able display progress of a process within a window without constantly returning to the main render loop, and ideally without multithreading my application. So my question is how best to do that.

I've identified how to (rather badly) re-render a frame from within a function (with my minimal code below).

But I wonder if its possible to copy the frame information and use it as the beginning of the next frame and just append new information to it.

Presumably I could also restrict the bufferswap to only repaint the parts I needed, but I'm not sure how to identify what has been invalidated by imgui.

Incidentally, @ocornut Thank you for your responses to my other post. My apologies - I was trying NOT to hog you attention by putting all the questions in one place.

Standalone, minimal, complete and verifiable example: (see #2261)

// if you put renderWindow in your separate imgui helpers library then include these dependencies
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"
#include <GLFW/glfw3.h>
// and this define in the .h
void renderWindow(GLFWwindow* window, ImVec4 clear_color);

long fibonacci(unsigned n) {
    if (n < 2) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

void renderWindow(GLFWwindow* window, ImVec4 clear_color) {
    // render the last frame
    ImGui::Render();
    int display_w, display_h;
    glfwGetFramebufferSize(window, &display_w, &display_h);
    glViewport(0, 0, display_w, display_h);
    glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w);
    glClear(GL_COLOR_BUFFER_BIT);
    ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());

    // push the rendered frame to the window
    glfwSwapBuffers(window);

    // Start the next Dear ImGui frame
    ImGui_ImplOpenGL3_NewFrame();
    ImGui_ImplGlfw_NewFrame();
    ImGui::NewFrame();

}

void renderWithinFunction(GLFWwindow* window, ImVec4 clear_color) {
    std::string progress;
    renderWindow(window, clear_color);
    for (int i = 0; i < 48; i++) {
        ImGui::Begin("Progress");
            int value = 0;
            std::cout << "f(47) = " << fibonacci(i) << '\n';
            progress += ".";
            ImGui::Text("Show Progress:");
            ImGui::Text(progress.c_str());
        ImGui::End();

        renderWindow(window, clear_color);
    }
}


// The call is made from within the main paint/render loop
while (!glfwWindowShouldClose(window)) {
//...
    renderWithinFunction(window, clear_color);
//...
}

@PathogenDavid
Copy link
Contributor

@dgm3333

I feel like your question is off topic to what's being discussed in this thread.

To quickly answer your question though: If you want your UI to be interactive while intensive compute is happening at the same time, your program is inherently no longer linear. You should be marshaling the information you want to present in the UI from your background compute thread to the UI thread. (Also not sure why you describe this as "a pain", that doesn't strike me as painful at all, especially if it's just a progress bar.)

I don't think the solution you're proposing is nearly as simple as you think it would be and has a bunch of problems you aren't considering. (Especially around input.) Dear ImGui ties a lot of internal things to the concept of a specific frame and is inherently single-threaded.

If you really think this is something you need, you should open another thread and justify why marshaling data between threads is painful in your situation.

@dgm3333
Copy link

dgm3333 commented May 17, 2021

Thanks for your response, and apologies yet again, I thought the redraw aspect was relevant.
In response to your challange I also tested threaded vs linear on my own problem and fairly solidly proved myself wrong.
It's likely too simple to be of any real use for anyone but a nooblike me, (and is definitely off topic so you're welcome to delete if desired) but here is a multithreaded fibonacci version which slots into the imgui example file.

#include <mutex>
std::mutex mtxlock;
int fibProgress = 0;
bool abortThread = false;

long fibonacci(unsigned n) {
    {
        std::lock_guard<std::mutex> lock(mtxlock);
        if (abortThread) return 0;
        fibProgress = n;
    }
    if (n < 2) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

int main(int, char**)
{
    //...

    bool show_fibonacci_window = true;
    std::thread fibThread(fibonacci, 50);
    // Main loop
    while (!glfwWindowShouldClose(window))
    {
        // Poll and Start Frame...


        if (show_fibonacci_window)
        {
            int curProgress;
            {
                std::lock_guard<std::mutex> lock(mtxlock);
                curProgress = fibProgress;
            }
            ImGui::Begin("Fibonacci Progress", &show_fibonacci_window);   // Pass a pointer to our bool variable (the window will have a closing button that will clear the bool when clicked)
            ImGui::Text("Fibonacci Progress");
            ImGui::Text(std::to_string(curProgress).c_str());
            ImGui::End();
        }


        // Rendering...
    }
    {
        std::lock_guard<std::mutex> lock(mtxlock);
        abortThread = true;
    }
    fibThread.join();
    // Cleanup...
}

@ocornut ocornut closed this as completed May 27, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants