如何在main thread与render thread之间实现1-2帧延迟,优化多线程渲染稳定性
实现1-2帧渲染延迟的调整方案
核心逻辑
你当前的实现仅存储了单帧待执行命令,主线程每次Flush都会直接覆盖上一帧未执行的命令,无法维持固定帧间隔。要实现1-2帧的延迟限制,核心思路是把待执行的命令按帧为单位存放到长度固定的队列中,队列最大长度设为你需要的最大延迟帧数(通常为2),当队列满时主线程的Flush操作会阻塞,等待渲染线程消费完一帧后再继续,从而保证两个线程的进度差不会超过设定的阈值。
调整后的完整代码
#include <iostream> #include <queue> #include <mutex> #include <functional> #include <thread> #include <condition_variable> #include <atomic> struct CommandQueue { // 构造时指定最大待处理帧数,设为2即最多2帧延迟 explicit CommandQueue(size_t max_pending_frames = 2) : m_max_pending_frames(max_pending_frames) {} // 仅主线程调用,收集当前帧的GL调用 void Submit(std::function<void()> command) { m_current_frame_commands.push(std::move(command)); } // 仅主线程调用,当前帧命令提交完成,推入待执行队列 void Flush() { std::unique_lock<std::mutex> lock(m_mutex); // 如果待执行队列已满,等待渲染线程消费一帧 m_frame_available_cv.wait(lock, [this]() { return m_pending_frames.size() < m_max_pending_frames || m_quit.load(std::memory_order_relaxed); }); if (m_quit) return; // 把当前帧的所有命令作为一个整体加入待执行队列 m_pending_frames.push(std::move(m_current_frame_commands)); // 通知渲染线程有新的帧可执行 m_render_cv.notify_one(); } // 仅渲染线程调用,执行一帧的所有命令 bool Execute() { auto render_commands = WaitForFrame(); if(render_commands.empty()) { return false; } while(!render_commands.empty()) { auto cmd = std::move(render_commands.front()); render_commands.pop(); cmd(); } // 执行完一帧,通知主线程队列有空位可以提交新帧 { std::lock_guard<std::mutex> lock(m_mutex); m_frame_available_cv.notify_one(); } return true; } void Quit() { { std::lock_guard<std::mutex> lock(m_mutex); m_quit.store(true, std::memory_order_relaxed); } // 唤醒两个条件变量,避免线程阻塞无法退出 m_render_cv.notify_one(); m_frame_available_cv.notify_one(); } private: std::queue<std::function<void()>> WaitForFrame() { std::unique_lock<std::mutex> lock(m_mutex); m_render_cv.wait(lock, [this]() { return !m_pending_frames.empty() || m_quit.load(std::memory_order_relaxed); }); std::queue<std::function<void()>> result; if (!m_pending_frames.empty()) { result = std::move(m_pending_frames.front()); m_pending_frames.pop(); } return result; } size_t m_max_pending_frames; std::mutex m_mutex; std::condition_variable m_render_cv; // 通知渲染线程有新帧可执行 std::condition_variable m_frame_available_cv; // 通知主线程队列有空位可提交新帧 std::queue<std::function<void()>> m_current_frame_commands; // 主线程正在收集的当前帧命令 std::queue<std::queue<std::function<void()>>> m_pending_frames; // 待执行的帧队列,每个元素对应一整帧的命令 std::atomic_bool m_quit{false}; }; // 测试用示例main函数 // 注意:示例代码中用到的window变量需要替换为你自己的SDL窗口实例 int main() { // 这里传2就是最多2帧延迟,传1就是1帧延迟 CommandQueue commandQueue(2); std::thread renderThread([&](){ while(true) { if(!commandQueue.Execute()) { break; } } }); bool quit = false; while(!quit) { // 你的业务逻辑,收集当前帧命令 commandQueue.Submit([](){ glClear(GL_COLOR_BUFFER_BIT); }); commandQueue.Submit([](){ glClearColor(1.0f, 0.0f, 0.0f, 1.0f); }); commandQueue.Submit([](){ glViewport(0, 0, 1600, 900); }); commandQueue.Submit([window](){ SDL_GL_SwapWindow(window); }); // 提交当前帧 commandQueue.Flush(); } commandQueue.Quit(); renderThread.join(); return 0; }
调整说明
- 你可以通过CommandQueue构造函数的入参调整最大延迟帧数,传1就是强制两线程进度差不超过1帧,传2就是最多2帧间隔,符合主流引擎的实现逻辑
- 渲染线程执行完一帧后才会通知主线程提交新帧,不会出现主线程跑太快远超渲染线程的情况
- 退出逻辑做了兼容处理,退出时会同时唤醒两个线程的条件变量,不会出现死锁问题
内容的提问来源于stack exchange,提问作者BrodaJarek3
相关产品推荐
相关产品推荐

