|
Optimizing Isometric Tiles |
David Collins
Member #14,171
March 2012
|
I just yesterday decided to switch from using Allegro 4 to 5. What I'm trying to do is draw a fairly large number of tiles... probably comparable to as many as you'd see in your average session of Roller Coaster Tycoon. I have tile culling in place, but it's still very slow when the map fills the screen. When the camera is on the edges, it's better. Right now the game is VERY simple. More of a skeleton. So maybe I made some kind of error somewhere. My INIT function: 1int init() //start up necessary allegro tools and other objects
2{
3 al_init(); //allegro itself
4
5 al_set_new_display_flags(ALLEGRO_FULLSCREEN);
6
7 int dsk_w = 1360, dsk_h =768; //my monitor's native res
8 //get_desktop_resolution(&dsk_w,&dsk_h); //something left over from AL4
9 display = al_create_display(dsk_w, dsk_h); //create display
10
11
12 al_init_image_addon(); //bitmaps, pngs, jpegs etc
13 al_init_font_addon(); //for fonts, just bitmap ones
14 al_init_primitives_addon(); //rectangles, circles, triangles etc
15 al_init_ttf_addon(); // true type fonts (scalable vector fonts)
16 al_install_mouse(); //the mouse
17 al_install_keyboard(); //the keyboard
18 al_install_audio(); //for audio, WAVs,
19 al_init_acodec_addon(); //audio codecs like OGG and FLAC
20
21
22 srand(time(NULL));
23
24 font_default = al_load_font("font/calibri.ttf", 14, 0);
25
26 return 0;
27}
This is the main part of the screen refreshing function, very basic: al_wait_for_vsync(); al_flip_display(); al_clear_to_color(al_map_rgb(0,0,0));
This object's draw event is called once each frame. It draw the world tiles: 1Session::Session()
2{
3 Grid_Tiles = Grid<ALLEGRO_BITMAP*>(128,128,NULL);
4
5 camX=500;
6 camY=-200;
7
8 int seed=rand();
9
10 ALLEGRO_BITMAP* tiletest1 = BMP["tiletest1.png"];
11 ALLEGRO_BITMAP* tiletest2 = BMP["tiletest2.png"];
12
13
14 for(int a=0; a<Grid_Tiles.w; a++)
15 for(int b=0; b<Grid_Tiles.h; b++)
16 {
17 if (perlin(a, b, 3, seed)>0.4)
18 {
19 Grid_Tiles.set(a,b,tiletest1);
20 }
21 else
22 Grid_Tiles.set(a,b,tiletest2);
23 }
24
25}
26
27void Session::draw()
28{
29
30 if (key[ALLEGRO_KEY_UP]) camY-=3;
31 if (key[ALLEGRO_KEY_DOWN]) camY+=3;
32 if (key[ALLEGRO_KEY_RIGHT]) camX+=6;
33 if (key[ALLEGRO_KEY_LEFT]) camX-=6;
34
35 selectX=-1;
36 selectY=-1;
37
38 int drawX, drawY;
39
40 int viewX1=std::max(0,(((camX)/(TILE_W-1))+(camY/TILE_H))-3);
41 int viewX2=std::min(Grid_Tiles.w,((((camX)+SCREEN_W)/(TILE_W-1))+((camY)+SCREEN_H)/TILE_H)+5);
42 int viewY1=std::min(Grid_Tiles.h-1,((((camX)+SCREEN_W)/(TILE_W-1))-(camY)/TILE_H)+5);
43 int viewY2=std::max(0,(((camX)/(TILE_W))-(((camY)+SCREEN_H)/TILE_H))-3);
44
45 //ALLEGRO_BITMAP* selectorback = BMP["tileselector_back.png"];
46 //ALLEGRO_BITMAP* selectorfront = BMP["tileselector_front.png"];
47
48
49 for(int a=viewX1; a<viewX2; a++)
50 {
51 for(int b=viewY1; b>=viewY2; b--)
52 {
53 drawX=(b*(int)(TILE_W/2-1))+(a*(int)(TILE_W/2-1))-camX;
54 if (drawX<-TILE_W || drawX>SCREEN_W)
55 continue;
56
57 drawY=(a*(int)(TILE_H/2))-(b*(int)(TILE_H/2))-camY;
58 if(drawY<-200 || drawY>SCREEN_H)
59 continue;
60
61
62
63 //TODO: Set selectX and selectY based on where the mouse is pointing
64
65 //if (selectX==a && selectY==b)
66 // al_draw_bitmap(selectorback, drawX, drawY, 0);
67
68 al_draw_bitmap(Grid_Tiles.get(a,b), drawX, drawY, 0);
69
70 if (Grid_Actors->get(a,b)!=NULL)
71 {
72 Grid_Actors->get(a,b)->draw(drawX, drawY);
73 }
74
75 /*if (selectX==a && selectY==b)
76 {
77 al_draw_bitmap(selectorfront, drawX, drawY, 0);
78 al_draw_textf(font_default,al_map_rgb(255,150,30),drawX-20,drawY-10,0, "(%d,%d)",a,b);
79 }*/
80 }
81 }
82
83}
Here is the Bitmap resource handler. Maybe I'm loading them incorrectly? 1#include "bitmaps.h"
2#include <allegro5/allegro_native_dialog.h>
3#include <allegro5/allegro_image.h>
4
5
6ALLEGRO_BITMAP* BitmapHandler::operator[](std::string filename)
7{
8
9 ALLEGRO_BITMAP* result = my_map[filename];
10
11 if (!result) //file hasn't been loaded yet
12 {
13 result=load(filename);
14 }
15 if (!result) //load error
16 {
17 my_map.erase(filename);
18 }
19
20 return result;
21}
22
23ALLEGRO_BITMAP* BitmapHandler::load(std::string filename)
24{
25 ALLEGRO_BITMAP* result = al_load_bitmap((char*)("gfx/"+filename).c_str());
26
27 if (!result)
28 al_show_native_message_box(NULL,"ERROR", "Image Could Not Load",(char*)filename.c_str(),NULL, ALLEGRO_MESSAGEBOX_ERROR);
29 else
30 my_map[filename] = result;
31
32 return result;
33}
34
35//THIS IS NOT USED ANYWHERE YET:
36void BitmapHandler::unload(std::string filename)
37{
38
39 al_destroy_bitmap(my_map[filename]);
40 my_map.erase(filename);
41}
42
43
44//THIS IS NOT USED ANYWHERE YET:
45void BitmapHandler::reload(std::string filename)
46{
47 unload(filename);
48 my_map[filename]=load(filename);
49}
50
51void BitmapHandler::clear()
52{
53 std::map<std::string, ALLEGRO_BITMAP*>::iterator it;
54 for (it = my_map.begin(); it != my_map.end(); ++it )
55 {
56 al_destroy_bitmap((*it).second);
57 }
58 my_map.clear();
59}
And the Grid class that I created. [Needed this to have a structure that returns a default value on an out-of-bounds get()] 1#pragma once
2
3#include <vector>
4
5template<class T>
6class Grid
7{
8 private:
9 std::vector<T> field;
10 T default_value;
11
12 public:
13 int w,h,size;
14
15 Grid()
16 {
17 w=0;
18 h=0;
19 size=0;
20 default_value=0;
21 }
22
23 Grid(int W, int H, T def)
24 {
25 w=W;
26 h=H;
27 size=w*h;
28 default_value=def;
29 for ( int i = 0; i < size; i++ )
30 {
31 field.push_back(default_value);
32 }
33 }
34
35 T get(int x, int y)
36 {
37 if (x<0 || y<0 || x>=w || y>=h) return default_value;
38 return field[x + y*w];
39 }
40
41 void set(int x, int y, T val)
42 {
43 if (x<0 || y<0 || x>=w || y>=h) return;
44 field[x + y*w] = val;
45 }
46
47 void fill(T val)
48 {
49 for(int a=0; a<size; a++)
50 {
51 field[a] = val;
52 }
53 }
54};
I'll get yelled at for this, but here's my "global.h". To the main CPP, it declares and initializes global variables. Outside the main.cpp, it declares them as external. 1#pragma once
2
3#include <vector>
4#include <algorithm>
5
6#include <allegro5/allegro.h>
7#include <allegro5/allegro_font.h>
8#include <allegro5/allegro_audio.h>
9#include <allegro5/allegro_acodec.h>
10#include <allegro5/allegro_ttf.h>
11#include <allegro5/allegro_image.h>
12#include <allegro5/allegro_primitives.h>
13#include <allegro5/allegro_native_dialog.h>
14
15#include "game.h"
16#include "gameobject.h"
17#include "clarionmath.h"
18#include "grid.h"
19#include "bitmaps.h"
20
21#ifndef MAINCPP
22#define GLOB(X,V) extern X;
23#else
24#define GLOB(X,V) X V;
25#undef MAINCPP
26#endif
27
28
29///GLOBAL DEFINES
30#define SCREEN_W al_get_display_width(display)
31#define SCREEN_H al_get_display_height(display)
32#define SCREEN_BITMAP al_get_backbuffer(display)
33
34///ENUMS
35namespace ROOM
36{
37enum ENUM
38{
39 MENU,
40 GAME
41};
42}
43
44namespace DIR
45{
46enum ENUM
47{
48 NORTH,
49 EAST,
50 SOUTH,
51 WEST,
52 UP,
53 DOWN
54};
55}
56
57
58enum MOUSE_BUTTONS
59{
60 MBLEFT=0,
61 MBRIGHT=1,
62 MBMIDDLE=2
63};
64
65
66///GLOBAL VARS
67//remember to use GLOB(Declaration,Definition) to define global variables and avoid linker errors
68
69GLOB(Game* Clarion,)
70
71GLOB(const int MASK,=16711935)
72
73GLOB(unsigned int LAST_CLOCK,=clock())
74GLOB(double FPS,=1)
75GLOB(int TARGET_FPS,=60)
76
77//dimensions of various things
78GLOB(int TILE_W,=50)
79GLOB(int TILE_H,=25)
80GLOB(int UI_W,=200)
81GLOB(int UI_H,=120)
82
83//resource holders (hold maps of resources)
84GLOB(BitmapHandler BMP,)
85
86//keyboard data
87GLOB(ALLEGRO_KEYBOARD_STATE KBSTATE,)
88GLOB(bool key[ALLEGRO_KEY_MAX],)
89GLOB(bool key_was[ALLEGRO_KEY_MAX],)
90GLOB(bool key_pressed[ALLEGRO_KEY_MAX],)
91GLOB(bool key_released[ALLEGRO_KEY_MAX],)
92
93//mouse data
94GLOB(ALLEGRO_MOUSE_STATE MOUSE,)
95GLOB(bool mouse_was[3],)
96GLOB(bool mouse_pressed[3],)
97GLOB(bool mouse_released[3],)
98
99//fonts
100GLOB(ALLEGRO_FONT* font_default,)
101
102//display
103GLOB(ALLEGRO_DISPLAY* display,)
104
105
106
107
108///FUNCTIONS
109int init(); //start up necessary allegro tools and other objects
110
111void update_input();
112
113/*void soundplay(ALLEGRO_SAMPLES* S);
114void soundloop(ALLEGRO_SAMPLES* S);*/
115
116void refresh_screen(); //flips screen
117
118//draw a UI frame using a small picture as the base
119void draw_frame(ALLEGRO_BITMAP* frame, int x, int y, int w, int h, bool inner=0);
120
121int color_compare(ALLEGRO_COLOR c1, ALLEGRO_COLOR c2);
|
Trent Gamblin
Member #261
April 2000
|
tl;dr all of it. But I have one tip anyway. Using individual bitmaps is going to be slow on OpenGL or D3D. What you should do is put your tiles in one sheet (if possible, or 2, 3 sheets if not). Then draw everything in one swoop. Something like this: al_hold_bitmap_drawing(true); // loop drawing sub bitmaps must be the same parent al_hold_bitmap_drawing(false); Like that. Then there are very few OpenGL state changes. If you need to keep your tiles in individual files on disk, then load and draw them onto a single large bitmap then destroy them. That big bitmap is called a texture atlas. <shameless plug>If you want something that can easily create atlases from loaded bitmaps (the atlases are static, once you create them they can't be changed and that's the main shortcoming), I have an atlas library here: http://www.nooskewl.com/content/open-source. </shameless plug>
|
David Collins
Member #14,171
March 2012
|
Well, so far there are only TWO kinds of tiles, so I'm not sure if that's the cause. Since all the tiles are 64x64 I shouldn't have a problem making this atlas you speak of. Can you explain exactly what al_hold_bitmap_drawing(true); does? Are you saying that each individual tile should be a sub bitmap of the atlas? I'm going to try this out anyhow. Thanks for the tip. |
Trent Gamblin
Member #261
April 2000
|
Two tiles can be just as bad as 1000. What that code does is start collecting vertices in a buffer, each call to al_draw_bitmap is batched up and when you finally call al_hold_bitmap_drawing(false) it only has to bind that one texture (the atlas) once, and then just passes the gpu a bunch of vertex (triangle) data. The really slow part is switching textures and using an atlas avoids that. If you have just two textures you still have to switch over and over between them. Atlases are very very common in games using OpenGL/D3D and that's why.
|
David Collins
Member #14,171
March 2012
|
Amazing! I made a TileHandler, similar to the above BitmapHandler, except it stores sub-bitmaps to a 1024 by 1024 atlas bitmap. You use TILE["tile.png"]; and it places that tile in the next available spot on the atlas if it hasn't already been loaded. It returns the pointer to the sub-bitmap. After using this instead of the bitmap handler to store the tiles, and adding the al_hold_bitmap_drawing(); function in the world drawing loop, my framerate is through the roof. Thanks a ton for the help. EDIT: So, this begs the question. How do I get around my hard limit of 256 64x64 tiles? I'm thinking I should just make new tile file loads loop back to the start of the atlas (0,0) if it's full, right? Tricky. |
Trent Gamblin
Member #261
April 2000
|
It depends how many tiles you need per level. If you need no more than 256, reset your atlas each level. If you need more, you'll need several sheets and a modified drawing algorithm. The drawing algo would look something like this: for each layer for each sheet hold drawing true loop drawing all tiles on sheet X release hold
|
|