You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让winit应用以稳定60FPS帧率运行?

解决winit 0.30.5稳定60FPS定时更新的问题

你的代码无法稳定60FPS的核心问题是:仅在main函数中设置了一次ControlFlow::WaitUntil,事件循环到达该时间点后没有更新下一个等待时间,导致后续只会进入ControlFlow::Wait状态,无法持续触发定时逻辑。此外,依赖RedrawRequested重复请求重绘的方式容易受系统窗口管理器限制,无法保证稳定帧率。

需要对ApplicationHandler实现添加以下修改:

具体修改步骤

  1. 在App结构体中添加计时字段:记录上次更新的时间,用于计算下一个60FPS的时间点。
  2. 实现about_to_wait方法:这是winit新事件循环模式中,事件循环即将进入等待状态前的回调,适合设置下一次定时触发的时间并请求重绘/执行逻辑。
  3. 更新控制流逻辑:在about_to_wait中动态设置下一个WaitUntil的时间点,确保事件循环每隔16.67ms(约60FPS)被唤醒一次。
  4. 调整RedrawRequested逻辑:移除重复调用request_redraw(),改为由定时触发的about_to_wait主动请求重绘,避免不必要的系统交互。

修改后的完整代码

use std::time::{Duration, Instant};

use winit::application::ApplicationHandler;
use winit::event::WindowEvent;
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
use winit::window::{Window, WindowId};

const NANOS_PER_SEC: u64 = 1_000_000_000;
const FRAME_DURATION: Duration = Duration::from_nanos(NANOS_PER_SEC / 60);

#[derive(Default)]
struct App {
    window: Option<Window>,
    last_update_time: Option<Instant>,
}

impl ApplicationHandler for App {
    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
        self.window = Some(event_loop.create_window(Window::default_attributes()).unwrap());
        self.last_update_time = Some(Instant::now());
    }

    fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
        match event {
            WindowEvent::CloseRequested => {
                println!("The close button was pressed; stopping");
                event_loop.exit();
            }
            WindowEvent::RedrawRequested => {
                // 在这里执行渲染/逻辑更新
                if let Some(last_time) = self.last_update_time {
                    let elapsed = last_time.elapsed();
                    println!("FPS: {:>6.2}", 1.0 / elapsed.as_secs_f64());
                    self.last_update_time = Some(Instant::now());
                }

                // 不需要重复调用request_redraw,由about_to_wait定时触发
            }
            _ => ()
        }
    }

    // 添加about_to_wait方法,处理定时逻辑
    fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
        let next_update_time = self.last_update_time.unwrap_or_else(Instant::now) + FRAME_DURATION;
        event_loop.set_control_flow(ControlFlow::WaitUntil(next_update_time));
        
        // 请求重绘,触发RedrawRequested事件
        if let Some(window) = &self.window {
            window.request_redraw();
        }
    }
}

fn main() {
    let event_loop = EventLoop::new().unwrap();

    // 初始控制流设置为第一次等待时间
    let initial_wait_time = Instant::now() + FRAME_DURATION;
    event_loop.set_control_flow(ControlFlow::WaitUntil(initial_wait_time));

    let mut app = App::default();
    event_loop.run_app(&mut app).unwrap();
}

关键修改说明

  • about_to_wait方法:这是winit 0.29+版本引入的新回调,专门用于处理事件循环空闲前的逻辑。在这里动态更新下一次唤醒的时间,确保每帧间隔稳定在~16.67ms。
  • 帧间隔常量:定义FRAME_DURATION统一管理60FPS的时间间隔,避免重复计算。
  • 移除冗余的request_redraw():原代码在RedrawRequested中重复请求重绘可能导致系统额外的事件调度,改为由定时逻辑主动触发,更可控。
  • 初始化last_update_time:在resumed方法中初始化计时起点,避免首次计算出现错误。

这样修改后,事件循环会稳定每隔约16.67ms唤醒一次,触发about_to_wait并请求重绘,最终实现稳定的60FPS逻辑运行。

内容的提问来源于stack exchange,提问作者TwiceUponATime

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 22:18:11