博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
[工作积累] UE4 并行渲染的同步 - Sync between FParallelCommandListSet & FRHICommandListImmediate calls...
阅读量:5149 次
发布时间:2019-06-13

本文共 5224 字,大约阅读时间需要 17 分钟。

UE4 的渲染分为两个模式1.编辑器是同步绘制的 2.游戏里是FParallelCommandListSet并行派发的。

mesh渲染也分两类,static mesh 使用TStaticMeshDrawList 来绘制, skinned mesh是用DrawingPolicyFactory::DrawDynamicMesh来画。这两类绘制不管是异步还是同步都会调用。具体可以参考DepthRendering.cpp

实际上,有在DX12/Vulkan/Metal 这些支持paralle commit的API上才会真正并行派发,否则GRHIThread为nullptr,还是在最后某个时刻把所有Task阻塞式提交的,Task的执行顺序不确定,但不是并发的。

一般来说每个DrawList的DrawVisibleParallel会自己创建一个Task, DrawDynamicMesh都是自己创建的Task。这些task按不确定的顺序执行,因为有pre depth或者depth buffer,所以乱序绘制没有问题,只有半透明物体需需要按顺序绘制,所以只有一个DrawList,对应一个Task。Task内部的绘制都是按先后顺序的。

 

工作中遇到的问题:

目前自定义的部分流程是

1.draw objects A (parallel)

2.copy scene color (immediate)

3.draw objects B (parallel)

 

其中第二部必须在第一步结束之后才能开始。因为使用了FParallelCommandListSet, 并仿照DeferredShadingRenderer.cpp 里, 在向CommandList里添加调用以后,使用了ServiceLocalQueue() 来同步。

代码:

1 class FCustomPassDynamicDataThreadTask : public FRenderTask {...}; 2 class FCustomParallelCommandListSet : public FParallelCommandListSet {...}; 3  4 ... 5 //Step 1 6 FCustomParallelCommandListSet ParallelSet(View, RHICmdList, true, CVarRHICmdFlushRenderThreadTasks.GetValueOnRenderThread() == 0); 7 //Render Static Mesh 8 Scene->CustomDrawList.DrawVisibleParallel(ParallelSet.View.StaticMeshVisibilityMap, ParallelSet.View.StaticMeshBatchVisibility, ParallelSet); 9 10 // Render dynamic mesh11 FRHICommandList* CmdList = ParallelSet.NewParallelCommandList();12 FGraphEventRef AnyThreadCompletionEvent = TGraphTask
::CreateTask(ParallelSet.GetPrereqs(), ENamedThreads::RenderThread)13 .ConstructAndDispatchWhenReady(*this, *CmdList, View, ParallelSet.DrawRenderState);14 ParallelSet.AddParallelCommandList(CmdList, AnyThreadCompletionEvent);15 16 ServiceLocalQueue();17 18 //Step 219 RHICmdList.CopyToResolveTarget(...); // or CopySubTextureRegion 20 ...

 

发现并不能同步结果, 第二步复制的SceneColor里并没有第一步绘制的物体。

仔细查看代码,发现FParallelCommandListSet的dispatch都是在析构函数里执行的,比如DepthRendering.cpp 绘制depth pre pass: 而我的FCustomParallelCommandListSet也是(部分)抄他的(他注释里说了不要无脑复制粘贴),类似。

DepthRendering.cpp:

1 class FPrePassParallelCommandListSet : public FParallelCommandListSet 2 { 3 public: 4     FPrePassParallelCommandListSet(const FViewInfo& InView, FRHICommandListImmediate& InParentCmdList, bool bInParallelExecute, bool bInCreateSceneContext) 5         : FParallelCommandListSet(GET_STATID(STAT_CLP_Prepass), InView, InParentCmdList, bInParallelExecute, bInCreateSceneContext) 6     { 7         // Do not copy-paste. this is a very unusual FParallelCommandListSet because it is a prepass and we want to do some work after starting some tasks 8     } 9 10     virtual ~FPrePassParallelCommandListSet()11     {12         // Do not copy-paste. this is a very unusual FParallelCommandListSet because it is a prepass and we want to do some work after starting some tasks13         SetStateOnCommandList(ParentCmdList);14         Dispatch(true);15     }16 17     virtual void SetStateOnCommandList(FRHICommandList& CmdList) override18     {19         FParallelCommandListSet::SetStateOnCommandList(CmdList);20         FSceneRenderTargets::Get(CmdList).BeginRenderingPrePass(CmdList, false);21         SetupPrePassView(CmdList, View, DrawRenderState);22     }23 };

 

也就是说,需要FCustomParallelCommandListSet()析构以后,同步才有用,否则的话,还没有任务dispatch,sync什么。于是代码修改如下:

1 class FCustomPassDynamicDataThreadTask : public FRenderTask {...}; 2 class FCustomParallelCommandListSet : public FParallelCommandListSet {...}; 3  4 ... 5 //Step 1 6 //Note: the local scope is necessary because FCustomParallelCommandListSet dispatches in dector. 7 { 8     FCustomParallelCommandListSet ParallelSet(View, RHICmdList, true, CVarRHICmdFlushRenderThreadTasks.GetValueOnRenderThread() == 0); 9     //Render Static Mesh10     Scene->CustomDrawList.DrawVisibleParallel(ParallelSet.View.StaticMeshVisibilityMap, ParallelSet.View.StaticMeshBatchVisibility, ParallelSet);11 12     // Render dynamic mesh13     FRHICommandList* CmdList = ParallelSet.NewParallelCommandList();14     FGraphEventRef AnyThreadCompletionEvent = TGraphTask
::CreateTask(ParallelSet.GetPrereqs(), ENamedThreads::RenderThread)15 .ConstructAndDispatchWhenReady(*this, *CmdList, View, ParallelSet.DrawRenderState);16 ParallelSet.AddParallelCommandList(CmdList, AnyThreadCompletionEvent);17 }18 19 ServiceLocalQueue();20 RHICmdList.ImmediateFlush(EImmediateFlushType::DispatchToRHIThread);21 22 //Step 223 RHICmdList.CopyToResolveTarget(...); //or CopySubTextureRegion 24 ...

 是的,就是加了个花括号,问题就解决了一半。

 

另外,第二部Copy SceneColor的时候,可能在其他一个线程的Command还没完全派发到GPU,如果不同步的话,复制出来的SceneColor copy,在采样时会闪烁。

然而使用了ServiceLocalQueue()以后,结果仍然不正确。这样以来ServiceLocalQueue()的意义感觉不明 - 并不是在等待task执行结束。但可以确定的是DrawVisibleParallel/DrawDynamicMesh使用的异步模式,而传入的RHICmdList是FRHICommandListImmediate,也就是立即执行的,两种方式肯定需要同步。

既然ServiceLocalQueue()和预想的等待或者同步不同,所以尝试在ServiceLocalQueue()后面加上

RHICmdList.ImmediateFlush(EImmediateFlushType::DispatchToRHIThread);

结果才正确。

 

还有一个方式就是把一系列DrawVisibleParallel/DrawDynamic和CopyResovleTarget放在一个Task里,因为Task的内部执行是按顺序的,不需要同步,但是只有一个Task,在支持并行发射Command的GPU下就没有并发了。而且每个DrawVisibleParallel会创建一个Task,需要把这些所有操作合并到一个task里,具体没有试过。

如果把CopyResolveTarget放到另外一个Task,使用异步模式,结果也是不对的。虽然这些Task在非DX12/Vulkan/Metal下是非并发的,按顺序的,但是执行顺序是不确定的。

至于并发+同步开销大还是单一task效率更高,依赖于draw call的数量,具体需要profiling。

转载于:https://www.cnblogs.com/crazii/p/6879767.html

你可能感兴趣的文章
LINQ语法详解
查看>>
DICOM:DICOM3.0网络通信协议
查看>>
分享:FIFO 同步、异步以及Verilog代码实现
查看>>
《构建之法》读书笔记2
查看>>
enum 枚举一般用法 dotnet
查看>>
SVM理解
查看>>
ReportServer Tutorial
查看>>
SQL-Server存储过程基础
查看>>
微信网页 第三方登录原理详解(转)
查看>>
day12
查看>>
cobbler
查看>>
Codeforces Round #280 (Div. 2) E. Vanya and Field 数学
查看>>
红黑树实现
查看>>
【Android学习5】Clean 之后R文件丢失
查看>>
面试算法题汇总
查看>>
Django验证码的使用
查看>>
2017-2018-1 20155305 《信息安全系统设计基础》第七周学习总结
查看>>
网络攻防第十周作业20189304李小涛
查看>>
UFLDL Tutorial
查看>>
Excel的使用技巧
查看>>