...or at least foreign to Allegro coding standards. Everywhere I look whether it's this forum or the first few hundred hits on Google, all the sample code for frame locking does a poor job of not using 100% CPU.
I think we all know the FAQ's blurb on FPS locking. [url=http://alleg.sourceforge.net/faq.html#How_can_I_make_my_game_run_at_the_same_speed_on_any_computer_]Not a word is meantioned about CPU usage[/url].
The question is simple - how do I make my simple tile-drawing program not take 100% CPU? rest(0) does nothing. rest(10) or any other static number makes the program run choppily and it still takes close to 70% CPU.
It's annoying to see all these FPS threads, I know. I've waded through quite a few, but once more to be absolutely sure on the matter, ey?
You could rest(1) or Sleep(1) (under windows) whenever you are ahead:
1 | while (!finished) |
2 | { |
3 | while (ticks) |
4 | { |
5 | /* ... */ |
6 | drawgfx = TRUE; |
7 | --ticks; |
8 | } |
9 | |
10 | if (drawgfx) |
11 | { |
12 | /* ... */ |
13 | drawgfx = FALSE; |
14 | } |
15 | else |
16 | rest(1); // Sleep(1); |
17 | } |
Or you could look at variable timing, where you let it run as fast as possible and compensate by using delta values.
PixelShips Retro uses far less than 100% CPU resources on every computer I've tested it on. Actually, I've had one particular system measure it as low as a measly 2%, which couldn't possibly be right but hey, if that's what it wanted to believe.
I switched to using Sleep(1) on Windows over Allegro's rest(1) because it seems to agree with the OS better.
If rest(10) is still making your game use 70% CPU, then the problem is two-fold. One, you need to give some CPU time back to the computer. Two, you're program is already using tons of CPU power just to do its thing.
Let me guess: You're running at 800x600 resolution, 24/32 bpp, and are doing a full-redraw of the screen every frame.
Allegro isn't accelerated beyond some very simple optimizations, so if you're only using Allegro to render everything you need to set some boundaries to achieve a more balanced game. Such as 640x480, 16 bpp, etc.
.EDIT: There is one other thing I nearly forgot to mention. If you are programming on Windows all video memory must be locked before you can draw to it. By default, the only video surface in Allegro is the screen, and it will automatically lock and unlock for you... but that automatic process will wreak havoc if you rely on it for more than one write at a time. Either buffer all drawing to a memory bitmap and then draw that single bitmap to the screen, or call acquire_screen() before rendering everything and release_screen() when done rendering.
--- Kris Asick (Gemini)
--- http://www.pixelships.com
Kris and Matthew, the Sleep function works wonderfully on Windows :-D. I'm going to use that for now until I need it to be changed, as I would like some kind of cross-platform capability. Thanks for the info :-D. It dropped mine down ~70-80%.
...or at least foreign to Allegro coding standards.
Not at all! Check the source for the test program or the grabber.
all the sample code for frame locking does a poor job of not using 100% CPU.
That's because it's written to show how to run the same speed on any system, not to show how not to use 100% of teh CPU power. The best way to do that will depend on the game anyway.
rest(0) does nothing.
Correction: rest(0) gives up the rest of the timeslice to other processes that need time, allowing them to run. If no other process is running, it will return immediately. In other words, it will never reduce CPU usage.
rest(10)
Try rest(1); the result you see will also depend on where you call this. I typically call rest() if the logic does not need updating and the screen has already been drawn (ie, in situations where the game would busy-wait otherwise).
I switched to using Sleep(1) on Windows over Allegro's rest(1) because it seems to agree with the OS better.
Er... rest() calls Sleep() in Windows:
1 | /* tim_win32_rest: |
2 | * Rests the specified amount of milliseconds. |
3 | */ |
4 | static void tim_win32_rest(unsigned int time, AL_METHOD(void, callback, (void))) |
5 | { |
6 | unsigned int start; |
7 | unsigned int ms = time; |
8 | |
9 | if (callback) { |
10 | start = timeGetTime(); |
11 | while (timeGetTime() - start < ms) |
12 | (*callback)(); |
13 | } |
14 | else { |
15 | Sleep(ms); |
16 | } |
17 | } |
so I have no idea why you think Sleep() works better than rest().
Ideally what you want to have is the ability to change the state of the process to blocking (waiting in some queue... can't be executed until an event occurs). Then when the timer goes "pop" you want the process to wake up (timer elapsing causes the os to send a signal SIGALRM to wake up the blocked process). Allegro timers use a different thread so your main process will not block.
Under other platforms, they are usually implemented using threads, which run parallel to the main thread. Therefore timer callbacks on such platforms will not block the main thread when called, so you may need to use appropriate synchronisation devices (eg. mutexes, semaphores, etc.) when accessing data that is shared by a callback and the main thread.
You should be able to get around this by using the timer thread (probably have to be in the nasty callback) to wake up your main thread. Or you can use a different timer system to achieve ideal results.
A simple way to block your process is to use vsync().
Well, you could also take the time one message loop takes and name it dTime
if you want to e.g. get a framerate of 75 Hz, one frame is shown 1/75 seconds, so you
rest((1/75 - dTime) * 1000)
milliseconds.
The only problem in this solution is that it can't handle situations with dTime > 1/75, but I think this should be easily to solve.
In my code I usually do essentially what you're saying albeit with some modular stuff to ensure that I handle integer underflow correctly. Actually nowadays I mostly write my games to run with the vsync and employ a separate fixed rate logic. Assuming vsync is blocking (and at least in OpenGL, it seems to be on all three major platforms), that fixes the CPU time problem.
However in my emulators that doesn't make sense since you're starting from the position of "I must produce exactly 50 frame per second" and so dTime type stuff applies.
The only problem in this solution is that it can't handle situations with dTime > 1/75, but I think this should be easily to solve.
I do this: if dTime is too large (i.e. you took too long to draw this frame) then do the logic and keep time counting but don't draw the next one. So on machines that aren't quite fast enough you get frame skipping and if you can do 2 frames of logic, one drawn, one not, in the time you have to produce 1.5 frames of video then you can still sleep for 0.5 frames and give the other OS systems time to run.
The only problem comes when you end up perpetually behind, e.g. on systems that aren't fast enough or are just fast enough to run your program most of the time but some abnormal event (e.g. file access) has but you behind a small portion and you can't quite seem to catch up. I get around this quite easily. No more than 8 (or some other number I plucked out of thin air) frames are skipped. If the program finds itself trying to skip an 8th frame in a row then it just draws the frame and resets its timing.
Of course all dependent "real time" systems have to be able to handle the occasional time inconsistency too, but I don't suppose the average Allegro game has any of those implemented in their own code. I guess AUDIOSTREAMs that rely on the game processing reaching a certain stage before they know what sound to generate are the only example I can think of.
I tried most the methods in here and none really worked. Granted I did lose CPU usage when I dropped the window size from 640x480 to 256x240, but that's just annoyingly small. You can't be serious to tell me that Allegro is THAT slow. I'm just doing a few blits each frame.
Leverton: Sorry mate, your method just didn't work.
Asick: rest(1) didn't help, and I already am doing acquire_bitmap/release_bitmap [page flipping]
Dalrymple: Didn't know quite how I should have done yours. I set up a callback with BPS_TO_TIMER(TARGET_FPS) and then go into a while(!done) { vsync(); }? The callback handling the game of course.. and it just crashed.
psycho/Harte: Your method seems to have worked the best, however it still took 50% CPU even with 256x240 resolution. Closer, but still not good enough.
If it turns out that Allegro is just slower than my a bowel movement, I'm going to have to rethink how I teach this blasted seminar. OpenGL for 2D just seems like too much complication than these students need. I just can't believe that it's that slow, Asick.
post some code; perhaps you are doing something crazy to cause the bad performance.
EDIT: Like having a different color depth than your desktop in windowed mode for example.
1 | #include <allegro.h> |
2 | #include <stdio.h> |
3 | #include "rect.h" |
4 | |
5 | /* *** DEFINES *** */ |
6 | |
7 | #define TARGET_FPS 60 |
8 | |
9 | //size of the tiles |
10 | #define TILE_W 16 |
11 | #define TILE_H 16 |
12 | |
13 | //max tiles that can fit in the viewport |
14 | #define TILE_MX (viewport.Width() / TILE_W) |
15 | #define TILE_MY (viewport.Height() / TILE_H) |
16 | |
17 | //map tile and pixel dimensions |
18 | #define MAP_X 100 |
19 | #define MAP_Y 100 |
20 | #define MAP_PX (MAP_X * TILE_W) |
21 | #define MAP_PY (MAP_Y * TILE_H) |
22 | |
23 | /* *** GLOBALS *** */ |
24 | |
25 | BITMAP *buffer; |
26 | BITMAP *pages[2]; |
27 | CRect viewport; |
28 | CRect player(1, 1, TILE_W, TILE_H); |
29 | int close_button_pressed = FALSE; |
30 | int page = 0; |
31 | volatile int t = 0; |
32 | |
33 | /* *** FUNCTION PROTOTYPES *** */ |
34 | |
35 | void init(); |
36 | void deinit(); |
37 | void input(); |
38 | void tick(); |
39 | void close_button_handler(void); |
40 | |
41 | void input() { |
42 | //make sure we're looking at the keys pressed currently |
43 | poll_keyboard(); |
44 | |
45 | if(key[KEY_UP]) { |
46 | player.SetTop(player.top - 1); |
47 | } |
48 | if(key[KEY_DOWN]) { |
49 | player.SetTop(player.top + 1); |
50 | } |
51 | if(key[KEY_LEFT]) { |
52 | player.SetLeft(player.left - 1); |
53 | } |
54 | if(key[KEY_RIGHT]) { |
55 | player.SetLeft(player.left + 1); |
56 | } |
57 | } |
58 | |
59 | int main() { |
60 | init(); |
61 | |
62 | PALETTE pal; |
63 | BITMAP *tiles = load_bitmap("tiles.bmp", pal); |
64 | bool draw = false; |
65 | int begintime, endtime; |
66 | |
67 | //create a 2D map full of 4 tiles randomly |
68 | int map[100][100], x, y, tick; |
69 | for(y = 0; y < 100; y++) { |
70 | for(x = 0; x < 100; x++) { |
71 | map[y][x] = rand() % 4; //random number 0-3 |
72 | } |
73 | } |
74 | |
75 | //enter the main loop |
76 | while (!key[KEY_ESC] && !close_button_pressed) { |
77 | begintime = t; |
78 | //catch keypresses to move around our map |
79 | input(); |
80 | |
81 | if(draw) { |
82 | buffer = pages[page]; |
83 | acquire_bitmap(buffer); //used with release_screen() to prevent resource loss |
84 | |
85 | //center the view around the moving player |
86 | viewport.SetCenter(player.GetCenter()); |
87 | |
88 | //Check viewport bounds |
89 | if(viewport.left < 0) { viewport.SetLeft(0); } |
90 | if(viewport.top < 0) { viewport.SetTop(0); } |
91 | if(viewport.right >= MAP_PX) { viewport.SetRight(MAP_PX-1); } |
92 | if(viewport.bottom >= MAP_PY) { viewport.SetBottom(MAP_PY-1); } |
93 | |
94 | //Calculate tile position and the number of pixels to shift |
95 | CPoint camera(viewport.left / TILE_W, viewport.top / TILE_H), |
96 | shift(viewport.left % TILE_W, viewport.top % TILE_H); |
97 | |
98 | //draw our beautiful map |
99 | for(y = 0; y <= TILE_MY; y++) { |
100 | for(x = 0; x <= TILE_MX; x++) { |
101 | blit(tiles, buffer, map[camera.y + y][camera.x + x] * TILE_W, 0, (x * TILE_W) - shift.x, (y * TILE_H) - shift.y, TILE_W, TILE_H); |
102 | } |
103 | } |
104 | //draw our player |
105 | rectfill(buffer, player.left - viewport.left, player.top - viewport.top, player.right - viewport.left, player.bottom - viewport.top, makecol(200,200,200)); |
106 | |
107 | release_bitmap(buffer); |
108 | show_video_bitmap(buffer); |
109 | page = 1 - page; |
110 | } |
111 | endtime = t - begintime; |
112 | if( endtime >= (1000 / TARGET_FPS) ) { draw = false; } |
113 | else { |
114 | draw = true; |
115 | rest( (1000 / TARGET_FPS) - endtime); |
116 | } |
117 | } |
118 | |
119 | destroy_bitmap(tiles); |
120 | |
121 | deinit(); |
122 | return 0; |
123 | } |
124 | END_OF_MAIN() |
125 | |
126 | void tick() |
127 | { |
128 | t++; |
129 | } |
130 | END_OF_FUNCTION(tick) |
131 | |
132 | void close_button_handler(void) |
133 | { |
134 | close_button_pressed = TRUE; |
135 | } |
136 | END_OF_FUNCTION(close_button_handler) |
137 | |
138 | void init() { |
139 | int depth, res; |
140 | allegro_init(); |
141 | depth = desktop_color_depth(); |
142 | if (depth == 0) depth = 32; |
143 | set_color_depth(depth); |
144 | res = set_gfx_mode(GFX_AUTODETECT_WINDOWED, 256, 240, 0, 0); |
145 | if (res != 0) { |
146 | allegro_message(allegro_error); |
147 | exit(-1); |
148 | } |
149 | |
150 | install_timer(); |
151 | install_keyboard(); |
152 | |
153 | set_window_title("Making Mario - Lesson 2"); |
154 | |
155 | //create the back buffer |
156 | pages[0] = create_video_bitmap(SCREEN_W, SCREEN_H); |
157 | pages[1] = create_video_bitmap(SCREEN_W, SCREEN_H); |
158 | |
159 | //set viewport to view the entire screen |
160 | viewport.SetDimensions(SCREEN_W, SCREEN_H); |
161 | |
162 | //make sure the buffer stays within the viewport (VIEWPORT CAN'T BE BIGGER THAN SCREEN) |
163 | set_clip_rect(pages[0], 0, 0, viewport.right, viewport.bottom); |
164 | set_clip_rect(pages[1], 0, 0, viewport.right, viewport.bottom); |
165 | |
166 | //put the player in the center of the screen |
167 | player.SetCenter(viewport.GetCenter()); |
168 | |
169 | set_close_button_callback(close_button_handler); |
170 | |
171 | //Do timer initializations |
172 | LOCK_VARIABLE(t); |
173 | LOCK_FUNCTION(increment_t); |
174 | //allow the user to close the app |
175 | LOCK_FUNCTION(close_button_handler); |
176 | install_int_ex(tick, 1);//BPS_TO_TIMER(60)); //lock FPS to 60 |
177 | |
178 | srand(time(NULL)); //randomize to timer |
179 | } |
180 | |
181 | void deinit() { |
182 | //release the memory used for the back buffer |
183 | destroy_bitmap(pages[0]); |
184 | destroy_bitmap(pages[1]); |
185 | clear_keybuf(); |
186 | } |
Change the resolution back to 640x480 to see what I mean.
Change the resolution back to 640x480 to see what I mean.
Your code can't be compiled without rect.h.
We need the rest of your source if you want us to test it
But at a glance, that aquire_bitmap looks wrong. If you need it, put it just before you start your blit's, and release it just after.
The problem is likely to be install_int_ex. Allegro's timers are very bad at anything particularly high resolution (including the every ms tick you want), and will clump together calls to your callback. In practice you often get a better result from using a much lower resolution timer and being more coarse about your rest breaks. Being careful to allow spare time to accumulate before you blow it all on a rest, of course.
In my "Nuclear War!" for last year's Christmas hack I used a 200 BPS timer to compensate for a 50 FPS display and tend to see 20-30% CPU usage on machines I've tested. Another alternative, if you don't mind being Windows-only, is to use the Win32 function GetTickCount(), which just returns how many ms it has been since your program started and should work much better than an Allegro timer. SDL provides SDL_GetTicks() to do the same if you want to look into doing the same on non-Windows platforms.
Post tiles.bmp while you're at it.
Oh, also note that 1000/60 (i.e. ms / your wanted FPS) isn't an integer so as you have it, you'll actually run at 62.5 fps assuming integer divide underflows rather than rounds (which I have to admit I have no idea about).
http://ninkazu.the-infidel.net/lesson2.zip
Sorry about the rect.h thing. Bleh.
::EDIT::
And I want cross-platform compatability, so no GetTickCount() for me. I'd like to hold off on SDL or any other lib if possible too.
And I want cross-platform compatability, so no GetTickCount() for me.
You need to check ALLEGRO_VRAM_SINGLE_SURFACE in that case, and if it is defined, allocate a virtual screen large enough to accomodate multiple pages.
With that change, I tested your program, in 640x480. I never saw more than 5% CPU usage. Tested on an AMD64-3200 running Linux with the plain X11 driver (ie, no hardware acceleration whatsoever).
Well I'm not sure how the single page functionality is supposed to work. How are you supposed to draw to it and how are you supposed to blit the backbuffer? Is there a standard or what's the deal?
Strange that it got 5%... I don't have nearly as good a processor, but Centrino 2ghz should be able to draw that much with no problems. Geh, I really don't want to have to do this in OpenGL. That would require me to learn that crap before I have to learn it in my graphics class next semester :/
Should I even be worrying about CPU? It's nice to have something you can play wirelessly in class without having just 30 minutes of battery life, but that's really not what these lessons are about.
I suppose I haven't stated my goals - I'm creating a series of lessons and exercises to informally teach students at my university how to make a simple game like Super Mario Bros. So looking at this "lesson 2" I'm still wondering where I should go for lesson 3. I'm thinking about introducing collision detection, jumping physics and organizing the code into a more structured MVC pattern. I just hope that's not too much for one lesson. Maybe I'll take out the jumping physics and just add a simple frame-based animation structure.
What do you guys think? I'm open to suggestion (just don't bicker about source control and programming language like they did on the last forum I asked this).
::EDIT::
Just to see how this current timing is fairing, I boosted the resolution to 640x480 again and only saw a jump of a couple percentage points of CPU. There has to be a way to get it down. The drawing should not be an issue, just when and how long to rest..
::EDIT 2::
I tried with the tick at every 5ms instead of 1ms and I'm getting pretty stable framerates at 60 fps, which is about the same as the 1ms. Still getting the same CPU usage though. Here's the updated relevant code
endtime = t - begintime; if( endtime >= (200 / TARGET_FPS) ) { draw = false; } else { draw = true; rest( ((200 / TARGET_FPS) - endtime) * 5); }
begintime = t before the logic and rendering.
I realized my timer wasn't called with the right value (5 instead of MSEC_TO_TIMER(5)) so that's fixed too.
Evert: I know about rest() calling Sleep(), but that doesn't negate that there was a marked performance increase when I switched from rest() and Allegro timers to directly calling Sleep() and using the Windows high-performance timers. (Which Allegro should theoretically also be using after checking the same file that routine was in.) I can't explain it but there must be a reason why because the difference is there.
Ninkazu: First of all, when you do page flipping you should not be doing any game logic while you've got a video page locked. The only thing you should be doing between calls to the acquire and release commands are drawing commands. The longer you lock a page of video memory for the longer everything running on your computer has to wait for that process to finish before memory handling can go back to normal.
The second thing is that if your desktop is set to 32-bit colour your code is defaulting to it as well. 32-bit colour is twice slower to draw in memory than 16-bit, and has to be done entirely by the CPU. The speed of your computer will make a huge difference. When it comes down to the graphics card however, there are a number of factors that come into play for how quickly the CPU can transmit data to video bitmaps. You may have a particular combination that doesn't like 32-bit colour so much, further slowing down the draw operation.
Force your program into 8-bit or 16-bit, depending on how you want to go about it, then see if that helps. There are almost no reasons to need 32-bit over 16-bit in a non-accelerated program.
Lastly, your calculations for your rest periods are completely out of whack.
To understand this and modify your code properly, consider that 1000 / 60 = 16.666666667, while endtime will almost never be anything but 0 or 1 unless your framerate drops below 60. Thus all of your calculations using endtime must be completely off. Scratch that, the timing code is ok.
But your calls to rest() should be simple. One thing you could do is reset your timer to go at a rate of 60 ticks a second, then time your game logic to the timer. Then, call rest(1) in a loop until another timer tick has passed, then process it. It's one way to go about doing it.
Two other last things. Try updating your graphics drivers and switching to full-screen instead of Windowed. It could be that your graphics card does not handle Windowed drawing very well.
--- Kris Asick (Gemini)
--- http://www.pixelships.com
Fullscreen 640x480 16-bit still runs at around 45-55%.
I think the code I uploaded didn't have the acquire moved, but the code I've been testing on does, so that's taken care of too.
Can you post an example of the logic and rest loop? It sounds like the first bit of code posted in this thread, which I tried at no help (100% CPU)
You still haven't mentioned what kind of CPU and graphics card you have. 55% might be as good as you can hope for depending on the complexity of what you're doing.
Here is some mock-up code to demonstrate a fixed-time game loop along with the ability to drop frames if the framerate dies.
1 | volatile int tick; |
2 | |
3 | void tick_timer (void) |
4 | { |
5 | tick++; |
6 | } |
7 | END_OF_FUNCTION(tick_timer); |
8 | |
9 | void main (void) |
10 | { |
11 | int quit_loop = 0; |
12 | int draw_frame = 0; |
13 | |
14 | // Initialize Everything |
15 | |
16 | LOCK_VARIABLE(tick); |
17 | LOCK_FUNCTION(tick_timer); |
18 | |
19 | install_timer(tick_timer,BPS_TO_TIMER(60)); |
20 | |
21 | // Game Loop Follows: |
22 | |
23 | do { |
24 | while (tick > 0) |
25 | { |
26 | // Game Logic goes here |
27 | |
28 | draw_frame = 1; |
29 | tick--; |
30 | } |
31 | if (draw_frame) |
32 | { |
33 | // Lock video memory (if page flipping) and |
34 | // draw the screen. |
35 | |
36 | draw_frame = 0; |
37 | rest(0); // Give at least something back to Windows. |
38 | } |
39 | else |
40 | rest(1); // Or Sleep(1) if you're in Windows |
41 | } while (!quit_loop); |
42 | |
43 | // Uninit Everything |
44 | } |
45 | END_OF_MAIN() |
That's so ugly to me... I'm too used to real-time anymore. In either case, that's the way I would aim for what you're trying to do.
One other thing to consider is that 60 is a tough framerate to maintain depending on how complex your game is. You might want to try aiming for 30 instead. (In fact, if you implement frame dropping as I have above, you may see things go at 30 instead of 60!)
--- Kris Asick (Gemini)
--- http://www.pixelships.com
I must have been doing something wrong the first time I tried that method. I tried it this time with a 30 fps target at fullscreen 640x480x16 and hovered around 20%.
Cool. Thanks for the help.
[ATI Radeon X300 256mb... it's sad, my laptop is my gaming computer... I need to upgrade this summer when I'm working at NVIDIA]
Er... rest() calls Sleep() in Windows:
Well then why when I ctrl alt delete does my game crash when I call rest, but when I call sleep it works flawlessly?
Well then why when I ctrl alt delete does my game crash when I call rest, but when I call sleep it works flawlessly?
That's how you recognize you have made a big memory overwrite: when making irrelevent changes DOES change the result.
Also, sleep != Sleep. One is a libc function, the other is a win32 function.
Well then why when I ctrl alt delete does my game crash when I call rest, but when I call sleep it works flawlessly?
You screwed up somewhere? Just look at the source I posted above; if you don't have a callback, rest() calls Sleep() directly.
Doesn't crash after ctrl alt delete
while(!key[KEY_ESC]) { FpsCounter::NewFrameStarted(); deltaTime = FpsCounter::GetDeltaTime(); firstmap.Draw(0, 0, 1); Canvas::Refresh(); Sleep(1); }
Crashes after ctrl alt delete
edit:
nm
rest(0); crashes.
You'll need to post your entire project to determine what's wrong. rest(1) under windows ends up calling Sleep(1) after two or three regular function calls.
http://www.allegro.cc/forums/thread/589424
It's attached to that thread.
Well then why when I ctrl alt delete does my game crash when I call rest, but when I call sleep it works flawlessly?
What version of Allegro are you using? In the Windows version of 4.2.0, there's a bug that causes the program to crash when CTRL+ALT+DEL is pressed. This has been fixed in 4.2.1
AE.