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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 00:09:03