Search code examples
javaopengltextureslwjgl

OpenGL Texture Renders black eventhough its data is not null


I have written a working 2D Batch Renderer in LWJGL OpenGL that works with colours. When I added Texture support to it, The Quad Rendered is always black. The data is read by STBImage just fine. But it still renders black. Here's the shaders: Vertex: Fragment:

#version 450 core

in vec4 v_Colour;
in vec2 v_TexCoord;
in float v_TexIndex;

uniform sampler2D u_TextureSlots[16];

void main() {
    int index = int(v_TexIndex);
    gl_FragColor = texture(u_TextureSlots[index], v_TexCoord) * v_Colour;
}

The Renderer's Submit method

public static void Submit(Texture texture, Rect drawRect, Rect uvRect, Vector4f colour) {
    if (hasRoom) {
        texture.BindSlot(textureIndex + 1);
        int index = spriteCount++;
        int offset = index * 6 * VERTEX_COUNT;
        StoreData(offset, textureIndex++, drawRect, uvRect, colour);
        if (textureIndex >= 16) hasRoom = false;
        if (spriteCount >= maxBatchSize) hasRoom = false;
    }
}
private static void StoreData(int offset, int textureIndex, Rect drawRect, Rect uvRect, Vector4f colour) {
    float x1 = drawRect.x;
    float y1 = drawRect.y;
    float x2 = drawRect.x + drawRect.width;
    float y2 = drawRect.y + drawRect.height;
    float u1 = uvRect.x;
    float v1 = uvRect.y;
    float u2 = uvRect.x + uvRect.width;
    float v2 = uvRect.y + uvRect.height;
    for (int vertex = 0; vertex < 6; vertex++) {
        int currentIndex = offset + vertex * VERTEX_COUNT;
        float x = vertex == 0 || vertex == 3 || vertex == 5 ? x1 : x2;
        float y = vertex == 0 || vertex == 3 || vertex == 1 ? y1 : y2;
        float u = x == x1 ? u1 : u2;
        float v = y == y1 ? v1 : v2;
        vertices[currentIndex] = x;
        vertices[currentIndex + 1] = y;
        vertices[currentIndex + 2] = u;
        vertices[currentIndex + 3] = v;
        vertices[currentIndex + 4] = colour.x;
        vertices[currentIndex + 5] = colour.y;
        vertices[currentIndex + 6] = colour.z;
        vertices[currentIndex + 7] = colour.w;
        vertices[currentIndex + 8] = textureIndex;
    }
}

Texture:

public Texture(String filepath) {
        IntBuffer width = BufferUtils.createIntBuffer(1);
        IntBuffer height = BufferUtils.createIntBuffer(1);
        IntBuffer channels = BufferUtils.createIntBuffer(1);

        ByteBuffer data = STBImage.stbi_load(filepath, width, height, channels, 0);
        m_Width = width.get();
        m_Height = height.get();
        m_Channels = channels.get();

        m_RendererID = glCreateTextures(GL_TEXTURE_2D);
        glBindTexture(GL_TEXTURE_2D, m_RendererID);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_Width, m_Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
        glTextureParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTextureParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
        assert data != null : "Image " + filepath + " doesn't exist";
        STBImage.stbi_image_free(data);
    }

Solution

  • The behavior of the fragment shader is undefined, because of:

    int index = int(v_TexIndex);
    ... u_TextureSlots[index]...;
    

    u_TextureSlots is an array of samplers and v_TexIndex is a fragment shader input, thus index is not a Dynamically uniform expression.
    Note, undefined behavior means that it may work, but the specification does not guarantee that it will work. The behavior will be different on various hardware.

    See GLSL version 4.60 (most recent) (from OpenGL Shading Language 4.60 Specification - 4.1.7. Opaque Types):
    (Of course this rule applies to GLSL version 4.50, too)

    When aggregated into arrays within a shader, these types can only be indexed with a dynamically uniform expression, or texture lookup will result in undefined values.


    I recommend to use sampler2DArray (see Sampler) rather than an array of sampler2D. When you use a sampler2DArray, then you don't need any indexing at all, because the "index" is encoded in the 3rd component of the texture coordinate at texture lookup (see Texture).


    Furthermore, either specify the texture target and use glTexParameter

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    

    or the texture object and use glTextureParameteri

    glTextureParameteri(m_RendererID, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTextureParameteri(m_RendererID, GL_TEXTURE_MAG_FILTER, GL_NEAREST);