Twinklebear / LPCGame

Working on a C++ tile based 'engine' using SDL
MIT License
18 stars 1 forks source link

Slow Draw() #23

Open btstevens89 opened 11 years ago

btstevens89 commented 11 years ago
On a 23x40 grid, we are getting between 50 to 22 fps, best case vs. worst case.

Neither of these numbers are acceptable since all we're drawing right now is the background.

I want to speed this up

I had two idea for how to do this.

  1. Take the mBox of the current map, render all of the tiles that are in view as one Texture, and draw that large texture every iteration. The camera will contain information as to whether or not it has moved. If it has moved, we re-create the large texture.
  2. Save the image of the map to the hard drive and just have the mBox move around this one image as need. This would further speed up the program because we wouldn't have to build up another texture if the camera moved.

Both ideas require me writing something that can combine textures, which I'm not sure how difficult that will be since I haven't researched it. It can't be too hard to figure out.

The 20x43 map lookups are killing us and either way we choose here we will reduce the load they cause significantly.

I guess I'll start with the first idea and then if we want/need we can evolve it into idea two.

Thoughts?

btstevens89 commented 11 years ago

Actually I could just do idea 1, but create the full map as the large texture and move it around. duh.

Twinklebear commented 11 years ago

Yep yep, idea 1 is best. After creating the map for the first time its layout will never change as it's just a background so batching its draw into one big texture and just moving it around is best. Just need to find a way for combining SDL_Textures into a big texture, might be able to get some information at the SDL forums or Google of course heh.

btstevens89 commented 11 years ago

:cry: SDL's documentation is terrible. I don't see a thing about editing or building new textures. Google returned nothing either. Maybe I can use libpng to build the full image and then use that.

Twinklebear commented 11 years ago

Ya the documentation is a bit lacking. It may be a bit of a pain to do it with SDL, it looks like you'd have to load the tileset images as SDL_Surfaces then make a new SDL_Surface that's the size of the entire map image and then blit the tiles onto the map, then convert the map SDL_Surface to an SDL_Texture to be used for rendering. So kind of a pain.

So you'd load the tileset images into SDL_Surfaces via

SDL_Surface *s = IMG_Load("tileset.png");

then make a map sized surface via: SDL_CreateRGBSurface using the code from the example (lousy endianness)

and then blit the tiles onto the map surface with SDL_BlitSurface using the clip and destination rects.

Then once the map texture is made, convert it with SDL_CreateTextureFromSurface. Use the function from window.h SurfaceToTexture for this part.

I wonder how long this would take/memory usage of the map image. It would only be done once but then we keep the map in memory the whole time.

Or maybe a streaming texture would be doable? And set it up that way without using SDL_Surfaces? Hmm. Not as familiar with SDL streaming textures.

It looks like you could do a streaming texture way for this too. Create with SDL_CreateTexture with the SDL_TextureAccess option for streaming, then get the pixels of the tiles and lock the map texture SDL_LockTexture for access and do updates with SDL_UpdateTexture then when you're done, unlock it SDL_UnlockTexture. Interesting indeed. Although how would you get the tile's pixels instead of the whole texture?

Perhaps there's a better way to batch the draw calls? It's an area I haven't spent much time looking into so I can't be of much help unfortunately. Let me know what you find though.

Also, if you find OpenGL specific stuff about batching draw calls you may want to bookmark it and/or send me a link, since I plan to migrate to straight OpenGL for rendering and SDL for window management, input and audio eventually. Then we can use fancy shaders and other cool stuff.

btstevens89 commented 11 years ago

Bilt looks great, I'm going to try to implement that now.

btstevens89 commented 11 years ago

So this is where I am at with this. I've verified the surface is being blit sucessfully with SDL_SaveBMP(map, "out.bmp");. A .bmp of the full map is created.

Something must be going wrong during Window::SurfaceToTexture(map); because the texture that is drawn is always empty. Do you see anything unusual?

//Create the empty map's surface
SDL_Surface* map = SDL_CreateRGBSurface(0, lastCamera->SceneBox().W(), lastCamera->SceneBox().H(), 32, rmask, gmask, bmask, amask);
if (map < 0)
    std::cout << SDL_GetError() << std::endl;

//Create a std::map to store prviously opened tile surfaces
std::map<std::string,SDL_Surface*> mymap;

//Blit each tile onto the map's surface
for (int i = 0; i < mTiles.size(); i++){

    //Get the surface for the current tile
    std::string file = mTileSet.get()->File(mTiles[i].Name()).c_str();
    std::map<std::string,SDL_Surface*>::const_iterator found = mymap.find(file);
    SDL_Surface* surf;
    if (found == mymap.end()){
        mymap[file] = IMG_Load(file.c_str());
        surf = mymap[file];
        SDL_UnlockSurface(surf);
    }
    else
        surf = found->second;

    //Display error if surface isn't found
    if (surf < 0)
        std::cout << SDL_GetError() << std::endl;

    //Determine boxes for the Blit
    SDL_Rect DestR;
    Rectf box = mTiles[i].Box();
    DestR.x = box.X();
    DestR.y = box.Y();
    DestR.h = box.H();
    DestR.w = box.W();
    SDL_Rect ClipR;
    Rectf ClipB = mTileSet->Clip(mTiles[i].Name());
    ClipR.x = ClipB.X();
    ClipR.y = ClipB.Y();
    ClipR.h = ClipB.H();
    ClipR.w = ClipB.W();

    //Preform Blit
    int suc = SDL_BlitSurface(surf, &ClipR, map, &DestR);

    if (suc != 0)
        std::cout << SDL_GetError() << std::endl;
}

//Save an image of the full map
SDL_SaveBMP(map, "out.bmp");

//Convert the map surface to a texture (also free's the map after completion)
SDL_Texture* tex = Window::SurfaceToTexture(map);

//Display the map
Rectf pos(xOffset, yOffset, lastCamera->Box().W(), lastCamera->Box().H());
Window::DrawTexture(tex, pos);

//Free the surfaces
for(std::map<std::string,SDL_Surface*>::iterator it = mymap.begin(); it != mymap.end(); it++)
    SDL_FreeSurface(it->second);
Twinklebear commented 11 years ago

Hmm, I'm not sure. If the out.bmp looks as it should then I'm not sure quite what's going on in SurfaceToTexture. It's being called after Window::Init and there's no entry in the debug log "Failed to convert surface"? Those are the two issues that I think would be possible. Not sure here. Maybe test the SurfaceToTexture on some regular surfaces loaded from existing images to make sure it's ok?

Maybe if this doesn't work out it's time to start looking more seriously into dropping into straight OpenGL for rendering? I'm not sure how familiar you are with OpenGL, I'm not very experienced but I've got a little project setup where I've been playing with OpenGL and SDL2 and have some links to lessons you may find useful.

I planned to do it anyways in the future, but perhaps now is that time in the future? haha

btstevens89 commented 11 years ago

SDL_Surface* map = SDL_CreateRGBSurface(0, lastCamera->SceneBox().W(), lastCamera->SceneBox().H(), 32, 0,0,0,0); Makes the function work. I'm going to finish implementing the idea, clean up the code and then push it.

Twinklebear commented 11 years ago

Interesting! Let me know how much of a boost this gives and changes in memory usage. Pretty neat stuff.

btstevens89 commented 11 years ago

The map that was previously getting 22fps is now getting 59.94fps. That's a full 23x40 map which takes up the entire screen. Pretty awesome speed up.

Twinklebear commented 11 years ago

Ah that's really nice! The map texture probably isn't that big in memory either right? How big where the bmp files?

Oh also, how have you been measuring the framerate? Just throwing a calculation into the game loop, or did you manage to get Fraps to pick it up?

btstevens89 commented 11 years ago

I think the BMP images are about 3KB per tile. Not great but I believe any SDL_Texture/SDL_Surface will be of equivalent size since there is no compression.

A 43x20 map will be 2.7MB, while a 100x100 map will be close to 30MB.

Maybe the program should try to maximize efficiency somehow and only render 5MB portions of the map? It can always re-create the map when the camera exceeds the rendered boundaries. We can even have it on it's own thread so it doesn't slow down the game.

I've been measuring framerate with a function thrown into the game loop. Nothing fancy.

Twinklebear commented 11 years ago

I guess that isn't too bad for now, if you can trim it down to do only the camera area + a bit extra, the bit extra would give you time to prepare the next map area when the player starts to leave. I guess how much extra room you'd need to render to give yourself time would depend on how long it takes to prepare the next section. But it may be kind of tricky to do this.

It may be best to just leave it as is until we move to OpenGL where I'm sure we can do something better. Since it works there's not too much need to get it completely perfect since it'll be replaced down the line anyways, and 30MB isn't so much.

btstevens89 commented 11 years ago

Is the 60fps hardcoded somewhere?

Twinklebear commented 11 years ago

Sort of, Vsync is currently enabled, so whatever your monitor's refresh rate is the cap. See window.cpp line 44

mRenderer.reset(SDL_CreateRenderer(mWindow.get(), -1, SDL_RENDERER_ACCELERATED | 
SDL_RENDERER_PRESENTVSYNC));

Take out the VSYNC bit to disable it. I was using it to cap the framerate from going to high and using up too much cpu. If we're able to go higher, we could bump the cap up to something like 120-160 or something.

btstevens89 commented 11 years ago

I just want to look at it for debugging and ensuring we always stay above 60fps.