Sign in to follow this  
Decrius

2D and performance (using textures)

Recommended Posts

So, I wrote my own cute little 2D tile-based engine that works fine as far as I want it for now :). Only one point is nagging me: is has a FPS of 68. What it does: it draws 9*9 (and 10*10 if the character is moving) quads, each having its own texture. The texture and the quad are both 64 pixels in width and height. It determines what texture is needed via an array containing numbers indicating what texture to be used on certain positions. I did some research, and found out that the drawing part took 1000 times more time then anything else together I do per frame...that is nagging me ^^. How can I optimise my code? I heard about using the GL_TEXTURE_RECTANGLE_EXT extension...but that didn't really do much (nothing to be honest). The relevant code: The main function:
int main()
{
    Graphics graphics(512, 512);    

    int frames = 1;
    bool running = true;
    while(running)
    {
        graphics.resize();

        glfwSetCharCallback(handle_keyboard);

        graphics.draw(); // takes WAY too much time

        running = !glfwGetKey(GLFW_KEY_ESC) && glfwGetWindowParam(GLFW_OPENED);
        frames++;
    }
    return 0;
}

The class:
class Graphics
{
    int width, height, old_width, old_height;
    GLuint textures[n_textures];

    void draw_rect(GLuint, int, int, int, int);

    public:
        Graphics(int, int);
        ~Graphics();
        bool init(int, int);
        void resize();
        void draw();
};

Graphics::Graphics(int width, int height)
{
    glewInit();
    glfwInit();
    if(!glfwOpenWindow(width, height, 0, 0, 0, 0, 0, 0, GLFW_WINDOW))
    {
        return;
    }
    glfwSetWindowTitle("GLFW Application");

    old_width = old_height = 0;
    resize();

    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    //glEnable(GL_TEXTURE_2D);
    glEnable(GL_TEXTURE_RECTANGLE_EXT);

    corona::Image *image;
    for (int i = 0; i < n_textures; i++)
    {
        image = corona::OpenImage(texture_paths[i]);
        if (!image)
        {
            printf("Error: image error");
            return;
        }

        glGenTextures(1, &textures[i]);
        glBindTexture(GL_TEXTURE_RECTANGLE_EXT, textures[i]);

        //corona::PixelFormat format = image->getFormat();

        glTexParameterf(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameterf(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameterf(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_WRAP_S, GL_REPEAT);
        glTexParameterf(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_WRAP_T, GL_REPEAT);

        glTexImage2D(GL_TEXTURE_RECTANGLE_EXT, 0, GL_RGB, image->getWidth(), image->getHeight(), 0, GL_RGB, GL_UNSIGNED_BYTE, image->getPixels());
    }
    delete image;
}

Graphics::~Graphics()
{
    glDeleteTextures(n_textures, textures);
    glfwTerminate();
}

void Graphics::resize()
{    
    glfwGetWindowSize(&width, &height);

    if (width != old_width || height != old_height)
    {
        height = height > 0 ? height : 1;

        glViewport(0, 0, width, height);

        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glOrtho(0.0f, TILE_SIZE*TILES, TILE_SIZE*TILES, 0.0f, -1.0f, 1.0f);

        glMatrixMode(GL_MODELVIEW);

        old_width = width;
        old_height = height;
    }
}

void Graphics::draw()
{
    glClear(GL_COLOR_BUFFER_BIT);
    glLoadIdentity();

    for (int y = 0; y < 9; y++)
    {
        for (int x = 0; x < 9; x++)
        {
            draw_rect(textures[map[y][x]], x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE);
            x++;
        }
        y++;
    }
    glfwSwapBuffers();
}

void Graphics::draw_rect(GLuint texture, int x, int y, int w, int h)
{
    glBindTexture(GL_TEXTURE_RECTANGLE_EXT, texture);
    glBegin(GL_QUADS);
        glTexCoord2i(0, 0); glVertex2i(x, y);
        glTexCoord2i(64, 0); glVertex2i(x+w, y);
        glTexCoord2f(64, 64); glVertex2i(x+w, y+h);
        glTexCoord2f(0, 64); glVertex2i(x, y+h);
    glEnd();
}

Global variables:
const int n_textures = 2;
char *texture_paths[n_textures] = {"data/dirt.png", "data/grass.png"};

int map[18][18] = {
    {0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0},
    {1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1},
    {0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1},
    {1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0},
    {0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1},
    {1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0},
    {0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1},
    {1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0},
    {0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1},
    {1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0},
    {0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1},
    {1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0},
    {0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1},
    {1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0},
    {0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1},
    {1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0},
    {0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1},
    {1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0}
};

Thank you :)

Share this post


Link to post
Share on other sites
You'll want to batch your draw calls, that is draw everything with the same texture at once since switching textures is expensive. You might also want to not use immediate mode and probably should only be calling glBegin/End once per frame.

Share this post


Link to post
Share on other sites
GL_TEXTURE_RECTANGLE_ARB is not faster, but it is to be used in cases where you for example need a texture which fits the screen resolution perfectly (like 1600x1200).
So put GL_TEXTURE_2D back in your code. [smile]

Looking through your code I can't see any big mistakes, I haven't used GLFW myself though, and don't know anything about it.

However, you'll want to make as few glBindTexture() and glBegin() calls as possible, so you should render those tiles that share the same texture object in succession. If you have just two textures, you'll need just two glBegin() calls and two glBindTexture() calls. This will speed things up significantly.

I hope this helps.

Share this post


Link to post
Share on other sites
Ok thanks :). I rewrote a bit so it would only call glBegin()/glEnd/glBindTexture once...added 2 or 3 frames per second (its only a small map).
Quote:
Original post by Neutrinohunter
68 FPS?, you sure its not got VSync enabled?
Ok, I looked in the GLFW user's manual, I set the buffer swap interval to zero, ran the program and WOW: FPS gets to 4200!
The guide also says:
Quote:
Using a swap interval of zero can be useful for benchmarking purposes, when it is not desirable to measure the time it takes to wait for the vertical retrace. However, a swap interval of 1 generally gives better visual quality.
Should I have an interval or not? With interval means low FPS but they say better quality...is that worth it?

Thank you guys :)

Share this post


Link to post
Share on other sites
It means that it will update with the screen refresh on the vertical refresh rate but that should be fine not to have Vysnc enabled, most of the time I don't see any difference. Though one would say if your progam goes lower than around 30FPS you will see a problem.

Neutrinohunter

Share this post


Link to post
Share on other sites
Quote:

The guide also says:
Quote:
Using a swap interval of zero can be useful for benchmarking purposes, when it is not desirable to measure the time it takes to wait for the vertical retrace. However, a swap interval of 1 generally gives better visual quality.
Should I have an interval or not? With interval means low FPS but they say better quality...is that worth it?

Enabling v-sync simply means that the buffer-swap in double-buffered rendering in synchronized with monitor's vertical refresh (which is occuring at 60-100Hz usually). Normally, even if your scene is rendering at 4000fps, your monitor can still only display 60-100 frames per second. Enabling v-sync prevents tearing (buffer swap occurring in middle of monitor drawing your image), and the only reason for disabling it (apart from benchmarking) is when your scene renders at lower fps than the monitor refresh (lets say 58fps with 60Hz monitor) because then you miss one refresh and have to wait for another, effectively lowering the actual fps up to half (so you got 30fps instead of 59 in worst case).

Basically this is the option that should be left to user to choose (also note that some drivers have the v-sync enabled by default).

Share this post


Link to post
Share on other sites
While it sounds like you don't really have a performance problem after all, one relatively easy "optimization" to do in tile based games is putting many (or all) of the tiles in one texture. For example, a 512x512 texture can hold 64 tiles of your current tile size. This means you could render all your tile quads within one glBegin/glEnd pair.

You probably shouldn't worry about optimizations at this point though; making the gameplay happen is more important.

Share this post


Link to post
Share on other sites
Quote:
Original post by Fingers_
You probably shouldn't worry about optimizations at this point though; making the gameplay happen is more important.


Very true ;), but I don't want to discover that when I finished it, it is slow as hell ^^ and that I'd have to rewrite it all =/ lol

Quote:
Original post by Fingers_
While it sounds like you don't really have a performance problem after all, one relatively easy "optimization" to do in tile based games is putting many (or all) of the tiles in one texture. For example, a 512x512 texture can hold 64 tiles of your current tile size. This means you could render all your tile quads within one glBegin/glEnd pair.


That's only for loading the textures isn't it? As I only load them once it wouldn't do much.
And if you mean the drawing, the map can be different each time, as the player moves through it (it has scrolling).

Though...it would improve performance if I didn't redraw the scene if it wasn't needed.

Can I somehow "save" a buffer (the map, not the object which move constantly) so I can keep using it if the character doesn't move? (character is always centered, so when it moves the map has to be redrawn)

Share this post


Link to post
Share on other sites
[Quote]That's only for loading the textures isn't it? As I only load them once it wouldn't do much.
And if you mean the drawing, the map can be different each time, as the player moves through it (it has scrolling). [/Quote]

I think what he means is that you can avoid glBindTexture's in this way. You can just use glTexCoord to draw a different texture on a quad. Pretty smart, but a little more complex I guess.

Share this post


Link to post
Share on other sites
Basically he means: put all your tile textures into one larger texture, and when it comes to drawing, instead of doing a bind texture for each tile, do 1 bind texture at the beginning of the map drawing, and then use glTexCoord as kamos suggested to draw a portion of the large texture. This means that you need to know the tile's textures co-ordinates inside the larger texture. You'll need some form of meta-data to define what portion of the larger texture is which tile, or arrange the tiles logically somehow.

Share this post


Link to post
Share on other sites
So, I would draw the map tile by tile (9x9 as that is what fills the user's screen, the rest of the map is outside the window/hidden), then dump that into a large texture in a buffer (not on disc), then each time the screen still has to draw THAT particular part of the map it will draw the large texture.

Then when the map changes (the character walks so the map is scrolling), we recalculate the large texture.

How can I draw to a texture buffer instead to the video buffer? So: How can I generate a texture dynamically (where I create the content on the fly)?

Thanks so far guys :)

PS: glfwSleep() (glfw's cross-platform function for Sleep()) can only sleep in milliseconds it seems, whatever small value I use it keeps the framerate at ~68 (without it was ~4100). I once heard you should never limit the framerate in games, the only thing I want to prevent with sleeping is that CPU goes to 100%. Do we have a function (SDL has it atleast, but I rather use something more lightweight as I only want the sleep function) that can sleep for microseconds? And thus barely affect the framerate but only keeps the CPU his friend?

And is it recommended to get the FPS as high as possible?

Share this post


Link to post
Share on other sites
Quote:
Original post by Decrius
And is it recommended to get the FPS as high as possible?


Up to a certain point yes, and this threshold is the screen's refresh rate.
There's no point in rendering more frames per second than the screen can display.

Share this post


Link to post
Share on other sites
In my opinion you are trying to "fire a cannon to kill a mosquito": the overhead that is caused by drawing multiple quads instead of one big texture (which you have to generate) is not that big I would say. It's interesting to try to get it to work the way you want it and definitely a good OpenGL exercise, but if you are more anxious to get on with your game then I wouldn't spend time on this. (IMHO)

I've noticed that the Sleep() function (where you specify the amount of time to sleep in millisecond) seems to sleep the same amount of time for any number lower than 10 (ms). _My guess_ is that the internal scheduler which schedules which process can use the processor does not "wake up" a sleeping process until 10 ms or so have passed. Thus making every call to the Sleep function with a value of below 10 ms act as a 10 ms sleep. But this is just _my guess_ and if I'm wrong please correct me.

FPS as high as possible is not very friendly towards the processor. You said you were getting an FPS of 4000. Your screen cannot even show all of these frames that were generated and it is just a waste of processor power. I think when your program reaches the point where 100% CPU gets you to 100 FPS or less you should aim for a FPS as high as possible. Or you could make a distinction between certain scenes of the game: displaying a static screen requires no FPS at all, where as the main game could run at a FPS as high as possible.

Share this post


Link to post
Share on other sites
I didn't mean drawing just one quad, nor generating textures on the fly. You'd assemble the atlas texture in a paint program of your choice by copying & pasting your current tiles into one image. This is particularly easy when all the tiles are the same size.

When drawing, you bind the atlas texture and then draw all your quads within one glBegin/glEnd. Each type of tile has an index, which you use to get the UV coordinates like this (assuming the texture is a 8x8 grid of 64 pixel tiles):

tileindex = map[y][x];
u = (tileindex % 8) * .125;
v = (tileindex / 8) * .125;
glTexCoord2f(u, v); // bottom left vertex
glTexCoord2f(u + .125, v); // bottom right vertex
glTexCoord2f(u + .125, v + .125); // top right vertex
glTexCoord2f(u, v + .125); // top left vertex

Share this post


Link to post
Share on other sites
Ok, eventually the FPS wasn't very accurate. I waited 100 milliseconds before I update the caption (in which I display the frame rate). The FPS then is ~68, however when I wait shorter (10 milliseconds or shorter) I get an FPS of ~125. Weird...

I tried to put it into display lists...didn't gave a noticable optimisation, but it will optimise a little yes ;).

I think it's working okay now :), thanks for all your thinking and replies. Really helped me :). I should now continue with the actual game hehe :P

Quote:
Original post by Interesting Dave
1/8 = 0.125


O really? :P xD

Share this post


Link to post
Share on other sites
More important than sprite sheets is batching by texture, but if you already do that, then I happen to love using sprite sheets.

Does anyone know if binding a larger texture takes longer, or if it takes the same time independent of texture size?

Share this post


Link to post
Share on other sites
Quote:
Original post by Boder
Does anyone know if binding a larger texture takes longer, or if it takes the same time independent of texture size?

I don't see why it would - binding a texture is basically just assigning a pointer after all. It could take a little longer to render using a larger texture, because of caching issues.

Share this post


Link to post
Share on other sites
Quote:
Original post by Boder
More important than sprite sheets is batching by texture, but if you already do that, then I happen to love using sprite sheets.


Batching by texture? What do you mean exactly?

Share this post


Link to post
Share on other sites
Quote:
Original post by Boder
More important than sprite sheets is batching by texture, but if you already do that, then I happen to love using sprite sheets.


Batching by texture? What do you mean exactly?

Share this post


Link to post
Share on other sites
You minimize the glBind* by drawing all the sprites from one texture, then switching to the next texture and drawing all the sprites from that texture.

Sometimes it happens naturally from the code layout, but sometimes not.

Imagine a tilemap of dirt and grass tiles, each from a separate 64x64 texture. One way is to loop through the tilemap, check if it's a dirt or grass tile and bind and draw the tile. The other way is to bind the grass texture, draw all the grass tiles, and then do the same for the dirt texture and tiles.

But using a tilesheet negates all that because you only have one texture. [grin]

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this