3
\$\begingroup\$

Alright, so I'm pretty new to OpenGL programming. I've been writing my own engine, initially using the fixed function pipeline. I then did a 1:1 replacement of the fixed-function methods with shaders and it all worked perfectly (but slow, I wasn't instancing things then).

My game is only 2D but I need my engine be as efficient as possible because I'm planning on doing all sorts of intense effects like particles and real-time per-pixel lighting.

The Plan:

My game's sprites are all contained in one giant texture atlas and the dimensions of their cells are never going to change. I decided that I'll store all the cell data in a Uniform Buffer Object as a GL_STATIC_DRAW so I don't waste time resending data that never changes.

In my render loop my engine buffers sprite positions and cellIDs as Instance objects in a std::vector. At the end of the render loop I send the contents of the std::vector to the GPU and tell it to render them as instances using the data in the UBO.

I've attempted an implementation of this idea but now it doesn't appear to render anything (I'm left with a black screen) while spitting out no errors. I know I haven't broken OpenGL (too badly) because my fixed-function replacement code for rendering primitive solids still works alongside it.

The Broken Implementation:

Note I'm using:

  • C++
  • OpenGL 3.3
  • GLFW
  • GLEW
  • OpenGL Mathematics (GLM)

Here's the code for the cells contained within the UBO:

struct UBO_Cell {  
    glm::vec2 vert[4]; // Simple XY corners (could just make it W/H and save 2 vec2s)
    glm::vec2 tex[4]; // [0..1] texel coordinates.
};

Here's the code that creates the UBO:

void Texture::CreateUniformBufferObject() {
    uboReady = true;
    for( int i = 0; i < numberOfCells; ++i ) {
        ubo[i].vert[0].x = cells[i].clip.x;
        ubo[i].vert[0].y = cells[i].clip.y;
        ubo[i].vert[1].x = cells[i].clip.x + cells[i].clip.w;
        ubo[i].vert[1].y = cells[i].clip.y;
        ubo[i].vert[2].x = cells[i].clip.x + cells[i].clip.w;
        ubo[i].vert[2].y = cells[i].clip.y + cells[i].clip.h;
        ubo[i].vert[3].x = cells[i].clip.x;
        ubo[i].vert[3].y = cells[i].clip.y + cells[i].clip.h;

        ubo[i].tex[0].x = ( cells[i].clip.x + TEXEL_SHRINK ) / texW;
        ubo[i].tex[0].y = ( cells[i].clip.y + TEXEL_SHRINK ) / texH;
        ubo[i].tex[1].x = ( cells[i].clip.x + cells[i].clip.w - TEXEL_SHRINK ) / texW;
        ubo[i].tex[1].y = ( cells[i].clip.y + TEXEL_SHRINK ) / texH;
        ubo[i].tex[2].x = ( cells[i].clip.x + cells[i].clip.w - TEXEL_SHRINK ) / texW;
        ubo[i].tex[2].y = ( cells[i].clip.y + cells[i].clip.h - TEXEL_SHRINK ) / texH;
        ubo[i].tex[3].x = ( cells[i].clip.x + TEXEL_SHRINK ) / texW;
        ubo[i].tex[3].y = ( cells[i].clip.y + cells[i].clip.h - TEXEL_SHRINK ) / texH;
    }
}

Here's the code for each Instance object:

struct Instance {
    glm::mat4 transform; // transform/scale/rotation matrix to put the instance where it needs to be (calculated CPU side for the moment).
    glm::uint cellID; // Index of this sprite's cell in the cell array
};

Here's my vertex shader:

#version 330

uniform mat4 projectionMatrix;

#define MAX_NUM_OF_CELLS 512 // Arbitrary maximum

struct Cell { // Mirror of the C++ struct.
    vec2 vert[4];
    vec2 tex[4];
};

layout(std140) uniform Cells { // The UBO with the cells.
    Cell cells[MAX_NUM_OF_CELLS];
}; // Perhaps I should use instance_name[MAX_NUM_OF_CELLS] instead?

// The transform and cellID are sent as VBOs.
in mat4 in_transformMatrix;
flat in uint in_cellID;

flat out int vertID; // So I can pass the vertex ID to the fragment shader.

void main() {
    vertID = gl_VertexID; // I'm assuming this goes from 0..3 when rendering a quad?
    gl_Position = projectionMatrix * in_transformMatrix * vec4( cells[cellID].vert[vertID].x, cells[cellID].vert[vertID].y, 0.0, 1.0 );
};

Here's the fragment shader:

#version 330

uniform sampler2D textureUnit;

const vec4 texColour = vec4( 1.0, 1.0, 1.0, 1.0 );

#define MAX_NUM_OF_CELLS 512

struct Cell{
    vec2 vert[4];
    vec2 tex[4];
};

layout(std140) uniform Cells {
    Cell cells[MAX_NUM_OF_CELLS];
};

flat in uint in_cellID;
flat in int vertID;

out vec4 out_fragColour;

void main() {
    out_fragColour = texture2D( textureUnit, cells[in_cellID].tex[vertID] ) * texColour;
};

If it helps, here's how I set up my shader buffers:

void TextureShader::GenerateBuffers() {
    glGenBuffers( 1, &cellBufferID );
    glBindBuffer( GL_UNIFORM_BUFFER, cellBlockID );
    glBufferData( GL_UNIFORM_BUFFER, sizeof( UBO_Cell ) * MAX_NUM_OF_UBO_CELLS, NULL, GL_STATIC_DRAW );
    glBindBuffer( GL_UNIFORM_BUFFER, 0 );

    glGenBuffers( 1, &instanceBufferID );
    glBindBuffer( GL_ARRAY_BUFFER, instanceBufferID );
    glBufferData( GL_ARRAY_BUFFER, sizeof( Texture::Instance ) * 1024, NULL, GL_STREAM_DRAW );
    glBindBuffer( GL_ARRAY_BUFFER, 0 );

    glGenBuffers( 1, &ibo );
    glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, ibo );
    glBufferData( GL_ELEMENT_ARRAY_BUFFER, 4 * sizeof(GLuint), NULL, GL_STREAM_DRAW );
    glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 );
}

And here's my drawing function:

// Switch Program
glUseProgram( id ); // Because I switch between this and the shader I use to render primitives.

// Setting Projection Matrix
glm::mat4 projectionMatrix = NuWindow::GetProjectionMatrix(); // This will get factored out eventually.
glUniformMatrix4fv( projectionMatrixLocation, 1, GL_FALSE, (const GLfloat*)&projectionMatrix );

// Binding Texture
glBindTexture( GL_TEXTURE_2D, textureID );

// Binding Buffers
glBindBuffer( GL_ARRAY_BUFFER, instanceBufferID );
glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, ibo );
glUniformBlockBinding( id, cellLocation, 1 );
glBindBufferBase( GL_UNIFORM_BUFFER, 1, cellBufferID ); // As far as I know this binds the buffer like glBindBuffer() as well.

// Enabling Vertex Array Attributes
glEnableVertexAttribArray( transformMatrixLocation ); // A mat4 in GLSL takes up 4 locations.
glEnableVertexAttribArray( transformMatrixLocation + 1 );
glEnableVertexAttribArray( transformMatrixLocation + 2 );
glEnableVertexAttribArray( transformMatrixLocation + 3 );
glEnableVertexAttribArray( cellIdLocation );

// Buffering Instances as a VBO
glVertexAttribPointer( transformMatrixLocation, 4, GL_FLOAT, GL_FALSE, sizeof( Texture::Instance ), (GLvoid*)offsetof( Texture::Instance, transform ) );
glVertexAttribPointer( transformMatrixLocation + 1, 4, GL_FLOAT, GL_FALSE, sizeof( Texture::Instance ), (GLvoid*)( offsetof( Texture::Instance, transform ) + sizeof( GLfloat ) * 4) );
glVertexAttribPointer( transformMatrixLocation + 2, 4, GL_FLOAT, GL_FALSE, sizeof( Texture::Instance ), (GLvoid*)( offsetof( Texture::Instance, transform ) + sizeof( GLfloat ) * 8) );
glVertexAttribPointer( transformMatrixLocation + 3, 4, GL_FLOAT, GL_FALSE, sizeof( Texture::Instance ), (GLvoid*)( offsetof( Texture::Instance, transform ) + sizeof( GLfloat ) * 12) );
glVertexAttribPointer( cellIdLocation, 1, GL_UNSIGNED_INT, GL_FALSE, sizeof( Texture::Instance ), (GLvoid*)offsetof( Texture::Instance, cellID ) );
glVertexAttribDivisor( transformMatrixLocation, 1); // Each one of these is only a per-instance thing.
glVertexAttribDivisor( transformMatrixLocation + 1, 1);
glVertexAttribDivisor( transformMatrixLocation + 2, 1);
glVertexAttribDivisor( transformMatrixLocation + 3, 1);
glVertexAttribDivisor( cellIdLocation, 1);
//glBufferData( GL_ARRAY_BUFFER, sizeof( Texture::Instance ) * instances.size(), instances.data(), GL_STREAM_DRAW );
glBufferData( GL_ARRAY_BUFFER, sizeof( Texture::Instance ), (GLvoid*)&dummy_instance, GL_STREAM_DRAW );

// Setting the IBO
GLuint indicies[] = { 0, 1, 2, 3 }; // This is where I think I'm going wrong, I'm not 100% where the IBO comes into this process.
                                    // Does this feed into GLSL's gl_VertexID? If it does it works as I expect a tiny bit.
glBufferData( GL_ELEMENT_ARRAY_BUFFER, 4 * sizeof(GLuint), (GLvoid*)&indicies, GL_STREAM_DRAW );

// Setting the UBO
if( !texture->uboReady ) texture->CreateUniformBufferObject(); // Creates the std::vector of UBO_Cells if I doesn't exist already.
glBufferData( GL_UNIFORM_BUFFER, sizeof( UBO_Cell ) * MAX_NUM_OF_UBO_CELLS, &(texture->ubo), GL_STATIC_DRAW );
    // Yes I know I said this was supposed to be fire and forget but I wanted all this code in the one place for debugging.

// Drawing
glDrawElementsInstanced( GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, 0, instances.size() );

// Disabling Vertex Attributes
glDisableVertexAttribArray( transformMatrixLocation );
glDisableVertexAttribArray( transformMatrixLocation + 1 );
glDisableVertexAttribArray( transformMatrixLocation + 2 );
glDisableVertexAttribArray( transformMatrixLocation + 3 );
glDisableVertexAttribArray( cellIdLocation );

// Unbinding Buffers
glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 );
glBindBuffer( GL_ARRAY_BUFFER, 0 );
glBindBuffer( GL_UNIFORM_BUFFER, 0 );

I've been staring at this code for weeks and I can't find out what's wrong. I guess its either something really small or maybe I'm barking up the wrong tree.


Thanks in advance for any help you guys can give me, I'd put a bounty on this thing but don't have enough rep!

\$\endgroup\$

closed as off-topic by Josh Jan 4 '14 at 18:12

  • This question does not appear to be about game development within the scope defined in the help center.
If this question can be reworded to fit the rules in the help center, please edit the question.

  • \$\begingroup\$ I'd take a closer look if I had the time, but it seems over-complicated for 2D drawing, especially your drawing function. Take a step back, and get a triangle/quad displaying on screen without any of the extras? And I recommend looking into vertex array objects, so that you don't need to do quite so many binds/unbinds and state changes during your drawing function. \$\endgroup\$ – Fault Dec 5 '13 at 13:53
  • \$\begingroup\$ Well I'd like to help, but only have the time to link you to my implementation. I finished 2D instancing myself (first time, not familiar with OpenGL either), so you can take a look at the source. I used a texture buffer object, which is more efficient than a uniform array (and has less strict memory requirements). Note that I build the transforms of each sprite on the GPU: bitbucket.org/rgaul/sel/src. If you have questions you can contact me. \$\endgroup\$ – RandyGaul Dec 15 '13 at 8:52
  • \$\begingroup\$ Were you able to solve this? You should post an answer if you were. \$\endgroup\$ – MichaelHouse Dec 19 '13 at 16:55
  • \$\begingroup\$ Alas, no. Instead of hoping the internet would solve my problems for me I bought a few books on the topic (OpenGL SuperBible most notably) and realised that my implementation was so bad I decided to start all over. Now if this question could somehow be closed, that would be nice. \$\endgroup\$ – Foodstuff Dec 20 '13 at 9:13
  • \$\begingroup\$ This question appears to be off-topic because it is about debugging your code for you. \$\endgroup\$ – Josh Jan 4 '14 at 18:12

Browse other questions tagged or ask your own question.