The function that loads the image from a file and creates the texture (straight from the ImGui docs):
bool LSUtilities::LoadTextureFromFile(const char* filename, GLuint* out_texture, int* out_width, int* out_height) {
int image_width = 0;
int image_height = 0;
unsigned char* image_data = stbi_load(filename, &image_width, &image_height, NULL, 4);
if (image_data == NULL)
return false;
GLuint image_texture;
glGenTextures(1, &image_texture);
glBindTexture(GL_TEXTURE_2D, image_texture);
// Setup filtering parameters for display
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// Upload pixels into texture
#if defined(GL_UNPACK_ROW_LENGTH) && !defined(__EMSCRIPTEN__)
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
#endif
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image_width, image_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image_data);
stbi_image_free(image_data);
*out_texture = image_texture;
*out_width = image_width;
*out_height = image_height;
glBindTexture(GL_TEXTURE_2D, 0);
return true;
The code that uses this function to create the image preview (this is inside the render loop):
int texture_preview_width = 0;
int texture_preview_height = 0;
GLuint texture_preview = 0;
bool ret = LSUtilities::LoadTextureFromFile(Resources.textures[selectedItemID].c_str(), &texture_preview, &texture_preview_width, &texture_preview_height);
IM_ASSERT(ret);
ImVec2 previewSize = {
ImGui::GetContentRegionAvailWidth(),
(texture_preview_height * ImGui::GetContentRegionAvailWidth()) / texture_preview_width
ImGui::Image((ImTextureID)texture_preview, previewSize);
I've tried deleting the texture with glDeleteTextures(1, &texture_preview)
after the ImGui::Image
call but the code above is inside the render loop and that appears to just delete the texture before ImGui can load it.
You should load the texture from outside the main loop (during init or by demand later), save the textureId somewhere and render it in mainloop.
If you create the texture again and again on each frame you are exhausting the memory, and ImGUI defer all drawings to the end of the Frame so you must not delete the texture inside the mainloop before ImGui renders.
selected = get_user_selection_on_some_event(...)
// Check if image selection has actually changed (by the euser) and update cache ONLY in that case
if selected != selected_cache:
selected_cache = selected
textureId_cache = load_texture_from_disk(selected_cache)
// render the cached texture if any
if textureId_cache:
render_texture(textureId_cache)
if textureId_cache:
free_texture( textureId_cache )
There is no problem calling opengl from mainloop, the problem is to load the texture each time.
PS: In realworld applications you should load your assets in a different thread for optimal performance anyway.
I think I sorted it. I moved the following code to outside the main loop:
GLuint texture_preview;
glGenTextures(1, &texture_preview);
glBindTexture(GL_TEXTURE_2D, texture_preview);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
and then in the mainloop:
int image_width = 0;
int image_height = 0;
unsigned char* image_data = stbi_load(Resources.textures[selectedItemID].c_str(), &image_width, &image_height, NULL, 4);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image_width, image_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image_data);
stbi_image_free(image_data);
ImVec2 previewSize = {
ImGui::GetContentRegionAvailWidth(),
(image_height * ImGui::GetContentRegionAvailWidth()) / image_width
ImGui::Image((ImTextureID)texture_preview, previewSize);
I then unbind and delete at the end outside the render loop. Fixes the memory leak. However, the memory allocated isn't freed when I select something that isn't an image. Not a huge concern as memory allocated is less than 1MB usually but still feels inefficient.
How would I defer the stbi_load
call to outside the main loop if it needs a path?
#4628 (comment)
This will not put it outside the mainloop which is not required, but it will use it only as needed, without unnecessary reloading. To move it out of the mainloop you need another thread. But you can leave it single threaded by now, this is really basic stuff.