首页 > 解决方案 > 从分离线程停止主游戏循环的最佳方法

问题描述

我正在使用 SDL 开发 C++ 游戏。我开始关注Lazyfoo 的教程,并一直在使用 XCode 在 MacOS 中构建它。这个周末,我一直在查看是否可以在 Visual Studio 2019 中的 Windows 上编译它。我已经成功了,但我不确定我必须做出的改变之一。

游戏使用 ImageMagick (Magick++) 进行一些绘图,然后使用 SDL 将其作为纹理存储在视频卡内存中(SDL 自己的绘图功能还不够)。绘图部分非常慢,但不需要同步发生,所以我选择将该过程放在它自己的线程中,这在我的 Mac 上工作得非常好。

现在我在 Windows 上运行它,当游戏试图绘制一个新角色时,我偶尔会看到崩溃。我将其缩小到SDL_CreateTextureFromSurface(),它从线程内调用并使用从 Magick++ 图像创建的表面创建新纹理。这只发生在我使用多线程时,如果我禁用它(并在它绘制某些东西时杀死我的帧率),它工作得很好。

我查看了互斥锁,但所有建议都建议在线程上使用join,该线程会main()在整个绘图期间阻塞,并使其毫无意义。我认为我很有可能误解了这一点!

bool相反,我在一个作为对线程的引用传递的类中设置了两个s。当SDL_Thread_Waiting被线程设置true为时,游戏循环main()暂时暂停并设置SDL_Main_Pausedtrue告诉线程它可以安全地访问渲染器。然后线程设置SDL_Main_Pausedfalse完成时,游戏循环继续。

这意味着 ImageMagick 可以在它自己的线程中尽可能慢,但在最后一步,我可以短暂暂停主游戏以允许存储纹理而不会发生任何崩溃。

下面的示例代码。游戏在这一点上非常庞大,但这应该显示我在做什么:

// class.hpp
Class Game {
    Public:
    bool is_running;
    bool SDL_Thread_Waiting = false;
    bool SDL_Main_Paused = false;
    // ...
}
// main.cpp
Game game;

int main(int argc, char* argv[]) {    
    game.is_running = true;

    while (game.is_running) {
        // Regular game stuff!

        if (game.SDL_Thread_Waiting) {
            game.SDL_Main_Paused = true;
            while (game.SDL_Main_Paused) {
                // do.. nothing?
            }
        }
    }
// Threaded bits
void generate_character() {
    std::thread th(make_texture, std::ref(game));
    th.detach();
}

SDL_Texture* make_texture(Game& game) {
    // Heavy lifting by ImageMagick goes here

    SDL_Thread_Waiting = true;
    while (!SDL_Main_Paused) {
        std::this_thread::sleep_for(std::chrono::milliseconds(25));
    }

    // SDL_CreateTextureFromSurface()

    SDL_Main_Paused = false;
    SDL_Thread_Waiting = false;
    return texture;
}

这是从分离的线程中短暂阻止主游戏循环的合适方法吗?我是不是误会mutex了,这会更适合这里吗?

我对 C++ 和 SDL 很陌生,如果我的整个问题是基于进一步的误解,我深表歉意!

标签: c++multithreadingsdl

解决方案


您所描述的是互斥体行为。您已经实现了糟糕的自旋锁 - 这可能会因为锁变量上的错误同步而失败(一个线程可能会设置变量但其他线程尚未看到此更改),并且在锁定时会浪费 CPU 周期,因为它会一直在循环中旋转。使用SDL_mutexor std::mutex,它们似乎正是您想要的。

至于从多个线程访问渲染器 - 嗯,不太好。SDL 文档明确指出您确实不应该这样做(除非您完全确定正在使用 SDL 的渲染器实现并且知道该实现的行为方式)。考虑改变您的方法 - 例如,在单独的线程中渲染到内存缓冲区/表面,然后发出信号,以便渲染线程可以获取缓冲区并将其转换为纹理。您甚至可以在此处进行双缓冲以最大程度地减少停顿。

有些事情可以改进但需要更多的工作:创建线程不是免费的,为什么不创建一次并等待 mutex/semaphore/condvar 信号来渲染下一帧?SDL_CreateTextureFromSurface假设静态纹理,为什么不创建一次流纹理并使用SDL_LockTexture&&更新它memcpy


推荐阅读