Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

set_loop_mode doesn't limit fps #708

Open
SuperCuber opened this issue Feb 12, 2021 · 4 comments
Open

set_loop_mode doesn't limit fps #708

SuperCuber opened this issue Feb 12, 2021 · 4 comments

Comments

@SuperCuber
Copy link

SuperCuber commented Feb 12, 2021

OS: Windows 10
GPU: GTX1060

Code:

use nannou::prelude::*;

struct Model;

fn main() {
    nannou::app(model).event(event).simple_window(view).run();
}

fn model(app: &App) -> Model {
    app.set_loop_mode(LoopMode::rate_fps(2.0));
    Model
}

fn event(_app: &App, _model: &mut Model, _event: Event) {}

fn view(app: &App, _model: &Model, frame: Frame) {
    let draw = app.draw();
    draw.background()
        .color(Rgb::from_components((60u8, 60u8, 60u8)));
    draw.line()
        .points(
            Point2 {
                x: frame.rect().x(),
                y: frame.rect().y(),
            },
            Point2 {
                x: frame.rect().x(),
                y: frame.rect().y() + app.time.sin() * 100.0,
            },
        )
        .color(Rgb::from_components((1.0, 0.0, 0.0)));
    draw.to_frame(app, &frame).unwrap();
}

Expected behavior: visually very choppy animation

Actual behavior: fps is synced to monitor refresh rate which is 144hz for me (verified by fps measuring tool)

@sidwellr
Copy link
Contributor

I'm experiencing the same issue. With app.set_loop_mode(LoopMode::rate_fps(2.0)); I expect update and view to be called every half second to result in 2 frames per second, but the parameter seems to be ignored.

@mitchmindtree
Copy link
Member

Thanks for the report @SuperCuber, @sidwellr!

In nannou's old vulkano backend, the variable rate used to be achieved by using the Mailbox present mode.

I believe at the time of the refactor toward wgpu, any present mode other than vsync was still a work in progress. It looks like there are now Immediate, Mailbox and Fifo variants available as of wgpu 0.5, so it might be a good time to revisit this. https://docs.rs/wgpu/0.7.1/wgpu/enum.PresentMode.html

@tqwewe
Copy link

tqwewe commented Feb 21, 2022

An ugly hack to get around this is to put in both view and update (at the top of the function):

if app.elapsed_frames() % 60 != 0 {
    return;
}

@seanlinsley
Copy link

seanlinsley commented Sep 30, 2022

Constantly limiting the frame rate like @tqwewe suggested was causing animations to be choppy. I worked around that by adding this code to my render function:

use std::sync::atomic::{AtomicU64, Ordering};

static RENDER_TICKS: AtomicU64 = AtomicU64::new(0);

fn should_render(model: &Model) -> bool {
    let render_ticks = RENDER_TICKS.load(Ordering::SeqCst);
    let should_render = model.ticks != render_ticks;
    if should_render {
        RENDER_TICKS.fetch_add(model.ticks - render_ticks, Ordering::SeqCst);
    }
    should_render
}

pub fn view(app: &App, model: &Model, frame: Frame) {
    if !should_render(model) {
        return
    }
    // the rest of your view rendering here
}

And then incrementing a new model.ticks attribute anytime the app.event callback runs, or when out-of-band code in my update callback knows a render is needed.

This means:

  • the full frame rate is available when a user is interacting with the UI
  • non-UI code can trigger a re-render as needed
  • otherwise, no renders are performed

I could see a benefit to combining both approaches and still performing a few renders a second so fewer code paths need to increment model.ticks.

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

No branches or pull requests

5 participants