利用游戏循环和openGL之间的多线程


10

在基于openGL渲染器的游戏环境中交谈:

假设有两个线程:

  1. 更新游戏中对象的游戏逻辑和物理等

  2. 根据游戏对象中的数据对每个游戏对象进行openGL绘制调用(线程1不断更新)

除非您在游戏的当前状态下每个游戏对象都有两个副本,否则您必须暂停线程1,而线程2进行抽奖,否则游戏对象将在该对象的抽奖中途更新。是不可取的!

但是停止线程1以安全地从线程2进行绘制调用会杀死多线程/并发的整个目的

除了使用成百上千或同步对象/围栅之外,还有没有更好的方法可以利用多核体系结构提高性能?

我知道我仍然可以使用多线程为尚未成为当前游戏状态一部分的对象加载纹理和编译着色器,但是如何在不影响绘制和更新的情况下对活动/可见对象执行此操作?

如果我在每个游戏对象中使用单独的同步锁怎么办?这样,任何线程都只会阻塞一个对象(理想情况),而不是整个更新/绘制周期!但是在每个对象上锁定(游戏可能有上千个对象)的代价是多少?


1.假设只有两个线程无法很好地扩展,那么4核是非常普遍的,只会增加。2.最佳实践策略答案:应用命令查询责任隔离和参与者模型/组件实体系统。3.现实的答案:只需用锁和其他东西破解传统的C ++方式即可。

一个通用数据存储,一个锁。
贾斯汀

@justin就像我用同步锁所说的那样,多线程的唯一优势将在白天被残酷地谋杀,更新线程将不得不等待直到绘制线程对所有对象进行调用,然后绘制线程才会等待,直到更新循环完成更新内容为止!这比单线程方法更糟糕
Allahjane'1

1
带有异步图形API(例如openGL)的游戏中的多线程方法似乎仍然是活跃的话题,尚无标准或接近完美的解决方案
Allahjane 2015年

1
引擎和渲染器公用的数据存储区不应成为游戏对象。应该代表渲染器可以尽快处理。
贾斯汀2015年

Answers:


13

您所描述的使用锁的方法效率很低,而且很可能比使用单个线程慢。在每个线程中保留数据副本的另一种方法在“速度方面”可能会很好地工作,但是要保持副本同步,其内存成本和代码复杂性都很高。

有几种替代方法,一种流行的多线程渲染解决方案是使用命令的双缓冲区。这包括在单独的线程中运行渲染器后端,在该线程中执行所有绘制调用以及与渲染API的通信。运行游戏逻辑的前端线程通过命令缓冲区(双缓冲)与后端渲染器通信。使用此设置,一帧完成时只有一个同步点。当前端使用渲染命令填充一个缓冲区时,后端则消耗了另一个缓冲区。如果两个线程都平衡良好,则不应饿死。但是,这种方法不是最优的,因为它在渲染的帧中引入了延迟,此外,OpenGL驱动程序可能已经在其自己的过程中进行了此操作,因此必须仔细评估性能提升。它最多也只使用两个内核。不过,这种方法已在一些成功的游戏中使用,例如Doom 3Quake 3

更好地利用多核CPU的更具可伸缩性的方法是基于独立任务的方法,您在该方法中触发一个异步请求,该异步请求在辅助线程中得到服务,而触发该请求的线程则继续进行其他工作。理想情况下,该任务应该与其他线程没有依赖性,以避免锁定(也应避免瘟疫等共享/全局数据!)。基于任务的体系结构在游戏的本地化部分中更有用,例如计算动画,AI寻路,过程生成,场景道具的动态加载等。游戏自然是充满事件的,大多数事件都是异步的,因此使它们在单独的线程中运行很容易。

最后,我建议阅读:


只是好奇!您如何知道《毁灭战士3》使用了这种方法?我以为开发人员永远不会放弃技术!
Allahjane

4
@ Allahjane,Doom 3是开源的。在我提供的链接中,您会找到有关游戏总体架构的评论。而且您错了,是的,很难找到完全的开源游戏,但是开发人员通常会在博客,论文和诸如GDC之类的活动中展示他们的技术和技巧。
glampert

1
是一本很棒的书!感谢您让我意识到这一点!您会打勾:)
Allahjane 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.