c++linuxopenglglfwimgui

OpenGL texture is black when being rendered inside imgui


Using OpenGL 4.6 with OpenGL ES 3.2 (according to glxinfo -B) on Arch Linux.

While using the official wiki snippet code for rendering an image as a texture in imgui, I ran into the issue of the image being rendered as a completely black texture. Here is my (slightly) modified helper function from the wiki that loads an image using STB and puts it into a texture.

// Simple helper function to load an image into a OpenGL texture with common settings
bool load_texture_from_file(const char* filename, GLuint* out_texture, unsigned char **out_raw_image, 
                            int* out_width, int* out_height, int* out_channels) {
    unsigned char* image_data = stbi_load(filename, out_width, out_height, out_channels, 4); 
    if (image_data == NULL) {
        printf("Failed to load image: %s\n", stbi_failure_reason());
        return false;
    }
    // Create a OpenGL texture identifier
    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);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // This is required on WebGL for non power-of-two textures
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // Same

    GLenum format;
    if (*out_channels == 3)
        format = GL_RGB;
    else if (*out_channels == 4)
        format = GL_RGBA;
    else {
        // Handle unsupported channel count
        printf("Unsupported number of channels: %d\n", *out_channels);
        stbi_image_free(image_data);
        return false;
    }
    // Upload pixels into texture
#if defined(GL_UNPACK_ROW_LENGTH) && !defined(__EMSCRIPTEN__)
        glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
#endif

    glTexImage2D(GL_TEXTURE_2D, 0, format, *out_width, *out_height, 0, format, GL_UNSIGNED_BYTE, image_data);

    GLenum error = glGetError();
    if (error != GL_NO_ERROR)
    {
        printf("OpenGL error after glTexImage2D: %x\n", error);
        stbi_image_free(image_data);
        return false;
    }

    *out_raw_image = image_data;
    *out_texture = image_texture;
    assert(image_texture != 0 && *out_texture != 0);
    return true;
}

I am somewhat convinced that my issue lies somewhere in how I'm handling converting the image to a texture so imgui can render it, because I am not getting any errors loading the image using stb_image. Surprisingly, I'm not getting any OpenGL errors either from my load_texture_from_file function. In my code, load_texture_from_file is called within display_ui which does the following:

void display_ui(const GLFWvidmode *mode) {

    static int width, height, channels;
    static char input[256] =                "";
    static bool show_original =             false;
    static bool show_preview =              false;
    static unsigned char *image_data =      NULL;
    static GLuint texture =                 0;

    ImGui::Begin("Workshop", nullptr, ImGuiWindowFlags_NoResize
     | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse);

    ImVec2 main_panel_size = ImVec2(2 * ImGui::GetContentRegionAvail().x / 3,
                                     ImGui::GetContentRegionAvail().y - 75);
    ImVec2 side_panel_size = ImVec2(ImGui::GetContentRegionAvail().x / 3,
                                     (ImGui::GetContentRegionAvail().y - 80) / 2);

    ImGui::SetWindowSize(main_panel_size);
    ImVec2 parent_cursor_start = ImGui::GetCursorPos();
    ImGui::BeginChild("Main panel", main_panel_size, true);
    ImGui::SetNextItemWidth(200.0f);
    if (ImGui::BeginTabBar("tab_bar", ImGuiTabBarFlags_none))
    {
        if (ImGui::BeginTabItem("Original image"))
        {
            if(show_original) {
                ImGui::Text("pointer = %p", texture);
                ImGui::Text("size = %d x %d", width, height);
                ImGui::Image((void*)(intptr_t)&texture, ImVec2(width, height));
            }
            ImGui::EndTabItem();
        }
    }

// ... irrelevant code is omitted for brevity
// mostly just unrelated gui stuff

           
    if (ImGuiFileDialog::Instance()->Display("ChooseFileDlgKey")) {
        if (ImGuiFileDialog::Instance()->IsOk()) {
            std::string file_path_name = ImGuiFileDialog::Instance()->GetFilePathName();
            std::string file_path = ImGuiFileDialog::Instance()->GetCurrentPath();
            sprintf(input, "%s", file_path_name.c_str());
        }
        ImGuiFileDialog::Instance()->Close();
    }
    // process file path image if user clicks button
    if(ImGui::Button("Open input file")) {
        if(!load_texture_from_file(input, &texture, &image_data, &width, &height, &channels)) {
            ImGui::OpenPopup("Error loading image");
            show_original = false;
        } else {
            show_original = true;
        }
    }
    if(ImGui::BeginPopup("Error loading image")) {
        ImGui::Text("Error loading image, select a valid path");
        if(ImGui::Button("OK")) {
            ImGui::CloseCurrentPopup();
        }
        ImGui::EndPopup();
    }
    ImGui::End();

}

In case this might be related to some improper setup of OpenGL or something along those lines, note that I call display_ui() inside the main imgui rendering while loop shown below:

void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
    glViewport(0, 0, width, height);
}

void render_gui() {
    glfwSetErrorCallback(glfw_error_callback);
    if (!glfwInit())
        return;

    // Decide GL+GLSL versions
#if defined(IMGUI_IMPL_OPENGL_ES2)
    // GL ES 2.0 + GLSL 100
    const char* glsl_version = "#version 100";
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
    glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
#elif defined(__APPLE__)
    // GL 3.2 + GLSL 150
    const char* glsl_version = "#version 150";
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);  // 3.2+ only
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);            // Required on Mac
#else
    // GL 3.0 + GLSL 130
    const char* glsl_version = "#version 130";
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
#endif
    // Create window with graphics context
    GLFWmonitor *monitor = glfwGetPrimaryMonitor();
    const GLFWvidmode *mode = glfwGetVideoMode(monitor);
    GLFWwindow* window = glfwCreateWindow(mode->width, mode->height, "Pixelify", nullptr, nullptr);
    if (window == nullptr)
        return;
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
    glfwSwapInterval(1); // Enable vsync

    // Setup Dear ImGui context
    IMGUI_CHECKVERSION();
    ImGui::CreateContext();
    ImGuiIO& io = ImGui::GetIO(); (void)io;

    // Setup Dear ImGui style
    ImGui::StyleColorsDark();

    // Setup Platform/Renderer backends
    ImGui_ImplGlfw_InitForOpenGL(window, true);
    ImGui_ImplOpenGL3_Init(glsl_version);

    // Main loop
    while (!glfwWindowShouldClose(window)) {
        glfwPollEvents();

        // Start the Dear ImGui frame
        ImGui_ImplOpenGL3_NewFrame();
        ImGui_ImplGlfw_NewFrame();
        ImGui::NewFrame();

        ImGui::SetNextWindowPos(ImVec2(0, 0));
        ImGui::SetNextWindowSize(ImVec2(mode->width, mode->height));

        display_ui(mode);

        // Rendering
        ImGui::Render();
        int display_w, display_h;
        glfwGetFramebufferSize(window, &display_w, &display_h);
        glViewport(0, 0, display_w, display_h);
        glClear(GL_COLOR_BUFFER_BIT);
        ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());

        if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
            GLFWwindow* backup_current_context = glfwGetCurrentContext();
            ImGui::UpdatePlatformWindows();
            ImGui::RenderPlatformWindowsDefault();
            glfwMakeContextCurrent(backup_current_context);
        }
        glfwSwapBuffers(window);
    }
    // Cleanup
    ImGui_ImplOpenGL3_Shutdown();
    ImGui_ImplGlfw_Shutdown();
    ImGui::DestroyContext();

    glfwDestroyWindow(window);
    glfwTerminate();
    return;
}

For context, heres the result I get while using this sample image as a test case for rendering it as a texture:

Unfortunately the image is of a nice mountain, but imgui/OpenGL disagrees :(


Solution

  • You are not supposed to pass &texture to ImGui::Image but texture:

    ImGui::Image((void*)(intptr_t)texture, ImVec2(width, height));
    

    The backend code undoes this conversion without trying to dereference it as a pointer:

    GL_CALL(glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->GetTexID()));