bevy: Vsync no longer throttles rendering in v0.4.0

Bevy version v0.4.0

Operating system & version

Arch Linux 5.9.11

What you did

use bevy::prelude::*;

fn main() {
    App::build()
        .add_plugins(DefaultPlugins)
        .add_plugin(bevy::diagnostic::FrameTimeDiagnosticsPlugin)
        .add_plugin(bevy::diagnostic::PrintDiagnosticsPlugin::default())
        .run();
}

What you expected to happen

Using Bevy v0.3.0:

❯ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.07s
     Running `target/debug/bevy_vsync`
Diagnostics:
---------------------------------------------------------------------------------------------
frame_count                                                      : 64.000000   (avg 64.000000)

frame_time                                                       : 0.016700    (avg 0.016689)

fps                                                              : 59.950072   (avg 59.930779)

What actually happened

Using Bevy v0.4.0:

❯ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.08s
     Running `target/debug/bevy_vsync`
Diagnostics:
---------------------------------------------------------------------------------------------
frame_count                                                      : 332.000000  (avg 332.000000)

frame_time                                                       : 0.002922    (avg 0.003053)

fps                                                              : 328.634749  (avg 328.075462)

Additional information

This was observed under X11.

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 21 (10 by maintainers)

Most upvoted comments

1/60 of a second is not a latency that impacts every game type (not at all for GUI applications);

If the GUI has draggable elements, you will notice the lag between the cursor moving and the draggable element moving. Even if it is just a single frame.

The nonscientific nature of the claims that <10 ms can be humanly perceived and affect your gameplay.

There is a reason VR headsets use framerates >60Hz. Even if it doesn’t impact desktop gaming much, as far as I know it is noticable when you move your head in a VR headset. (Don’t own one, so can’t try to check for myself.)

Short term I think the fix should be to revert to FIFO as the cost of no frame limiting is pretty high and the “fix” is easy. Medium term we should bake in some frame limiting (which will be slightly involved due to the issues mentioned above). Then we can flip back to “mailbox with fifo fallback”.

Same behavior on my laptop which has Intel integrated graphics:

         apiVersion     = 4202641 (1.2.145)
         driverVersion  = 83898369 (0x5003001)
         vendorID       = 0x8086
         deviceID       = 0x5916
         deviceType     = PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU
         deviceName     = Intel(R) HD Graphics 620 (KBL GT2)
       Present Modes: count = 4
                PRESENT_MODE_IMMEDIATE_KHR
                PRESENT_MODE_MAILBOX_KHR
                PRESENT_MODE_FIFO_KHR
                PRESENT_MODE_FIFO_RELAXED_KHR

I was looking through the source of vkcube and found this: https://github.com/KhronosGroup/Vulkan-Tools/blob/1f550ddec2f3519f3ee7fd9cb1941b5642796ae2/cube/cube.c#L1234-L1263

    // The FIFO present mode is guaranteed by the spec to be supported
    // and to have no tearing.  It's a great default present mode to use.
    VkPresentModeKHR swapchainPresentMode = VK_PRESENT_MODE_FIFO_KHR;

    //  There are times when you may wish to use another present mode.  The
    //  following code shows how to select them, and the comments provide some
    //  reasons you may wish to use them.
    //
    // It should be noted that Vulkan 1.0 doesn't provide a method for
    // synchronizing rendering with the presentation engine's display.  There
    // is a method provided for throttling rendering with the display, but
    // there are some presentation engines for which this method will not work.
    // If an application doesn't throttle its rendering, and if it renders much
    // faster than the refresh rate of the display, this can waste power on
    // mobile devices.  That is because power is being spent rendering images
    // that may never be seen.

    // VK_PRESENT_MODE_IMMEDIATE_KHR is for applications that don't care about
    // tearing, or have some way of synchronizing their rendering with the
    // display.
    // VK_PRESENT_MODE_MAILBOX_KHR may be useful for applications that
    // generally render a new presentable image every refresh cycle, but are
    // occasionally early.  In this case, the application wants the new image
    // to be displayed instead of the previously-queued-for-presentation image
    // that has not yet been displayed.
    // VK_PRESENT_MODE_FIFO_RELAXED_KHR is for applications that generally
    // render a new presentable image every refresh cycle, but are occasionally
    // late.  In this case (perhaps because of stuttering/latency concerns),
    // the application wants the late image to be immediately displayed, even
    // though that may mean some tearing.

So like @yuri91 is saying, it sounds like the high framerate is to be expected and is in fact just mailbox working as intended. And if we wanted to cap rendering to 60 fps we need to either use FIFO, or wait manually. I’m curious, why did we switch to mailbox? @cart

Edit: I think the “not optimal for mobile” disclaimer is not only true for smartphones, but any device that runs on battery power, such as laptops. Battery is also not the only reason why unthrottled rendering might be undesirable, especially if Bevy is meant to be used for non-game applications. If we make a Bevy GUI editor in Bevy, I’d like it to not max out my CPU.

Considering that:

  • 1/60 of a second is not a latency that impacts every game type (not at all for GUI applications);
  • Not every device can afford the extra power draw of mailbox;
  • Not every device is limited by 60Hz, and 1/144 second latency might be acceptable where 1/60 wasn’t;
  • If updates occur at fixed timesteps (as recommended by many game dev source) then mailbox doesn’t improve latency when compared with fifo;
  • The nonscientific nature of the claims that <10 ms can be humanly perceived and affect your gameplay.

Considering all these factors, then I really think the kind of vsync used should be a configurable parameter.

We switched to mailbox because it cut input latency, which in retrospect is probably just by nature of not blocking on vsync framerates.

wgpu examples use mailbox by default, and they do have built in frame limiting: https://github.com/gfx-rs/wgpu-rs/blob/4c518912790ee011cebcd75e9879647554e3261c/examples/framework.rs#L235

We should probably do the same, but the “request redraw” approach they use will require some changes on our end (ex: breaking rendering out from the main schedule so it can be executed separately on-demand). There might be some nuance there because the current system was built under the assumption that rendering runs immediately after game logic. And the LAST stage runs after rendering. We’d need to decide how to handle that. These changes could either be very easy or very hard 😄

I don’t know much about Vulkan, but I was curious about this issue so I read some stuff about presentation modes.

As far as I understand, the mailbox mode does not guarantee a capped frame rate. Even in the wgpu definition of the enum the comments seem to imply so:

pub enum PresentMode {
    /// The presentation engine does **not** wait for a vertical blanking period and
    /// the request is presented immediately. This is a low-latency presentation mode,
    /// but visible tearing may be observed. Will fallback to `Fifo` if unavailable on the
    /// selected  platform and backend. Not optimal for mobile.
    Immediate = 0,
    /// The presentation engine waits for the next vertical blanking period to update
    /// the current image, but frames may be submitted without delay. This is a low-latency
    /// presentation mode and visible tearing will **not** be observed. Will fallback to `Fifo`
    /// if unavailable on the selected platform and backend. Not optimal for mobile.
    Mailbox = 1,
    /// The presentation engine waits for the next vertical blanking period to update
    /// the current image. The framerate will be capped at the display refresh rate,
    /// corresponding to the `VSync`. Tearing cannot be observed. Optimal for mobile.
    Fifo = 2,
}

I also read the following articles about it:

As far as I understand, with the mailbox presentation mode there is a capped 60fps frame rate only if the swap chain has only two available images. Otherwise the application is free to present a new image in the mailbox without having to wait for vsync.

Is this correct? and if so, is it the case that there are only two images used by bevy? I am not sure where to look in the code to check.

EDIT: I think that wgpu uses 3 images by default (see https://github.com/gfx-rs/wgpu/blob/7e3965bb5a6f8da0692237fb1fd63fe03434b405/wgpu-core/src/swap_chain.rs#L52), but uses less if the maximum number of available images is lower (https://github.com/gfx-rs/wgpu/blob/2287ae3f8a7246e8b47b73a1de6fe2410c42b8c8/wgpu-core/src/device/mod.rs#L3922).

Is it possible that on my platform I get 3, while other platforms get 2 and so get capped to 60fps? (EDIT: I tried changing it, and it didn’t make a difference)

Sorry if all of this makes no sense 😅

@alice-i-cecile it seems like we can close this, no? Vsync is now configureable.

I changed the title, since vsync seems to actually still prevent tearing which is its main purpose.