imgui icon indicating copy to clipboard operation
imgui copied to clipboard

Rendering texture to an ImGui::Image() and OpenGL backend

Open katashisano opened this issue 3 months ago • 1 comments

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:

Image Image

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

katashisano avatar Nov 19 '25 07:11 katashisano

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?

ocornut avatar Nov 24 '25 16:11 ocornut