[Unreal Engine] 使用RenderDoc调试异步执行的Compute Shader

动机

如果Compute Shader是异步执行的,那么ALT+F12抓帧将无法捕获到该Compute Shader的调用。使用GPUReadback又要多写依托答辩代码,还要依靠Debugger调试。

解决方案

在RDG中插入开始捕获和结束捕获的Pass,IRenderCaptureProvider::Get()会通过UE的ModularFeature,去查找实现了IRenderCaptureProvider类的功能模块,并返回其引用。这里如果配置好了RenderDoc,就会正确返回RenderDocPlugin中的实现。

ENQUEUE_RENDER_COMMAND(QwQVoxel)([this] (FRHICommandListImmediate& CmdList)
	{
		FRDGBuilder GraphBuilder(CmdList);
		FGlobalShaderMap* ShaderMap = GetGlobalShaderMap(GMaxRHIFeatureLevel);
		check(ShaderMap);

		// RenderDoc Capture
		FRDGPassRef BeginCapturePass = GraphBuilder.AddPass(
			RDG_EVENT_NAME("BeginCapture"),
			ERDGPassFlags::None,
			[] (FRHICommandListImmediate& RHICommandListLocal)
		{
			IRenderCaptureProvider::Get().BeginCapture(&RHICommandListLocal, IRenderCaptureProvider::ECaptureFlags_Launch);
		});
		FRDGPassRef EndCapturePass = GraphBuilder.AddPass(RDG_EVENT_NAME("End Capture"), ERDGPassFlags::None, [] (FRHICommandListImmediate& RHICmdList) 
		{
			IRenderCaptureProvider::Get().EndCapture(&RHICmdList);
		});
		GraphBuilder.AddPassDependency(BeginCapturePass, EndCapturePass);
		const auto EnsureCaptured = [BeginCapturePass, EndCapturePass, &GraphBuilder](FRDGPassRef& NewPass)
		{
			if (NewPass)
			{
				GraphBuilder.AddPassDependency(BeginCapturePass, NewPass);
				GraphBuilder.AddPassDependency(NewPass, EndCapturePass);
			}
		};

		// Atomic counter buffer
		static constexpr uint32 DEFAULT_COUNTER_VALUES[] { 0, 0, 0, 0 };
		FRDGBufferDesc Desc = FRDGBufferDesc::CreateUploadDesc(sizeof(uint32), 4);
		Desc.Usage |= EBufferUsageFlags::UnorderedAccess;
		FRDGBufferRef CounterBuffer = GraphBuilder.CreateBuffer(Desc, TEXT("Voxel Atomic Counter"));
		GraphBuilder.QueueBufferUpload(CounterBuffer, DEFAULT_COUNTER_VALUES, std::size(DEFAULT_COUNTER_VALUES));
		FRDGBufferUAVRef CounterBufferUAV = GraphBuilder.CreateUAV(CounterBuffer, EPixelFormat::PF_R32_UINT);

		// Uniform buffer
		FVoxelMarchingCubeUniformParameters* UniformParameters =GraphBuilder.AllocParameters<FVoxelMarchingCubeUniformParameters>();
		UniformParameters->VoxelSize = DimensionX;
		UniformParameters->SurfaceIsoValue = 0.f;
		UniformParameters->TotalCubes = (DimensionX + 1) * (DimensionY + 1) * (DimensionZ + 1);
		TRDGUniformBufferRef<FVoxelMarchingCubeUniformParameters> UniformParametersBuffer = GraphBuilder.CreateUniformBuffer(UniformParameters);

		// Nanovdb data buffer
		nanovdb::NanoGrid<float>* GridData = HostVdbBuffer.grid<float>();
		FRDGBufferDesc GridBufferDesc = FRDGBufferDesc::CreateUploadDesc(sizeof(float), HostVdbBuffer.size());
		FRDGBufferRef GridBuffer = GraphBuilder.CreateBuffer(GridBufferDesc, TEXT("Voxel Data Buffer"));
		GraphBuilder.QueueBufferUpload(GridBuffer, GridData, HostVdbBuffer.size());
		FRDGBufferSRVRef GridBufferSRV = GraphBuilder.CreateSRV(GridBuffer, EPixelFormat::PF_R32_UINT);

		// Cube index offset buffer
		FRDGBufferDesc CubeIndexOffsetBufferDesc = FRDGBufferDesc::CreateBufferDesc(sizeof(uint32), DimensionX * DimensionY * DimensionZ);
		FRDGBufferRef CubeIndexOffsetBuffer = GraphBuilder.CreateBuffer(CubeIndexOffsetBufferDesc, TEXT("Cube Index Offset"));
		FRDGBufferUAVRef CubeIndexOffsetBufferUAV = GraphBuilder.CreateUAV(CubeIndexOffsetBuffer, EPixelFormat::PF_R32_UINT);
		FRDGBufferSRVRef CubeIndexOffsetBufferSRV = GraphBuilder.CreateSRV(CubeIndexOffsetBuffer, EPixelFormat::PF_R32_UINT);

		{
			auto CSRef = ShaderMap->GetShader<FVoxelMarchingCubesCalcCubeIndexCS>();
			auto* Parameters = GraphBuilder.AllocParameters<FVoxelMarchingCubesCalcCubeIndexCS::FParameters>();

			Parameters->Counter = CounterBufferUAV;
			Parameters->MarchingCubeParameters = UniformParametersBuffer;
			Parameters->SrcVoxelData = GridBufferSRV;
			Parameters->OutCubeIndexOffsets = CubeIndexOffsetBufferUAV;

			FRDGPassRef CalcPass = FComputeShaderUtils::AddPass(GraphBuilder, RDG_EVENT_NAME("Voxel Marching Cubes Calc CubeIndex"), CSRef, Parameters, GetDispatchSize(DimensionX * DimensionY * DimensionZ));

			EnsureCaptured(CalcPass);
		}

		GraphBuilder.Execute();
	});

​EnsureCaptured​函数被调用时会显式声明对应Pass的依赖,以便执行的顺序是正确的。