ENQUEUE_RENDER_COMMAND
一个最常见的东西
ENQUEUE_RENDER_COMMAND
传进来,就是接受一个 lambda,然后内层
1 2 template <typename TSTR, typename LAMBDA>FORCEINLINE_DEBUGGABLE void EnqueueUniqueRenderCommand (LAMBDA&& Lambda)
根据当前是渲染线程还是 game 线程来决定如何执行这个 lambda
谁调用了 BasePass
因为截帧的时候看到 BasePass 里面是处理 mesh 的材质
于是打算从这里开始看
Rendering Thread
在
1 2 3 4 5 6 7 void FDeferredShadingSceneRenderer::RenderBasePassInternal ( FRDGBuilder& GraphBuilder, const FRenderTargetBindingSlots& BasePassRenderTargets, FExclusiveDepthStencil::Type BasePassDepthStencilAccess, FRDGTextureRef ForwardScreenSpaceShadowMask, bool bParallelBasePass, bool bRenderLightmapDensity)
打断点,是在 FRunnableThreadWin
触发
于是可以看到,UE 是把 Game 线程和 Render 线程分开的
从堆栈底层往上,看到 void RenderingThreadMain( FEvent* TaskGraphBoundSyncEvent )
这里面除了性能分析插桩、事件,主要部分是,把 TaskGraph 的当前线程设置为 render 线程,然后 TaskGraph 进入任务处理循环,直到 return
于是看到 ENQUEUE_RENDER_COMMAND
的 lambda 最终是加到了 task 里面,处理 task 就是处理 render command
那么 base pass 这个相关的 render command 是 FRendererModule::BeginRenderingViewFamily
里面发起的
FRendererModule::BeginRenderingViewFamily
之前的分析已经可以看到,render thread 的工作就是不停地执行 TaskGraph 内的任务
所以渲染框架的逻辑还是在 game thread
之前看到一个 base pass 相关的 render command 是在 FRendererModule::BeginRenderingViewFamily
里面发起的
于是现在在 FRendererModule::BeginRenderingViewFamily
打断点,看看 game thread 这边的逻辑
从底层上来是 UGameEngine::Tick
,这里也就是各个功能的 tick,不出所料,gameobject, slate 等
然后是 FViewport::Draw
,这里更多是准备 canvas
UGameViewportClient::Draw
再对 canvas 做一些操作,然后就到了 FRendererModule::BeginRenderingViewFamily
了
那其实 FSceneViewFamily* ViewFamily
这个东西已经包含了渲染世界所需要的信息了
查看这个变量,确实
既有 World 又有 Primitives、Lights,已经够了
FRendererModule::BeginRenderingViewFamily
内,有一个 World->SendAllEndOfFrameUpdates();
更新 component 的状态,然后就是发送 render command
1 2 3 4 5 6 7 8 ENQUEUE_RENDER_COMMAND (FDrawSceneCommand)( [SceneRenderer, DrawSceneEnqueue](FRHICommandListImmediate& RHICmdList) { ... RenderViewFamily_RenderThread (RHICmdList, SceneRenderer); FlushPendingDeleteRHIResources_RenderThread (); });
从 game thread 进到 render thread:RenderViewFamily_RenderThread
于是查看 RenderViewFamily_RenderThread
前面是等待所有未完成的渲染任务、更新延迟资源,处理鼠标点击拾取物体的功能,然后是渲染场景
1 SceneRenderer->Render (RHICmdList);
奇怪的是这后面还写了头发的渲染
感觉这个调用层级有点不协调,算了,在看框架的时候这不是重点
然后这个 render 就到了 FDeferredShadingSceneRenderer::Render
FDeferredShadingSceneRenderer::Render
一开始,Scene->UpdateAllPrimitiveSceneInfos
应该是更新 mesh 相关的渲染信息?
然后是视口矩形、天空大气、多 GPU、等待 RT 可写、VT 分配、深度缓冲,暂时略过不看
然后是找到所有的可见物体
1 2 3 4 5 6 7 8 RHICmdList.ImmediateFlush (EImmediateFlushType::DispatchToRHIThread); bool bDoInitViewAftersPrepass = false ;{ SCOPED_GPU_STAT (RHICmdList, VisibilityCommands); bDoInitViewAftersPrepass = InitViews (RHICmdList, BasePassDepthStencilAccess, ILCTaskData); }
然后是有一个 GPU 场景更新 UpdateGPUScene(RHICmdList, *Scene);
不细看的话,暂时不知道是指什么场景
然后是 Pre Z Pass、延迟渲染的 GBuffer 相关的计算、Early occlusion queries、Early Shadow depth rendering、体积云初始化、大气 LUT、用于间接光照的 Light Propagation Volumes、体积雾计算、体积云计算、头发计算、forward 阴影渲染等
然后是 BasePass
1 RenderBasePass (GraphBuilder, BasePassDepthStencilAccess, SceneColorTexture.Target, SceneDepthTexture.Target, DepthLoadAction, ForwardScreenSpaceShadowMaskTexture);
然后是速度矢量渲染(TAA 相关)、毛发渲染 BasePass、天光 RayTracing、Pre-lighting composition lighting stage(用于 SSAO 和延迟贴花等)、用于延迟渲染的毛发渲染 BasePass、自定义纹理的重建(用于 velocity, custom depth, and SSAO)、然后又是一些反射和天空光照渲染、体积云等
然后是渲染半透明物体,这里可以看到半透明物体也是可以渲染速度向量的
然后是后处理 pass,之后应该没有啥了
FDeferredShadingSceneRenderer::RenderBasePass
FDeferredShadingSceneRenderer::Render
发起了很多渲染对象的渲染,不透明,半透明,体积云,体积雾,头发,阴影
最简单的还是看不透明物体是怎么渲染的
于是进入 FDeferredShadingSceneRenderer::RenderBasePass
可以看到,它也只是一层包装。前面是关于如何 clear 纹理,然后进入 impl
1 RenderBasePassInternal (GraphBuilder, BasePassRenderTargets, BasePassDepthStencilAccess, ForwardShadowMaskTexture, bDoParallelBasePass, bRenderLightmapDensity);
进来,显而易见是两个 View.ParallelMeshDrawCommandPasses[EMeshPass::BasePass].DispatchDraw
可能在画不透明物体,因为其他的绘制命令都是 EditorPrimitives
和 SkyPass
,一看就不相关
我调试的时候是进入了
1 2 3 4 5 6 7 8 9 10 GraphBuilder.AddPass ( RDG_EVENT_NAME ("BasePassParallel" ), PassParameters, ERDGPassFlags::Raster | ERDGPassFlags::SkipRenderPass, [this , &View, PassParameters](FRHICommandListImmediate& RHICmdList) { Scene->UniformBuffers.UpdateViewUniformBuffer (View); FRDGParallelCommandListSet ParallelCommandListSet (RHICmdList, GET_STATID (STAT_CLP_BasePass), *this , View, FParallelCommandListBindings (PassParameters)); View.ParallelMeshDrawCommandPasses[EMeshPass::BasePass].DispatchDraw (&ParallelCommandListSet, RHICmdList); });
BasePass 内的逻辑
分发绘制 command
于是进到 void FParallelMeshDrawCommandPass::DispatchDraw(FParallelCommandListSet* ParallelCommandListSet, FRHICommandList& RHICmdList) const
看
这个函数是负责拆分渲染任务,均分到多个线程并行工作
前面是准备上传顶点缓冲到 GPU Scene 然后是均分 task
分配循环内的循环体内的这两句,分配 task,应该会进到下一层,负责实际绘制逻辑把
1 2 3 FGraphEventRef AnyThreadCompletionEvent = TGraphTask<FDrawVisibleMeshCommandsAnyThreadTask>::CreateTask (&Prereqs, RenderThread) .ConstructAndDispatchWhenReady (*CmdList, TaskContext.MeshDrawCommands, TaskContext.MinimalPipelineStatePassSet, PrimitiveIdsBuffer, BasePrimitiveIdsOffset, TaskContext.bDynamicInstancing, TaskContext.InstanceFactor, TaskIndex, NumTasks); ParallelCommandListSet->AddParallelCommandList (CmdList, AnyThreadCompletionEvent, NumDraws);
它分配完了 task 之后就退出来了
这个 task 实际执行还是在 render thread ProcessTasksUntilQuit
里
最终调用到 FDrawVisibleMeshCommandsAnyThreadTask
的 DoTask
,它也只是
1 SubmitMeshDrawCommandsRange (VisibleMeshDrawCommands, GraphicsMinimalPipelineStateSet, PrimitiveIdsBuffer, BasePrimitiveIdsOffset, bDynamicInstancing, StartIndex, NumDraws, InstanceFactor, RHICmdList);
的包装
但是我突然意识到,这里的 VisibleMeshDrawCommands
已经是涉及到了要画什么 mesh 了
可能是我的 mesh 比较少吧,我这一个 FDrawVisibleMeshCommandsAnyThreadTask
的 VisibleMeshDrawCommands
就已经包含所有要画的 mesh,因为我通过 renderdoc 看到的就是这个数量
SubmitMeshDrawCommandsRange
里面就是遍历这个传入的 VisibleMeshDrawCommands
,对每一个 command 提交
1 FMeshDrawCommand::SubmitDraw (*VisibleMeshDrawCommand.MeshDrawCommand, GraphicsMinimalPipelineStateSet, PrimitiveIdsBuffer, PrimitiveIdBufferOffset, InstanceFactor, RHICmdList, StateCache);
发起 draw call
前面看的是怎么分配
现在这个 FMeshDrawCommand::SubmitDraw
就是怎么绘制网格了
前面是获取并设置图形管线状态、设置模板测试参考值、设置顶点流、设置着色器绑定(绑定常量缓冲、纹理、采样器等资源)
然后就是 draw call
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 if (MeshDrawCommand.IndexBuffer){ if (MeshDrawCommand.NumPrimitives > 0 ) { RHICmdList.DrawIndexedPrimitive ( MeshDrawCommand.IndexBuffer, MeshDrawCommand.VertexParams.BaseVertexIndex, 0 , MeshDrawCommand.VertexParams.NumVertices, MeshDrawCommand.FirstIndex, MeshDrawCommand.NumPrimitives, MeshDrawCommand.NumInstances * InstanceFactor ); } else { RHICmdList.DrawIndexedPrimitiveIndirect ( MeshDrawCommand.IndexBuffer, MeshDrawCommand.IndirectArgs.Buffer, MeshDrawCommand.IndirectArgs.Offset ); } } else { if (MeshDrawCommand.NumPrimitives > 0 ) { RHICmdList.DrawPrimitive ( MeshDrawCommand.VertexParams.BaseVertexIndex + MeshDrawCommand.FirstIndex, MeshDrawCommand.NumPrimitives, MeshDrawCommand.NumInstances * InstanceFactor); } else { RHICmdList.DrawPrimitiveIndirect ( MeshDrawCommand.IndirectArgs.Buffer, MeshDrawCommand.IndirectArgs.Offset ); }
这熟悉的结构
可能别的我都不熟,但是这个有 index buffer 就 draw indexed 否则直接 draw 的 draw call 形式,在我自己写的渲染器都是这样的,感动了
DrawCall Debug
可以看到他 FMeshDrawCommand::SubmitDraw
这里还有 debug 示例
看 MeshDrawCommand.DebugData.MaterialName
和 MeshDrawCommand.DebugData.ResourceName
就可以看到 mesh 来源,还有材质名称,方便定位问题
BasePass DrawCall 是如何绑定渲染着色器资源的
现在我有一些自己的着色器资源,有纹理,有 uniform 等等,它们是怎么传进来的?
从 draw call 找着色器绑定,没找到
于是看到 FMeshDrawCommand::SubmitDraw
的绑定着色器相关
1 MeshDrawCommand.ShaderBindings.SetOnCommandList (RHICmdList, MeshPipelineState.BoundShaderState.AsBoundShaderState (), StateCache.ShaderBindings);
进到 FMeshDrawShaderBindings::SetOnCommandList
,可以看到他只是根据一个 frequency 变量确认 shader 类型
最终不同的 shader 类型都是要调用 SetShaderBindings
,但是参数不同
于是看到 FMeshDrawShaderBindings::SetShaderBindings
一开始看到 Uniform Buffers 绑定
从 SingleShaderBindings 中获取所有 Uniform Buffer 指针和对应的参数信息。
遍历所有 Uniform Buffer,检查当前绑定状态是否与缓存不同,避免重复绑定。
绑定 Uniform Buffer。
更新缓存状态
这个利用缓存的逻辑之后也是一样的
所以就可以看一下各自的命令了
Uniform Buffers 绑定
1 RHICmdList.SetShaderUniformBuffer (Shader, Parameter.BaseIndex, UniformBuffer);
Sampler 绑定
1 RHICmdList.SetShaderSampler (Shader, Parameter.BaseIndex, Sampler);
SRV(Shader Resource View)绑定
1 RHICmdList.SetShaderResourceViewParameter (Shader, Parameter.BaseIndex, SRV);
Texture 绑定
1 RHICmdList.SetShaderTexture (Shader, Parameter.BaseIndex, Texture);
还有一个 LooseParameter,不知道干啥的,先跳过吧
但是当我对这些绑定的代码打断点的时候,发现代码从来没击中断点
一开始我还以为是,这些绑定只在物体新添加到场景的时候绑定一次,之后就利用缓存了
于是重新启动游戏,发现我的断点从来没有击中过
那就非常神奇了,不知道是谁绑定了纹理?
从 PSSetShaderResources 找纹理绑定,没找到
因为我在 RenderDoc 可以看到它绑定纹理的 API 是 PSSetShaderResources
于是去 UE 源码查他这个绑定的 API
发现他在源码里面的使用都是在 UE 的 UI 库 slate 中才有使用
要不然就是一个 ClearShaderResource
有在使用
这,完全找不到是怎么绑定的……
从着色器资源类找
看别人的博客,发现这方面也有讲述
FShaderParameter
是着色器的寄存器绑定参数, 它的类型可以是float1/2/3/4,数组, UAV等.
FShaderResourceParameter
是着色器资源绑定(纹理或采样器)
FRWShaderParameter
与 UAV or SRV 相关
TShaderUniformBufferParameter
与 uniform 相关
但是我在 FShaderResourceParameter::Bind
和 FRWShaderParameter::SetTexture
打断点,都没有命中
后来我开 Editor 发现可以命中,但是命中的都是 uniform shader parameter,堆栈里面显示的是 editor primitives 的绘制
感觉这并不是 Editor 或者 Game 的构建配置的问题,纯粹是 Editor 有一些特殊调用而已
这种异常看上去是编译器优化了什么东西
于是使用 PRAGMA_DISABLE_OPTIMIZATION
PRAGMA_ENABLE_OPTIMIZATION
但是还是没有效果
从 SetShaderParameters 找
看别人博客 https://logins.github.io/graphics/2021/03/31/UE4ShadersIntroduction.html ,他说
1 2 template <typename TRHICmdList, typename TShaderClass, typename TShaderRHI>inline void SetShaderParameters (TRHICmdList& RHICmdList, const TShaderRef<TShaderClass>& Shader, TShaderRHI* ShadeRHI, const typename TShaderClass::FParameters& Parameters)
是最常用的绑定函数
看了一下,确实有很多绑定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 for (const FShaderParameterBindings::FResourceParameter& ParameterBinding : Bindings.ResourceParameters){ EUniformBufferBaseType BaseType = (EUniformBufferBaseType)ParameterBinding.BaseType; switch (BaseType) { case UBMT_TEXTURE: { auto ShaderParameterRef = *(FRHITexture**)(Base + ParameterBinding.ByteOffset); RTBindingsWriter.SetTexture (ParameterBinding.BaseIndex, ShaderParameterRef); } break ; case UBMT_SRV: { FRHIShaderResourceView* ShaderParameterRef = *(FRHIShaderResourceView**)(Base + ParameterBinding.ByteOffset); RTBindingsWriter.SetSRV (ParameterBinding.BaseIndex, ShaderParameterRef); } break ; case UBMT_UAV: { FRHIUnorderedAccessView* ShaderParameterRef = *(FRHIUnorderedAccessView**)(Base + ParameterBinding.ByteOffset); RTBindingsWriter.SetUAV (ParameterBinding.BaseIndex, ShaderParameterRef); } break ; case UBMT_SAMPLER: { FRHISamplerState* ShaderParameterRef = *(FRHISamplerState**)(Base + ParameterBinding.ByteOffset); RTBindingsWriter.SetSampler (ParameterBinding.BaseIndex, ShaderParameterRef); } break ; case UBMT_RDG_TEXTURE: { auto GraphTexture = *reinterpret_cast <FRDGTexture* const *>(Base + ParameterBinding.ByteOffset); checkSlow (GraphTexture); GraphTexture->MarkResourceAsUsed (); RTBindingsWriter.SetTexture (ParameterBinding.BaseIndex, GraphTexture->GetRHI ()); } break ; case UBMT_RDG_TEXTURE_SRV: case UBMT_RDG_BUFFER_SRV: { auto GraphSRV = *reinterpret_cast <FRDGShaderResourceView* const *>(Base + ParameterBinding.ByteOffset); checkSlow (GraphSRV); GraphSRV->MarkResourceAsUsed (); RTBindingsWriter.SetSRV (ParameterBinding.BaseIndex, GraphSRV->GetRHI ()); } break ; case UBMT_RDG_TEXTURE_UAV: case UBMT_RDG_BUFFER_UAV: { auto UAV = *reinterpret_cast <FRDGUnorderedAccessView* const *>(Base + ParameterBinding.ByteOffset); checkSlow (UAV); UAV->MarkResourceAsUsed (); RTBindingsWriter.SetUAV (ParameterBinding.BaseIndex, UAV->GetRHI ()); } break ; default : checkf (false , TEXT ("Unhandled resource type?" )); break ; } }
但是我查找了一下它的引用,怎么都是 RayTracing 在用?没有别人在用了。
打断点,发现还是有一个 UpdateGPUScene(RHICmdList, *Scene);
在用,最终到 FComputeShaderUtils::Dispatch
。但是似乎和 Base Pass 怎么绑定 mesh 的没有关系
在 FShaderResourceParameter 加 Log
1 2 3 void FShaderResourceParameter::Bind (const FShaderParameterMap& ParameterMap,const TCHAR* ParameterName,EShaderParameterFlags Flags) { UE_LOG (LogTemp, Warning, TEXT ("FShaderResourceParameter::Bind here!!!!!" ));
也没有输出
重新看一下渲染流程
看了
https://github.com/donaldwuid/unreal_source_explained/blob/master/main/rendering.md
也没有解决我的问题,就是纹理是从哪里加载过来的
从 UTexture2D 出发
那些地方都打不到断点,于是从 UTexture2D
出发打断点
研究了一下,觉得 UTexture::SetResource
很像是跟渲染资源相关的地方
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void UTexture::SetResource (FTextureResource* InResource) { check (!IsInActualRenderingThread () && !IsInRHIThread ()); PrivateResource = InResource; ENQUEUE_RENDER_COMMAND (SetResourceRenderThread)([this , InResource](FRHICommandListImmediate& RHICmdList) { PrivateResourceRenderThread = InResource; }); }
这里还说了需要 init 资源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 UTexture::SetResource(FTextureResource *) Texture.cpp:147 UTexture::UpdateResource() Texture.cpp:190 UTexture2D::UpdateResource() Texture2D.cpp:440 UTexture::PostLoad() Texture.cpp:481 UTexture2D::PostLoad() Texture2D.cpp:377 UObject::ConditionalPostLoad() Obj.cpp:1092 FAsyncPackage::PostLoadObjects() AsyncLoading.cpp:6424 FAsyncPackage::TickAsyncPackage(bool, bool, float &, FFlushTree *) AsyncLoading.cpp:5590 FAsyncLoadingThread::ProcessAsyncLoading(int &, bool, bool, float, FFlushTree *) AsyncLoading.cpp:4098 FAsyncLoadingThread::TickAsyncThread(bool, bool, float, bool &, FFlushTree *) AsyncLoading.cpp:4856 FAsyncLoadingThread::TickAsyncLoading(bool, bool, float, FFlushTree *) AsyncLoading.cpp:4556 FAsyncLoadingThread::FlushLoading(int) AsyncLoading.cpp:7022 FlushAsyncLoading(int) AsyncPackageLoader.cpp:643 LoadPackageInternal(UPackage *, const wchar_t *, unsigned int, FLinkerLoad *, FArchive *, const FLinkerInstancingContext *) UObjectGlobals.cpp:1144 LoadPackage(UPackage *, const wchar_t *, unsigned int, FArchive *, const FLinkerInstancingContext *) UObjectGlobals.cpp:1469
这个调用堆栈也很清晰
感觉上一层的 update 应该是核心逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 void UTexture::UpdateResource () { ReleaseResource (); if ( FApp::CanEverRender () && !HasAnyFlags (RF_ClassDefaultObject) ) { FTextureResource* NewResource = CreateResource (); SetResource (NewResource); if (NewResource) { ... ENQUEUE_RENDER_COMMAND (SetTextureReference)([this , NewResource](FRHICommandListImmediate& RHICmdList) { NewResource->SetTextureReference (TextureReference.TextureReferenceRHI); }); BeginInitResource (NewResource); ... } } }
这里的 SetResource
和 SetTextureReference
就是简单的 set,没有做别的事情,很舒服
但是在看的时候,发现他这个注释
1 2 3 4 class FTextureResource : public FTexture
他的成员
1 2 3 FTextureReferenceRHIRef TextureReferenceRHI;
似乎有点关系
也看到 class UTexture : public UStreamableRenderAsset, public IInterface_AssetUserData
的成员
1 2 3 4 5 6 7 class FTextureResource * PrivateResource;class FTextureResource * PrivateResourceRenderThread;
说明它是考虑了运行时重新加载纹理的
然后在 FRenderResource::InitResource
打断点,蹲到纹理资源的 init
调用到 FStreamableTextureResource::InitRHI
这其中重要的应该是
FTexture2DResource::CreateTexture
还有一个 RHIUpdateTextureReference(TextureReferenceRHI, TextureRHI);
create 就是调用平台特定的 API 去创建 GPU 资源句柄
reference 这里还是不知道干什么的
reload package 之后,再会调用一次 texture 的 UTexture2D::PostLoad
,跟之前一样
回来看 UTexture
,我想知道 Material 是怎么使用到这个材质的,或者是别的什么,总之,渲染器是怎么获取并绑定这个纹理的
于是看到这个 getter
1 2 3 4 5 ENGINE_API FTextureResource* GetResource () ;ENGINE_API const FTextureResource* GetResource () const ;
这个 getter 是绑定了 UTexture
的 TFieldPtrAccessor<FTextureResource> Resource;
成员
不断蹲,蹲到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 UTexture::GetResource() Texture.cpp:132 UE4Function_Private::TFunctionRefCaller::Call(void *) Function.h:539 UE4Function_Private::TFunctionRefBase::operator()() Function.h:676 FUniformExpressionSet::FillUniformBuffer(const FMaterialRenderContext &, const FUniformExpressionCache &, unsigned char *, int) MaterialUniformExpressions.cpp:1433 FMaterialRenderProxy::EvaluateUniformExpressions(FUniformExpressionCache &, const FMaterialRenderContext &, FRHICommandList *) MaterialShared.cpp:2956 <lambda_4b8eb6ac...>::operator()(Type) MaterialShared.cpp:3213 UMaterialInterface::IterateOverActiveFeatureLevels<…>(<lambda_4b8eb6ac...>) MaterialInterface.h:861 FMaterialRenderProxy::UpdateDeferredCachedUniformExpressions() MaterialShared.cpp:3205 TEnqueueUniqueRenderCommandType<`FRendererModule::BeginRenderingViewFamily'::`2'::UpdateDeferredCachedUniformExpressionsName,<lambda_af3a665d491aaad33361bc0d189d73fc> >::DoTask(Type,const TRefCountPtr<FGraphEvent> &) RenderingThread.h:183 TGraphTask<TEnqueueUniqueRenderCommandType<`FRendererModule::BeginRenderingViewFamily'::`2'::UpdateDeferredCachedUniformExpressionsName,<lambda_af3a665d491aaad33361bc0d189d73fc> > >::ExecuteTask(TArray<FBaseGraphTask *,TSizedDefaultAllocator<32> > &,Type) TaskGraphInterfaces.h:886 FNamedTaskThread::ProcessTasksNamedThread(int, bool) TaskGraph.cpp:710 FNamedTaskThread::ProcessTasksUntilQuit(int) TaskGraph.cpp:601 FTaskGraphImplementation::ProcessThreadUntilRequestReturn(Type) TaskGraph.cpp:1480 RenderingThreadMain(FEvent *) RenderingThread.cpp:372
这个
1 void FUniformExpressionSet::FillUniformBuffer (const FMaterialRenderContext& MaterialRenderContext, const FUniformExpressionCache& UniformExpressionCache, uint8* TempBuffer, int TempBufferSize) const
看上去像是建立起材质和纹理之间的关系的部分
这里包含多个纹理类型的绑定,取其中 Texture2D 的来看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 for (int32 ExpressionIndex = 0 ; ExpressionIndex < GetNumTextures (EMaterialTextureParameterType::Standard2D); ExpressionIndex++){ const FMaterialTextureParameterInfo& Parameter = GetTextureParameter (EMaterialTextureParameterType::Standard2D, ExpressionIndex); const UTexture* Value = nullptr ; GetTextureValue (EMaterialTextureParameterType::Standard2D, ExpressionIndex, MaterialRenderContext, MaterialRenderContext.Material, Value); ... void ** ResourceTableTexturePtr = (void **)((uint8*)BufferCursor + 0 * SHADER_PARAMETER_POINTER_ALIGNMENT); void ** ResourceTableSamplerPtr = (void **)((uint8*)BufferCursor + 1 * SHADER_PARAMETER_POINTER_ALIGNMENT); BufferCursor = ((uint8*)BufferCursor) + (SHADER_PARAMETER_POINTER_ALIGNMENT * 2 ); check (BufferCursor <= TempBuffer + TempBufferSize); const uint32 ValidTextureTypes = MCT_Texture2D | MCT_TextureVirtual | MCT_TextureExternal; bool bValueValid = false ; if (Value && Value->Resource && Value->TextureReference.TextureReferenceRHI && (Value->GetMaterialType () & ValidTextureTypes) != 0u ) { FSamplerStateRHIRef* SamplerSource = &Value->Resource->SamplerStateRHI; const ESamplerSourceMode SourceMode = Parameter.SamplerSource; if (SourceMode == SSM_Wrap_WorldGroupSettings) { SamplerSource = &Wrap_WorldGroupSettings->SamplerStateRHI; } else if (SourceMode == SSM_Clamp_WorldGroupSettings) { SamplerSource = &Clamp_WorldGroupSettings->SamplerStateRHI; } if (*SamplerSource) { *ResourceTableTexturePtr = Value->TextureReference.TextureReferenceRHI; *ResourceTableSamplerPtr = *SamplerSource; bValueValid = true ; } else { ensureMsgf (false , TEXT ("Texture %s had invalid sampler source." ), *Value->GetName ()); } } if (!bValueValid) { check (GWhiteTexture->TextureRHI); *ResourceTableTexturePtr = GWhiteTexture->TextureRHI; check (GWhiteTexture->SamplerStateRHI); *ResourceTableSamplerPtr = GWhiteTexture->SamplerStateRHI; } }
可见,Material 一开始就存储了 UTexture*
,纹理和材质的关系是已经建立好了
现在是要建立 Uniform buffer 和 texture 之间的关系
简单来说就是,计算缓冲区写入位置,写入纹理和采样器指针
如果,我是说如果,这个材质的渲染对我这个 2D 纹理的获取完全是依赖于 UniformExpression 的,那么就可以确定,材质仅仅通过这个路径被 GPU 获取
研究 FMaterialRenderProxy
第一次调用应该是在 FRendererModule::BeginRenderingViewFamily
在这里有
1 2 3 4 5 ENQUEUE_RENDER_COMMAND (UpdateDeferredCachedUniformExpressions)( [](FRHICommandList& RHICmdList) { FMaterialRenderProxy::UpdateDeferredCachedUniformExpressions (); });
调用到 FMaterialRenderProxy::UpdateDeferredCachedUniformExpressions
FMaterialRenderProxy::UpdateDeferredCachedUniformExpressions
这里可以看到是如何遍历 FMaterialRenderProxy
的
精简之后是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 void FMaterialRenderProxy::UpdateDeferredCachedUniformExpressions () { ... for (TSet<FMaterialRenderProxy*>::TConstIterator It (DeferredUniformExpressionCacheRequests); It; ++It) { FMaterialRenderProxy* MaterialProxy = *It; UMaterialInterface::IterateOverActiveFeatureLevels ([&](ERHIFeatureLevel::Type InFeatureLevel) { const FMaterial* Material = MaterialProxy->GetMaterialNoFallback (InFeatureLevel); if (Material && Material->GetRenderingThreadShaderMap ()) { FMaterialRenderContext MaterialRenderContext (MaterialProxy, *Material, nullptr ); MaterialProxy->EvaluateUniformExpressions (MaterialProxy->UniformExpressionCache[(int32)InFeatureLevel], MaterialRenderContext); } }); } DeferredUniformExpressionCacheRequests.Reset (); }
他这里是遍历一个 DeferredUniformExpressionCacheRequests
,跳转可见,这是一个类的全局变量
1 2 TSet<FMaterialRenderProxy*> FMaterialRenderProxy::MaterialRenderProxyMap; TSet<FMaterialRenderProxy*> FMaterialRenderProxy::DeferredUniformExpressionCacheRequests;
FMaterialRenderProxy 全局列表
找 DeferredUniformExpressionCacheRequests
的增删。Add 的逻辑在 FMaterialRenderProxy::CacheUniformExpressions
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void FMaterialRenderProxy::CacheUniformExpressions (bool bRecreateUniformBuffer) { InitResource (); ... DeferredUniformExpressionCacheRequests.Add (this ); InvalidateUniformExpressionCache (bRecreateUniformBuffer); if (!GDeferUniformExpressionCaching) { FMaterialRenderProxy::UpdateDeferredCachedUniformExpressions (); } }
先把自己加到全局变量的列表
如果重建,那么有一个专门的 invalidate 的函数 InvalidateUniformExpressionCache
然后如果设置了 cache,那么之后 update UniformExpression
如果没有设置 cache,那么立即 update UniformExpression
看下他这个 invalidate 的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 void FMaterialRenderProxy::InvalidateUniformExpressionCache (bool bRecreateUniformBuffer) { ... ++UniformExpressionCacheSerialNumber; for (int32 i = 0 ; i < ERHIFeatureLevel::Num; ++i) { UniformExpressionCache[i].bUpToDate = false ; UniformExpressionCache[i].CachedUniformExpressionShaderMap = nullptr ; ... if (bRecreateUniformBuffer) { UniformExpressionCache[i].UniformBuffer = nullptr ; } } }
这个注释似乎指的是,材质重新编译的时候,需要重建 uniform buffer,因为 uniform buffer 的 layout 会发生改变
and 不得不说的是,UE 里面的 uniform buffer 和 glsl 中的 uniform 变量,是不一样的概念。好令人错乱。
然后这个重建函数应该被 FMaterialUpdateContext
调用,因为 mesh command 的缓存里面,缓存了 uniform buffer 的指针,所以重建还需要把 mesh command 的缓存里面的 uniform buffer 的指针缓存给清了
那么他的意思应该是,FMaterialUpdateContext
可以这样清缓存
添加 FMaterialRenderProxy 到全局列表的源头
找谁调用了 FMaterialRenderProxy::CacheUniformExpressions
除了 Material 类自己的方法,还看到一个 FExternalTextureRegistry::RegisterExternalTexture
很酷
1 2 3 4 5 6 7 8 9 10 11 void FExternalTextureRegistry::RegisterExternalTexture (const FGuid& InGuid, FTextureRHIRef& InTextureRHI, FSamplerStateRHIRef& InSamplerStateRHI, const FLinearColor& InCoordinateScaleRotation, const FLinearColor& InCoordinateOffset) { ... TextureEntries.Add (InGuid, FExternalTextureEntry (InTextureRHI, InSamplerStateRHI, InCoordinateScaleRotation, InCoordinateOffset)); for (const FMaterialRenderProxy* MaterialRenderProxy : ReferencingMaterialRenderProxies) { const_cast <FMaterialRenderProxy*>(MaterialRenderProxy)->CacheUniformExpressions (false ); } }
它是可以直接遍历所有相关联的 MaterialRenderProxy
然后直接遍历 cache
搜了一下他这个成员 TSet<const FMaterialRenderProxy*> ReferencingMaterialRenderProxies;
的类型 TSet<const FMaterialRenderProxy*>
,结果发现只有它有
我还希望 Texture 相关的类型也有这个相关性呢,如果有,那就很好了
回到 Material 相关的搜索结果
1 2 3 4 5 6 7 8 9 template <typename ParameterType>void GameThread_UpdateMIParameter (const UMaterialInstance* Instance, const ParameterType& Parameter) void FMaterialRenderProxy::CacheUniformExpressions_GameThread (bool bRecreateUniformBuffer) void SetShaderMapsOnMaterialResources_RenderThread (FRHICommandListImmediate& RHICmdList, FMaterialsToUpdateMap& MaterialsToUpdate)
都感觉很像
随便选了一个 FMaterialRenderProxy::CacheUniformExpressions_GameThread
开始不断找 usage,最终找到 UMaterial::PostLoad
删掉编辑器相关的、兼容性相关的、统计相关的,精简为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void UMaterial::PostLoad () { PropagateDataToMaterialProxy (); for (UObject* Texture : CachedExpressionData.ReferencedTextures) { if (Texture) { Texture->ConditionalPostLoad (); } } CacheResourceShadersForRendering (false ); }
先传播 data,然后保证纹理加载正常,然后缓存 shader 资源用于渲染
其中 UMaterial::CacheResourceShadersForRendering
是和 uniform buffer 相关
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 void UMaterial::CacheResourceShadersForRendering (bool bRegenerateId, EMaterialShaderPrecompileMode PrecompileMode) { if (bRegenerateId) { FlushResourceShaderMaps (); } FMaterialResourceDeferredDeletionArray ResourcesToFree; if (FApp::CanEverRender ()) { const EMaterialQualityLevel::Type ActiveQualityLevel = GetCachedScalabilityCVars ().MaterialQualityLevel; uint32 FeatureLevelsToCompile = GetFeatureLevelsToCompileForRendering (); TArray<FMaterialResource*> ResourcesToCache; while (FeatureLevelsToCompile != 0 ) { const ERHIFeatureLevel::Type FeatureLevel = (ERHIFeatureLevel::Type)FBitSet::GetAndClearNextBit (FeatureLevelsToCompile); const EShaderPlatform ShaderPlatform = GShaderPlatformForFeatureLevel[FeatureLevel]; FMaterialResource* CurrentResource = FindOrCreateMaterialResource (MaterialResources, this , nullptr , FeatureLevel, ActiveQualityLevel); check (CurrentResource); ResourcesToCache.Reset (); ResourcesToCache.Add (CurrentResource); CacheShadersForResources (ShaderPlatform, ResourcesToCache, PrecompileMode); } ... RecacheUniformExpressions (true ); } if (ResourcesToFree.Num ()) { ENQUEUE_RENDER_COMMAND (CmdFreeUnusedMaterialResources)( [ResourcesToFreeRT = MoveTemp (ResourcesToFree)](FRHICommandList&) { for (int32 Idx = 0 ; Idx < ResourcesToFreeRT.Num (); ++Idx) { delete ResourcesToFreeRT[Idx]; } }); } }
可以看出,这里是先缓存 FMaterialResource
类型的对象,然后缓存 UniformExpression
UMaterial::RecacheUniformExpressions
内部对默认的 Material 实例调用我之前想找的 CacheUniformExpressions_GameThread
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void UMaterial::RecacheUniformExpressions (bool bRecreateUniformBuffer) const { bool bUsingNewLoader = EVENT_DRIVEN_ASYNC_LOAD_ACTIVE_AT_RUNTIME && GEventDrivenLoaderEnabled; if (!bUsingNewLoader) { UMaterial::GetDefaultMaterial (MD_Surface); } if (DefaultMaterialInstance) { DefaultMaterialInstance->CacheUniformExpressions_GameThread (bRecreateUniformBuffer); } }
看上去,一个材质是一个抽象的概念,实际逻辑一定有一个实例来完成,是这个意思?