|
This thread is locked; no one can reply to it. |
1
2
|
FPS locking is easy - not taking 100% CPU is hard |
Ninkazu
Member #7,861
October 2006
|
...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? |
Matthew Leverton
Supreme Loser
January 1999
|
You could rest(1) or Sleep(1) (under windows) whenever you are ahead:
Or you could look at variable timing, where you let it run as fast as possible and compensate by using delta values. |
Kris Asick
Member #1,424
July 2001
|
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) --- Kris Asick (Gemini) |
Matthew Dalrymple
Member #7,922
October 2006
|
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%. =-----===-----===-----= |
Evert
Member #794
November 2000
|
Quote: ...or at least foreign to Allegro coding standards. Not at all! Check the source for the test program or the grabber. Quote: 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. Quote: 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. Quote: 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). Quote: 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:
so I have no idea why you think Sleep() works better than rest(). |
Goalie Ca
Member #2,579
July 2002
|
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. Quote: 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(). ------------- |
psycho
Member #5,042
September 2004
|
Well, you could also take the time one message loop takes and name it dTime 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. |
Thomas Harte
Member #33
April 2000
|
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. Quote: 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. [My site] [Tetrominoes] |
Ninkazu
Member #7,861
October 2006
|
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. |
Jonatan Hedborg
Member #4,886
July 2004
|
post some code; perhaps you are doing something crazy to cause the bad performance.
|
Ninkazu
Member #7,861
October 2006
|
Change the resolution back to 640x480 to see what I mean. |
LennyLen
Member #5,313
December 2004
|
Quote: Change the resolution back to 640x480 to see what I mean. Your code can't be compiled without rect.h.
|
Jonatan Hedborg
Member #4,886
July 2004
|
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.
|
Thomas Harte
Member #33
April 2000
|
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. [My site] [Tetrominoes] |
Evert
Member #794
November 2000
|
Post tiles.bmp while you're at it. |
Thomas Harte
Member #33
April 2000
|
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). [My site] [Tetrominoes] |
Ninkazu
Member #7,861
October 2006
|
http://ninkazu.the-infidel.net/lesson2.zip Sorry about the rect.h thing. Bleh. ::EDIT:: |
Evert
Member #794
November 2000
|
Quote: 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). |
Ninkazu
Member #7,861
October 2006
|
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:: ::EDIT 2:: 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. |
Kris Asick
Member #1,424
July 2001
|
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.
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) --- Kris Asick (Gemini) |
Ninkazu
Member #7,861
October 2006
|
Fullscreen 640x480 16-bit still runs at around 45-55%. 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) |
Kris Asick
Member #1,424
July 2001
|
You still haven't mentioned what kind of Here is some mock-up code to demonstrate a fixed-time game loop along with the ability to drop frames if the framerate dies.
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) --- Kris Asick (Gemini) |
Ninkazu
Member #7,861
October 2006
|
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. |
Matthew Dalrymple
Member #7,922
October 2006
|
Evert said: 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? =-----===-----===-----= |
Audric
Member #907
January 2001
|
Quote: 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. |
|
1
2
|