#include #include #define IMGUI_DEFINE_MATH_OPERATORS #include "imgui.h" #include "imgui_internal.h" #include "imgui_impl_sdl2.h" #include "imgui_impl_opengl3.h" #include #include // 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 callback; long long everyMS = 0; long long lastRender = 0; explicit Render( ImGuiID id, const std::function &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 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 &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::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; }