Files
UnrealEngine/Engine/Plugins/PCG/Source/PCGCompute/Private/PCGTextureReadback.cpp
2025-05-18 13:04:45 +08:00

183 lines
7.0 KiB
C++

// 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<float4>, SourceTexture)
SHADER_PARAMETER_TEXTURE(Texture2DArray<float4>, SourceTextureArray)
SHADER_PARAMETER_SAMPLER(SamplerState, SourceSampler)
SHADER_PARAMETER(FVector2f, SourceDimensions)
SHADER_PARAMETER(int32, SourceTextureIndex)
SHADER_PARAMETER_UAV(RWTexture2D<float4>, 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<void(void* OutBuffer, int32 ReadbackWidth, int32 ReadbackHeight)>& 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<FPCGTextureReadbackCS> 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<FRHIGPUTextureReadback> GPUTextureReadback = MakeUnique<FRHIGPUTextureReadback>(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<void(void* OutBuffer, int32 ReadbackWidth, int32 ReadbackHeight)>& AsyncCallback)
{
ENQUEUE_RENDER_COMMAND(SceneDrawCompletion)(
[Params, AsyncCallback](FRHICommandListImmediate& RHICmdList)
{
Dispatch_RenderThread(RHICmdList, Params, AsyncCallback);
});
}
void FPCGTextureReadbackInterface::Dispatch(const FPCGTextureReadbackDispatchParams& Params, const TFunction<void(void* OutBuffer, int32 ReadbackWidth, int32 ReadbackHeight)>& AsyncCallback)
{
if (IsInRenderingThread())
{
Dispatch_RenderThread(GetImmediateCommandList_ForRenderCommand(), Params, AsyncCallback);
}
else
{
Dispatch_GameThread(Params, AsyncCallback);
}
}
#undef LOCTEXT_NAMESPACE
#undef PCG_NUM_THREADS_PER_GROUP_DIMENSION