Setting FPS
-
Found out something that might help if it's not too late. FUZE buffers at least one frame. By 'buffer', I mean that there is a queue. If you're pumping out 60 FPS with ease, you'll have the frame you're currently working on and the one being displayed; but in between those, there can be at least one waiting to be displayed. When you call update(), your current frame doesn't get displayed. You do have to wait for the next vertical sync, but when that comes, only the frame from the queue gets displayed, your then current frame goes to the queue, you get given back control and can work on the next frame. Given that we can get tearing free 45 FPS, I suppose it's just fully triple buffering all of the time? That would make sense, it's definitely a very beginner friendly setup.
What this means is: If you do nothing every other frame, you can achieve a solid lock to effectively 30 FPS (FUZE will happily display 60 because it counts the empty frames, too, and the frame time graph will go a bit crazy)
loop float dt = deltaTime() // empty frame update() dt += deltaTime() // dt will now be 1/30 in the best case clear() // do usual work, using dt as timestep update() repeat
Or more robustly, you can do all or some of your non-rendering tasks during the empty frame. This requires a bit more care with the deltaTime since you need a good value during both steps:
float dt = 1/30 loop float nextDeltaTime = deltaTime() // do computational only work, using dt as timestep update() nextDeltaTime += deltaTime() clear() // do rest of the work, still using dt as timestep update() dt = nextDeltaTime repeat
Other schemes may work, too. Key is to call update() one extra time per iteration with no preceding draw calls and take care to collect all deltaTime() values.
Needless to say, this relies on undocumented behavior. It's not clear that an empty update() call doesn't cause issues (inserted black screens, for example), and the frame buffering behavior can also change.
-
Interesting post, thanks.
-
@Z-Mann Thank you for this idea! It makes sense. I think I ran into this buffer queue sideways when I tried the approach @PB_____ suggested above. Somehow, I was not able to figure out the "right" dt to correctly get a "do-nothing" frame.
I actually tried something similar to your suggestion, the division of computation and rendering across two frames. Unfortunately, I messed it up and got "flashing" back and forth between blank frames and my scene. I think I will try again - it seems like the most promising approach, as far as I can understand right now!
-
In my game, I'm not quite hitting 30 fps. Would I gain anything by using the pattern in @Z-Mann's answer above?
Currently I cannot fit both the calculations and the drawing command in the 1/30 seconds that would be necessary for a stable 30 fps frame rate (I'm currently hitting about 22-28 fps) . But if I do all calculations in one 1/30 second frame, call update(), and then do the drawing in the next, would I be able to get to more stable frame rate that way?
-
Also, @Z-Mann, could you (or someone else) perhaps elaborate a little bit about the difference between timestep and delta time, and when and how to use which?
-
@vinicity Delta Time and Timestep are used mostly interchangeably. Mostly! Delta Time refers more to the measured time of a frame (that would be why deltaTime() is the name of the function), while Timestep is the value you use to advance your game world, as in
timestep = deltaTime() x = x + v * timestep
for simulating linear movement with speed v. Normally, those would be equal. But if you apply a fast forward or slow motion effect, you put a factor in between the two, as in
timestep = deltaTime() * 2 // fast forward at double speed x = x + v * timestep
Or, sometimes it's necessary to calculate the game advancement between two render frames in two simulation steps:
timestep = deltaTime() * 0.5 x = x + v * timestep x = x + v * timestep
That doesn't make sense in this simple example, of course, but racing games sometimes do this to accurately simulate suspension and ground traction.
Here, you have the opposite situation, you do one game update per several 'render' frames.
I don't think you get any advantage from the two step scheme if you never ever hit 30 FPS. Only if you have large frame-to-frame variance in processing/rendering time, it might help to flatten the bumps a bit. But the scheme should totally be extendable to three frames to try and lock to stable 20 FPS, if that's a framerate your game is still playable at. You need to distribute the work, then, the buffering only gets you so far. So (adopting the timestep variable name)
float timestep = 1/20 loop float nextTimestep = deltaTime() // do computational only work, using timestep update() nextTimestep += deltaTime() // do some more computational only work, using timestep update() nextTimestep += deltaTime() clear() // do rest of the work, still using timestep update() timestep = nextTimestep repeat
-
Thank you for the explanation. Very interesting, indeed. I have never really thought about or used these concepts before.
-
Alright, I've taken another try with it! I tried the "split frame" approach again. However, the optimal division of work still eludes me: In my main test case, I am somehow still not keeping up with the frame rate.
So... I am broadening the discussion a bit to ask a related question: How "expensive" are certain functions compared to others? Are there "known" bottlenecks to be careful of?
One thing I tested last night was object groups. In my Starfighter game, I use object groups to make a custom camera. In a small test program, I set up an array of 200 randomly moving objects, testing for collisions between each pair with objectIntersect. If I place all 200 objects in a group, I got around 13fps. If I did not put the objects in a group, I got closer to 15fps. I thought that was an interesting find.
My next idea is harder to test with a simple program, so I thought I would ask here: In a long program file (>1000 lines), is there a "penalty" for calling functions from opposite "ends" of the program?
-
You could probably also do
myTimer = setTimer(1/30, -1, functionName) // Stuff function functionName // Some other rendering-related code update() return void
It might not suit every situation, but it's worth a thought.
I'm not sure about the -1 though, but it worked for my tests, and it should last for over a hundred hours at least, even if the timer can run out 🤔
-
That’s an interesting approach, and seems easy enough to try.
-
DaddyJDM: Apparently, it is known that FUZE is not fast with multidimensional arrays. Other than that though, I dunno. I often wonder about things like that myself, what’s expensive and what’s not etc.
Like, there’s a ‘distance’ function in FUZE, which is generally known to be expensive because to get the distance between vectors, part of the way to do it is to use square roots, which is expensive. Maybe because it’s a built-in function though, it’s not too bad. Also, am I slowing things down by using really long variable names? I dunno!
-
@toxibunny the interpreter probably at least tokenizes your code. I wouldn't worry about names (but I could be wrong)
Built-in functions also run at native speeds from what I can tell so they can probably also be safely used at will.