bevyengine / bevyengine/bevy

Res<Time> is unreliable / jittery

Open
#4,669 18 comments 32 reactions 0 assignees View on GitHub
A-Rendering A-Transform C-Bug
Dominant language
Rust
Stars
48.2k
Forks
4.8k
Avg merge
3d 22h
Merged PRs (30d)
161

Description

Bevy version: 0.7 and current `main`.

---

Bevy's `Time` resource, which is the standard and recommended source of timing information in Bevy, that everything should use for anything timing-related, is inaccurate in typical game situations.

The issue occurs, because `Time` is updated in a system at the beginning of the `First` stage in the main app schedule, using `std` time.

This might be fine for a pure cpu simulation use case, where the app just runs as fast as possible on the CPU. However, it is inadequate for games.

The exact instant when Time is updated depends on when the time update system happens to be scheduled to run. It is not tied to actual rendering frame timings in any way.

For animation use cases (this includes most practical uses of delta time! moving objects, moving the camera …), really anything where the timing information is needed to control what is to be displayed on the screen, the `Time` resource will not accurately represent the actual frame timings. This could lead to artifacts, like jitter or hiccups.

Usage of "fixed timestep" is also affected, as it derives its fixed updates from `Time`.

---

The issue can be observed if we enable vsync (which should lock Bevy to the display refresh rate and result in identical timings every frame), and print the value of `time.delta()` every frame update. The result is something like this:

```
...
16.688ms
16.560ms
16.807ms
16.487ms
16.811ms
16.635ms
17.327ms
15.999ms
16.980ms
16.151ms
16.895ms
16.637ms
16.681ms
16.628ms
16.629ms
16.734ms
16.739ms
16.501ms
16.731ms
16.765ms
16.711ms
16.528ms
16.544ms
16.921ms
16.561ms
16.530ms
16.732ms
16.699ms
...
```

You can see that these timings vary by at least a few hundred microseconds every frame, sometimes even as much as ~1ms from frame to frame. This is, i guess, small enough that nobody has raised an issue in Bevy yet :D … but definitely large enough to risk causing real issues with animation/physics/etc.

---

The real solution to this problem would be to use *frame presentation timings* (time reported by the OS/driver corresponding to when the rendered frame is actually sent to the screen), which requires support from the underlying graphics APIs. `wgpu` does not yet provide anything for this use case. This is understandable, as there is no standard API in Vulkan yet either. AFAIK, only Android, and *maybe* recent versions of DirectX, and *i think* some Mesa extensions on linux, provide such functionality.

Relevant work in Vulkan: https://github.com/KhronosGroup/Vulkan-Docs/pull/1364

In the meantime, we should explore ways to improve the accuracy of `Time` in any way we can. The `First` stage does not seem like the best place to do it.

Maybe a value that is much closer to the true frame timings (and likely good enough for most use cases) could be obtained from the Render schedule somehow? Maybe in the Prepare stage when the swapchain texture is obtained (as this is where the "vsync wait" happens)?

Please discuss.

Related issue: #3768

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.