2
\$\begingroup\$

I started developing a small cross platform game engine using OpenGL, I am working actually on a sprite batch renderer.
Everything is working fine on OS X, iOS, Win32, and some Android Devices. Here is the Results :

screen shot Mali 400
The image on the left show the correct result, and was tested on Samsung galaxy S1, Galaxy J5, Galaxy S3, Galaxy Tab 3. The device that give these weird results on the right is a Samsung Galaxy Core 2, With a Mali-400 MP renderer.
I start getting that weird result when I use more than one texture.

Here is my Sprite Batch class :

Glyph::Glyph(const maths::vec2 &position, const maths::vec2 &dimensions, const maths::vec4 &uvRect, GLuint texture, unsigned int color, float zOrder) :
textureID(texture) {

    a_zOrder = zOrder;

    topLeft.m_color = color;
    topLeft.setPosition(position.x, position.y + dimensions.y);
    topLeft.setUV(uvRect.x, uvRect.y + uvRect.w);

    bottomLeft.m_color = color;
    bottomLeft.setPosition(position.x, position.y);
    bottomLeft.setUV(uvRect.x, uvRect.y);

    bottomRight.m_color = color;
    bottomRight.setPosition(position.x + dimensions.x, position.y);
    bottomRight.setUV(uvRect.x + uvRect.z, uvRect.y);

    topRight.m_color = color;
    topRight.setPosition(position.x + dimensions.x, position.y + dimensions.y);
    topRight.setUV(uvRect.x + uvRect.z, uvRect.y + uvRect.w);
}


//SpriteBatch
ORendererSpriteBatch::ORendererSpriteBatch(): m_vboID(0), m_vaoID(0)
{
    Init();
}

ORendererSpriteBatch::~ORendererSpriteBatch()
{
    if (m_vboID != 0) {
        glDeleteBuffers(1, &m_vboID);
    }

    if (m_vaoID != 0) {
        glDeleteVertexArrays(1, &m_vaoID);
    }
}

void ORendererSpriteBatch::Init()
{
    createVertexArray();
}

void ORendererSpriteBatch::Begin()
{
    m_renderBatches.clear();

    // Makes _glpyhs.size() == 0, however it does not free internal memory.
    // So when we later call emplace_back it doesn't need to internally call new.
    m_glyphs.clear();
}

void ORendererSpriteBatch::Submit(const OSprite* renderable)
{
    m_glyphs.emplace_back(renderable->GetPosition(), renderable->GetSize(), renderable->GetUV(), renderable->GetTID(), renderable->GetColor(), renderable->GetZOrder());
}

void ORendererSpriteBatch::End()
{
    // Set up all pointers for fast sorting
    m_glyphPointers.resize(m_glyphs.size());
    for (size_t i = 0; i < m_glyphs.size(); i++) {
        m_glyphPointers[i] = &m_glyphs[i];
    }

    sortGlyphs();
    createRenderBatches();
}

void ORendererSpriteBatch::Flush(OLayer2D *layer)
{
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    ORendererFactory::OShader_Simple2D->bind();
    glActiveTexture(GL_TEXTURE0);
    ORendererFactory::OShader_Simple2D->setUniform1i("u_diffuse", 0);
    ORendererFactory::OShader_Simple2D->setUniformMat4("u_MVP", layer->getCamera()->getCameraMatrix());
    glBindVertexArray(m_vaoID);

    for (size_t i = 0; i < m_renderBatches.size(); i++) {
        glBindTexture(GL_TEXTURE_2D, m_renderBatches[i].texture);

        glDrawArrays(GL_TRIANGLES, m_renderBatches[i].offset, m_renderBatches[i].numVertices);
    }

    ORendererFactory::OShader_Simple2D->unbind();
    glBindVertexArray(0);
}


void ORendererSpriteBatch::createRenderBatches() {
    // This will store all the vertices that we need to upload
    std::vector <VertexData2D> vertices;
    // Resize the buffer to the exact size we need so we can treat
    // it like an array
    vertices.resize(m_glyphPointers.size() * 6);

    if (m_glyphPointers.empty()) {
        return;
    }

    int offset = 0; // current offset
    int cv = 0; // current vertex

    //Add the first batch
    m_renderBatches.emplace_back(offset, 6, m_glyphPointers[0]->textureID);
    vertices[cv++] = m_glyphPointers[0]->topLeft;
    vertices[cv++] = m_glyphPointers[0]->bottomLeft;
    vertices[cv++] = m_glyphPointers[0]->bottomRight;
    vertices[cv++] = m_glyphPointers[0]->bottomRight;
    vertices[cv++] = m_glyphPointers[0]->topRight;
    vertices[cv++] = m_glyphPointers[0]->topLeft;
    offset += 6;

    //Add all the rest of the glyphs
    for (size_t cg = 1; cg < m_glyphPointers.size(); cg++) {

        // Check if this glyph can be part of the current batch
        if (m_glyphPointers[cg]->textureID != m_glyphPointers[cg - 1]->textureID) {
            // Make a new batch
            m_renderBatches.emplace_back(offset, 6, m_glyphPointers[cg]->textureID);
        } else {
            // If its part of the current batch, just increase numVertices
            m_renderBatches.back().numVertices += 6;
        }
        vertices[cv++] = m_glyphPointers[cg]->topLeft;
        vertices[cv++] = m_glyphPointers[cg]->bottomLeft;
        vertices[cv++] = m_glyphPointers[cg]->bottomRight;
        vertices[cv++] = m_glyphPointers[cg]->bottomRight;
        vertices[cv++] = m_glyphPointers[cg]->topRight;
        vertices[cv++] = m_glyphPointers[cg]->topLeft;
        offset += 6;
    }

    glBindVertexArray(m_vaoID);

    // Bind our VBO
    glBindBuffer(GL_ARRAY_BUFFER, m_vboID);
    // Orphan the buffer (for speed)
    glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(VertexData2D), NULL, GL_DYNAMIC_DRAW);
    // Upload the data
    glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.size() * sizeof(VertexData2D), vertices.data());

    glBindVertexArray(0);
    // Unbind the VBO
    //        glBindBuffer(GL_ARRAY_BUFFER, 0);

}

void ORendererSpriteBatch::createVertexArray() {

    // Generate the VAO if it isn't already generated
    if (m_vaoID == 0) {
        glGenVertexArrays(1, &m_vaoID);
    }

    // Bind the VAO. All subsequent opengl calls will modify it's state.
    glBindVertexArray(m_vaoID);

    //G enerate the VBO if it isn't already generated
    if (m_vboID == 0) {
        glGenBuffers(1, &m_vboID);
    }
    glBindBuffer(GL_ARRAY_BUFFER, m_vboID);

    //Tell opengl what attribute arrays we need
    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);
    glEnableVertexAttribArray(2);

    glVertexAttribPointer(0, 2, GL_FLOAT,         GL_FALSE, sizeof(VertexData2D), (void *)offsetof(VertexData2D, m_vertex));
    glVertexAttribPointer(1, 4, GL_UNSIGNED_BYTE, GL_TRUE , sizeof(VertexData2D), (void *)offsetof(VertexData2D, m_color));
    glVertexAttribPointer(2, 2, GL_FLOAT        , GL_FALSE, sizeof(VertexData2D), (void *)offsetof(VertexData2D, m_uv));

    glBindVertexArray(0);

}

void ORendererSpriteBatch::sortGlyphs() {

    std::stable_sort(m_glyphPointers.begin(), m_glyphPointers.end(), compareFunction);

}

bool ORendererSpriteBatch::compareFunction(Glyph* a, Glyph* b) {
    if (a->a_zOrder == b->a_zOrder) {
        return (a->textureID < b->textureID);
    }
    return (a->a_zOrder < b->a_zOrder);
}


And here is how I call that class :

    m_CurrentRenderer->Begin();

    for (const OSprite* renderable : m_Renderables)
        if(m_Camera->isBoxInView(renderable->GetPosition(), renderable->GetSize())){
            renderable->Submit(m_CurrentRenderer);
        }

    m_CurrentRenderer->End();
    m_CurrentRenderer->Flush(this);

Any suggestion about what might cause that bug is welcome.

\$\endgroup\$
1
\$\begingroup\$

Since the rendering glitch shows tiles and half-tiles that might hint at a texture format or encoding/decoding issue (lack of ETC2 support?).

Make sure the textures are square, power-of-twos and try with different sizes and formats.

The other thing I'd try is adjust the vertex format to be padded to a multiple of 16 and not cross 16-byte boundaries.

eg: if it is arranged v[4,4],c[4],uv[4,4] (numbers=bytes) this cuts the uvs at the 16byte boundary. It's unlikely to be the issue but this may force the driver to CPU-copy the vertex buffer to re-arrange the attribute order and pad the vertex to a size that the GPU supports natively.

I'd try rearranging it v[4,4,4],c[4],uv[4,4],pad[8] with a valid Z value in the vertex just in case there's an issue in the CPU-copy function and it may run faster on some GPUs.

Since it renders fine on some but not the other(s) if those suggestions don't fix the problem I'd keep looking into driver/GPU limitations that may cause this.

\$\endgroup\$
  • \$\begingroup\$ thanks, Textures are all POT and square, with no compression. I'll try adding the padding and see what that will give. \$\endgroup\$ – Mehdi Jan 5 '16 at 18:47
  • \$\begingroup\$ It didn't work :( \$\endgroup\$ – Mehdi Jan 5 '16 at 19:45
  • \$\begingroup\$ Try calling glDisable(GL_DEPTH_TEST); to make sure depth testing is disabled in void ORendererSpriteBatch::Flush(OLayer2D *layer) in case it changes something. \$\endgroup\$ – Stephane Hockenhull Jan 5 '16 at 23:28
  • \$\begingroup\$ unfortunately nothing changed \$\endgroup\$ – Mehdi Jan 6 '16 at 0:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.