I was trying to batch quads with the same texture and blend mode into the single draw call using glDrawElementsInstanced. As long as I use just a position, texture coordinates, and projection matrix as inputs to my shader, everything works just fine. But if I add color as another input to the shader, storing it in at the instancesVBO, triangles are getting "corrupted", and most of the quads I`m trying to render are rendered wrong. What is interesting, not all of them are.

My instancedVBO setup is as follows:

        glGenBuffers(1, &instanceMatrixVBO);
        glBindBuffer(GL_ARRAY_BUFFER, instanceMatrixVBO);
        glBufferData(GL_ARRAY_BUFFER, MAX_BATCH_SIZE * sizeof(QuadData), nullptr, GL_STATIC_DRAW);

        glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)0);
        glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(sizeof(glm::vec4)));
        glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(2 * sizeof(glm::vec4)));
        glVertexAttribPointer(5, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(3 * sizeof(glm::vec4)));
        glVertexAttribPointer(6, 4, GL_FLOAT, GL_FALSE, sizeof(glm::vec4), (void*)(4 * sizeof(glm::vec4)));

        glVertexAttribDivisor(2, 1);
        glVertexAttribDivisor(3, 1);
        glVertexAttribDivisor(4, 1);
        glVertexAttribDivisor(5, 1);
        glVertexAttribDivisor(6, 1);

        glBindBuffer(GL_ARRAY_BUFFER, 0);


        struct QuadData
            glm::mat4 position;
            glm::vec4 color;

VBO data update:

    glBindBuffer(GL_ARRAY_BUFFER, instanceMatrixVBO);

    glBufferSubData(GL_ARRAY_BUFFER, 0, std::min(m_test_batch.size(), MAX_BATCH_SIZE) * (sizeof(QuadData)), m_test_batch.data());

    glBindBuffer(GL_ARRAY_BUFFER, 0);

MAX_BATCH_SIZE is a constexpr set to 100 000, and, as far as I know, is not exceeded at any point.

The shader, as far as I know, works just fine, but just in case:

    const char *vertexShaderSource = ""
        "#version 330 core\n"
        "layout (location = 0) in vec3 aPos;\n"
        "layout (location = 1) in vec2 aTexCoord;\n"
        "layout (location = 6) in vec4 aColor;\n"
        "layout (location = 2) in mat4 aTransformMatrix;\n"
        "out vec2 oTexCoord;\n"
        "out vec4 oColor;\n"
        "uniform mat4 uModel;\n"
        "void main()\n"
        "   gl_Position = aTransformMatrix * vec4(aPos, 1.0);\n"
        "   oColor = aColor;"
        "   oTexCoord = aTexCoord;"
  • \$\begingroup\$ That symptom makes it sound like the strides you've provided for the data are wrong, so the GPU looks at the wrong bytes in the data stream when trying to get the position. Sometimes it lines up and you get a quad drawn correctly, but then it drifts out of alignment again like a slightly out of tune piano note making a beat frequency against a tuning fork's reference tone. Double-check your data sizes and offsets/strides. You might have a simple copy-paste error somewhere. \$\endgroup\$
    – DMGregory
    Jul 21, 2023 at 11:06

1 Answer 1


Your attribute pointers do not match the layout of the data you're buffering.

The stride parameter specifies how many bytes to step forward to get from the start of data for element 1 to the start of data for element 2, and so on. Your code claims that's 64 bytes for the first 4 attributes (sizeof(glm::mat4)) but only 16 bytes for the last attribute (sizeof(glm::vec4)), so that already doesn't make sense. The second colour will be read from inside the block of data for the first position.

In fact, both these numbers are wrong, because your data is an array of this structure:

struct QuadData
    glm::mat4 position;  // 64 bytes
    glm::vec4 color;     // 16 bytes

That's 64+16 = 80 bytes. (Assuming no padding/alignment between subsequent entries - double-check that, as I'm not deeply familiar with those alignment rules).

That means the second entry in your array of QuadData comes 80 bytes after the first, and your strides provided in glVertexAttribPointer need to reflect that spacing.

Without this, the position tries to step forward by only sizeof(glm::mat4) (64 bytes), and starts reading the second quad's position out of the data for the first quad's colour, because that's what's in the 64th-68th bytes of the data stream. It keeps trying to read colours in the position attribute until the 6th quad it draws, which lines up correctly with the data for the 5th quad (at 20 * 16 = 320 bytes along the buffer). This is why you see some quads (about 1 in 5) drawn approximately correctly while others are horribly corrupted.

  • \$\begingroup\$ You're right, thanks! I thought that has something to do with how I pass the data, but couldn`t find a place \$\endgroup\$ Jul 21, 2023 at 13:27

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .