Skip to content

Instantly share code, notes, and snippets.

@marcj
Created August 8, 2024 22:28
Show Gist options
  • Save marcj/b3c9a054e29b0f55e8f8891655b5f043 to your computer and use it in GitHub Desktop.
Save marcj/b3c9a054e29b0f55e8f8891655b5f043 to your computer and use it in GitHub Desktop.
#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