-
Notifications
You must be signed in to change notification settings - Fork 341
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Unrestrained high rendering frame rates cause high gpu utilization (inability to cap to 60) #1107
Comments
Are you opposed to using delta time? I've overridden most of |
@MSGhero Thanks for the reply. Actually, we do something similar to that already with a Gameplay-wise things run as smooth as butter, but like you said the real FPS can get incredibly high, which seems like wasted resources (and a burning hot GPU). If there were some way to simply interleave rendering in between the logic updates I feel like our issue would be solved. But if we were to leverage delta time I fear it wouldn't address the main issue with the high frame rates, since our goal is to cap the rate of render calls. From my understanding, the main purpose behind delta-based update loops is to smooth out gameplay visuals based on varying machine performance, but we want to avoid wasting resources by just not drawing altogether if there were no actual graphical changes. So I imagine even in a delta-time world this problem still exists in Heaps (and it seems likely other Heaps games probably exhibit the same problem regardless, Dead Cells being a prime example) |
I see your point. My head was a level higher in modulating |
Yeah, it seems like I'm getting a similar response from members of the Haxe Discord as well. If tweaking hlsdl/hldx is the only solution I'm ok with it, but I'm just a little surprised there is no exposed mechanism to constrain the rendering loop at a higher level. |
You can still add additional Sys.sleep by measuring the difference between
two haxe.Timer.stamp() so the process is paused and locked to ~60fps
Le mar. 11 oct. 2022 à 02:36, Greg McLeod ***@***.***> a
écrit :
… Yeah, it seems like I'm getting a similar response from members of the
Haxe Discord as well. If tweaking hlsdl/hldx is the only solution I'm ok
with it, but I'm just a little surprised there is no exposed mechanism to
constrain the rendering loop at a higher level.
—
Reply to this email directly, view it on GitHub
<#1107 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAHZXQBWL2WUZEPKPGPOS5TWCSZAFANCNFSM6AAAAAARBTOSYE>
.
You are receiving this because you are subscribed to this thread.Message
ID: ***@***.***>
|
Thanks nicolas, I think we may try something like that as well if nothing else pans out. Even though ideally i'd like to see a solid locked/precise 60fps, adding sleeps would address the core issue with constraining the render calls to an upper limit |
Update: So I was able to solve/workaround the issue after discovering the source of my struggles with For example, a printout of seconds elapsed after a
As you can see above, DX is off by 10x. What's especially strange is that if you add another level of precision with
So it's clear from this that DirectX is capable of more precise timing but it is not consistent with SDL's precision. It's unclear to me the reason why since it seems to work fine if I sleep for a whole number, like 1 second. As for my workaround, a spin lock seems to be the only solution in DX. Not great for the CPU usage but definitely an improvement as far as resource consumption is concerned. The fix I introduced locally involved replacing the Line 84 in 901b39f
Psuedo-code below: // Related helper vars
// desiredFrameRate = 120.0; // Set this to the rough value that you want the frame rate to be capped at.
// updateFrameRate = 60.0; // Set this to match game update loop (required in order to skip frames when we want to render fewer frames than the update tick speed)
// timeAccumulator = 0.0; // This tracks accumulated frame time (used with updateFrameRate to calculate when to redraw)
// static var lastFrame = 0.0 // For tracking elapsed time of update loop
if( !vsync) {
var desiredRefreshTime:Float = 1 / desiredFrameRate;
var now = haxe.Timer.stamp();
var spent = now - lastFrame;
timeAccumulator += lastFrame == 0 ? desiredRefreshTime : spent;
// If frame rate > 0 (i.e. not unlimited), and some extra time remains for the update tick to hit target frame rate
if (desiredFrameRate > 0 && spent < desiredRefreshTime) {
// Sleep to cap the frame rate (but don't sleep longer than the update frame rate)
var sleepSecs:Float = desiredRefreshTime - spent;
if (sleepSecs < 1 / updateFrameRate) {
preciseSleep(sleepSecs); // Either sleep() or spin lock depending on target
}
// Update time accumulator
spent = haxe.Timer.stamp() - now;
timeAccumulator += spent;
}
// Only render if enough time has passed to satisfy refresh rate (i.e. for FPS below 60, we need to skip some frames)
if (timeAccumulator >= desiredRefreshTime) {
// Use while loop to conserve remainder millis
while (timeAccumulator - desiredRefreshTime > 0) {
timeAccumulator -= desiredRefreshTime;
}
cur.driver.present();
}
lastFrame = haxe.Timer.stamp();
} else {
// Always redraw
cur.driver.present();
} It's a little verbose and could be improved, but it gets the job done. If vsync is off, the update loop will respect the frame rate cap. It also accounts for rendering fewer frames than the game's native frame rate (i.e. 30 fps in a 60fps game). Hopefully if anyone else runs into this problem this code will point them in the right direction on how to address! |
I don't see how DX/GL could affect Sys.sleep ? Aren't these results different because of vertical sync ? |
At Shiro we currently do a soft spin lock to mitigate high framerates:
the 1ms safety margin is there to prevent VSync from missing a frame, which seems to roughly work (so disabling VSync you usually go from 60fps to something that fluctuates around 66fps) |
So I'm having trouble trying to figure out a way to cap the rendering frame rate of our game to 60 in a consistent, reliable way regardless of the rendering target (hlsdl, hldx, etc).
For example, with vsync on and a 165hz monitor (as to be expected with heaps out of the box):
And vsync off it will easily get over 2000fps with over 90% gpu utilization. For this reason we want to introduce a hard cap at 60 since there is no visual benefit to rendering unchanged frames (we don't process subframes nor use delta times).
So far I experimented a bit with
hxd.Timer
, in addition to playing with the sleep timer in SDL (#941), but I still haven't been able to get the frame rate under control. The SDL sleep tweak was the closet to a solution, but the frame rate appears to be wobbly/unstable, and that change is also SDL-specific. I should also note that Dead Cells appears to exhibit similar behavior.I understand the concept that vsync should be synced with the monitor refresh rate and uncapping it should lift the restriction, however it's not too uncommon for determinstic 2D games to lock at a hard 60. In a perfect world I'd love to just see the game hold at 60 when vsync is off. If anyone has any guidance on the matter it would be greatly appreciated!
The text was updated successfully, but these errors were encountered: