Skip to content

Instantly share code, notes, and snippets.

@marcj
Created August 8, 2024 22:28
Show Gist options
  • Select an option

  • Save marcj/b3c9a054e29b0f55e8f8891655b5f043 to your computer and use it in GitHub Desktop.

Select an option

Save marcj/b3c9a054e29b0f55e8f8891655b5f043 to your computer and use it in GitHub Desktop.

Revisions

  1. marcj created this gist Aug 8, 2024.
    307 changes: 307 additions & 0 deletions imgui_cache_layer.cpp
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,307 @@
    #include <functional>
    #include <iostream>

    #define IMGUI_DEFINE_MATH_OPERATORS
    #include "imgui.h"
    #include "imgui_internal.h"

    #include "imgui_impl_sdl2.h"
    #include "imgui_impl_opengl3.h"

    #include <OpenGL/gl3.h>
    #include <SDL.h>

    // Function to create a texture
    inline GLuint createTexture(int width, int height) {
    std::cout << "create texture size " << width << "x" << height << std::endl;
    GLuint texture;
    glGenTextures(1, &texture);
    // glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glBindTexture(GL_TEXTURE_2D, 0); // Unbind the texture
    return texture;
    }

    // Function to create a framebuffer and attach the texture to it
    inline GLuint createFramebuffer(GLuint texture) {
    GLuint fbo;
    glGenFramebuffers(1, &fbo);
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);

    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);

    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
    std::cerr << "Framebuffer not complete!" << std::endl;
    }

    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    return fbo;
    }

    struct OffscreenRender {
    struct Render {
    ImGuiID id;
    GLuint fbo{0};
    GLuint texture{0};
    bool needsRender = true;
    ImVec2 size{0, 0};
    ImVec2 frameBufferSize{0, 0};
    std::function<void()> callback;
    long long everyMS = 0;
    long long lastRender = 0;

    explicit Render(
    ImGuiID id,
    const std::function<void()> &callback
    ): id(id), callback(callback) {
    }

    Render &every(long long ms) {
    everyMS = ms;
    return *this;
    }

    Render &withSize(float width, float height) {
    size = ImVec2(width, height);
    return *this;
    }

    Render &withSize(ImVec2 s) {
    size = s;
    return *this;
    }
    };

    ImGuiContext *context = nullptr;
    std::unordered_map<ImGuiID, Render> renders;

    void init() {
    if (context) return;
    auto currentContext = ImGui::GetCurrentContext();
    auto &oldIo = currentContext->IO;
    context = ImGui::CreateContext(ImGui::GetIO().Fonts);
    auto &io = context->IO;
    io.BackendRendererUserData = oldIo.BackendRendererUserData;
    io.BackendPlatformUserData = oldIo.BackendPlatformUserData;
    io.BackendPlatformName = oldIo.BackendPlatformName;
    io.BackendFlags = oldIo.BackendFlags;
    io.FontGlobalScale = oldIo.FontGlobalScale;
    io.FontAllowUserScaling = oldIo.FontAllowUserScaling;
    io.FontDefault = oldIo.FontDefault;
    io.DisplaySize = oldIo.DisplaySize;
    io.DisplayFramebufferScale = oldIo.DisplayFramebufferScale;
    ImGui::SetCurrentContext(currentContext);
    }

    ~OffscreenRender() {
    for (auto &render: renders) {
    if (render.second.texture) glDeleteTextures(1, &render.second.texture);
    if (render.second.fbo) glDeleteFramebuffers(1, &render.second.fbo);
    }
    }

    /**
    * Creates the textures so that next render() call uses the texture instead of the rendering callback.
    * This needs to be called outside of the main ImGui loop (not between ImGui::NewFrame() and ImGui::Render()).
    * After ImGui::Render() is a good place.
    */
    void createTextures() {
    for (auto &render: renders) {
    doRender(render.second);
    }
    }

    /**
    * Initially calls the callback to render the content and measures the size of the content.
    * Places the callback into a queue to be rendered later via createTextures() to turn the callback
    * rendering pipeline into a static texture.
    */
    Render &render(
    const char *label,
    const std::function<void()> &callback
    ) {
    auto id = ImGui::GetID(label);
    auto found = renders.find(id);
    if (found == renders.end()) {
    found = renders.insert_or_assign(id, Render(id, callback)).first;
    }
    auto &render = found->second;

    if (render.texture) {
    auto size = render.frameBufferSize.x == 0 ? context->IO.DisplaySize : render.frameBufferSize;

    ImGuiWindow *window = ImGui::GetCurrentWindow();
    ImVec2 pos = window->DC.CursorPos;
    const ImRect bb(pos, pos + render.size);
    ImGui::ItemSize(bb);
    if (!ImGui::ItemAdd(bb, 0)) return render;
    auto texture = (void *) (intptr_t) render.texture;
    auto uv0 = ImVec2(0, 1);
    auto uv1 = ImVec2(1, 0);
    static auto col = ImGui::GetColorU32(ImVec4(1, 1, 1, 1));
    window->DrawList->AddImage(texture, pos, pos + size, uv0, uv1, col);
    } else {
    callback();
    }

    return render;
    }

    void doRender(Render &render) {
    auto currentMS = std::chrono::duration_cast<std::chrono::milliseconds>(
    std::chrono::system_clock::now().time_since_epoch()
    ).count();

    auto diff = currentMS - render.lastRender;
    if (!render.everyMS || diff < render.everyMS) return;
    render.lastRender = currentMS;

    init();
    auto size = render.frameBufferSize.x == 0 ? context->IO.DisplaySize : render.frameBufferSize;
    if (!render.fbo) {
    render.texture = createTexture(
    size.x * context->IO.DisplayFramebufferScale.x,
    size.y * context->IO.DisplayFramebufferScale.y
    );
    render.fbo = createFramebuffer(render.texture);
    }
    auto currentContext = ImGui::GetCurrentContext();
    ImGui::SetCurrentContext(context);

    glBindFramebuffer(GL_FRAMEBUFFER, render.fbo);
    // glViewport(0, 0, render.size.x * 2, render.size.y * 2);
    // glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    context->IO.DisplaySize = ImVec2(size.x, size.y);
    // context->IO.DisplayFramebufferScale = ImVec2(2, 2);
    // ImGui_ImplSDL2_NewFrame();
    // ImGui_ImplOpenGL3_NewFrame();
    ImGui::NewFrame();

    ImGui::PushID(render.id);
    ImGui::SetNextWindowPos(ImVec2(0, 0));
    ImGui::SetNextWindowSize(context->IO.DisplaySize);
    //set WindowPadding to 0
    ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));

    ImGui::Begin("##offscreen", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
    ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse |
    ImGuiWindowFlags_NoBringToFrontOnFocus |
    ImGuiWindowFlags_NoBackground);
    ImVec2 initialCursorPos = ImGui::GetCursorPos();
    render.callback();
    auto second = ImGui::GetItemRectMax();
    render.size = ImVec2(second.x - initialCursorPos.x, second.y - initialCursorPos.y);
    ImGui::End();
    ImGui::PopID();
    ImGui::PopStyleVar();
    ImGui::Render();
    ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());

    glBindFramebuffer(GL_FRAMEBUFFER, 0); // Unbind the framebuffer
    ImGui::SetCurrentContext(currentContext);
    }
    };

    int main() {
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_GAMECONTROLLER) != 0) {
    std::cerr << "Error: " << SDL_GetError() << std::endl;
    return 2;
    }

    SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);

    SDL_Window *window = SDL_CreateWindow("test", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720,
    SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
    SDL_GLContext gl_context = SDL_GL_CreateContext(window);
    SDL_GL_MakeCurrent(window, gl_context);
    SDL_GL_SetSwapInterval(1);

    // Setup Dear ImGui context
    IMGUI_CHECKVERSION();
    ImGui::CreateContext();
    ImGuiIO &io = ImGui::GetIO();
    ImGui::StyleColorsDark();
    ImGui_ImplSDL2_InitForOpenGL(window, gl_context);
    ImGui_ImplOpenGL3_Init("#version 150");

    OffscreenRender offscreenRender;

    // Main loop
    bool running = true;
    auto maxFPS = 60;

    while (running) {
    auto startingTick = SDL_GetTicks();
    SDL_Event event;
    while (SDL_PollEvent(&event)) {
    ImGui_ImplSDL2_ProcessEvent(&event);
    if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && event.window.windowID ==
    SDL_GetWindowID(window)) {
    running = false;
    }
    if (event.type == SDL_QUIT) {
    running = false;
    }
    }

    ImGui_ImplSDL2_NewFrame();
    ImGui_ImplOpenGL3_NewFrame();

    // Start a new ImGui frame
    ImGui::NewFrame();

    // Clear the screen
    glClearColor(0.45f, 0.55f, 0.60f, 1.00f);
    glClear(GL_COLOR_BUFFER_BIT);

    // Display the texture
    ImGui::SetNextWindowPos(ImVec2(0, 0));
    ImGui::SetNextWindowSize(ImGui::GetIO().DisplaySize);
    ImGui::Begin("mainWindow", nullptr,
    ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
    ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBringToFrontOnFocus |
    ImGuiWindowFlags_NoBackground);

    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);

    ImGui::End();

    // Render ImGui
    ImGui::Render();
    ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());

    // Swap buffers
    SDL_GL_SwapWindow(window);

    // After ImGui::Render() is a good place to create the textures
    offscreenRender.createTextures();

    if ((1000 / maxFPS) > SDL_GetTicks() - startingTick) {
    SDL_Delay(1000 / maxFPS - (SDL_GetTicks() - startingTick));
    }
    }

    // Cleanup
    ImGui_ImplOpenGL3_Shutdown();
    ImGui_ImplSDL2_Shutdown();
    ImGui::DestroyContext();
    SDL_GL_DeleteContext(gl_context);
    SDL_DestroyWindow(window);
    SDL_Quit();

    return 0;
    }