flagship的理想与现实

创新+实践

  C++博客 :: 首页 :: 联系 :: 聚合  :: 管理
  8 Posts :: 0 Stories :: 27 Comments :: 0 Trackbacks

常用链接

留言簿(8)

我参与的团队

搜索

  •  

最新评论

阅读排行榜

评论排行榜

        目前的3D引擎的渲染帧和逻辑帧都是在一个线程上运行的,在网络游戏中大量玩家聚集,繁重的骨骼动画计算和粒子计算极大的拖累了渲染帧数,有两种有效措施:1、控制同屏显示人数,但玩家体验不好 2、帧数低于某值时减少动画Tick频率,但带来的问题是动画不连贯。
        如果考虑使用多线程优化,最容易想到的就是采用平行分解模式,将骨骼动画计算和粒子计算写成两个for循环,然后用OpenMP将其多线程化,但事实上这样并不会提高多少效率,这两者计算仍然要阻滞渲染帧,线程的创建也有一定的消耗。于是我想到了一种极端的解决方案,采用任务分解模式,将渲染和逻辑完全分离到两个线程去,互不影响,当然这样线程同步会是大问题,毕竟线程的数量和BUG的数量是成正比的。
        我们首先来分析下这两个线程分别需要做什么工作,需要那些数据。渲染线程需要获取实体的位置、材质等信息,并交给GPU渲染,逻辑线程需要更新实体的位置、材质、骨骼动画等数据,很显然一个写入一个读取,这为我们实现一个没有线程同步的多线程3D渲染系统提供了可能。
        为了让读取和写入不需要Lock,我们需要为每一份数据设计一个带有冗余缓存的结构,读取线程读取的是上次写入完成的副本,而写入线程则向新的副本写入数据,并在完成后置上最新标记,置标记的操作为原子操作即可。以Vector为例,这个结构大致是这样的:
struct VectorData 
{
        Vector4f    m_pVector[DATACENTER_CACHE];
       
int         m_iIndex;

        VectorData()
    
{
            memset( m_pVector, 
0, DATACENTER_CACHE * sizeof(Vector4f) );
            m_iIndex 
= 0;
        }


       
void    Write( Vector4f& rVector )
    
{
           
int iNewIndex = m_iIndex == DATACENTER_CACHE - 1 ? 0 : m_iIndex + 1;
            m_pVector[iNewIndex] 
= rVector;
            m_iIndex 
= iNewIndex;
        }


        Vector4f
&    Read()
  
{
            
return m_pVector[m_iIndex];
        }

}
;
        当然我们可以用模板来写这个结构,让其适用于int,float,matrix等多种数据类型,余下的工作就简单了,将所有有共享数据的类的成员变量都定义为以上这种数据类型,例如我们可以定义:
        SharedData<Matrix4f>  m_matWorld;
        在渲染线程中调用pDevice->SetWorldMatrix( m_matWorld.Read() );
        在逻辑线程中调用m_matWorld.Write( matNewWorld );

        需要注意的是,这种方案并非绝对健壮,当渲染线程极慢且逻辑线程极快的情况下,有可能写入了超过了DATACENTER_CACHE次,而读取却尚未完成,那么数据就乱套了,当然真要出现了这种情况,游戏早已经是没法玩了,我测试的结果是渲染帧小于1帧,逻辑帧大于10000帧,尚未出现问题。
        FlagshipEngine采用了这一设想,实际Demo测试结果是,计算25个角色的骨骼动画,从静止到开始奔跑,单线程的情况下,帧数下降了20%~30%,而使用多线程的情况下,帧数完全没有变化!
posted on 2009-01-04 21:15 flagship 阅读(5356) 评论(7)  编辑 收藏 引用 所属分类: FlagshipEngine

Feedback

# re: 3D引擎多线程:渲染与逻辑分离 2009-01-05 09:20 LOGOS
yf同学吧。使劲的踩一下,oye,处女踩
我看过的一个同步方案,和你的不同
逻辑线程在每帧结束的时候,将新的渲染context同步到渲染线程中
渲染线程在没有新的context的仍用老的context渲染

你使用了过于底层的数据结构作为数据的同步方案,看起来以后的拓展性不是太好。
另外,jl同学说了,m_index=index在intel的CPU上也许是原子操作,但是其他CPU就不一定了。所以read方法读取到的是不是一个合法的索引值很难说
  回复  更多评论
  

# re: 3D引擎多线程:渲染与逻辑分离 2009-01-05 10:41 flagship
啊!被发现了。。hwh同学。。你也在这里啊
你看过的同步方案,给我个链接吧

安全性的问题,我查过,在32位x86 CPU上int的赋值应该是原子操作,不过考虑保险,想试着改成InterlockedIncrement试试看,不知道会不会损失效率
@LOGOS
  回复  更多评论
  

# re: 3D引擎多线程:渲染与逻辑分离 2009-01-05 11:08 LOGOS
http://www.cnblogs.com/cproom/archive/2007/11/26/972548.html
  回复  更多评论
  

# re: 3D引擎多线程:渲染与逻辑分离 2009-01-16 11:06 落魄江湖-随风而行
第一次尝试评论,先测试一下  回复  更多评论
  

# re: 3D引擎多线程:渲染与逻辑分离 2009-01-16 11:22 落魄江湖-随风而行
关于多线程(或smp)渲染,是一个比较麻烦的手段,通过doom3 sdk结合quake3源代码,我这里也总结一下DOOM3/QUAKE3的多线程(SMP)渲染技术框架,供参考。
首先定义逻辑,逻辑端不涉及任何渲染操作和资源载入,仅仅是各个ENTITY的动态变化和物理碰撞检测等,可以形成一套脚本语言,分配一个线程,称为脚本线程,专门从事业务逻辑处理,逻辑代码由逻辑程序员编写。
其次,定义渲染,渲染分为两个线程,即渲染前端线程和渲染后端线程
渲染前端线程的主要作用是进行场景管理,可视性检测,形成COMMAND BUFFFER命令,在渲染前端线程里面不涉及到任何的渲染API的调用,例如GL/D3D等,然后由渲染前端将可视化的渲染数据提交给渲染后端,渲染后端从COMMANDBUFFER中获得渲染数据后,进行BATCH,设置渲染状态等,提交给渲染API进入GPU硬件流水线
使用COMMAND BUFFER事实上已经成为并行渲染系统的标准解决方案了,D3D FOR XBOX版本的API函数里面包含COMMAND BUFFER接口,事实上QUAKE3是第一个引擎使用COMMAND BUFFER实现SMP渲染的引擎。

随风而行 QQ群 38224573 3D引擎研发[1]

  回复  更多评论
  

# re: 3D引擎多线程:渲染与逻辑分离 2009-01-16 16:06 flagship
@落魄江湖-随风而行
COMMAND BUFFER 我不太了解,这就去查一下
我想问的是:逻辑线程如何与渲染前端线程同步的?  回复  更多评论
  

# re: 3D引擎多线程:渲染与逻辑分离 2009-02-05 16:21 conan
这个就是和ringbuffer差不多  回复  更多评论
  


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理