PDA

View Full Version : Simple 2D OpenGL+SDL Code Optimization Questions



artisticdude
03-17-2013, 03:50 PM
Hi all,

I've created a very simple program (<200 lines of code), using SDL and OpenGL, that basically creates two quads and moves them horizontally towards the right side of the screen by increments of 1 pixel per frame. I want it to be a 2D game eventually, so I'm using glOrtho(), and my vertices only have x and y coordinates. The code I'm posting below compiles and runs just fine on my mac running OS X 10.6.8 with OpenGL 2.1, and on my Ubuntu 12.04 virtual machine (which is running on top of the same mac). It might also run on Windows, but my Windows machine is currently out of commission, so I haven't been able to test it. While the code works fine and does what I want, I can't help but feel that there are some things I could/should optimize, or things that I'm doing incorrectly but that still work for whatever reason. Thus, I have three questions:


First, is there a more efficient/safer way to change the vertex coordinates (which in this case would mean incrementing the x coordinate of each vertex by 1 each frame)? I've been looking at shaders recently, though I'm just starting to learn how to them, and it seems like a vertex shader might be able to accomplish this task better (i.e. more efficiently/succinctly) than my current code?

Second, although there are only ever two quads in the program at this point, eventually I plan on adding more quads, and there won't be a constant, predictable number of quads at any given time (since I'll be adding and removing them from the scene dynamically over the course of the program). Since I can't predict how many quads there are going to be at any given time, I can't reserve a VBO of a certain size and expect my quads' data to always fit exactly within the bounds of that VBO. Hence, I have to call glBufferData() at the beginning of each frame to replace the current contents of the VBO with the new data that contains all the new vertices (which might contain the same vertices but with different x and y coordinates, or it might have fewer or more vertices than the data that was present in the VBO in the frame before, since I might have added or removed quads in the new frame). Is callin glBufferData() to essentially overwrite the data already contained in the buffer the best method of updating the vertices in the VBO, given that I can't count on the size of the data in the VBO remaining constant?

Finally, since GL_QUADS is deprecated in modern versions of OpenGL, I've switched to using GL_TRIANGLE_STRIP. However, this presents a problem when drawing. I want to make as few drawing calls as possible in order to minimize the CPU/GPU communication in each frame, so I'd prefer to store all the vertices of both quads in one vector like I do currently and then make a single draw call that draws both quads at once, rather than having to make a seperate draw call for each quad. The problem that I've been running into here is that since I'm using GL_TRIANGLE_STRIP, if I make one call to glDrawArrays(), OpenGL renders both quads as one giant triangle strip instead of two smaller triangle strip. I did some research and came up with glMultiDrawArrays(), which works fine and renders both quads as two individual triangle strips, but it's rather cumbersome and forces me to add two additional vectors (startingElements and counts) to keep track of at which index a quad's vertices begin, and how many vertices there are per quad. Is there another (hopefully less cumbersome) way to continue using only one draw call to render multiple quads whose vertices are stored in the same vector, without rendering them as one big triangle strip?

Those specific questions aside, I'd also be happy to hear any other suggestions for improvement. It's a relatively simple program, but there is still certainly plenty of room for error, and I really want to learn OpenGL the right way as much as possible. :)

Thanks for your time!


//Include all the OS-specific headers and define all OS-specific macros
//This must be done before any other includes
#if defined(_WINDOWS) || defined(_WIN32)
#include <windows.h>
#endif
#if defined(__APPLE__)
#include <OpenGL/OpenGL.h>
#endif
#if defined (__linux__)
#define GL_GLEXT_PROTOTYPES
#include <GL/glx.h>
#endif

#include <GL/gl.h>
#include <SDL.h>
#include <vector>


struct Vertex
{
//vertex coordinates
GLint x;
GLint y;
};


//Declare our global variables
const int SCREEN_WIDTH = 1024;
const int SCREEN_HEIGHT = 768;
const int FPS = 60;

SDL_Surface *screen; //the screen

std::vector<Vertex> vertices; //the actual vertices for the quads
std::vector<GLint> startingElements; //the index where the 4 vertices of each quad begin in the 'vertices' vector
std::vector<GLint> counts; //the number of vertices for each quad

GLuint VBO = 0; //the handle to the vertex buffer


void createVertex(int x, int y)
{
Vertex vertex;
vertex.x = x;
vertex.y = y;
vertices.push_back(vertex);
}

void createQuad(int x, int y, int w, int h)
{
//Since we're drawing the quads using GL_TRIANGLE_STRIP, the vertex drawing
//order is from top to bottom, left to right, like so:
//
// 1-------3
// | |
// | |
// | |
// 2-------4

createVertex(x, y); //top-left vertex
createVertex(x, y+h); //bottom-left vertex
createVertex(x+w, y); //top-right vertex
createVertex(x+w, y+h); //bottom-right vertex

counts.push_back(4); //each quad will always have exactly 4 vertices
startingElements.push_back(startingElements.size() *4);
}


void init()
{
//initialize SDL
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER);

screen = SDL_SetVideoMode(SCREEN_WIDTH, SCREEN_HEIGHT, 0, SDL_OPENGL);

#if defined(__APPLE__)
//Enable vsync so that we don't get tearing when rendering
GLint swapInterval = 1;
CGLSetParameter(CGLGetCurrentContext(), kCGLCPSwapInterval, &swapInterval);
#endif

//Disable depth testing, lighting, and dithering, since we're going to be doing 2D rendering only
glDisable(GL_DEPTH_TEST);
glDisable(GL_LIGHTING);
glDisable(GL_DITHER);
glPushAttrib(GL_DEPTH_BUFFER_BIT | GL_LIGHTING_BIT);

//Set the projection matrix
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, -1.0, 1.0);

//Set the modelview matrix
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();

//Create VBO
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, vertices.size()*sizeof(Vertex), &vertices.front(), GL_DYNAMIC_DRAW);
}


void gameLoop()
{
//create the first quad at 0,0 with a width and height of 60 pixels
createQuad(0,0,60,60);
//create the second quad at 0,100 with a width and height of 200 pixels
createQuad(0,100,200,200);

int frameDuration = 1000/FPS; //the set duration (in milliseconds) of a single frame
int currentTicks;
int pastTicks = SDL_GetTicks();
bool done = false;
SDL_Event event;

while(!done)
{
//handle user input
while(SDL_PollEvent(&event))
{
switch(event.type)
{
case SDL_QUIT:
done = true;
break;
}
}

//increment the x coordinate of each vertex +1 pixel to the right every frame
for (int i=0; i<vertices.size(); i++)
{
vertices[i].x++;
}


//Clear the color buffer
glClear(GL_COLOR_BUFFER_BIT);

glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, vertices.size()*sizeof(Vertex), &vertices.front(), GL_DYNAMIC_DRAW);

glEnableClientState(GL_VERTEX_ARRAY);

//Set vertex data
glVertexPointer(2, GL_INT, sizeof(Vertex), 0);
//Draw the quads
glMultiDrawArrays(GL_TRIANGLE_STRIP, &startingElements.front(), &counts.front(), counts.size());

glDisableClientState(GL_VERTEX_ARRAY);

glBindBuffer(GL_ARRAY_BUFFER, 0);


//Check to see if we need to delay the duration of the current frame to match the set framerate
currentTicks = SDL_GetTicks();
int currentDuration = (currentTicks - pastTicks); //the duration of the frame so far
if (currentDuration < frameDuration)
{
SDL_Delay(frameDuration - currentDuration);
}
pastTicks = SDL_GetTicks();

// flip the buffers
SDL_GL_SwapBuffers();
}
}


void cleanUp()
{
glDeleteBuffers(1, &VBO);

SDL_FreeSurface(screen);
SDL_Quit();
}


int main(int argc, char *argv[])
{
init();
gameLoop();
cleanUp();

return 0;
}