DonaldW's github pages
Unreal Source Explained (USE) is an Unreal source code analysis, based on profilers.
For more infomation, see the repo in github.
See Table of Contents for the complete content list. Some important contents are listed below,
In order to debug your shaders in the GPU debuggers, you should modify Engine/Config/ConsoleVariables.ini:
; Uncomment to get detailed logs on shader compiles and the opportunity to retry on errors
r.ShaderDevelopmentMode=1
; Uncomment when running with a graphical debugger (but not when profiling)
r.Shaders.Optimize=0
r.Shaders.KeepDebugInfo=1
For GPU Scene, it’s disabled by default in mobile, you can enable it by setting r.Mobile.SupportGPUScene=1
in your project’s DefaultEngine.ini.
Like Blueprint, Material Editor is also a node-based visual scripiting envrironment, for creating Material Shader.
Each node is an Expression, you can use various kinds of expressions (e.g., Texture Sample, Add, etc.) to write your own shader logic. Exprssions eventually flow into the Result Node (e.g., M_Char_Barbrous above) via Pins (e.g., Base Color, Metallic). Material shader can promote variables into Parameters.
Material Instance is subclass of Material Shader. It’s data oriented and only specify the input argument of parent material shader. Changes made to material shader will cause shader recompilation, while changes of material instance won’t.
Unreal duplicates most rendering-related things into 2 threads, the game thread and the rendering thread.
Most rendering codes are in 3 modules, Engine, Renderer and RenderCore. During C++ linking,
Following is some important rendering-related classes, you can summarize it by these patterns:
U**
are all in game thread,
F**
are all in rendering thread,
F**Proxy
and FMaterial
’s codes are all in Engine module,Game Thread | Rendering Thread | Rendering Thread |
---|---|---|
Engine Module | Engine Module | Rendering Module |
World (Scene): | ||
UWorld |
FScene |
|
ULevel |
||
USceneComponent |
||
Primitive: | ||
UPrimitiveComponent |
FPrimitiveSceneProxy |
|
FPrimitiveSceneInfo |
||
F**SceneProxy ,inehrited from FPrimitiveSceneProxy ,has FVertexyFactory and UMaterialInterface |
||
View: | ||
FSceneView |
||
ULocalPlayer |
FSceneViewState |
|
FSceneRenderer ,derived class: FMobileSceneRenderer |
||
Light: | ||
ULightComponent |
FLightSceneProxy |
|
FLightSceneInfo |
||
Material and Shader: | ||
UMaterialInterface ,derived class: UMaterial and UMaterialInstance |
FMaterial ,derived class: FMaterialResource and FMaterialRenderProxy |
|
FShaderType ,derived class: FMaterialShaderType |
||
FShader ,derived class: FMaterialShader |
Unreal uses mtlpp, a C++ Metal wrapper, to glue its RHI codes and Metal APIs together.
For better support of massive primitives, GPU driven pipeline and ray-tracing, Epic has refactored and introduce a new Mesh Drawing Pipeline(MDP) in 4.22. And Epic gave a talk about it.
The new pipeline is summarized by Epic as:
Compared to the old immediate mode pipeline, the new MDP is kinda retain mode. It adds new FMeshDrawCommand
to cache the draw commands, then merge and sort them. FMeshPassProcessor
replaces the old Drawing Policy to generate commands.
The new MDP is all about caching. Here is its 3 different caching code paths,
Which means, during one frame,
FMeshBatch
,FMeshBatch
and FMeshDrawCommand
.FPrimitiveSceneProxy
(link) is just the rendering thread counterpart of UPrimitiveComponent
. Both of them is intended to be subclassed to support different primitive types, for example,
UPrimitiveComponent |
FPrimitiveSceneProxy |
---|---|
UStaticMeshComponent |
FStaticMeshSceneProxy |
USkeletalMeshComponent |
FSkeletalMeshSceneProxy |
UHierarchicalInstancedStaticMeshComponent |
FHierarchicalStaticMeshSceneProxy |
ULandscapeComponent |
FLandscapeComponentSceneProxy |
… | … |
FMeshBatch
cantains all infomations about all passes of one primitive, including the vertex buffer (in vertex factory) and material, etc.
/**
* A batch of mesh elements, all with the same material and vertex buffer
*/
struct FMeshBatch
{
TArray<FMeshBatchElement,TInlineAllocator<1> > Elements;
...
uint32 ReverseCulling : 1;
uint32 bDisableBackfaceCulling : 1;
/**
* Pass feature relevance flags.
*/
uint32 CastShadow : 1; // Whether it can be used in shadow renderpasses.
uint32 bUseForMaterial : 1; // Whether it can be used in renderpasses requiring material outputs.
uint32 bUseForDepthPass : 1; // Whether it can be used in depth pass.
uint32 bUseAsOccluder : 1; // Hint whether this mesh is a good occluder.
...
/** Vertex factory for rendering, required. */
const FVertexFactory* VertexFactory;
/** Material proxy for rendering, required. */
const FMaterialRenderProxy* MaterialRenderProxy;
...
};
For static mesh batches, they are stored in their primitive, see the ownership chain below,
FPrimitiveSceneInfo* FPrimitiveSceneProxy::PrimitiveSceneInfo
TArray<FStaticMeshBatch> FPrimitiveSceneInfo::StaticMeshes
and they are collected once they are added to the scene, and cached,
For dynamic mesh batches, FMeshElementCollector FSceneRenderer::MeshCollector
(link) stores an array of FMeshBatch
.
During each frame in InitView()
, FSceneRenderer
calls FPrimitiveSceneProxy::GetDynamicMeshElements()
to generate the dynamic FMeshBatch
,
FMeshDrawCommand
(link) describes a mesh pass draw call, captured just above the RHI. It just contains the only data needed to draw.
class FMeshDrawCommand
{
public:
/** Resource bindings */
FMeshDrawShaderBindings ShaderBindings;
FVertexInputStreamArray VertexStreams;
FRHIIndexBuffer* IndexBuffer;
/** PSO */
FGraphicsMinimalPipelineStateId CachedPipelineId;
/** Draw command parameters */
uint32 FirstIndex;
uint32 NumPrimitives;
uint32 NumInstances;
...
}
For static draw commands, they are stored in FScene
, see the ownership chain below,
FScene* FSceneRenderer::Scene
FCachedPassMeshDrawList FScene::CachedDrawLists[EMeshPass::Num];
TSparseArray<FMeshDrawCommand> FCachedPassMeshDrawList::MeshDrawCommands
For dynamic draw commands they are stored in the FViewInfo
, see the ownership chain below,
TArray<FViewInfo> FSceneRenderer::Views
TStaticArray<FParallelMeshDrawCommandPass, EMeshPass::Num> FViewInfo::ParallelMeshDrawCommandPasses
FMeshDrawCommandPassSetupTaskContext FParallelMeshDrawCommandPass::TaskContext
,FDynamicMeshDrawCommandStorage FMeshDrawCommandPassSetupTaskContext::MeshDrawCommandStorage
TChunkedArray<FMeshDrawCommand> FDynamicMeshDrawCommandStorage::MeshDrawCommands
And the static and dynamic draw commands are built by these following calls,
Dynamic draw commands, since they are view-dependant and stored in FViewInfo
, they are initiated from FMobileSceneRenderer::InitViews()
or FSceneRenderer::ComputeViewVisibility()
, and both of them go to GenerateDynamicMeshDrawCommands()
.
Static draw commands, since they ared stored in the FScene
, they are initiated from FScene::AddPrimitive()
, which of cause, right after the primitive is added to the scene.
After that, both the dynamic and static draw commands share the same remaining code path from FMobileBasePassMeshProcessor::AddMeshBatch()
to FMeshPassProcessor::BuildMeshDrawCommands<..>()
.