Jump to content
  • Advertisement
Sign in to follow this  
Decrius

2D and performance (using textures)

This topic is 3787 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

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);
        if (!image)
        {
            printf("Error: image error");
            return;
        }

        glGenTextures(1, &textures);
        glBindTexture(GL_TEXTURE_RECTANGLE_EXT, textures);

        //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
Advertisement
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
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
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!