ocornut / imgui

Dear ImGui: Bloat-free Graphical User interface for C++ with minimal dependencies
MIT License
61.45k stars 10.34k forks source link

Using shaders with ImGui::Image in separate functions. #7682

Closed cursedastronaut closed 5 months ago

cursedastronaut commented 5 months ago

Version/Branch of Dear ImGui:

Version 1.90.0, Branch: master (master/docking/etc.)

Back-ends:

imgui_impl_glfw.cpp + imgui_impl_opengl3.cpp

Compiler, OS:

Linux + GCC

Full config/build information:

Dear ImGui 1.90.3 (19030)
--------------------------------
sizeof(size_t): 8, sizeof(ImDrawIdx): 2, sizeof(ImDrawVert): 20
define: __cplusplus=201103
define: __linux__
define: __GNUC__=11
--------------------------------
io.BackendPlatformName: imgui_impl_glfw
io.BackendRendererName: imgui_impl_opengl3
io.ConfigFlags: 0x00000003
 NavEnableKeyboard
 NavEnableGamepad
io.ConfigInputTextCursorBlink
io.ConfigWindowsResizeFromEdges
io.ConfigMemoryCompactTimer = 60.0
io.BackendFlags: 0x0000000E
 HasMouseCursors
 HasSetMousePos
 RendererHasVtxOffset
--------------------------------
io.Fonts: 1 fonts, Flags: 0x00000000, TexSize: 512,64
io.DisplaySize: 1280.00,720.00
io.DisplayFramebufferScale: 1.00,1.00
--------------------------------
style.WindowPadding: 8.00,8.00
style.WindowBorderSize: 1.00
style.FramePadding: 4.00,3.00
style.FrameRounding: 0.00
style.FrameBorderSize: 0.00
style.ItemSpacing: 8.00,4.00
style.ItemInnerSpacing: 4.00,4.00

Details:

My Issue/Question:

I have a texture I draw every frame and generate once in a while (at every modification) using glGenTextures. Since it lags a lot, I tried to edit the texture directly on the GPU, using a shader. I'm pretty new to using shaders, and I never used one with ImGui. I wonder if I applied the shaders correctly considering the way ImGui draws?

Drawing code:

if (generateOnce) {
    glGenTextures(1, &tabs[current_tab].textureID);
    glBindTexture(GL_TEXTURE_2D, tabs[current_tab].textureID);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tabs[current_tab].post.img.r.size(), tabs[current_tab].post.img.r[0].size(), 0, GL_RGBA, GL_UNSIGNED_BYTE, tabs[current_tab].imageData.data());
    generateOnce = false;
    // Set texture parameters (you may need to adjust these based on your requirements)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

    // Use the shader program, bind the texture, and draw a quad
    glBindTexture(GL_TEXTURE_2D, tabs[current_tab].textureID);
}
glUseProgram(shaderProgram);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tabs[current_tab].textureID);
glUniform1i(glGetUniformLocation(shaderProgram, "inputTexture"), tabs[current_tab].textureID);
std::cout << tabs[current_tab].textureID << std::endl;
ImGui::Image(
    (void*)(intptr_t)tabs[current_tab].textureID,
    ImVec2(tabs[current_tab].width * tabs[current_tab].zoom, tabs[current_tab].height * tabs[current_tab].zoom)
);

generateOnce is now NEVER SET to true as I wanted to try editing the texture using shaders.

(the shaders are supposed to turn the whole image black, just to test) Here is the shader code:

const char* vertexShaderSource = R"(
    #version 330 core
    layout (location = 0) in vec2 position;
    layout (location = 1) in vec2 texCoord;
    out vec2 TexCoord;
    void main()
    {
        gl_Position = vec4(position, 0.0, 1.0);
        TexCoord = texCoord;
    }
)";

// Fragment shader source (the one you provided)
const char* fragmentShaderSource = R"(
    #version 330 core
    uniform sampler2D inputTexture;
    in vec2 TexCoord;
    out vec4 fragColor;
    void main()
    {
        vec4 texColor = texture(inputTexture, TexCoord);
        fragColor = vec4(0.0, 0.0, 0.0, texColor.a);
    }
)";

void checkCompileErrors(GLuint shader, std::string type)
{
    GLint success;
    GLchar infoLog[1024];
    if(type != "PROGRAM")
    {
        glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
        if(!success)
        {
            glGetShaderInfoLog(shader, 1024, NULL, infoLog);
            std::cout << "ERROR::SHADER_COMPILATION_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
        }
    }
    else
    {
        glGetProgramiv(shader, GL_LINK_STATUS, &success);
        if(!success)
        {
            glGetProgramInfoLog(shader, 1024, NULL, infoLog);
            std::cout << "ERROR::PROGRAM_LINKING_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
        }
    }
}

void VisualIDK::initShaders() {
    GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
    glCompileShader(vertexShader);
    checkCompileErrors(vertexShader, "VERTEX");

    GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
    glCompileShader(fragmentShader);
    checkCompileErrors(fragmentShader, "FRAGMENT");

    GLuint shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);
    checkCompileErrors(shaderProgram, "PROGRAM");

    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);
}

To test, I have a white image, and it never changes despite the shaders being called. What have I done wrong ? Also, initShaders in called in VisualIDK s constructor.

Screenshots/Video:

image (It always stays white)

Minimal, Complete and Verifiable Example code:


GLuint shaderProgram;

const char* vertexShaderSource = R"(
    #version 330 core
    layout (location = 0) in vec2 position;
    layout (location = 1) in vec2 texCoord;
    out vec2 TexCoord;
    void main()
    {
        gl_Position = vec4(position, 0.0, 1.0);
        TexCoord = texCoord;
    }
)";

// Fragment shader source (the one you provided)
const char* fragmentShaderSource = R"(
    #version 330 core
    uniform sampler2D inputTexture;
    in vec2 TexCoord;
    out vec4 fragColor;
    void main()
    {
        vec4 texColor = texture(inputTexture, TexCoord);
        fragColor = vec4(0.0, 0.0, 0.0, texColor.a);
    }
)";

void checkCompileErrors(GLuint shader, std::string type)
{
    GLint success;
    GLchar infoLog[1024];
    if(type != "PROGRAM")
    {
        glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
        if(!success)
        {
            glGetShaderInfoLog(shader, 1024, NULL, infoLog);
            std::cout << "ERROR::SHADER_COMPILATION_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
        }
    }
    else
    {
        glGetProgramiv(shader, GL_LINK_STATUS, &success);
        if(!success)
        {
            glGetProgramInfoLog(shader, 1024, NULL, infoLog);
            std::cout << "ERROR::PROGRAM_LINKING_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
        }
    }
}

void initShaders() {
    GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
    glCompileShader(vertexShader);

    GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
    glCompileShader(fragmentShader);

    shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);

    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);
}

int textureID;
bool generateOnce = true;
//in your main function
initShaders();
//in your main loop
ImGui::Begin("Image");
int width = 0; //edit that
int height = 0; //edit that
if (generateOnce) {
    glGenTextures(1, textureID);
    glBindTexture(GL_TEXTURE_2D, tabs[current_tab].textureID);
    //complete to load your texture
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, ..., ..., 0, GL_RGBA, GL_UNSIGNED_BYTE, ...);
    generateOnce = false;
    // Set texture parameters (you may need to adjust these based on your requirements)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

    // Use the shader program, bind the texture, and draw a quad
    glBindTexture(textureID);
}
glUseProgram(shaderProgram);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tabs[current_tab].textureID);
glUniform1i(glGetUniformLocation(shaderProgram, "inputTexture"), textureID);
ImGui::Image(
        (void*)(intptr_t)textureID,
        ImVec2(width, height)
);

Whole code is accessible here.

EDIT: Using AddCallback applies the shader but to the whole screen (which I don't want). Maybe I did not understand how to use it...?

ocornut commented 5 months ago

This is not a OpenGL question per-se. I think you need to render to texture? https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples#rendering-your-game-scene-into-a-texture Try using RenderDoc to debug your rendering steps.

glUseProgram(shaderProgram);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tabs[current_tab].textureID);
glUniform1i(glGetUniformLocation(shaderProgram, "inputTexture"), textureID);

This is not having any effect, there are no draw calls.

ImGui::Image() doesn't DIRECTLY generate a draw call, it emits commands that will be turned into draw calls by ImGui_ImplOpenGL3_RenderDrawData().

You need to be following a tutorial to render to texture with OpenGL, e.g. this one http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-14-render-to-texture/