【UE源码分析】PSOcache预编译的源码流程分析

温(mian)馨(ze)提(sheng)示(ming):

  1. 此文是个人学习过程记录,内容质量差
  2. 今天刚好学习UE一个月,本文是作为前两篇的笔记补全计划^ ^
  3. 本来一开始只是看看PSO背后的代码逻辑,没想到本来一两篇笔记就能搞好的,我写了好几篇
  4. 经过对PSO预编译流程的梳理,从一开始不知道怎么读代码到能捋清一个模块确实收获很多,但看到源码后也深感自己的不足,自己远没有能总结出像样的笔记的能力,所以暂时以后也不更新了^^
  5. 立个flag,我要当图形学和游戏引擎糕手!明年九月我要去拯救世界!

初始化PSO

int32 FEngineLoop::PreInitPreStartupScreen(const TCHAR* CmdLine)启动屏幕的预处理的前处理

​ void FShaderPipelineCache::Initialize(EShaderPlatform Platform)在这里初始化ShaderPipelineCache并且根据平台创建FShaderPipelineCache

​ void FPipelineFileCache::Initialize(uint32 InGameVersion)在这里初始化PipelineFileCache

int32 FEngineLoop::PreInitPreStartupScreen(const TCHAR* CmdLine)启动屏幕的预处理的后处理

​ bool FShaderPipelineCache::OpenPipelineFileCache(EShaderPlatform Platform)在这里面调用了一图流靠近右上角的open函数

​ bool FShaderPipelineCache::Open(FString const& Name, EShaderPlatform Platform)除了调用FPipelineFileCache::OpenPipelineFileCache以外,还创建了LocalPreFetchedTasks来为后续做准备

​ bool FPipelineFileCache::OpenPipelineFileCache(FString const& Name, EShaderPlatform Platform, FGuid& OutGameFileGuid)就是在这里面创建了TOC

对编译任务做准备

void FShaderPipelineCache::Tick( float DeltaTime )这一步是每帧都在做的,对准备编译任务来说,核心部分是以下代码:

	if (ReadyForNextBatch() && (OrderedCompileTasks.Num() || FetchTasks.Num()))
	{
        uint32 Num = 0;
        if (BatchSize > static_cast<uint32>(FetchTasks.Num()))
        {
            Num = BatchSize - FetchTasks.Num();
        }
        Num = FMath::Min(Num, (uint32)OrderedCompileTasks.Num());
            
		if (FetchTasks.Num() < (int32)Num)
		{
			TDoubleLinkedList<FPipelineCacheFileFormatPSORead*> NewBatch;
		
			Num -= FetchTasks.Num();
            for (auto Iterator = OrderedCompileTasks.CreateIterator(); Iterator && Num > 0; ++Iterator)
            {
                bool bHasShaders = true;
                for (FSHAHash const& Hash : Iterator->Shaders)
                {
                    bHasShaders &= FShaderCodeLibrary::ContainsShaderCode(Hash);
                }
            	if (bHasShaders)
            	{
            		FPipelineCacheFileFormatPSORead* Entry = new FPipelineCacheFileFormatPSORead;
					Entry->Hash = Iterator->Hash;
					Entry->Ar = new FShaderPipelineCacheArchive;
					
					// Add to both new batch and fetch lists
					NewBatch.AddTail(Entry);
					FetchTasks.AddTail(Entry);
					
	            	Iterator.RemoveCurrent();
                    FPlatformAtomics::InterlockedIncrement(&TotalActiveTasks);
                    FPlatformAtomics::InterlockedDecrement(&TotalWaitingTasks);
                    --Num;
            	}
            }
			
			FPipelineFileCache::FetchPSODescriptors(NewBatch);
		}

        if (static_cast<uint32>(FetchTasks.Num()) > BatchSize)
        {
            UE_LOG(LogRHI, Warning, TEXT("FShaderPipelineCache: Attempting to pre-compile more jobs (%d) than the batch size (%d)"), FetchTasks.Num(), BatchSize);
        }
        
		PreparePipelineBatch(FetchTasks);
	}

主要干了四个事情:判断和添加读取的数量,预取ShaderCodeLibrary,取得相应PSO的描述符,准备管线Batch

​ void FPipelineFileCache::FetchPSODescriptors(TDoubleLinkedList<FPipelineCacheFileFormatPSORead*>& Batch) 从这里获取之前准备好的TOC的数据

​ void FShaderPipelineCache::PreparePipelineBatch(TDoubleLinkedList<FPipelineCacheFileFormatPSORead*>& PipelineBatch) 在这里面通过FShaderCodeLibrary::PreloadShader函数来预加载PSO的shader,如果加载成功就把异步读取的任务加入到ReadTasks中

预编译PSO

void FShaderPipelineCache::Tick( float DeltaTime ),对这一步来说,核心是以下代码:

	if (ReadyForPrecompile())
	{
		SCOPE_SECONDS_ACCUMULATOR(STAT_PreCompileTotalTime);
		SCOPE_CYCLE_COUNTER(STAT_PreCompileTime);
		
		uint32 Start = FPlatformTime::Cycles();

		PrecompilePipelineBatch();

		uint32 End = FPlatformTime::Cycles();

		if (BatchTime > 0.0f)
		{
			float ElapsedMs = FPlatformTime::ToMilliseconds(End - Start);
			if (ElapsedMs < BatchTime)
			{
				BatchSize++;
			}
			else if (ElapsedMs > BatchTime)
			{
				if (BatchSize > 1)
				{
					BatchSize--;
				}
				else
				{
					UE_LOG(LogRHI, Warning, TEXT("FShaderPipelineCache: Cannot reduce BatchSize below 1 to meet target of %f ms, elapsed time was %f ms)"), BatchTime, ElapsedMs);
				}
			}
		}
	}

主要干了两个事情,第一个是调用PrecompilePipelineBatch,第二个是动态调节BatchSize的大小

​ void FShaderPipelineCache::PrecompilePipelineBatch() 这个函数是判断了CompileTasks和BatchSize的最小值后调用可以用上的最多的CompileTasks来通过Precompile函数进行预编译

​ bool FShaderPipelineCache::Precompile(FRHICommandListImmediate& RHICmdList, EShaderPlatform Platform, FPipelineCacheFileFormatPSO const& PSO) 这里就是进行了预编译,核心代码如下:

		if(FPipelineCacheFileFormatPSO::DescriptorType::Graphics == PSO.Type)
		{
			FGraphicsPipelineStateInitializer GraphicsInitializer;
			
			FRHIVertexDeclaration* VertexDesc = PipelineStateCache::GetOrCreateVertexDeclaration(PSO.GraphicsDesc.VertexDescriptor);
			GraphicsInitializer.BoundShaderState.VertexDeclarationRHI = VertexDesc;
			
			FVertexShaderRHIRef VertexShader;
			if (PSO.GraphicsDesc.VertexShader != FSHAHash())
			{
				VertexShader = FShaderCodeLibrary::CreateVertexShader(Platform, PSO.GraphicsDesc.VertexShader);
				GraphicsInitializer.BoundShaderState.VertexShaderRHI = VertexShader;
			}

	#if PLATFORM_SUPPORTS_TESSELLATION_SHADERS
			FHullShaderRHIRef HullShader;
			if (PSO.GraphicsDesc.HullShader != FSHAHash())
			{
				HullShader = FShaderCodeLibrary::CreateHullShader(Platform, PSO.GraphicsDesc.HullShader);
				GraphicsInitializer.BoundShaderState.HullShaderRHI = HullShader;
			}

			FDomainShaderRHIRef DomainShader;
			if (PSO.GraphicsDesc.DomainShader != FSHAHash())
			{
				DomainShader = FShaderCodeLibrary::CreateDomainShader(Platform, PSO.GraphicsDesc.DomainShader);
				GraphicsInitializer.BoundShaderState.DomainShaderRHI = DomainShader;
			}
	#endif
			FPixelShaderRHIRef FragmentShader;
			if (PSO.GraphicsDesc.FragmentShader != FSHAHash())
			{
				FragmentShader = FShaderCodeLibrary::CreatePixelShader(Platform, PSO.GraphicsDesc.FragmentShader);
				GraphicsInitializer.BoundShaderState.PixelShaderRHI = FragmentShader;
			}

	#if PLATFORM_SUPPORTS_GEOMETRY_SHADERS
			FGeometryShaderRHIRef GeometryShader;
			if (PSO.GraphicsDesc.GeometryShader != FSHAHash())
			{
				GeometryShader = FShaderCodeLibrary::CreateGeometryShader(Platform, PSO.GraphicsDesc.GeometryShader);
				GraphicsInitializer.BoundShaderState.GeometryShaderRHI = GeometryShader;
			}
	#endif
			auto BlendState = GetOrCreateBlendState(PSO.GraphicsDesc.BlendState);
			GraphicsInitializer.BlendState = BlendState;
			
			auto RasterState = GetOrCreateRasterizerState(PSO.GraphicsDesc.RasterizerState);
			GraphicsInitializer.RasterizerState = RasterState;
			
			auto DepthState = GetOrCreateDepthStencilState(PSO.GraphicsDesc.DepthStencilState);
			GraphicsInitializer.DepthStencilState = DepthState;

			for (uint32 i = 0; i < MaxSimultaneousRenderTargets; ++i)
			{
				GraphicsInitializer.RenderTargetFormats[i] = PSO.GraphicsDesc.RenderTargetFormats[i];
				GraphicsInitializer.RenderTargetFlags[i] = PSO.GraphicsDesc.RenderTargetFlags[i];
			}
			
			GraphicsInitializer.RenderTargetsEnabled = PSO.GraphicsDesc.RenderTargetsActive;
			GraphicsInitializer.NumSamples = PSO.GraphicsDesc.MSAASamples;

			GraphicsInitializer.SubpassHint = (ESubpassHint)PSO.GraphicsDesc.SubpassHint;
			GraphicsInitializer.SubpassIndex = PSO.GraphicsDesc.SubpassIndex;
			
			GraphicsInitializer.DepthStencilTargetFormat = PSO.GraphicsDesc.DepthStencilFormat;
			GraphicsInitializer.DepthStencilTargetFlag = PSO.GraphicsDesc.DepthStencilFlags;
			GraphicsInitializer.DepthTargetLoadAction = PSO.GraphicsDesc.DepthLoad;
			GraphicsInitializer.StencilTargetLoadAction = PSO.GraphicsDesc.StencilLoad;
			GraphicsInitializer.DepthTargetStoreAction = PSO.GraphicsDesc.DepthStore;
			GraphicsInitializer.StencilTargetStoreAction = PSO.GraphicsDesc.StencilStore;
			
			GraphicsInitializer.PrimitiveType = PSO.GraphicsDesc.PrimitiveType;
			GraphicsInitializer.bFromPSOFileCache = true;
			
			// This indicates we do not want a fatal error if this compilation fails
			// (ie, if this entry in the file cache is bad)
			GraphicsInitializer.bFromPSOFileCache = 1;
			
			// Use SetGraphicsPipelineState to call down into PipelineStateCache and also handle the fallback case used by OpenGL.
			SetGraphicsPipelineState(RHICmdList, GraphicsInitializer, EApplyRendertargetOption::DoNothing, false);
			bOk = true;
		}

这里先是创建了FGraphicsPipelineStateInitializer GraphicsInitializer,然后分别设置其BoundShaderState、BlendState、RasterizerState、DepthStencilState等等很多东西,不过我之前看的是OpenGL的,由于OpenGL没有PSO的概念,虽然PSO也收集了,但是只会用到BoundShaderState。收集完后会调用SetGraphicsPipelineState函数

​ void SetGraphicsPipelineState(FRHICommandList& RHICmdList, const FGraphicsPipelineStateInitializer& Initializer, EApplyRendertargetOption ApplyFlags, bool bApplyAdditionalState) 这个函数主要干两个事情,第一个是调用GetAndOrCreateGraphicsPipelineState函数来创建真正的PSO,然后调用SetGraphicsPipelineState设置管线状态

​ FGraphicsPipelineState* PipelineStateCache::GetAndOrCreateGraphicsPipelineState(FRHICommandList& RHICmdList, const FGraphicsPipelineStateInitializer& OriginalInitializer, EApplyRendertargetOption ApplyFlags) 从这个函数里面去取得或者创建PSO,重点部分如下:

		if (DoAsyncCompile)
		{
			OutCachedState->CompletionEvent = TGraphTask<FCompilePipelineStateTask>::CreateTask().ConstructAndDispatchWhenReady(OutCachedState, *Initializer);
			RHICmdList.AddDispatchPrerequisite(OutCachedState->CompletionEvent);
		}
		else
		{
			OutCachedState->RHIPipeline = RHICreateGraphicsPipelineState(*Initializer);
			if(!OutCachedState->RHIPipeline)
			{
				HandlePipelineCreationFailure(*Initializer);
			}
		}

如果使用异步编译的话,启动一个异步任务来编译渲染管线状态,并确保在这个状态编译完成之前,任何依赖于它的渲染命令都不会被执行。不使用的话就通过RHICreateGraphicsPipelineState来执行,这就和之前的笔记PSO函数调用对接上了^^

​ FORCEINLINE_DEBUGGABLE void SetGraphicsPipelineState(class FGraphicsPipelineState* GraphicsPipelineState, const FBoundShaderStateInput& ShaderInput, bool bApplyAdditionalState)这个函数首先通过ExecuteSetGraphicsPipelineState来获得RHIGraphicsPipelineState,接着通过RHISetGraphicsPipelineState来调用RHISetGraphicsPipelineState,紧接着里面会调用RHISetBoundShaderState,这也和之前笔记的PSO函数调用对接上了^^

mesh使用PSO的流程

首先要了解meshdrawingpipeline

MeshDrawingPipeline

在这里插入图片描述

网格体渲染从"FPrimitiveSceneProxy"开始,这是游戏线程的"UPrimitiveComponent"渲染线程表示。"FPrimitiveSceneProxy"负责通过对"GetDynamicMeshElements"和"DrawStaticElements"的回调将FMeshBatch提交给渲染器。

"FMeshBatch"包含了通道确定最终着色器绑定和渲染状态所需的所有内容。

下一步是将"FMeshBatch"转换为一个特定于网格体通道的"FMeshDrawCommand"。"FMeshDrawCommand"是"FMeshBatch"和RHI之间的接口。它是一个完全无状态的绘制描述,存储了RHI需要知道的关于网格体绘制的所有信息。"FMeshDrawCommand"是由一个特定于网格体通道的"FMeshPassProcessor"根据"FMeshBatch"创建的。

最后,"SubmitMeshDrawCommands"用于将"FMeshDrawCommand"转换为RHICommandList上设置的一系列RHI命令。

我们的重点是MeshDrawingPipeline中是怎么和PSO扯上关系的,所以从BuildMeshDrawCommands这步开始

BuildMeshDrawCommands

因为每个"FMeshPassProcessor"必须通过"BuildMeshDrawCommands()“来调用通道着色器的"GetShaderBindings()”,所以通过"ShaderElementData"参数到"BuildMeshDrawCommands()"来将任意数据从"FMeshPassProcessor"传递到"GetShaderBindings()"调用。

template<typename PassShadersType, typename ShaderElementDataType>
void FMeshPassProcessor::BuildMeshDrawCommands(
	const FMeshBatch& RESTRICT MeshBatch,
	uint64 BatchElementMask,
	const FPrimitiveSceneProxy* RESTRICT PrimitiveSceneProxy,
	const FMaterialRenderProxy& RESTRICT MaterialRenderProxy,
	const FMaterial& RESTRICT MaterialResource,
	const FMeshPassProcessorRenderState& RESTRICT DrawRenderState,
	PassShadersType PassShaders,
	ERasterizerFillMode MeshFillMode,
	ERasterizerCullMode MeshCullMode,
	FMeshDrawCommandSortKey SortKey,
	EMeshPassFeatures MeshPassFeatures,
	const ShaderElementDataType& ShaderElementData)
{
	const FVertexFactory* RESTRICT VertexFactory = MeshBatch.VertexFactory;
	const FPrimitiveSceneInfo* RESTRICT PrimitiveSceneInfo = PrimitiveSceneProxy ? PrimitiveSceneProxy->GetPrimitiveSceneInfo() : nullptr;

	FMeshDrawCommand SharedMeshDrawCommand;

	SharedMeshDrawCommand.SetStencilRef(DrawRenderState.GetStencilRef());

	FGraphicsMinimalPipelineStateInitializer PipelineState;
	PipelineState.PrimitiveType = (EPrimitiveType)MeshBatch.Type;
	PipelineState.ImmutableSamplerState = MaterialRenderProxy.ImmutableSamplerState;

	EVertexInputStreamType InputStreamType = EVertexInputStreamType::Default;
	if ((MeshPassFeatures & EMeshPassFeatures::PositionOnly) != EMeshPassFeatures::Default)				InputStreamType = EVertexInputStreamType::PositionOnly;
	if ((MeshPassFeatures & EMeshPassFeatures::PositionAndNormalOnly) != EMeshPassFeatures::Default)	InputStreamType = EVertexInputStreamType::PositionAndNormalOnly;

	check(VertexFactory && VertexFactory->IsInitialized());
	FRHIVertexDeclaration* VertexDeclaration = VertexFactory->GetDeclaration(InputStreamType);

	check(!VertexFactory->NeedsDeclaration() || VertexDeclaration);

	SharedMeshDrawCommand.SetShaders(VertexDeclaration, PassShaders.GetUntypedShaders(), PipelineState);

	PipelineState.RasterizerState = GetStaticRasterizerState<true>(MeshFillMode, MeshCullMode);

	check(DrawRenderState.GetDepthStencilState());
	check(DrawRenderState.GetBlendState());

	PipelineState.BlendState = DrawRenderState.GetBlendState();
	PipelineState.DepthStencilState = DrawRenderState.GetDepthStencilState();
   	PipelineState.DrawShadingRate = GetShadingRateFromMaterial(MaterialResource.GetShadingRate());

	check(VertexFactory && VertexFactory->IsInitialized());
	VertexFactory->GetStreams(FeatureLevel, InputStreamType, SharedMeshDrawCommand.VertexStreams);

	SharedMeshDrawCommand.PrimitiveIdStreamIndex = VertexFactory->GetPrimitiveIdStreamIndex(InputStreamType);

	int32 DataOffset = 0;
	if (PassShaders.VertexShader.IsValid())
	{
		FMeshDrawSingleShaderBindings ShaderBindings = SharedMeshDrawCommand.ShaderBindings.GetSingleShaderBindings(SF_Vertex, DataOffset);
		PassShaders.VertexShader->GetShaderBindings(Scene, FeatureLevel, PrimitiveSceneProxy, MaterialRenderProxy, MaterialResource, DrawRenderState, ShaderElementData, ShaderBindings);
	}

	if (PassShaders.HullShader.IsValid() & PassShaders.DomainShader.IsValid())
	{
		FMeshDrawSingleShaderBindings HullShaderBindings = SharedMeshDrawCommand.ShaderBindings.GetSingleShaderBindings(SF_Hull, DataOffset);
		FMeshDrawSingleShaderBindings DomainShaderBindings = SharedMeshDrawCommand.ShaderBindings.GetSingleShaderBindings(SF_Domain, DataOffset);
		PassShaders.HullShader->GetShaderBindings(Scene, FeatureLevel, PrimitiveSceneProxy, MaterialRenderProxy, MaterialResource, DrawRenderState, ShaderElementData, HullShaderBindings);
		PassShaders.DomainShader->GetShaderBindings(Scene, FeatureLevel, PrimitiveSceneProxy, MaterialRenderProxy, MaterialResource, DrawRenderState, ShaderElementData, DomainShaderBindings);
	}

	if (PassShaders.PixelShader.IsValid())
	{
		FMeshDrawSingleShaderBindings ShaderBindings = SharedMeshDrawCommand.ShaderBindings.GetSingleShaderBindings(SF_Pixel, DataOffset);
		PassShaders.PixelShader->GetShaderBindings(Scene, FeatureLevel, PrimitiveSceneProxy, MaterialRenderProxy, MaterialResource, DrawRenderState, ShaderElementData, ShaderBindings);
	}

	if (PassShaders.GeometryShader.IsValid())
	{
		FMeshDrawSingleShaderBindings ShaderBindings = SharedMeshDrawCommand.ShaderBindings.GetSingleShaderBindings(SF_Geometry, DataOffset);
		PassShaders.GeometryShader->GetShaderBindings(Scene, FeatureLevel, PrimitiveSceneProxy, MaterialRenderProxy, MaterialResource, DrawRenderState, ShaderElementData, ShaderBindings);
	}

	SharedMeshDrawCommand.SetDebugData(PrimitiveSceneProxy, &MaterialResource, &MaterialRenderProxy, PassShaders.GetUntypedShaders(), VertexFactory);

	const int32 NumElements = MeshBatch.Elements.Num();

	for (int32 BatchElementIndex = 0; BatchElementIndex < NumElements; BatchElementIndex++)
	{
		if ((1ull << BatchElementIndex) & BatchElementMask)
		{
			const FMeshBatchElement& BatchElement = MeshBatch.Elements[BatchElementIndex];
			FMeshDrawCommand& MeshDrawCommand = DrawListContext->AddCommand(SharedMeshDrawCommand, NumElements);

			DataOffset = 0;
			if (PassShaders.VertexShader.IsValid())
			{
				FMeshDrawSingleShaderBindings VertexShaderBindings = MeshDrawCommand.ShaderBindings.GetSingleShaderBindings(SF_Vertex, DataOffset);
				FMeshMaterialShader::GetElementShaderBindings(PassShaders.VertexShader, Scene, ViewIfDynamicMeshCommand, VertexFactory, InputStreamType, FeatureLevel, PrimitiveSceneProxy, MeshBatch, BatchElement, ShaderElementData, VertexShaderBindings, MeshDrawCommand.VertexStreams);
			}

			if (PassShaders.HullShader.IsValid() & PassShaders.DomainShader.IsValid())
			{
				FMeshDrawSingleShaderBindings HullShaderBindings = MeshDrawCommand.ShaderBindings.GetSingleShaderBindings(SF_Hull, DataOffset);
				FMeshDrawSingleShaderBindings DomainShaderBindings = MeshDrawCommand.ShaderBindings.GetSingleShaderBindings(SF_Domain, DataOffset);
				FMeshMaterialShader::GetElementShaderBindings(PassShaders.HullShader, Scene, ViewIfDynamicMeshCommand, VertexFactory, EVertexInputStreamType::Default, FeatureLevel, PrimitiveSceneProxy, MeshBatch, BatchElement, ShaderElementData, HullShaderBindings, MeshDrawCommand.VertexStreams);
				FMeshMaterialShader::GetElementShaderBindings(PassShaders.DomainShader, Scene, ViewIfDynamicMeshCommand, VertexFactory, EVertexInputStreamType::Default, FeatureLevel, PrimitiveSceneProxy, MeshBatch, BatchElement, ShaderElementData, DomainShaderBindings, MeshDrawCommand.VertexStreams);
			}

			if (PassShaders.PixelShader.IsValid())
			{
				FMeshDrawSingleShaderBindings PixelShaderBindings = MeshDrawCommand.ShaderBindings.GetSingleShaderBindings(SF_Pixel, DataOffset);
				FMeshMaterialShader::GetElementShaderBindings(PassShaders.PixelShader, Scene, ViewIfDynamicMeshCommand, VertexFactory, EVertexInputStreamType::Default, FeatureLevel, PrimitiveSceneProxy, MeshBatch, BatchElement, ShaderElementData, PixelShaderBindings, MeshDrawCommand.VertexStreams);
			}


			if (PassShaders.GeometryShader.IsValid())
			{
				FMeshDrawSingleShaderBindings GeometryShaderBindings = MeshDrawCommand.ShaderBindings.GetSingleShaderBindings(SF_Geometry, DataOffset);
				FMeshMaterialShader::GetElementShaderBindings(PassShaders.GeometryShader, Scene, ViewIfDynamicMeshCommand, VertexFactory, EVertexInputStreamType::Default, FeatureLevel, PrimitiveSceneProxy, MeshBatch, BatchElement, ShaderElementData, GeometryShaderBindings, MeshDrawCommand.VertexStreams);
			}

			int32 DrawPrimitiveId;
			int32 ScenePrimitiveId;
			GetDrawCommandPrimitiveId(PrimitiveSceneInfo, BatchElement, DrawPrimitiveId, ScenePrimitiveId);

			FMeshProcessorShaders ShadersForDebugging = PassShaders.GetUntypedShaders();
			DrawListContext->FinalizeCommand(MeshBatch, BatchElementIndex, DrawPrimitiveId, ScenePrimitiveId, MeshFillMode, MeshCullMode, SortKey, PipelineState, &ShadersForDebugging, MeshDrawCommand);
		}
	}
}

在代码创建了中FGraphicsMinimalPipelineStateInitializer PipelineState这个类的源码注释中提到:

  • 不包含渲染目标状态的管线状态
  • 适用于在绘制之间渲染目标状态不会改变的网格绘制。
  • 注意:该类的大小会影响网格绘制遍历性能。

创建这个轻量的PipelineStateInitializer后调用了SetShaders函数并对其BoundShaderState赋值,至此如果是调用的PSOcache,那么应该是在这里计入缓存的管线id

SubmitMeshDrawCommands

在这个函数里面调用的是SubmitMeshDrawCommandsRange,此函数代码如下:

void SubmitMeshDrawCommandsRange(
	const FMeshCommandOneFrameArray& VisibleMeshDrawCommands,
	const FGraphicsMinimalPipelineStateSet& GraphicsMinimalPipelineStateSet,
	FRHIVertexBuffer* PrimitiveIdsBuffer,
	int32 BasePrimitiveIdsOffset,
	bool bDynamicInstancing,
	int32 StartIndex,
	int32 NumMeshDrawCommands,
	uint32 InstanceFactor,
	FRHICommandList& RHICmdList)
{
	FMeshDrawCommandStateCache StateCache;
	INC_DWORD_STAT_BY(STAT_MeshDrawCalls, NumMeshDrawCommands);

	for (int32 DrawCommandIndex = StartIndex; DrawCommandIndex < StartIndex + NumMeshDrawCommands; DrawCommandIndex++)
	{
		SCOPED_CONDITIONAL_DRAW_EVENTF(RHICmdList, MeshEvent, GEmitMeshDrawEvent != 0, TEXT("Mesh Draw"));

		const FVisibleMeshDrawCommand& VisibleMeshDrawCommand = VisibleMeshDrawCommands[DrawCommandIndex];
		const int32 PrimitiveIdBufferOffset = BasePrimitiveIdsOffset + (bDynamicInstancing ? VisibleMeshDrawCommand.PrimitiveIdBufferOffset : DrawCommandIndex) * sizeof(int32);
		checkSlow(!bDynamicInstancing || VisibleMeshDrawCommand.PrimitiveIdBufferOffset >= 0);
		FMeshDrawCommand::SubmitDraw(*VisibleMeshDrawCommand.MeshDrawCommand, GraphicsMinimalPipelineStateSet, PrimitiveIdsBuffer, PrimitiveIdBufferOffset, InstanceFactor, RHICmdList, StateCache);
	}
}

这个函数传入了FGraphicsMinimalPipelineStateInitializer 的参数给FMeshDrawCommand::SubmitDraw用,这个函数通过之前的MeshDrawCommand的CachedPipelineId来获取GraphicsMinimalPipelineStateSet,然后通过AsGraphicsPipelineStateInitializer函数来把这个轻量的管线状态变为FGraphicsPipelineStateInitializer,接下来会调用SetGraphicsPipelineState函数,到这里就和之前的预编译流程的后面部分成功对接^^

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值