Search code examples
c++openglglfwimgui

Render triangle and ImGui window at same time?


Having this simple code:

#include <iostream>

#include <imgui/imgui.h>
#include <imgui/backends/imgui_impl_glfw.h>
#include <imgui/backends/imgui_impl_opengl3.h>
#include <glad/glad.h>
#include <GLFW/glfw3.h>

void ImguiOnUpdate(int width, int height){
    // 1.1 set window size for imgui
    ImGuiIO& io = ImGui::GetIO();
    io.DisplaySize = ImVec2(width, height);

    // 1.2 new frame
    ImGui_ImplOpenGL3_NewFrame();
    ImGui::NewFrame();

    // 1.3 imgui demo window
    static bool show = true;
    ImGui::ShowDemoWindow(&show);

    // 1.4 imgui render
    ImGui::Render();
    ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
}

int main() {
    // ===OPENGL===
    int width = 1600;
    int height = 900;
    glfwInit();
    GLFWwindow *window = glfwCreateWindow(width, height, "LearnOpenGL", nullptr, nullptr);
    if (window == nullptr) {
        std::cerr << "failed to create GLFW window\n";
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, [](GLFWwindow *_, int width, int height) {
        glViewport(0, 0, width, height);
    });
    if (!gladLoadGLLoader((GLADloadproc) glfwGetProcAddress)) {
        std::cerr << "failed to initialize GLAD\n";
    }

    uint32_t va_, vb_, ib_;

    glGenVertexArrays(1, &va_);
    glBindVertexArray(va_);

    glGenBuffers(1, &vb_);
    glBindBuffer(GL_ARRAY_BUFFER, vb_);

    float vertices[] = {
            -0.5f, -0.5f, 0.0f,
            0.5f, -0.5f, 0.0f,
            0.0f, 0.5f, 0.0f
    };
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), nullptr);

    glGenBuffers(1, &ib_);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ib_);

    int indices[] = {0, 1, 2};
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    // ===IMGUI===
    // 1.1 Imgui Context
    ImGui::CreateContext();
    ImGui::StyleColorsDark();

    // 1.2 Imgui flags
    ImGuiIO& io = ImGui::GetIO();
    io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors;
    io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos;

    // 1.3 Imgui init for opengl
    ImGui_ImplOpenGL3_Init("#version 330");

    while(!glfwWindowShouldClose(window)){
        glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        glBindVertexArray(va_);
        glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, nullptr);

        ImguiOnUpdate(width, height);

        glfwSwapBuffers(window);
        glfwPollEvents();
    }
}

Without calling ImguiOnUpdate(width, height) in main loop, it shows triangle. But when I want to render triangle AND the imgui window (as a very common scenario with imgui, where you "debug" the scene - triangle for example - by tweaking the parameters in imgui window). But when I add the ImguiOnUpdate(), the triangle just flash in the beginning and then I can only see the imgui window, but not the triangle. So how to render both (as a usual case anyway)?


Solution

  • You seem to be relying on some default shader program to render your triangle for you. Apparently your driver provides that initially, but stops doing so when you use a shader (as part of ImGUI).

    You need to compile, link and use an explicit program to render your triangle and then the rendering should work:

    static const char* vertex_shader_text =
    R"glsl(#version 400 core
    in vec3 vPos;
    void main()
    {
        gl_Position = vec4(vPos,  1.0);
    })glsl";
     
    static const char* fragment_shader_text =
    R"glsl(#version 400 core
    out vec4 outColor;
    void main()
    {
        outColor = vec4(1.0, 0.0, 0.0, 1.0);
    })glsl";
    
    GLuint loadProgram() {
        GLint status;
        GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER);
        glShaderSource(vertex_shader, 1, &vertex_shader_text, NULL);
        glCompileShader(vertex_shader);
        glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &status);
        if (status != GL_TRUE) {
            cerr << "Error compiling vertex shader" << endl;
            char buffer[512];
            glGetShaderInfoLog(vertex_shader, 512, NULL, buffer);
            cerr << buffer << endl;
        }
    
        GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
        glShaderSource(fragment_shader, 1, &fragment_shader_text, NULL);
        glCompileShader(fragment_shader);
        glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &status);
        if (status != GL_TRUE) {
            cerr << "Error compiling fragment shader" << endl;
            char buffer[512];
            glGetShaderInfoLog(fragment_shader, 512, NULL, buffer);
            cerr << buffer << endl;
        }
    
        GLuint program = glCreateProgram();
        glAttachShader(program, vertex_shader);
        glAttachShader(program, fragment_shader);
        glLinkProgram(program);
        return program;
    }
    

    Inside main:

    GLuint program = loadProgram();
    

    and inside your loop, call glUseProgram(program); before you render.

    Unrelated, you also need to call ImGui_ImplGlfw_NewFrame(); inside ImGuiOnUpdate() to process input events.