Hello,
Some time ago, we discussed the issue of [unstable CSV profiler FrameTime metrics on Android when the FPS is [Content removed]
At that time, we were informed that this is a known issue, affecting Epic Games as well, and unfortunately, no solution was available.
However, I’ve recently discovered that while FrameTime in the CSV profiler is calculated as the delta between the current and previous EndFrame, the FrameTime in stat unit is based on the delta between the app’s current and previous timestamps - essentially the same as DeltaTime in the Tick function. Unlike the EndFrame-based calculation, this method doesn’t have instability on Android when the FPS is locked.
In the attachments, you’ll find CSV traces showing both metrics: FrameTime (default) and FrameTime_StatUnit. As you’ll see, the second is significantly more stable when the FPS is capped at 30 or 60 and the device is performant enough to maintain that frame rate.
My questions are:
Why is FrameTime calculated differently for stat unit and the CSV profiler?
And are there any downsides to using the stat unit FrameTime in the CSV profiler, considering the clear advantage of improved stability on Android?
I’ve also found that the default CSV FrameTime metric exhibits the same instability on Windows, whereas the FrameTime_StatUnit once again demonstrates significantly greater stability.
[Image Removed]
Hi Alexander,
It appears FrameTime_StatUnit is computed based on a fixed time delta, whereas the computations in CsvProfiler is based on measured delta time, so the latter would properly represent perceived time.
Best regards.
Hi Stephane,
Could you please clarify what you mean by a fixed time delta? Are you referring to a value that is always calculated based on the target FPS, regardless of the actual frame time?
From what I understand, this isn’t the case - when low-end devices fail to maintain target FPS, this metric almost exactly matches the default FrameTime metric from the CSV profile, with a one-frame offset:
[Image Removed]As I see in UEngine::UpdateTimeAndHandleMaxTickRate, the stat unit FrameTime metric (FApp::CurrentTime - FApp::LastTime) includes the time spent on: actual game logic + waiting for render + the delay of the next frame to fit t.MaxFPS. In contrast, the FrameTime from the CSV profile includes: the delay of the current frame to fit t.MaxFPS + actual game logic + waiting for render.
The issue is that the delay to fit t.MaxFPS is calculated based on the duration of game logic from the previous frame. So if the engine waits for a certain amount of time, but the actual logic in the current frame takes longer than in the previous one, the current frame will exceed the t.MaxFPS target. This overshoot is only corrected in the next frame by reducing the delay before starting the game logic. However, if the game logic then happens to be shorter, the frame will end up being shorter than the t.MapFPS target. This is what causes the inconsistency in the CSV FrameTime metric, whereas the stat unit FrameTime metric doesn’t have this issue because it measures the game logic time + delay calculated based on the duration of that specific game logic execution, but not from the previous iteration. Please correct me if I’m wrong.
[Image Removed]
It is really interesting to know why the code inside FEngineLoop::Tick() is written in a way that sleep happens at the beginning of the frame (for the time calculated based on data from previous frames).
The alternative way would be to sleep after all the work is done: this approach allows to have frames that last exactly 33 ms (until the work that needs to be done is not taking more time).
Hi Pavel,
I can see the throttling that occurs in UpdateTimeAndHandleMaxTickRate. This seems to date back quite a long time but I’ll reach out as soon as I can gather additional information.
Best regards.
Hi Pavel,
After discussion with the development team, it seems the rationale for sleeping early is to reduce input latency. I am still discussing the issue of time feedback thottling hysteresis you have observed. I’ll reach out once there’s an update. It’s worth noting that for all intents and purposes, the game thread is running at a reliable fixed framerate, however, it is not aligned along the FEngineLoop::Tick frame boundary but from end of throttling to the next frame’s end of throttling. So it may just be a question of adjusting profiling scopes.
Best regards.