【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函数,到这里就和之前的预编译流程的后面部分成功对接^^

  • 30
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
要设置UE源码编译平台,需要按照以下步骤进行操作。 1. 获取Unreal Engine源代码。可以通过Epic Games的GitHub仓库或者Unreal Engine官网下载源代码压缩包。 2. 解压缩源代码到本地计算机的合适位置。 3. 安装必要的软件和工具。编译Unreal Engine需要一些依赖软件和工具,例如Visual Studio(推荐使用VS2017或更高版本)、Windows SDK、DirectX、CMake等。根据Unreal Engine版本和所需功能进行适当安装。 4. 打开解压缩后的源代码目录,找到Setup.bat文件并运行。此脚本将执行一系列设置和配置操作,以确保编译环境的正确性。 5. 选择要编译的目标平台。根据项目需求,使用Setup.bat脚本提供的选项选择要编译的目标平台,例如Windows、Mac、Linux等。 6. 使用CMake生成项目文件。在源代码目录中,找到GenerateProjectFiles.bat文件并运行。此脚本将使用CMake生成项目文件。 7. 打开生成的项目文件。根据所用的开发工具,例如Visual Studio,使用生成的项目文件打开Unreal Engine。这将加载Unreal Engine的代码,并准备进行编译。 8. 编译Unreal Engine。使用开发工具进行编译,确保选择正确的编译配置和目标平台。编译过程可能需要一些时间,具体时间取决于计算机性能和项目大小。 9. 完成编译后,可以开始使用编译后的Unreal Engine。根据需要,可以运行自己的项目或者使用Unreal Engine进行开发或测试。 需要注意的是,UE源码编译平台的设置过程可能会因个人计算机的配置和项目需求而略有差异。建议在参考官方文档和社区参考资料的基础上进行设置,并根据实际情况调整。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值