Search code examples
c++openglglsltexturesglfw

OpenGL binding multiple textures not working


Bear in mind that I have openGL 3 and glsl version 1.30, therefore, I don't have dynamic indexing nor glBindTextureUnit(). I have seen people batch render multiple textures in one draw call by simply activating the texture slot, binding the texture then setting the uniform, like so:

glUniform1i(glGetUniformLocation(program, "u_texture0"), 0);
glUniform1i(glGetUniformLocation(program, "u_texture1"), 1);

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture0);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture1);

However, when I do this:

#include <GL/glew.h>
#include <GLFW/glfw3.h>

#define STB_IMAGE_IMPLEMENTATION
#include "stb/stb_image.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

char* readFile(const char filePath[])
{
    FILE* file;
    file = fopen(filePath, "r");

    if(file == NULL)
    {
        perror("unable to load file");
        exit(1);
    }

    fseek(file, 0l, SEEK_END);

    char* buffer = (char*)malloc(ftell(file));

    rewind(file);

    char c;
    unsigned int i = 0;

    while((c = fgetc(file)) != EOF)
        buffer[i++] = c;

    fclose(file);

    return buffer;
}

GLuint loadShader(const char* src, GLenum type)
{
    GLuint shader = glCreateShader(type);

    glShaderSource(shader, 1, &src, NULL);
    glCompileShader(shader);

    // shader error handling
    int result;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &result);
    if(result == GL_FALSE)
    {
        int length;
        glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &length);
        char* message = (char*)alloca(length * sizeof(char));
        glGetShaderInfoLog(shader, length, &length, message);
        puts(message);
    }

    return shader;
}
GLuint loadTexture(const char* filePath)
{
    GLuint texture;
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);

    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);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    unsigned char* buffer;
    int width, height;

    stbi_set_flip_vertically_on_load(1);
    buffer = stbi_load(filePath, &width, &height, NULL, STBI_rgb_alpha);

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);

    if(buffer)
        stbi_image_free(buffer);

    return texture;
}

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

int main()
{
    // initialising GLFW
    if(!glfwInit())
    {
        perror("error with glfw!");
        exit(1);
    }

    // window creation
    GLFWwindow* window;

    window = glfwCreateWindow(800, 500, "window", NULL, NULL);
    glfwSetWindowSizeCallback(window, resetViewport);

    // binding renderring context
    glfwMakeContextCurrent(window);

    // initialising glew
    if(glewInit() != GLEW_OK)
        perror("error with glee!");

    // enabling blend
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    // creating program and shaders
    GLuint program = glCreateProgram();

    char* vertexShaderSource = readFile("vertex.glsl");
    char* fragmentShaderSource = readFile("fragment.glsl");
    GLuint vertexShader = loadShader(vertexShaderSource, GL_VERTEX_SHADER);
    GLuint fragmentShader = loadShader(fragmentShaderSource, GL_FRAGMENT_SHADER);

    glAttachShader(program, vertexShader);
    glAttachShader(program, fragmentShader);

    // deleting shaders
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);
    free(vertexShaderSource);
    free(fragmentShaderSource);

    // compiling and binding program
    glLinkProgram(program);
    glUseProgram(program);

    // creating vertex array
    GLuint vao;
    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);

    // creating vertex buffer
    GLuint vbo;
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);

    // vertex buffer layout
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), NULL);
    glBindAttribLocation(program, 0, "a_position");
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (const void*)(2 * sizeof(float)));
    glBindAttribLocation(program, 1, "a_textureCoordinate");
    glEnableVertexAttribArray(2);
    glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (const void*)(4 * sizeof(float)));
    glBindAttribLocation(program, 2, "a_index");

    // creating texture
    GLuint texture0 = loadTexture("image0.jpeg");
    GLuint texture1 = loadTexture("image1.jpeg");

    // vertex data
    float vertexes[] =
    {
    //   x      y       u     v         i (index)
        -1.0f, -1.0f,   0.0f, 0.0f,     0.0f,
        -1.0f,  0.0f,   0.0f, 1.0f,     0.0f,
         0.0f,  0.0f,   1.0f, 1.0f,     0.0f,
         0.0f, -1.0f,   1.0f, 0.0f,     0.0f,

         0.0f,  0.0f,   0.0f, 0.0f,     1.0f,
         0.0f, +1.0f,   0.0f, 1.0f,     1.0f,
        +1.0f, +1.0f,   1.0f, 1.0f,     1.0f,
        +1.0f,  0.0f,   1.0f, 0.0f,     1.0f,
    };

    // unbinding just incase
    glBindVertexArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    // rebinding
    glBindVertexArray(vao);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);

    // binding texture to texture slot

    glUniform1i(glGetUniformLocation(program, "u_texture0"), 0);
    glUniform1i(glGetUniformLocation(program, "u_texture1"), 1);
    
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texture0);
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, texture1);

    // sending data
    glBufferData(GL_ARRAY_BUFFER, 8 * 5 * sizeof(float), vertexes, GL_STATIC_DRAW);

    glLinkProgram(program);

    while(!glfwWindowShouldClose(window))
    {
        glfwPollEvents();

        // clear screen
        glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        // draw call
        glDrawArrays(GL_QUADS, 0, 8);

        // updating window's buffer
        glfwSwapBuffers(window);
    }

    // error logging
    GLenum error = glGetError();
    if(error != GL_NO_ERROR)
        printf("error: %u\n", error);

    // termination
    glDeleteTextures(1, &texture0);
    glDeleteTextures(1, &texture1);
    glDeleteBuffers(1, &vbo);
    glDeleteVertexArrays(1, &vao);
    glDeleteProgram(program);

    glfwTerminate();
}

With the fragment shader code being:

#version 130

in vec2 v_textureCoordinate;
flat in uint v_index;

uniform sampler2D u_texture0;
uniform sampler2D u_texture1;
uniform sampler2D u_texture2;
uniform sampler2D u_texture3;
uniform sampler2D u_texture4;
uniform sampler2D u_texture5;
uniform sampler2D u_texture6;
uniform sampler2D u_texture7;

out vec4 o_color;

void main()
{
    // dynamic indexing is not suppourted in GLSL version 1.30 therefore the most suitable solution is to switch case between them
    switch(v_index)
    {
        case 0u:
            o_color = texture2D(u_texture0, v_textureCoordinate);
            return;
        case 1u:
            o_color = texture2D(u_texture1, v_textureCoordinate);
            return;
        case 2u:
            o_color = texture2D(u_texture2, v_textureCoordinate);
            return;
        case 3u:
            o_color = texture2D(u_texture3, v_textureCoordinate);
            return;
        case 4u:
            o_color = texture2D(u_texture4, v_textureCoordinate);
            return;
        case 5u:
            o_color = texture2D(u_texture5, v_textureCoordinate);
            return;
        case 6u:
            o_color = texture2D(u_texture6, v_textureCoordinate);
            return;
        case 7u:
            o_color = texture2D(u_texture7, v_textureCoordinate);
            return;
        default:
            return;
    }
}

and the vertex shader code being:

#version 130

attribute vec4 a_position;
attribute vec2 a_textureCoordinate;
attribute float a_index;

out vec2 v_textureCoordinate;
flat out uint v_index;

void main()
{
    gl_Position = a_position;
    v_textureCoordinate = a_textureCoordinate;
    v_index = uint(a_index);
}

It renders both quads with the texture texture0, Am I doing something wrong? I am getting the impression that binding the second texture is unbinding the first texture. What am I missing?


Solution

  • glBindAttribLocation() has to be called before shader program linking to have any effect:

    GLuint prog = glCreateProgram();
    AttachShader( prog, GL_VERTEX_SHADER, vert );
    AttachShader( prog, GL_FRAGMENT_SHADER, frag );
    glBindAttribLocation(prog, 0, "a_position");
    glBindAttribLocation(prog, 1, "a_textureCoordinate");
    glBindAttribLocation(prog, 2, "a_index");
    glLinkProgram( prog );
    CheckStatus( prog, false );
    glUseProgram( prog );
    

    All together:

    screenshot of output

    #include <GL/glew.h>
    #include <GLFW/glfw3.h>
    #include <cstdio>
    #include <cstdlib>
    #include <iostream>
    
    void CheckStatus( GLuint obj, bool isShader )
    {
        GLint status = GL_FALSE, log[ 1 << 11 ] = { 0 };
        ( isShader ? glGetShaderiv : glGetProgramiv )( obj, isShader ? GL_COMPILE_STATUS : GL_LINK_STATUS, &status );
        if( status == GL_TRUE ) return;
        ( isShader ? glGetShaderInfoLog : glGetProgramInfoLog )( obj, sizeof( log ), NULL, (GLchar*)log );
        std::cerr << (GLchar*)log << "\n";
        std::exit( EXIT_FAILURE );
    }
    
    void AttachShader( GLuint program, GLenum type, const char* src )
    {
        GLuint shader = glCreateShader( type );
        glShaderSource( shader, 1, &src, NULL );
        glCompileShader( shader );
        CheckStatus( shader, true );
        glAttachShader( program, shader );
        glDeleteShader( shader );
    }
    
    const char* const vert = R"GLSL(
    #version 130
    
    attribute vec4 a_position;
    attribute vec2 a_textureCoordinate;
    attribute float a_index;
    
    out vec2 v_textureCoordinate;
    flat out uint v_index;
    
    void main()
    {
        gl_Position = a_position;
        v_textureCoordinate = a_textureCoordinate;
        v_index = uint(a_index);
    }    
    )GLSL";
    
    const char* const frag = R"GLSL(
    #version 130
    
    in vec2 v_textureCoordinate;
    flat in uint v_index;
    
    uniform sampler2D u_texture0;
    uniform sampler2D u_texture1;
    uniform sampler2D u_texture2;
    uniform sampler2D u_texture3;
    uniform sampler2D u_texture4;
    uniform sampler2D u_texture5;
    uniform sampler2D u_texture6;
    uniform sampler2D u_texture7;
    
    out vec4 o_color;
    
    void main()
    {
        // dynamic indexing is not suppourted in GLSL version 1.30 therefore the most suitable solution is to switch case between them
        switch(v_index)
        {
            case 0u:
                o_color = texture2D(u_texture0, v_textureCoordinate);
                return;
            case 1u:
                o_color = texture2D(u_texture1, v_textureCoordinate);
                return;
            case 2u:
                o_color = texture2D(u_texture2, v_textureCoordinate);
                return;
            case 3u:
                o_color = texture2D(u_texture3, v_textureCoordinate);
                return;
            case 4u:
                o_color = texture2D(u_texture4, v_textureCoordinate);
                return;
            case 5u:
                o_color = texture2D(u_texture5, v_textureCoordinate);
                return;
            case 6u:
                o_color = texture2D(u_texture6, v_textureCoordinate);
                return;
            case 7u:
                o_color = texture2D(u_texture7, v_textureCoordinate);
                return;
            default:
                return;
        }
    }
    )GLSL";
    
    GLuint loadTexture(const unsigned char* image, int w, int h)
    {
        GLuint texture;
        glGenTextures(1, &texture);
        glBindTexture(GL_TEXTURE_2D, texture);
        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);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, image);
    
        return texture;
    }
    
    void resetViewport(GLFWwindow* window, int width, int height)
    {
        glViewport(0, 0, width, height);
    }
    
    int main()
    {
        // initialising GLFW
        if(!glfwInit())
        {
            perror("error with glfw!");
            exit(1);
        }
    
        // window creation
        GLFWwindow* window;
    
        window = glfwCreateWindow(800, 500, "window", NULL, NULL);
        glfwSetWindowSizeCallback(window, resetViewport);
    
        // binding renderring context
        glfwMakeContextCurrent(window);
    
        // initialising glew
        if(glewInit() != GLEW_OK)
            perror("error with glee!");
    
        // enabling blend
        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    
        // creating program and shaders
        GLuint prog = glCreateProgram();
        AttachShader( prog, GL_VERTEX_SHADER, vert );
        AttachShader( prog, GL_FRAGMENT_SHADER, frag );
        glBindAttribLocation(prog, 0, "a_position");
        glBindAttribLocation(prog, 1, "a_textureCoordinate");
        glBindAttribLocation(prog, 2, "a_index");
        glLinkProgram( prog );
        CheckStatus( prog, false );
        glUseProgram( prog );      
        
        // creating vertex array
        GLuint vao;
        glGenVertexArrays(1, &vao);
        glBindVertexArray(vao);
    
        // creating vertex buffer
        GLuint vbo;
        glGenBuffers(1, &vbo);
        glBindBuffer(GL_ARRAY_BUFFER, vbo);
    
        // vertex buffer layout
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), NULL);
        glEnableVertexAttribArray(1);
        glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (const void*)(2 * sizeof(float)));
        glEnableVertexAttribArray(2);
        glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (const void*)(4 * sizeof(float)));
    
        // creating texture
        const unsigned char image1[] = { 255, 0, 0, 255 };
        GLuint texture0 = loadTexture( image1, 1, 1 );
        const unsigned char image2[] = { 0, 255, 0, 255 };
        GLuint texture1 = loadTexture( image2, 1, 1 );
    
        // vertex data
        float vertexes[] =
        {
        //   x      y       u     v         i (index)
            -1.0f, -1.0f,   0.0f, 0.0f,     0.0f,
            -1.0f,  0.0f,   0.0f, 1.0f,     0.0f,
             0.0f,  0.0f,   1.0f, 1.0f,     0.0f,
             0.0f, -1.0f,   1.0f, 0.0f,     0.0f,
    
             0.0f,  0.0f,   0.0f, 0.0f,     1.0f,
             0.0f, +1.0f,   0.0f, 1.0f,     1.0f,
            +1.0f, +1.0f,   1.0f, 1.0f,     1.0f,
            +1.0f,  0.0f,   1.0f, 0.0f,     1.0f,
        };
    
        // unbinding just incase
        glBindVertexArray(0);
        glBindBuffer(GL_ARRAY_BUFFER, 0);
    
        // rebinding
        glBindVertexArray(vao);
        glBindBuffer(GL_ARRAY_BUFFER, vbo);
    
        // binding texture to texture slot
    
        glUniform1i(glGetUniformLocation(prog, "u_texture0"), 0);
        glUniform1i(glGetUniformLocation(prog, "u_texture1"), 1);
        
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, texture0);
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, texture1);
    
        // sending data
        glBufferData(GL_ARRAY_BUFFER, 8 * 5 * sizeof(float), vertexes, GL_STATIC_DRAW);
    
        while(!glfwWindowShouldClose(window))
        {
            glfwPollEvents();
    
            // clear screen
            glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
            glClear(GL_COLOR_BUFFER_BIT);
    
            // draw call
            glDrawArrays(GL_QUADS, 0, 8);
    
            // updating window's buffer
            glfwSwapBuffers(window);
        }
    
        // error logging
        GLenum error = glGetError();
        if(error != GL_NO_ERROR)
            printf("error: %u\n", error);
    
        // termination
        glDeleteTextures(1, &texture0);
        glDeleteTextures(1, &texture1);
        glDeleteBuffers(1, &vbo);
        glDeleteVertexArrays(1, &vao);
        glDeleteProgram(prog);
    
        glfwTerminate();
    }