游戏引擎多线程二Word格式文档下载.docx
《游戏引擎多线程二Word格式文档下载.docx》由会员分享,可在线阅读,更多相关《游戏引擎多线程二Word格式文档下载.docx(10页珍藏版)》请在冰豆网上搜索。
4最后一个就是创建D3D设备的时候要用多线程标志,这个我刚开始知道有它,但文档上说它有效率损耗,总觉得这个方案的设计可以规避所有多线程问题,所以不使用这个标志也可以。
这个在我自己家里的机器上测试没问题,可是公司的机器就是死锁,还有奔溃,而且没有堆栈。
只怪自己不了解D3D内部怎么实现的了,只能用这个标志位了。
后来发现,问题出现在资源创建和lock的地方,因为资源创建是在主线程创建的,lock是在渲染线程lock,虽然创建和lock不是同一个资源,但发现同时跑D3D会有问题,创建资源是用的D3Ddevice
接口函数,Lock资源是用资源的接口函数,本来不应该有什么冲突,可能是D3D里面做什么处理,要访问同一个东西,而导致问题。
如果你的资源在渲染线程之前或者同步2个线程的时候都创建好,就不会有这个问题(事先创建好),但如果你做异步加载把创建资源扔到渲染线程,就可能要多考虑些问题了。
我建议,游戏中无论怎么加线程,主线程还是作为一个中转站的作用,这样可以减少问题复杂程度。
总之开了这个标志位会有性能损耗,但只要你创建资源不是始终都在和渲染线程并排的跑,应该问题不大,即使始终并排跑这种很少性能的减少,却可以规避设计复杂问题,也是值得的。
5还有一个问题就是一旦主线程资源删除,而渲染线程还在用到这个资源,如果用智能指针去管理,首先智能指针要具备线程安全性,第二,如果渲染线程这个时候ref为1,这个command执行完后,要析构才能让这个资源删除,否则不析构永远无法删除,这里有个潜在问题,如果资源析构的时候调用了主线程的函数(例如资源管理中,析构后要从资源管理中删除等),这个时候线程安全性就无法保证,这里就太多不可确定。
所以做一个资源GC功能很有必要,从这个资源ref为1(默认资源管理要保留一份,所以没人用的时候ref是1)的时候开始计时,这个时候没有其他在用,所以渲染肯定也不可见的,所以就不会进入渲染线程,到一定时间就可以把他GC掉,如果又有其他重新指向这个资源,那么把计时清0.
6.最后一个就是分辨率切换,窗口切换,涉及到的设备丢失问题。
这个问题处理就是一旦检测到窗口切换和设备丢失(这个检测都是在主线程来响应的),马上就不要跑主线程的添加rendercommand和渲染线程,而是把2个缓冲buffer全都清空,去处理设备丢失问题。
代码说明和运行效果
说了这么多,对程序员来说,看到代码和实现效果比什么都是重要的,上面提到的问题以及方法,都被我实现过了。
主多线程渲染架构
//如果设备不丢失,这里检测设备丢失,如果丢失先devicelost处理然后返回false,下一帧在进这个函数后,再做devicereset
if(VSRenderer:
:
ms_pRenderer->
CooperativeLevel())
{
VSRenderThreadSys:
ms_pRenderTreadSys->
Begin();
//通知渲染线程启动
if(VSSceneManager:
ms_pSceneManager)
{
VSSceneManager:
ms_pSceneManager->
Update(fTime);
}
//下面过程是添加rendercommand
VSRenderer:
BeginRendering();
Draw(fTime);
EndRendering();
if(VSRenderThreadSys:
ms_pRenderTreadSys&
&
VSResourceManager:
ms_bRenderThread)
VSRenderThreadSys:
ExChange();
//同步渲染线程,并交换buffer
else
//清空所有渲染rendercommand
ms_pRenderTreadSys)
Clear();
}
//GC功能
VSResourceManager:
GC();
voidVSRenderThreadSys:
Begin()
//设置一个准备填rendercommandbuffer
m_RenderThread.SetRender(m_RenderBuffer);
//启动渲染线程
m_RenderThread.Start();
//渲染线程运行,只要不触发被迫停止,它就会一直运行下去,如果rendercommandbuffer的所有数据都处理完毕,马上提醒主线程,不再等待
voidVSRenderThread:
Run()
while(!
IsStopTrigger())
if(m_pRenderBuffer)
m_pRenderBuffer->
Excuce();
m_pRenderBuffer=NULL;
m_Event.Trigger();
}
ExChange()
//主线程等待渲染线程完毕
m_RenderThread.m_Event.Wait();
//挂起渲染线程
m_RenderThread.Suspend();
m_RenderBuffer->
//交换2个buffer
Swap(m_UpdateBuffer,m_RenderBuffer);
//有些资源有双D3D资源的进行多线程的,要交换buffer
for(unsignedinti=0;
i<
VSBind:
ms_DynamicTwoBindArray.GetNum();
i++)
ms_DynamicTwoBindArray->
RenderCommand说明
//这里封装了D3DSetRenderState
boolVSDX9Renderer:
SetRenderState(D3DRENDERSTATETYPEState,DWORDValue)
structVSDx9RenderStatePara
D3DRENDERSTATETYPEState;
DWORDValue;
};
HRESULThResult=NULL;
VSDx9RenderStateParaRenderStatePara;
RenderStatePara.State=State;
RenderStatePara.Value=Value;
ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER(VSDx9SetRenderStateCommand,
VSDx9RenderStatePara,RenderStatePara,RenderStatePara,LPDIRECT3DDEVICE9,m_pDevice,m_pDevice,
hResult=m_pDevice->
SetRenderState(RenderStatePara.State,RenderStatePara.Value);
VSMAC_ASSERT(!
FAILED(hResult));
})
ENQUEUE_UNIQUE_RENDER_COMMAND_END
return!
FAILED(hResult);
这里的宏和unreal内部实现不太一样,我做修改,基本意思就是如果开启了多线程渲染,则把命令提交到buffer中,如果不是则直接运行。
所以你会看到2个hResult=m_pDevice->
其实第一个构建rendercommand的类的里面运行代码,第二是如果没开启多线程的话直接就运行。
再来看这个宏,你要是仔细读前面unreal的,你就知道里面嵌套了一个宏,只有嵌套的不一样
#defineENQUEUE_RENDER_COMMAND(TypeName,Params)\
if(VSResourceManager:
ms_bRenderThread)\
{\
TypeName*pCommand=(TypeName*)VSRenderThreadSys:
AssignCommand<
TypeName>
();
\
VS_NEW(pCommand)TypeNameParams;
}\
else\
如果是多线程的话,就构建实例,加入队列,如果不是,则直接运行代码
#defineENQUEUE_UNIQUE_RENDER_COMMAND_END}
再看一个
SetVertexShaderConstant(unsignedintuiStartRegister,void*pDate,
unsignedintRegisterNum,unsignedintuiType)
structVSDx9VertexShaderConstantPara
unsignedintuiStartRegister;
void*pDate;
unsignedintRegisterNum;
unsignedintuiType;
VSDx9VertexShaderConstantParaVertexShaderConstantPara;
VertexShaderConstantPara.uiStartRegister=uiStartRegister;
VertexShaderConstantPara.RegisterNum=RegisterNum;
VertexShaderConstantPara.uiType=uiType;
if(VSResourceManager:
VertexShaderConstantPara.pDate=VSRenderThreadSys:
Assign(uiType,RegisterNum);
VSMemcpy(VertexShaderConstantPara.pDate,pDate,RegisterNum*sizeof(VSREAL)*4);
else
VertexShaderConstantPara.pDate=pDate;
ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER(VSDx9SetVertexShaderConstantCommand,
VSDx9VertexShaderConstantPara,VertexShaderConstantPara,VertexShaderConstantPara,LPDIRECT3DDEVICE9,m_pDevice,m_pDevice,
if(VertexShaderConstantPara.uiType==VSUserConstant:
VT_BOOL)
hResult=m_pDevice->
SetVertexShaderConstantB(VertexShaderConstantPara.uiStartRegister,(constBOOL*)VertexShaderConstantPara.pDate,VertexShaderConstantPara.RegisterNum);
VSMAC_ASSERT(!
elseif(VertexShaderConstantPara.uiType==VSUserConstant:
VT_FLOAT)
SetVertexShaderConstantF(VertexShaderConstantPara.uiStartRegister,(constfloat*)VertexShaderConstantPara.pDate,VertexShaderConstantPara.RegisterNum);
VT_INT)
SetVertexShaderConstantI(VertexShaderConstantPara.uiStartRegister,(constint*)VertexShaderConstantPara.pDate,VertexShaderConstantPara.RegisterNum);
VSMAC_ASSERT(0);
return1;
这个封装了D3D设置vshader的函数,参数也是在buffer里面分配的,这里记录了,分配的地址和长度。