Created
August 8, 2024 22:28
-
-
Save marcj/b3c9a054e29b0f55e8f8891655b5f043 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment