Add rolling average option to Engine.get_frames_per_second()#63356
Add rolling average option to Engine.get_frames_per_second()#63356Calinou wants to merge 1 commit into
Engine.get_frames_per_second()#63356Conversation
f0d342e to
8682301
Compare
8682301 to
342a789
Compare
342a789 to
cc5a0c2
Compare
8b2b0e6 to
ad72599
Compare
|
Rebased and tested again, it works as expected. I update the MRP in OP. A strange issue occurs now when the engine starts: the reported FPS is negative for a brief period of time. I don't recall this happening previously, despite having made no code changes to the PR itself. This is a video of the MRP being run at 120 FPS, slowed down to 10% so you can see it more easily: negative_fps.mp4This does not occur with the script-based implementation in the MRP. |
|
You probably want to make sure the ratio doesn't exceed 1.0 since you're subtracting it from 1.0 on the line below. |
That does the trick, thanks 🙂 |
ad72599 to
7826d68
Compare
|
Rebased and tested again, it works as expected. (I've also tested |
|
Personally, I'd prefer if this were user-configurable in some way. The old behavior makes it easier to see when frame drops occur because it's just accumulating the frame count over the span of one second. Granted, there's better ways to detect frame drops, but I do generally prefer the old behavior. It'd be cool if there were an enum option so we could choose between accumulated frames and averaged frames. I see averaging useful for more intensive applications where the framerate is likely to vary. On the other hand, my main focus is on lightweight graphics where I care about the FPS consistently hitting the max refresh rate of the monitor. So I think there should be options to reflect both of these use case scenarios. |
|
Yeah I wrote the same on RC:
So although the code here is useful, imo it would be more useful with a switch between the versions e.g. |
7826d68 to
094ebb9
Compare
Engine.get_frames_per_second() smootherEngine.get_frames_per_second()
|
Rebased and tested again, it works as expected.
Done. This also paves the way to integrate #67136 more cleanly into the engine. Note that I didn't implement the minimum/maximum metrics in this PR, since the bookkeeping required is also present in #67136. I'll implement them in #67136 after rebasing it on top of this PR. |
1590233 to
168537b
Compare
This uses an averaging algorithm to smooth out the FPS counter. To opt into this new behavior, call `Engine.get_frames_per_second(Engine.FPS_METRIC_ROLLING_AVERAGE)`. The existing behavior is preserved (average over the last second) if no parameter is passed to `Engine.get_frames_per_second()`.
168537b to
1b4acc9
Compare
| enum FPSMetric { | ||
| FPS_METRIC_AVERAGE, | ||
| FPS_METRIC_ROLLING_AVERAGE, | ||
| FPS_METRIC_MAX, |
There was a problem hiding this comment.
A while since I did any enum binding, but do we need to bind a MAX?
Is it useful to users, and does it have any implications for when new values are added to the enum?
(Speaking as a user this was kind of confusing, as if metric MAX might give the max FPS rather than the max of the enum.)
There was a problem hiding this comment.
A while since I did any enum binding, but do we need to bind a
MAX?
It's not a strict requirement, but we generally do it for exposed enums. It allows users to loop over the size of the enum or implement cycling logic without hardcoding the size of the enum, as there is no reflection in GDScript on C++ enums. Reflection is supported on GDScript enums though, since they're internally dictionaries.
for metric in range(Engine.FPS_METRIC_MAX):
pass
# Or:
var fps_metric: Engine.FPSMetric = Engine.FPS_METRIC_AVERAGE
func _input(event: InputEvent) -> void:
if event.is_action_pressed("ui_accept"):
fps_metric = wrapi(fps_metric + 1, 0, Engine.FPS_METRIC_MAX)and does it have any implications for when new values are added to the enum?
No, we can safely change its value without breaking compatibility. It's one exception to enum value changes since we assume people are not relying on the _MAX value for anything else than looping/iteration.
(Speaking as a user this was kind of confusing, as if metric MAX might give the max FPS rather than the max of the enum.)
When the time comes to add a maximum metric, it'll be FPS_METRIC_MAXIMUM to avoid a conflict, and will be listed before FPS_METRIC_MAX (since that one always remains at the end of the enum).
|
This looks good. I did note that Grok calls this technique "exponential moving average" (EMA), and thinks this is the standard term for the exact math here, versus "rolling average" being broader more generic term. Otoh calling it "rolling average" means we could later change the exact technique. @Calinou any thoughts on this prior to approving? ("Smoothed FPS" is also an extremely common descriptive term apparently.) |
I would prefer keeping the term "rolling average", as I consider the exact smoothing algorithm to be an implementation detail. (This is indeed an exponentional moving average algorithm right now.) |
lawnjelly
left a comment
There was a problem hiding this comment.
Approve based on the PR does seem to do as intended (tested the artifact) and the code looks fine as far as I can see.
Could probably do with another reviewer confirming that we want this feature, as I think we generally prefer consensus before adding features (as it hasn't gone through a proposal as far as I can see?).
Otoh having different FPS metrics do seem useful imo.
This uses an averaging algorithm to smooth out the FPS counter. To opt into this new behavior, call
Engine.get_frames_per_second(Engine.FPS_METRIC_ROLLING_AVERAGE).The existing behavior is preserved (average over the last second) if no parameter is passed to
Engine.get_frames_per_second().This also improves the documentation a bit.
See also #18998, which is an earlier attempt at providing something like this (with a different formula). The same formula is included in the MRP (but commented out) for reference.
Testing project: test_fps_counter.zip
Press Space to toggle between core and script-based FPS reporting. Use core-based reporting to test this PR (or the current behavior, with a vanilla
masterbuild).Press Up/Down arrows to increase/decrease maximum FPS. Note that it's capped by V-Sync for testing purposes, but you can disable V-Sync in the project settings or run with
--disable-vsyncCLI argument if needed.TODO
frame_timeactually defines (I've also triedprocess_ticksto no avail). - For comparison, the script approach in the MRP does not have this issue.