【问题标题】:How to implement 1 or 2 frames lag between the main and the render thread?如何在主线程和渲染线程之间实现 1 或 2 帧延迟?
【发布时间】:2021-11-24 08:01:14
【问题描述】:

我读过引擎会跳过 1 或 2 帧并保持这个距离,以确保渲染线程和主线程不会前进太多。

我有一个非常简单的命令队列,它允许主线程排队命令并让渲染线程调度它们,但我不知道如何在这些线程之间保持 1/2 帧距离。

基本实现:

#include <iostream>
#include <queue>
#include <mutex>
#include <functional>
#include <thread>
#include <condition_variable>

struct CommandQueue
{
    //not thread-safe
    //called only by the main thread
    //collects all gl calls from the main thread
    void Submit(std::function<void()> command)
    {
        commands.push(std::move(command));
    }

    //called only by the main thread
    //when the current frame has finished pushing gl commands
    //we're ready to push them into the render thread
    void Flush()
    {
        { 
            std::unique_lock<std::mutex> lock(mutex);
            commandsToExecute = std::move(commands);
        }

        cv.notify_one();
    }

    //called only by the render thread
    //submit gl calls from our queue into the graphics queue
    bool Execute()
    {
        auto renderCommands = WaitForCommands();
        if(renderCommands.empty()) {
            return false;
        }

        while(!renderCommands.empty()) {
            auto cmd = std::move(renderCommands.front());
            renderCommands.pop();

            cmd();
        }

        return true;
    }

    void Quit()
    {
        quit.store(true, std::memory_order_relaxed);
        cv.notify_one();
    }

private:
    std::queue<std::function<void()>> WaitForCommands()
    {
        std::unique_lock<std::mutex> lock(mutex);
        cv.wait(lock, [this]() { return !commandsToExecute.empty() || quit.load(std::memory_order_relaxed); });

        auto result = std::move(commandsToExecute);
        return result;
    }

    std::mutex mutex;
    std::condition_variable cv;
    std::queue<std::function<void()>> commands;
    std::queue<std::function<void()>> commandsToExecute;

    std::atomic_bool quit{false};
};

int main()
{
    CommandQueue commandQueue;

    std::thread renderThread([&](){
        while(true) {
            if(!commandQueue.Execute()) {
                break;
            }
        }
    });

    bool quit = false;
    while(!quit) {
        //example commands...
        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([](){
            SDL_GL_SwapWindow(window);
        });

        //notify the render thread that there is work to be done
        commandQueue.Flush();
    }

    commandQueue.Flush();
    commandQueue.Quit();
    renderThread.join();

    return 0;
}

如何实现这个 1/2 帧延迟?

【问题讨论】:

  • 如果不让渲染线程滞后会出现问题吗?
  • @Jaan 是的。它会减慢应用程序的速度,因为主线程将太多命令推送到渲染线程中。结果,例如,当我想退出应用程序时,它需要大约 5 秒才能完成所有工作。而且我认为只有 2 帧同时运行就足够了。
  • @RichardCritten 这与图形和渲染线程有什么关系?
  • 我很困惑,你的应用程序是如何因为渲染线程的负载而变慢的?您的应用程序的核心不是在主线程上运行,任何渲染都被卸载到渲染线程吗?我看到的唯一线程连接是在主循环之外的应用程序末尾,因此不会减慢任何速度。

标签: c++ multithreading opengl


【解决方案1】:

我找到了 Filament Engine,它有 FrameSkipper 类,可以实现我需要的解决方案。

快速示例:

//what class should implement Tick functionality?
std::vector<std::function<bool()>> tickFunctions;

void RunAndRemove()
{
    auto it = tickFunctions.begin();
    while (it != tickFunctions.end()) {
        if ((*it)()) {
            it = tickFunctions.erase(it);
        }
        else ++it;
    }
}

void Tick(CommandQueue& queue)
{
    queue.Submit([]() { RunAndRemove(); });
}

struct Fence
{
    Fence(CommandQueue& queue)
            : queue(queue)
    {
        queue.Submit([this]() {
            fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);

            std::weak_ptr<Status> weak = result;
            tickFunctions.push_back([this, weak]() {
                auto result = weak.lock();
                if (result) {
                    GLenum status = glClientWaitSync(fence, 0, 0u);
                    result->store(status, std::memory_order_relaxed);

                    return (status != GL_TIMEOUT_EXPIRED);
                }
                return true;
            });
        });
    }

    ~Fence()
    {
        queue.Submit([fence = fence]() {
            glDeleteSync(fence);
        });
    }

    GLenum getFenceStatus()
    {
        if (!result) {
            return GL_TIMEOUT_EXPIRED;
        }

        return result->load();
    }

    GLsync fence = nullptr;

    using Status = std::atomic<GLenum>;
    std::shared_ptr<Status> result{ std::make_shared<Status>(GL_TIMEOUT_EXPIRED) };
    CommandQueue& queue;
};

struct FrameSkipper
{
    FrameSkipper(CommandQueue& queue, size_t latency = 1)
            : queue(queue), last(latency)
    {
    }

    ~FrameSkipper() = default;

    bool Begin()
    {
        auto& syncs = delayedSyncs;
        auto sync = syncs.front();
        if (sync) {
            auto status = sync->getFenceStatus();
            if (status == GL_TIMEOUT_EXPIRED) {
                // Sync not ready, skip frame
                return false;
            }
            sync.reset();
        }
        // shift all fences down by 1
        std::move(syncs.begin() + 1, syncs.end(), syncs.begin());
        syncs.back() = {};
        return true;
    }

    void End()
    {
        auto& sync = delayedSyncs[last];
        if (sync) {
            sync.reset();
        } sync = std::make_shared<Fence>(queue);
    }

private:
    static constexpr size_t MAX_FRAME_LATENCY = 4;

    using Container = std::array<std::shared_ptr<Fence>, MAX_FRAME_LATENCY>;
    mutable Container delayedSyncs{};

    CommandQueue& queue;

    size_t last;
};
int main()
{
    CommandQueue commandQueue;

    std::thread renderThread([&](){
        while(true) {
            if(!commandQueue.Execute()) {
                break;
            }
        }
    });

    FrameSkipper frameSkipper(commandQueue);

    auto BeginFrame = [&]() {
        Tick(commandQueue);

        if(frameSkipper.Begin()) {
            return true;
        }

        commandQueue.Flush();
        return false;
    };


    auto EndFrame = [&]() {
        frameSkipper.End();

        Tick(commandQueue);
        commandQueue.Flush();
    };

    bool quit = false;
    while(!quit) {
        if(BeginFrame()) {
            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([](){
                SDL_GL_SwapWindow(window);
            });

            EndFrame();
        }
    }

    commandQueue.Flush();
    commandQueue.Quit();
    renderThread.join();

    return 0;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-17
    • 1970-01-01
    相关资源
    最近更新 更多