// Copyright Epic Games, Inc. All Rights Reserved. #include "PCGTextureReadback.h" #include "GlobalShader.h" #include "RHIGPUReadback.h" #include "RenderGraphUtils.h" #include "Async/Async.h" #define LOCTEXT_NAMESPACE "PCGCompute" #define PCG_NUM_THREADS_PER_GROUP_DIMENSION 8 class PCGCOMPUTE_API FPCGTextureReadbackCS : public FGlobalShader { public: DECLARE_GLOBAL_SHADER(FPCGTextureReadbackCS); SHADER_USE_PARAMETER_STRUCT(FPCGTextureReadbackCS, FGlobalShader); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_TEXTURE(Texture2D, SourceTexture) SHADER_PARAMETER_TEXTURE(Texture2DArray, SourceTextureArray) SHADER_PARAMETER_SAMPLER(SamplerState, SourceSampler) SHADER_PARAMETER(FVector2f, SourceDimensions) SHADER_PARAMETER(int32, SourceTextureIndex) SHADER_PARAMETER_UAV(RWTexture2D, OutputTexture) END_SHADER_PARAMETER_STRUCT() public: static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment); OutEnvironment.SetDefine(TEXT("THREADGROUPSIZE_X"), PCG_NUM_THREADS_PER_GROUP_DIMENSION); OutEnvironment.SetDefine(TEXT("THREADGROUPSIZE_Y"), PCG_NUM_THREADS_PER_GROUP_DIMENSION); OutEnvironment.SetDefine(TEXT("THREADGROUPSIZE_Z"), 1); } }; IMPLEMENT_GLOBAL_SHADER(FPCGTextureReadbackCS, "/PCGComputeShaders/PCGTextureReadback.usf", "PCGTextureReadback_CS", SF_Compute); void FPCGTextureReadbackInterface::Dispatch_RenderThread(FRHICommandListImmediate& RHICmdList, const FPCGTextureReadbackDispatchParams& Params, const TFunction& AsyncCallback) { check(Params.SourceTexture && Params.SourceSampler); const bool bIsTextureArray = Params.SourceTexture->GetDesc().Dimension == ETextureDimension::Texture2DArray; FPCGTextureReadbackCS::FParameters PassParameters; PassParameters.SourceSampler = Params.SourceSampler; PassParameters.SourceDimensions = { (float)Params.SourceDimensions.X, (float)Params.SourceDimensions.Y }; if (bIsTextureArray) { const FRHITextureCreateDesc DummyTextureDesc = FRHITextureCreateDesc::Create2D(TEXT("PCGDummyTexture"), 1, 1, PF_G8) .SetFlags(ETextureCreateFlags::ShaderResource); FTextureRHIRef DummyTexture = RHICreateTexture(DummyTextureDesc); { uint32 DestStride; uint8* DestBuffer = (uint8*)RHICmdList.LockTexture2D(DummyTexture, 0, RLM_WriteOnly, DestStride, false); *DestBuffer = 0; RHICmdList.UnlockTexture2D(DummyTexture, 0, false); } PassParameters.SourceTexture = MoveTemp(DummyTexture); PassParameters.SourceTextureArray = Params.SourceTexture; PassParameters.SourceTextureIndex = Params.SourceTextureIndex; } else { const FRHITextureCreateDesc DummyTextureDesc = FRHITextureCreateDesc::Create2DArray(TEXT("PCGDummyTextureArray"), 1, 1, 1, PF_G8) .SetFlags(ETextureCreateFlags::ShaderResource); FTextureRHIRef DummyTexture = RHICreateTexture(DummyTextureDesc); { uint32 DestStride; uint8* DestBuffer = (uint8*)RHICmdList.LockTexture2DArray(DummyTexture, 0, 0, RLM_WriteOnly, DestStride, false); *DestBuffer = 0; RHICmdList.UnlockTexture2DArray(DummyTexture, 0, 0, false); } PassParameters.SourceTexture = Params.SourceTexture; PassParameters.SourceTextureArray = MoveTemp(DummyTexture); PassParameters.SourceTextureIndex = -1; } FRHITextureCreateDesc TargetTextureDesc = FRHITextureCreateDesc::Create2D(TEXT("PCGTexture Readback Compute Target"), Params.SourceDimensions.X, Params.SourceDimensions.Y, EPixelFormat::PF_B8G8R8A8) .SetClearValue(FClearValueBinding::None) .SetFlags(ETextureCreateFlags::UAV | ETextureCreateFlags::ShaderResource | ETextureCreateFlags::NoTiling ) .SetInitialState(ERHIAccess::UAVCompute) .DetermineInititialState(); check(TargetTextureDesc.IsValid()); // Create temporary output texture FTextureRHIRef OutputTexture = RHICreateTexture(TargetTextureDesc); PassParameters.OutputTexture = RHICmdList.CreateUnorderedAccessView(OutputTexture, FRHIViewDesc::CreateTextureUAV().SetDimensionFromTexture(OutputTexture)); TShaderMapRef ComputeShader(GetGlobalShaderMap(GMaxRHIFeatureLevel)); FComputeShaderUtils::Dispatch(RHICmdList, ComputeShader, PassParameters, FIntVector(FMath::DivideAndRoundUp(Params.SourceDimensions.X, PCG_NUM_THREADS_PER_GROUP_DIMENSION), FMath::DivideAndRoundUp(Params.SourceDimensions.Y, PCG_NUM_THREADS_PER_GROUP_DIMENSION), 1)); // Prepare OutputTexture to be copied RHICmdList.Transition(FRHITransitionInfo(OutputTexture, ERHIAccess::UAVCompute, ERHIAccess::CopySrc)); TUniquePtr GPUTextureReadback = MakeUnique(TEXT("PCGTextureReadbackCopy")); GPUTextureReadback->EnqueueCopy(RHICmdList, PassParameters.OutputTexture->GetTexture()); auto ExecuteAsync = [](auto&& RunnerFunc) -> void { if(IsInActualRenderingThread()) { AsyncTask(ENamedThreads::ActualRenderingThread, [RunnerFunc]() { RunnerFunc(RunnerFunc); }); } else { // In specific cases (Server, -onethread, etc) the RenderingThread is actually the same as the GameThread. // When this happens we want to avoid calling AsyncTask which could put us in a infinite task execution loop. // The reason is that if we are running this callback through the task graph we might stay in an executing loop until it has no tasks to execute, // since we are pushing a new task as long as our data isn't ready and we are not advancing the GameThread as we are already on the GameThread this causes a infinite task execution. // Instead delay to GameThread with ExecuteOnGameThread ExecuteOnGameThread(UE_SOURCE_LOCATION, [RunnerFunc]() { RunnerFunc(RunnerFunc); }); } }; auto RunnerFunc = [GPUTextureReadBackPtr = GPUTextureReadback.Release(), AsyncCallback, ExecuteAsync](auto&& RunnerFunc) -> void { if (GPUTextureReadBackPtr->IsReady()) { int32 ReadbackWidth = 0, ReadbackHeight = 0; void* OutBuffer = GPUTextureReadBackPtr->Lock(ReadbackWidth, &ReadbackHeight); AsyncCallback(OutBuffer, ReadbackWidth, ReadbackHeight); GPUTextureReadBackPtr->Unlock(); delete GPUTextureReadBackPtr; } else { ExecuteAsync(RunnerFunc); } }; ExecuteAsync(RunnerFunc); } void FPCGTextureReadbackInterface::Dispatch_GameThread(const FPCGTextureReadbackDispatchParams& Params, const TFunction& AsyncCallback) { ENQUEUE_RENDER_COMMAND(SceneDrawCompletion)( [Params, AsyncCallback](FRHICommandListImmediate& RHICmdList) { Dispatch_RenderThread(RHICmdList, Params, AsyncCallback); }); } void FPCGTextureReadbackInterface::Dispatch(const FPCGTextureReadbackDispatchParams& Params, const TFunction& AsyncCallback) { if (IsInRenderingThread()) { Dispatch_RenderThread(GetImmediateCommandList_ForRenderCommand(), Params, AsyncCallback); } else { Dispatch_GameThread(Params, AsyncCallback); } } #undef LOCTEXT_NAMESPACE #undef PCG_NUM_THREADS_PER_GROUP_DIMENSION