Rendering texture to an ImGui::Image() and OpenGL backend
Version/Branch of Dear ImGui:
Version 1.92.5 WIP, Branch: master (master/docking/etc.), no modifications
Back-ends:
imgui_impl_sdl3.cpp + imgui_impl_opengl3.cpp
Compiler, OS:
Linux (Arch Linux) + GCC using CMake
Full config/build information:
In a folder with all the necessary cpp files and headers:
c++ -lSDL3 -lGL -lGLEW *.cpp -o example
Details:
I'm working on a soundboard project. I want to draw some icons for each sound by rendering multiple images to a single opengl texture, then passing that into ImGui::Image with different UVs (so a texture atlas), however, I cannot get it to work: I debugged the program a little with renderdoc and noticed the icon does render to the texture framebuffer I'm passing to ImGui::Image (see capture below). However, I don't get the icon on the ImGui::Image. I have tried everything that has come to my mind, but I cannot figure it out! (Driving me crazy at this point, it's almost 3a.m. where I live atm). I modified the code below to comply with the guidelines (although it still requires stbi_image, found here: https://github.com/nothings/stb/blob/master/stb_image.h), however, here's the original code, in case it's useful: https://pastebin.com/4gtW5kJ2
Screenshots/Video:
Minimal, Complete and Verifiable Example code:
#include <stdio.h>
#include <iostream>
#include <GL/glew.h>
#include <SDL3/SDL_opengl.h>
#include <SDL3/SDL.h>
#include "imgui.h"
#include "imgui_impl_sdl3.h"
#include "imgui_impl_opengl3.h"
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
//func decl.
int processAndDraw();
const float vertices[] = {
-1.0f, 1.0f, 0.0f, 1.0f, // top left
1.0f, 1.0f, 1.0f, 1.0f, // top right
-1.0f, -1.0f, 0.0f, 0.0f, // bottom left
1.0f, -1.0f, 1.0f, 0.0f, // bottom right
};
const unsigned int indices[] = { 0, 1, 2, 1, 3, 2 };
static GLuint textureAtlas, textureShader, fbo, vao, vbo, ibo, iconTex;
SDL_Window* window;
SDL_GLContext gl_context;
ImGuiIO* io;
ImGuiWindowFlags windowFlags;
ImVec4 clear_color = ImVec4(0.0f, 0.03f, 0.07f, 1.00f);
bool show_demo_window = false;
int main() {
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD))
{
printf("Error: SDL_Init(): %s\n", SDL_GetError());
return 1;
}
// GL 3.0 + GLSL 130
const char* glsl_version = "#version 130";
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 4);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
float main_scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay());
SDL_WindowFlags window_flags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN | SDL_WINDOW_HIGH_PIXEL_DENSITY;
window = SDL_CreateWindow("Kat Soundboard", (int)(1280 * main_scale), (int)(800 * main_scale), window_flags);
if (window == nullptr)
{
printf("Error: SDL_CreateWindow(): %s\n", SDL_GetError());
return 1;
}
gl_context = SDL_GL_CreateContext(window);
if (gl_context == nullptr)
{
printf("Error: SDL_GL_CreateContext(): %s\n", SDL_GetError());
return 1;
}
SDL_GL_MakeCurrent(window, gl_context);
SDL_GL_SetSwapInterval(1); // Enable vsync
SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
SDL_ShowWindow(window);
// Setup Dear ImGui context
IMGUI_CHECKVERSION();
ImGui::CreateContext();
io = &ImGui::GetIO();
io->ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
io->ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
ImGui::StyleColorsDark();
// Setup scaling
ImGuiStyle& style = ImGui::GetStyle();
style.ScaleAllSizes(main_scale);
style.FontScaleDpi = main_scale;
// Setup Platform/Renderer backends
ImGui_ImplSDL3_InitForOpenGL(window, gl_context);
ImGui_ImplOpenGL3_Init(glsl_version);
// Load fonts
style.FontSizeBase = 20.0f;
io->Fonts->AddFontDefault();
GLenum err;
if ((err = glewInit()) != GLEW_OK) {
std::cout << "Couldn't initialise GLEW: " << glewGetErrorString(err);
return SDL_APP_FAILURE;
}
// Image rendering OpenGL setup code
// Framebuffer texture
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glGenTextures(1, &textureAtlas);
glBindTexture(GL_TEXTURE_2D, textureAtlas);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2560, 2560, 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_NEAREST);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureAtlas, 0);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) abort();
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// Shaders
GLuint vert, frag;
const char* _fSrcContents = "#version 330 core\n"
"out vec4 fragColor;\n"
"in vec2 texCoord;\n"
"uniform sampler2D tex;\n"
"void main() {\n"
"fragColor = texture(tex, texCoord);\n"
"}";
const char* _vSrcContents = "#version 330 core\n"
"layout (location = 0) in vec2 apos;\n"
"layout (location = 1) in vec2 _texCoord;\n"
"out vec2 texCoord;\n"
"void main() {\n"
"gl_Position = vec4(apos.x, apos.y, 0.0, 1.0);\n"
"texCoord = _texCoord;\n"
"}";
vert = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vert, 1, &_vSrcContents, NULL);
glCompileShader(vert);
frag = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(frag, 1, &_fSrcContents, NULL);
glCompileShader(frag);
textureShader = glCreateProgram();
glAttachShader(textureShader, vert);
glAttachShader(textureShader, frag);
glLinkProgram(textureShader);
glValidateProgram(textureShader);
glDeleteShader(vert);
glDeleteShader(frag);
std::cout << "aqui\n";
// Vertex buffer setup
glGenVertexArrays(1, &vao);
glGenBuffers(1, &vbo);
glGenBuffers(1, &ibo);
glBindVertexArray(vao);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(0, 2 * sizeof(float), GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*) 0);
glVertexAttribPointer(1, 2 * sizeof(float), GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*) (2 * sizeof(float)));
glBindVertexArray(0);
int w, h, ch;
FILE* file = fopen("../../assets/icon.png", "r");
unsigned char* imgData = stbi_load_from_file(file, &w, &h, &ch, 0);
// OpenGL code
glGenTextures(1, &iconTex);
glBindTexture(GL_TEXTURE_2D, iconTex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
if (ch == 4) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, imgData);
} else if (ch == 3) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, imgData);
} else abort();
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glClearColor(0.2f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glViewport(0, 0, 2560, 2560);
// glUniform2f(glGetUniformLocation(textureShader, "translation"), 0.0f, 0.0f);
glBindVertexArray(vao);
glUseProgram(textureShader);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, NULL);
glBindVertexArray(0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// func returns 0 on success
while (!processAndDraw()) {}
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplSDL3_Shutdown();
ImGui::DestroyContext();
SDL_GL_DestroyContext(gl_context);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
int processAndDraw() {
SDL_Event event;
while (SDL_PollEvent(&event))
{
ImGui_ImplSDL3_ProcessEvent(&event);
if (event.type == SDL_EVENT_QUIT)
return 1;
if (event.type == SDL_EVENT_WINDOW_CLOSE_REQUESTED && event.window.windowID == SDL_GetWindowID(window))
return 1;
}
if (SDL_GetWindowFlags(window) & SDL_WINDOW_MINIMIZED)
{
SDL_Delay(10);
return 0;
}
// Start the Dear ImGui frame
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplSDL3_NewFrame();
ImGui::NewFrame();
if (show_demo_window)
ImGui::ShowDemoWindow(&show_demo_window);
{
ImGuiWindowFlags windowFlags = 0;
windowFlags |= ImGuiWindowFlags_NoTitleBar;
windowFlags |= ImGuiWindowFlags_NoResize;
windowFlags |= ImGuiWindowFlags_NoMove;
windowFlags |= ImGuiWindowFlags_NoBackground;
windowFlags |= ImGuiWindowFlags_NoCollapse;
ImGui::Begin(" ", nullptr, windowFlags);
ImGui::SetWindowPos(ImVec2(.0f, .0f));
ImGui::SetWindowSize(ImVec2(io->DisplaySize.x, io->DisplaySize.y));
ImGui::Text("katsb (かつボード)");
ImGui::Checkbox("Demo Window", &show_demo_window);
ImGui::PushStyleVar(ImGuiStyleVar_ImageBorderSize, 4.0f);
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.45f, 0.9f, 1.0f, 1.0f));
ImGui::Image(textureAtlas, ImVec2(100.0f, 100.0f)); //, textureUvs.first, textureUvs.second);
ImGui::PopStyleColor();
ImGui::PopStyleVar();
ImGui::End();
}
// Rendering
ImGui::Render();
glViewport(0, 0, (int)io->DisplaySize.x, (int)io->DisplaySize.y);
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());
SDL_GL_SwapWindow(window);
return 0;
}
Edit: during debugging with renderdoc, I moved the image rendering code (lines ~126-139) into the main loop. Edit2: Came up with another solution, would still be interesting to see if this idea is possible tho, and how to make it
I only skimmed at the code and I am not an OpenGL expert but I cannot see what's wrong right away.
(1) Notice that your blurb calling glDrawElement() is using iconTex, and your ImGui::Image() is using textureAtlas.
(2) Is the RenderDoc screenshot from the draw calls emitted in ImGui_ImplOpenGL3_RenderDrawData() ?
(3) If you copy the code from https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples#example-for-opengl-users and try to use it do you get the same issue?