Interesting implementation. I decided to take it and see what I can do with it. When I tested your version, due to the time it takes to get to the Swap() command in Sync, the framerate does suffer, which is what we want to avoid using this method. Essentially, to reduce load times and potential future stutter, we should only do things when there is plentiful time. I tested in Tier 2 while rendering 10,000 tiles and 3 Spine skeletons with a default animation playing and got 3~4ms render time from my Update2D() Render2DFront() commands alone. I do not know what the time cost is for the rest of the Sync() command. However, I did take a look at your code in Tier 1 and modified it a bit to stabilize the framerate.
// Project: Test_Grounds
// Created: 2018-08-31
// show all errors
SetErrorMode(2)
// set window properties
SetWindowTitle( "Test_Grounds" )
SetWindowSize( 1024, 768, 0 )
SetWindowAllowResize( 1 ) // allow the user to resize the window
// set display properties
SetVirtualResolution( 1024, 768 ) // doesn't have to match the window
SetOrientationAllowed( 1, 1, 1, 1 ) // allow both portrait and landscape on mobile devices
SetSyncRate( 60, 1 ) // 30fps instead of 60 to save battery
SetScissor( 0,0,0,0 ) // use the maximum available screen space, no black borders
UseNewDefaultFonts( 1 ) // since version 2.0.22 we can use nicer default fonts
//Constants
#constant TARGET_FRAMETIME 0.0166
#constant FRAMETIME_BUFFER 0.0005 //Adjust based off of the tasks expected to be done. Setting this to 0 will make the frametime fluctuate.
//Project Members - Bad practice, used for demonstration purposes
Global Framestart# = 0.0 //Setup the first framestart value
Global Counter = 0 //Had to make global to make functions work.
Framestart# = Timer() //Global can only be literals apparently.
do
// Put normal game code here
// blah blah
//Moved to print before the render command is called.
Print(Counter)
Counter = 0
Print( ScreenFPS() )
Print( GetFrameTime() )
RenderStuff()
loop
Function RenderStuff()
time# = Timer() - Framestart#
If(TARGET_FRAMETIME > time#) //If there is spare time, update on the target frame time
Update(TARGET_FRAMETIME)
Else
Update(time#) //Otherwise, if the process is slower, update based off of real time
Endif
Render() //Render the frame
DoStuff()
Swap() //Swap the frame buffer to update the image
Framestart# = Timer() //Set the timer for pacing
EndFunction
Function DoStuff()
// Do "background" loading here
while Timer()-Framestart# < TARGET_FRAMETIME - FRAMETIME_BUFFER
// do your loading or whatever here
inc Counter
endwhile
EndFunction
If you check the framerate and frame time from your example to this one, you should notice that it sticks to 60fps and 0.01666667ms far more regularly, which is the goal. We cannot allow the GPU to lag the time behind by using all the processing time before Sync(), so we need to handle it manually to get the desired results. This could be further optimized by removing the elements of Sync not needed for a particular project. For example, in a pure 2D game, we do not need to update any 3D elements, which could save a few CPU cycles. I noticed I gained a fair bit of headroom with no (currently) changes if I just use Render2DFront() and remove Render2DBack(). Granted, for 99% of AppGameKit projects, that is all unneeded. I am seeking extra cycles just to push the limits of what I think AppGameKit is capable of.
I also added functions to increase readability of the main loop and processes.
After a minute of digging, I did the Update() part wrong, should be changed to:
Function RenderStuff()
time# = Timer() - Framestart#
If(TARGET_FRAMETIME > time#) //If there is spare time, update on the target frame time
Update(TARGET_FRAMETIME)
Else
Update(0.0) //Otherwise, if the process is slower, update based off of real time
Endif
Render() //Render the frame
DoStuff()
Swap() //Swap the frame buffer to update the image
Framestart# = Timer() //Set the timer for pacing
EndFunction
I may need to do this in my project too. It seems that Update(0) will update based off of actual elapsed time, which is what Tier2 defaults to using... I will need to do more tests...