Skip to content

[rcore] Document that GetFPS() must be called every frame - #6120

Closed
burinc wants to merge 1 commit into
raysan5:masterfrom
burinc:docs/getfps-call-cadence
Closed

[rcore] Document that GetFPS() must be called every frame#6120
burinc wants to merge 1 commit into
raysan5:masterfrom
burinc:docs/getfps-call-cadence

Conversation

@burinc

@burinc burinc commented Sep 3, 2026

Copy link
Copy Markdown

GetFPS() is a sampler rather than a query, and the header does not say so.

Each call takes at most one sample, gated on FPS_STEP, writes
GetFrameTime()/FPS_CAPTURE_FRAMES_COUNT into a 30-slot ring, and returns
1/sum-of-ring:

if ((GetTime() - last) > FPS_STEP)
{
    last = (float)GetTime();
    index = (index + 1)%FPS_CAPTURE_FRAMES_COUNT;
    average -= history[index];
    history[index] = fpsFrame/FPS_CAPTURE_FRAMES_COUNT;
    average += history[index];
}

fps = (int)roundf(1.0f/average);

So the ring needs 30 calls before average is the mean frame time. Calling
every frame reaches that in half a second. Calling from a timer, or from a
periodic summary, never reaches it: after n calls only n slots hold anything, so
it returns 1/(n * frame_time/30).

Measured

An app with a steady 17.02 ms frame time, one binary, the call cadence being the
only variable:

cadence readings
one call per second, from a cold ring 1773, 887, 591, 444, 354, 295, 253, 221
every frame, same process moments later 59, 59, 59, 58, 59

The first row is 1773/n to within a percent at every point, which is the ring
filling one slot per call.

Called every frame, as DrawFPS() does and as the examples do, it is correct.
The reason this is worth a comment is that misuse has no tell: no error, and a
plausible-looking number rather than an obviously wrong one. It took a
frame-time series that stayed flat while the reported FPS decayed to notice.

The change

Comment only, no behaviour change:

-RLAPI int GetFPS(void);                                 // Get current FPS
+RLAPI int GetFPS(void);                                 // Get current FPS (averages the last 30 calls, so call it every frame)

The rlparser outputs carry the header comment as the function description,
so they are updated to match rather than regenerated. That keeps the diff to one
line per file; happy to regenerate them properly instead if you prefer.

Verified against master at 9b2efc45, which is where this branches from.
GetFPS() is byte-identical there and at 6.0. raylib still builds with the
change (comment only).

Entirely reasonable to close this if per-frame calling is considered obvious
enough not to document.

GetFPS() is a sampler rather than a query. Each call takes at most one sample,
gated on FPS_STEP, writes GetFrameTime()/FPS_CAPTURE_FRAMES_COUNT into a 30-slot
ring and returns 1/sum-of-ring. So the ring needs 30 calls before the answer
means anything, which per-frame calling reaches in half a second and a timer or
a periodic summary never reaches at all.

Called every frame, as DrawFPS() does and as every example does, it is correct.
Called less often it silently returns a value that is too high by roughly the
factor the ring is short: measured on device at a steady 17.02 ms frame time,
one call per second from a cold ring gave 1773, 887, 591, 444, 354, 295, 253
and 221, which is 1773/n, while calling it every frame in the same process gave
a steady 59.

Nothing in the header said so, and the failure has no tell: no error, and a
plausible number rather than an obviously wrong one.

Comment only, no behaviour change. The rlparser outputs carry the header
comment as the function description, so they are updated to match rather than
regenerated, which keeps the diff to one line per file.
@raysan5

raysan5 commented Sep 4, 2026

Copy link
Copy Markdown
Owner

Thanks but I prefer to keep the comment simple, I think it's not a big deal.

@raysan5 raysan5 closed this Sep 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants