【问题标题】:OpenGL multithreadingOpenGL多线程
【发布时间】:2016-10-27 16:28:11
【问题描述】:

好的,我正在尝试将我的游戏引擎切换到多线程。我已经完成了关于如何使其在多线程应用程序中使用 OpenGL 的研究。我对渲染或切换上下文没有任何问题。让我的一段代码解释问题:):

for (it = (*mIt).Renderables.begin(); it != (*mIt).Renderables.end(); it++)
{
    //Set State Modeling matrix
    CORE_RENDERER->state.ModelMatrix = (*it).matrix;
    CORE_RENDERER->state.ActiveSubmesh = (*it).submesh;

    //Internal Model Uniforms
    THREAD_POOL->service.post([&]
    {
        for (unsigned int i = 0; i < CORE_RENDERER->state.ActiveShaderProgram->InternalModelUniforms.size(); i++)
        {
            CORE_RENDERER->state.ActiveShaderProgram->InternalModelUniforms[i]->Set( CORE_RENDERER->state.ModelMatrix);
        }

        CORE_RENDERER->state.ActiveSubmesh->_Render();
    });

    //Sleep(10);
}

我将快速解释代码中的哪些元素以使我的问题更清楚。 Renderables 是一个简单的 std::vector 元素,带有 _Render() 函数,可以完美运行。 CORE_RENDERER->state 是一个结构,它保存有关当前渲染状态的信息,例如当前材质属性以及当前子网格模型矩阵。所以 Matrix 和 Submesh 被存储到状态结构中(我知道这很慢,我可能会及时改变它:))下一段代码被发送到 THREAD_POOL->service 这实际上是 boost::asio::io_service 和只有一个线程,所以它就像一个渲染命令队列。这个想法是主线程提供有关要渲染什么以及进行截锥体剔除和其他测试的信息,而辅助线程则执行实际渲染。这工作正常,除了有一个小问题:

发送到线程池的代码开始执行,但在设置所有 InternalModelUniforms 和渲染子网格之前,执行 Renderables 的下一次迭代并且 ModelMatrix 和 ActiveSubmesh 都被更改。该程序不会崩溃,但两个信息都发生了变化,并且一些网格被渲染了一些矩阵是正确的,而另一些则不是,这会导致图像闪烁。对象出现在帧上,下一帧它们就消失了。只有当我启用 Sleep(10) 函数以确保代码在下一次迭代之前执行时,问题才会得到解决,这显然会扼杀获得性能的想法。对此最好的解决方案是什么?如何将命令发送到每个具有唯一内置数据的队列?也许我需要实现自己的命令队列和没有 io_service 的单线程?

我会继续我的研究,因为我知道有办法。这个想法是正确的,因为我得到了性能提升,因为渲染线程没有处理单个 if/else 语句:) 任何帮助或提示都会真正有帮助!

谢谢!

更新:

经过几个晚上的努力,我创建了一个非常原始的主线程和辅助线程之间的通信模型。我创建了一个代表辅助线程执行的基本命令的类:

class _ThreadCommand
{
public:
    _ThreadCommand() {}
    ~_ThreadCommand() {}

    virtual void _Execute() = 0;
    virtual _ThreadCommand* Clone() = 0;
};

这些属于此类的子命令具有 _Execute() 函数来执行任何需要执行的操作。渲染时的主线程填充这些命令的 boost::ptr_vector 而辅助线程继续检查是否有任何命令要处理。当找到命令时,它会将整个向量复制到 _AuxThread 内它自己的向量并清除原始向量。然后通过在每个上调用 _Execute 函数来执行命令:

void _AuxThread()
{
    //List of Thread commands
    boost::ptr_vector<_ThreadCommand> _cmd;

    //Infinitive loop
    while(CORE_ENGINE->isRunning())
    {
        boost::lock_guard<boost::mutex> _lock(_auxMutex);
        if (CORE_ENGINE->_ThreadCommands.size() > 0)
        {
            boost::lock_guard<boost::mutex> _auxLock(_cmdMutex);
            for (unsigned int i = 0; i < CORE_ENGINE->_ThreadCommands.size(); i++)
            {
                _cmd.push_back(CORE_ENGINE->_ThreadCommands[i].Clone());
            }

            //Clear commands
            CORE_ENGINE->_ThreadCommands.clear();

            //Execute Commands
            for (unsigned int i = 0; i < _cmd.size(); i++)
            {
                //Execute
                _cmd[i]._Execute();
            }

            //Empty _cmd
            _cmd.clear();
        }
    }

    //Notify main thread that we have finished
    CORE_ENGINE->_ShutdownCondition->notify_one();
}

我知道这是一种非常糟糕的方法。性能相当慢,我很确定这是因为所有的复制和互斥锁。但至少渲染器可以工作。您可以了解我想要实现的目标,但正如我所说,我对多线程非常陌生。这种情况的最佳解决方案是什么?我应该使用 asio::io_service 返回 ThreadPool 系统吗?如何向 AuxThread 提供命令以及必须发送到渲染器以正确方式执行任务的所有值?

【问题讨论】:

  • 使用std::future,你可以检查future是否准备好并进入下一个迭代而不是sleep
  • 好的,非常感谢 :) 我现在正在研究这个。但是考虑到未来的方法可能会浪费一些时间,这不会导致性能变慢吗?我的想法是主线程继续准备命令,辅助线程渲染它们。当主线程完成并且辅助线程仍然呈现时,主线程可以做其他事情,例如更新 OpenAL 音频流。我可能需要一个更好的解决方案,但我会试一试。非常感谢你!!! :)
  • 希望你知道像lock,mutex,critical section,atomic operation 这样的函数也不要忘记使用volatile 来表示线程之间受影响的变量(有些编译器会忽略它们,有些编译器在没有它们的情况下根本无法工作,例如 GCC对于我的 AT32UC3 芯片,如果未指定为 volatile,则每个线程/ISR 将为每个全局变量进行本地复制,这让我花了很多时间来发现它……)。当您没有足够的经验时,我不会从无锁编程开始

标签: c++ multithreading opengl boost-asio


【解决方案1】:

首先,警告。你的“小问题”一点也不小。这是竞争条件,在 C++ 中是未定义的行为,这反过来意味着任何事情都可能发生,包括:

  • 一切正常

  • 图像闪烁

  • 根本不渲染

  • 它在每个月的最后一个星期六崩溃。或者在您的计算机上正常工作并在其他人的计算机上崩溃。

说真的,永远不要依赖 UB,尤其是在编写库/框架/游戏引擎时。

现在谈谈你的问题。

让我们先把你的方法的任何实际好处放在一边,先解决它。

实际上,OpenGL 实现在底层使用了非常相似的东西。命令由驱动程序线程异步执行。我建议您阅读他们的实现,以了解如何改进您的设计。

您需要做的是以某种方式“捕获”您post 渲染命令时的状态。最简单的事情 - 将CORE_RENDERER-&gt;state 复制到闭包中并使用此副本进行渲染。不过,如果state 足够大,成本可能会很高。

另一种解决方案(OpenGL 就是这样做的)是将state 中的每一个更改也作为一个命令,所以

CORE_RENDERER->state.ModelMatrix = (*it).matrix;
CORE_RENDERER->state.ActiveSubmesh = (*it).submesh;

翻译成

Matrix matrix = (*it).matrix;
Submesh submesh = (*it).submesh;

THREAD_POOL->service.post([&,matrix,submesh]{
    CORE_RENDERER->state.ModelMatrix = matrix;
    CORE_RENDERER->state.ActiveSubmesh = submesh;
});

但是请注意,现在您不能简单地从主线程中读取CORE_RENDERER-&gt;state.ModelMatrix,因为它正在不同的线程中更改。您必须首先确保命令队列为空。

【讨论】:

  • 感谢您的回答!我不确定我是否 100% 得到了你,但这样你也会对硬件造成伤害吗?这是非常严重的!实际上,我在 3 天前第一次涉足多线程领域。正如你所说的捕获状态,这就是我想要做的。渲染器使用类似于 OGRE 的基于材质/技术/通道的渲染。由于一个通道用于多个可渲染对象,我认为最好的方法可能是“捕获”有关使用的通道和渲染的几何图形以及所有建模矩阵的信息。类似于复制渲染状态但信息较少:)
  • @Puso,关于硬件的事情是more of a joke,但它仍然会给你带来非常奇怪的麻烦,比如升级到新的编译器时停止工作,或者移植到新的编译器平台,或在您以外的计算机上运行。
  • 这实际上是我对多线程最大的担忧之一!我担心它根本无法在没有任何已知原因的情况下在所有机器上运行。因此,如果我想让它起作用,我需要小心。回到问题上来,实现这项工作的最佳方法是存储有关 pass 的信息并将其用于渲染?有什么有用的想法或文章吗? :)
  • @Puso,阅读命令设计模式,这可能是一个好的开始。它可以解决状态捕获的问题,并且通常与多线程/异步执行相关联,因此来源可以为您提供一些正确方法的指导。
猜你喜欢
  • 2015-08-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-06-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多