Files
UnrealEngine/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraBakerRendererOutputSparseVolumeTexture.cpp
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

686 lines
26 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "NiagaraBakerRendererOutputSparseVolumeTexture.h"
#include "NiagaraBakerOutputSparseVolumeTexture.h"
#include "NiagaraBakerSettings.h"
#include "NiagaraComponent.h"
#include "NiagaraSystemInstance.h"
#include "NiagaraSystemInstanceController.h"
#include "NiagaraComputeExecutionContext.h"
#include "NiagaraDataInterfaceGrid3DCollection.h"
#include "NiagaraDataInterfaceRenderTargetVolume.h"
#include "NiagaraBatchedElements.h"
#include "NiagaraSVTShaders.h"
#include "NiagaraShader.h"
#include "Engine/Canvas.h"
#include "Engine/TextureRenderTarget2D.h"
#include "Engine/VolumeTexture.h"
#include "HAL/PlatformFileManager.h"
#include "Misc/PathViews.h"
#include "Modules/ModuleManager.h"
#include "Factories/VolumeTextureFactory.h"
#include "TextureResource.h"
#include "UObject/UObjectGlobals.h"
#include "RenderGraphUtils.h"
#include "TextureRenderTargetVolumeResource.h"
#include "Engine/TextureRenderTargetVolume.h"
#include "Editor/SparseVolumeTexture/Public/SparseVolumeTextureFactory.h"
#include "SparseVolumeTexture/SparseVolumeTexture.h"
#include "SparseVolumeTexture/SparseVolumeTextureData.h"
#include "SparseVolumeTexture/ISparseVolumeTextureStreamingManager.h"
#if WITH_EDITOR
#include "AssetRegistry/AssetData.h"
#include "AssetRegistry/IAssetRegistry.h"
#include "Factories/Factory.h"
#include "IAssetTools.h"
#include "Misc/MessageDialog.h"
#include "AssetToolsModule.h"
#endif //WITH_EDITOR
TArray<FNiagaraBakerOutputBinding> FNiagaraBakerRendererOutputSparseVolumeTexture::GetRendererBindings(UNiagaraBakerOutput* InBakerOutput) const
{
TArray<FNiagaraBakerOutputBinding> OutBindings;
if (UNiagaraSystem* NiagaraSystem = InBakerOutput->GetTypedOuter<UNiagaraSystem>())
{
FNiagaraBakerOutputBindingHelper::ForEachEmitterDataInterface(
NiagaraSystem,
[&](const FString& EmitterName, const FString& VariableName, UNiagaraDataInterface* DataInterface)
{
if (UNiagaraDataInterfaceGrid3DCollection* Grid3D = Cast<UNiagaraDataInterfaceGrid3DCollection>(DataInterface))
{
TArray<FNiagaraVariableBase> GridVariables;
TArray<uint32> GridVariableOffsets;
int32 NumAttribChannelsFound;
Grid3D->FindAttributes(GridVariables, GridVariableOffsets, NumAttribChannelsFound);
for (const FNiagaraVariableBase& GridVariable : GridVariables)
{
const FString GridVariableString = GridVariable.GetName().ToString();
FNiagaraBakerOutputBinding& NewBinding = OutBindings.AddDefaulted_GetRef();
NewBinding.BindingName = FName(EmitterName + "." + VariableName + "." + GridVariableString);
NewBinding.MenuCategory = FText::FromString(EmitterName + " " + TEXT("Grid3DCollection"));
NewBinding.MenuEntry = FText::FromString(VariableName + "." + GridVariableString);
}
}
else if (UNiagaraDataInterfaceRenderTargetVolume* VolumeRT = Cast<UNiagaraDataInterfaceRenderTargetVolume>(DataInterface))
{
FNiagaraBakerOutputBinding& NewBinding = OutBindings.AddDefaulted_GetRef();
NewBinding.BindingName = FName(EmitterName + "." + VariableName);
NewBinding.MenuCategory = FText::FromString(EmitterName + " " + TEXT("VolumeRenderTarget"));
NewBinding.MenuEntry = FText::FromString(VariableName);
}
}
);
}
return OutBindings;
}
FIntPoint FNiagaraBakerRendererOutputSparseVolumeTexture::GetPreviewSize(UNiagaraBakerOutput* InBakerOutput, FIntPoint InAvailableSize) const
{
return InAvailableSize;
}
void FNiagaraBakerRendererOutputSparseVolumeTexture::RenderPreview(UNiagaraBakerOutput* InBakerOutput, const FNiagaraBakerRenderer& BakerRenderer, UTextureRenderTarget2D* RenderTarget, TOptional<FString>& OutErrorString) const
{
BakerRenderer.RenderSceneCapture(RenderTarget, ESceneCaptureSource::SCS_SceneColorHDR);
}
FIntPoint FNiagaraBakerRendererOutputSparseVolumeTexture::GetGeneratedSize(UNiagaraBakerOutput* InBakerOutput, FIntPoint InAvailableSize) const
{
return InAvailableSize;
}
void FNiagaraBakerRendererOutputSparseVolumeTexture::RenderGenerated(UNiagaraBakerOutput* InBakerOutput, const FNiagaraBakerRenderer& BakerRenderer, UTextureRenderTarget2D* RenderTarget, TOptional<FString>& OutErrorString) const
{
static FString SVTNotFoundError(TEXT("Sparse Volume Texture asset not found.\nPlease bake to see the result."));
static FString LoopedSVTNotFoundError(TEXT("Looped Sparse Volume Texture asset not found.\reverting to full frame range baked result."));
UNiagaraBakerOutputSparseVolumeTexture* BakerOutput = CastChecked<UNiagaraBakerOutputSparseVolumeTexture>(InBakerOutput);
UNiagaraBakerSettings* BakerSettings = BakerRenderer.GetBakerSettings();
UAnimatedSparseVolumeTexture* SVT = nullptr;
bool bPreviewLoopedOutput = true;
if (BakerOutput->bEnableLoopedOutput && BakerSettings->bPreviewLoopedOutput)
{
SVT = BakerOutput->GetAsset<UAnimatedSparseVolumeTexture>(BakerOutput->LoopedSparseVolumeTextureAssetPathFormat, 0);
if (SVT == nullptr)
{
OutErrorString = SVTNotFoundError;
}
}
if (SVT == nullptr)
{
SVT = BakerOutput->GetAsset<UAnimatedSparseVolumeTexture>(BakerOutput->SparseVolumeTextureAssetPathFormat, 0);
if (SVT == nullptr)
{
OutErrorString = SVTNotFoundError;
return;
}
}
const float WorldTime = BakerRenderer.GetWorldTime();
FCanvas Canvas(RenderTarget->GameThread_GetRenderTargetResource(), nullptr, FGameTime::CreateUndilated(WorldTime, FApp::GetDeltaTime()), BakerRenderer.GetFeatureLevel());
const FNiagaraBakerOutputFrameIndices FrameIndices = BakerSettings->GetOutputFrameIndices(BakerOutput, WorldTime);
BakerRenderer.RenderSparseVolumeTexture(RenderTarget, FrameIndices, SVT);
}
bool FNiagaraBakerRendererOutputSparseVolumeTexture::BeginBake(FNiagaraBakerFeedbackContext& FeedbackContext, UNiagaraBakerOutput* InBakerOutput)
{
UNiagaraBakerOutputSparseVolumeTexture* OutputVolumeTexture = CastChecked<UNiagaraBakerOutputSparseVolumeTexture>(InBakerOutput);
const FString AssetFullName = OutputVolumeTexture->GetAssetPath(OutputVolumeTexture->SparseVolumeTextureAssetPathFormat, 0);
IAssetRegistry* AssetRegistry = IAssetRegistry::Get();
TArray<FAssetData> FoundAssets;
bool FoundAsset = false;
if (AssetRegistry->GetAssetsByPackageName(FName(AssetFullName), FoundAssets))
{
if (FoundAssets.Num() > 0)
{
if (UObject* ExistingOject = StaticLoadObject(UAnimatedSparseVolumeTexture::StaticClass(), nullptr, *AssetFullName))
{
FoundAsset = true;
}
}
}
UNiagaraBakerOutputSparseVolumeTexture* BakerOutput = CastChecked<UNiagaraBakerOutputSparseVolumeTexture>(InBakerOutput);
UAnimatedSparseVolumeTexture* SVT = BakerOutput->GetAsset<UAnimatedSparseVolumeTexture>(BakerOutput->SparseVolumeTextureAssetPathFormat, 0);
if (SVT == nullptr)
{
SVTAsset = UNiagaraBakerOutput::GetOrCreateAsset<UAnimatedSparseVolumeTexture, USparseVolumeTextureFactory>(AssetFullName);
}
else
{
SVTAsset = NewObject<UAnimatedSparseVolumeTexture>(SVT->GetOuter(), UAnimatedSparseVolumeTexture::StaticClass(), *SVT->GetName(), RF_Public | RF_Standalone);
SVTAsset->PostEditChange();
}
if (!SVTAsset->BeginInitialize(1))
{
UE_LOG(LogNiagaraBaker, Error, TEXT("Cannot initialize SVT for baking"));
return false;
}
return true;
}
void FNiagaraBakerRendererOutputSparseVolumeTexture::BakeFrame(FNiagaraBakerFeedbackContext& FeedbackContext, UNiagaraBakerOutput* InBakerOutput, int FrameIndex, const FNiagaraBakerRenderer& BakerRenderer)
{
UNiagaraBakerOutputSparseVolumeTexture* OutputVolumeTexture = CastChecked<UNiagaraBakerOutputSparseVolumeTexture>(InBakerOutput);
FVolumeDataInterfaceHelper DataInterface;
TArray<FString> DataInterfacePath;
OutputVolumeTexture->SourceBinding.SourceName.ToString().ParseIntoArray(DataInterfacePath, TEXT("."));
if ( DataInterface.Initialize(DataInterfacePath, BakerRenderer.GetPreviewComponent()) == false )
{
return;
}
FNiagaraDataInterfaceProxyRenderTargetVolumeProxy* RT_Proxy_RenderTargetVolume = nullptr;
FNiagaraDataInterfaceProxyGrid3DCollectionProxy* RT_Proxy_Grid3D = nullptr;
if (DataInterface.VolumeRenderTargetDataInterface != nullptr)
{
RT_Proxy_RenderTargetVolume = DataInterface.VolumeRenderTargetProxy;
}
else if (DataInterface.Grid3DDataInterface != nullptr)
{
if (!DataInterface.Grid3DInstanceData_GameThread->UseRGBATexture)
{
UE_LOG(LogNiagaraBaker, Error, TEXT("Cannot bake SVTs from non RGBA Grid3D Collections"));
}
RT_Proxy_Grid3D = DataInterface.Grid3DProxy;
}
else
{
UE_LOG(LogNiagaraBaker, Error, TEXT("Cannot bake from data interface"));
}
//-OPT: Currently we are flushing rendering commands. Do not remove this until making access to the frame data safe across threads.
TArray<uint8> TextureData;
FIntVector VolumeResolution = FIntVector(-1, -1, -1);
EPixelFormat VolumeFormat = EPixelFormat::PF_A1;
ENQUEUE_RENDER_COMMAND(NDIRenderTargetVolume_CacheFrame)
(
[RT_Proxy_RenderTargetVolume, RT_Proxy_Grid3D, RT_InstanceID = DataInterface.SystemInstance->GetId(),
RT_TextureData = &TextureData, RT_VolumeResolution = &VolumeResolution, RT_VolumeFormat = &VolumeFormat](FRHICommandListImmediate& RHICmdList)
{
FRHIGPUTextureReadback RenderTargetReadback("ReadVolumeTexture");
uint32 BlockBytes = -1;
if (RT_Proxy_RenderTargetVolume)
{
if (const FRenderTargetVolumeRWInstanceData_RenderThread* InstanceData_RT = RT_Proxy_RenderTargetVolume->SystemInstancesToProxyData_RT.Find(RT_InstanceID))
{
*RT_VolumeResolution = InstanceData_RT->Size;
RenderTargetReadback.EnqueueCopy(RHICmdList, InstanceData_RT->RenderTarget->GetRHI(), FIntVector(0, 0, 0), 0, *RT_VolumeResolution);
BlockBytes = GPixelFormats[InstanceData_RT->RenderTarget->GetRHI()->GetFormat()].BlockBytes;
*RT_VolumeFormat = InstanceData_RT->RenderTarget->GetRHI()->GetFormat();
}
else
{
UE_LOG(LogNiagaraBaker, Error, TEXT("No valid volume RT DI to do readback from"));
return;
}
}
else if (RT_Proxy_Grid3D)
{
if (const FGrid3DCollectionRWInstanceData_RenderThread* InstanceData_RT = RT_Proxy_Grid3D->SystemInstancesToProxyData_RT.Find(RT_InstanceID))
{
if (InstanceData_RT->CurrentData)
{
*RT_VolumeResolution = InstanceData_RT->NumCells;
RenderTargetReadback.EnqueueCopy(RHICmdList, InstanceData_RT->CurrentData->GetPooledTexture()->GetRHI(), FIntVector(0, 0, 0), 0, *RT_VolumeResolution);
BlockBytes = GPixelFormats[InstanceData_RT->CurrentData->GetPooledTexture()->GetRHI()->GetFormat()].BlockBytes;
*RT_VolumeFormat = InstanceData_RT->CurrentData->GetPooledTexture()->GetRHI()->GetFormat();
}
else
{
UE_LOG(LogNiagaraBaker, Error, TEXT("No valid grid DI to do readback from"));
return;
}
}
else
{
UE_LOG(LogNiagaraBaker, Error, TEXT("No valid grid DI to do readback from"));
return;
}
}
else
{
UE_LOG(LogNiagaraBaker, Error, TEXT("No valid grid DI to do readback from"));
return;
}
// Sync the GPU. Unfortunately we can't use the fences because not all RHIs implement them yet.
RHICmdList.BlockUntilGPUIdle();
RHICmdList.FlushResources();
//Lock the readback staging texture
int32 RowPitchInPixels;
int32 BufferHeight;
const uint8* LockedData = (const uint8*)RenderTargetReadback.Lock(RowPitchInPixels, &BufferHeight);
int32 Count = RT_VolumeResolution->X * RT_VolumeResolution->Y * RT_VolumeResolution->Z * BlockBytes;
RT_TextureData->AddUninitialized(Count);
const uint8* SliceStart = LockedData;
for (int32 Z = 0; Z < RT_VolumeResolution->Z; ++Z)
{
const uint8* RowStart = SliceStart;
for (int32 Y = 0; Y < RT_VolumeResolution->Y; ++Y)
{
int32 Offset = 0 + Y * RT_VolumeResolution->X + Z * RT_VolumeResolution->X * RT_VolumeResolution->Y;
FMemory::Memcpy(RT_TextureData->GetData() + Offset * BlockBytes, RowStart, BlockBytes * RT_VolumeResolution->X);
RowStart += RowPitchInPixels * BlockBytes;
}
SliceStart += BufferHeight * RowPitchInPixels * BlockBytes;
}
//Unlock the staging texture
RenderTargetReadback.Unlock();
}
);
FlushRenderingCommands();
if (TextureData.Num() > 0)
{
#if WITH_EDITOR
UE::SVT::FTextureDataCreateInfo SVTCreateInfo;
SVTCreateInfo.VirtualVolumeAABBMin = FIntVector3::ZeroValue;
SVTCreateInfo.VirtualVolumeAABBMax = VolumeResolution;
SVTCreateInfo.FallbackValues[0] = FVector4f(0, 0, 0, 0);
SVTCreateInfo.FallbackValues[1] = FVector4f(0, 0, 0, 0);
SVTCreateInfo.AttributesFormats[0] = VolumeFormat;
SVTCreateInfo.AttributesFormats[1] = PF_Unknown;
UE::SVT::FTextureData SparseTextureData{};
bool Success = SparseTextureData.CreateFromDense(SVTCreateInfo, TArrayView<uint8, int64>((uint8*)TextureData.GetData(), (int64)TextureData.Num() * sizeof(TextureData[0])), TArrayView<uint8>());
if (!Success)
{
UE_LOG(LogNiagaraBaker, Error, TEXT("Cannot create SVT for data interface"));
return;
}
FTransform TransformToUse = FTransform::Identity;
FNiagaraVariableBase& BoundWorldSizeVar = OutputVolumeTexture->VolumeWorldSpaceSizeBinding.ResolvedParameter;
if (BoundWorldSizeVar.IsValid())
{
FVector3f WorldGridScale =
BakerRenderer.GetPreviewComponent()->GetOverrideParameters().GetParameterValueOrDefault<FVector3f>(BoundWorldSizeVar, FVector3f(1, 1, 1));
if (WorldGridScale.Length() < SMALL_NUMBER)
{
WorldGridScale = FVector3f(1, 1, 1);
}
FVector WorldScaleFVector(WorldGridScale.X, WorldGridScale.Y, WorldGridScale.Z);
// scale by volume resolution to get proper world space scale rendering
const FVector FloatResolution(VolumeResolution.X, VolumeResolution.Y, VolumeResolution.Z);
WorldScaleFVector /= FloatResolution;
TransformToUse.SetScale3D(WorldScaleFVector);
}
if (!SVTAsset->AppendFrame(SparseTextureData, TransformToUse))
{
UE_LOG(LogNiagaraBaker, Error, TEXT("Cannot append frame to SVT"));
}
#endif
}
}
void FNiagaraBakerRendererOutputSparseVolumeTexture::EndBake(FNiagaraBakerFeedbackContext& FeedbackContext, UNiagaraBakerOutput* InBakerOutput)
{
UNiagaraBakerSettings* BakerSettings = InBakerOutput->GetTypedOuter<UNiagaraBakerSettings>();
UNiagaraBakerOutputSparseVolumeTexture* OutputVolumeTexture = CastChecked<UNiagaraBakerOutputSparseVolumeTexture>(InBakerOutput);
if (!SVTAsset->EndInitialize())
{
UE_LOG(LogNiagaraBaker, Error, TEXT("Cannot EndInitialize on creating SVT"));
}
SVTAsset->PostLoad();
// make a second pass over the data and loop it
if (OutputVolumeTexture->bEnableLoopedOutput)
{
const FString LoopedAssetFullName = OutputVolumeTexture->GetAssetPath(OutputVolumeTexture->LoopedSparseVolumeTextureAssetPathFormat, 0);
IAssetRegistry* AssetRegistry = IAssetRegistry::Get();
TArray<FAssetData> FoundAssets;
bool FoundAsset = false;
if (AssetRegistry->GetAssetsByPackageName(FName(LoopedAssetFullName), FoundAssets))
{
if (FoundAssets.Num() > 0)
{
if (UObject* ExistingOject = StaticLoadObject(UAnimatedSparseVolumeTexture::StaticClass(), nullptr, *LoopedAssetFullName))
{
FoundAsset = true;
}
}
}
UNiagaraBakerOutputSparseVolumeTexture* BakerOutput = CastChecked<UNiagaraBakerOutputSparseVolumeTexture>(InBakerOutput);
UAnimatedSparseVolumeTexture* LoopedSVT = BakerOutput->GetAsset<UAnimatedSparseVolumeTexture>(BakerOutput->LoopedSparseVolumeTextureAssetPathFormat, 0);
if (LoopedSVT == nullptr)
{
LoopedSVTAsset = UNiagaraBakerOutput::GetOrCreateAsset<UAnimatedSparseVolumeTexture, USparseVolumeTextureFactory>(LoopedAssetFullName);
}
else
{
LoopedSVTAsset = NewObject<UAnimatedSparseVolumeTexture>(LoopedSVT->GetOuter(), UAnimatedSparseVolumeTexture::StaticClass(), *LoopedSVT->GetName(), RF_Public | RF_Standalone);
LoopedSVTAsset->PostEditChange();
}
if (!LoopedSVTAsset->BeginInitialize(1))
{
UE_LOG(LogNiagaraBaker, Error, TEXT("Cannot initialize looped SVT for baking"));
return;
}
// create dense output buffer
const float FrameRate = BakerSettings->FramesPerSecond;
const int32 TotalNumFrames = SVTAsset->GetNumFrames();
const int32 StartFrame = OutputVolumeTexture->StartTime * FrameRate;
const int32 BlendFrames = OutputVolumeTexture->BlendDuration * FrameRate;
const int32 LoopedFrames = TotalNumFrames - StartFrame - BlendFrames;
const int32 BlendStartFrame = TotalNumFrames - BlendFrames;
if (TotalNumFrames <= 0)
{
UE_LOG(LogNiagaraBaker, Error, TEXT("SVT sequence to loop must have > 0 frames"));
return;
}
if (StartFrame < 0)
{
UE_LOG(LogNiagaraBaker, Error, TEXT("Start frame must be greater than 0"));
return;
}
if (BlendFrames < 0)
{
UE_LOG(LogNiagaraBaker, Error, TEXT("Blend amount must be greater than 0"));
return;
}
if (LoopedFrames < 0)
{
UE_LOG(LogNiagaraBaker, Error, TEXT("Cannot loop SVT due to insufficient frames. Please reduce blend amount and start time or bake more frames."));
return;
}
for (int i = 0; i < LoopedFrames; ++i)
{
int32 OutFrameA = StartFrame + BlendFrames + i;
float LerpAmount = FMath::Clamp((1.0 * OutFrameA - BlendStartFrame) / BlendFrames, 0.0f, 1.0f);
int32 OutFrameB = StartFrame + 1.0 * LerpAmount * BlendFrames;
//
// nonblocking read SVT frames
//
const int32 MipLevel = 0;
const float SVTFrameRate = 0.0f;
const bool bBlocking = true;
const bool bHasFrameRate = false;
USparseVolumeTextureFrame* SparseVolumeTextureFrameA =
USparseVolumeTextureFrame::GetFrameAndIssueStreamingRequest(SVTAsset, GetTypeHash(InBakerOutput), SVTFrameRate, OutFrameA, MipLevel, bBlocking, bHasFrameRate);
USparseVolumeTextureFrame* SparseVolumeTextureFrameB =
USparseVolumeTextureFrame::GetFrameAndIssueStreamingRequest(SVTAsset, GetTypeHash(InBakerOutput+12345), SVTFrameRate, OutFrameB, MipLevel, bBlocking, bHasFrameRate);
// The streaming manager normally ticks in FDeferredShadingSceneRenderer::Render(), but the SVT->DenseTexture conversion compute shader happens in a render command before that.
// At execution time of that command, the streamer hasn't had the chance to do any streaming yet, so we force another tick here.
// Assuming blocking requests are used, this guarantees that the requested frame is fully streamed in (if there is memory available).
UE::SVT::GetStreamingManager().Update_GameThread();
if (SparseVolumeTextureFrameA == nullptr)
{
UE_LOG(LogNiagaraBaker, Error, TEXT("Invalid frame from baked SVT for looping"));
return;
}
if (SparseVolumeTextureFrameB == nullptr)
{
UE_LOG(LogNiagaraBaker, Error, TEXT("Invalid frame from baked SVT for looping"));
return;
}
FIntVector VolumeResolution = SparseVolumeTextureFrameA->GetVolumeResolution();
EPixelFormat VolumeFormat = SparseVolumeTextureFrameA->GetFormat(0);
//
// Perform blend and output results
//
TArray<uint8> TextureData;
ENQUEUE_RENDER_COMMAND(NDIRenderTargetVolumeUpdate)
(
[RT_TextureData = &TextureData, RT_VolumeResolution = VolumeResolution, RT_VolumeFormat = VolumeFormat, RT_LerpAmount = LerpAmount,
RT_SVTRenderResources_A = SparseVolumeTextureFrameA->GetTextureRenderResources(),
RT_SVTRenderResources_B = SparseVolumeTextureFrameB->GetTextureRenderResources()](FRHICommandListImmediate& RHICmdList)
{
if (RT_SVTRenderResources_A == nullptr)
{
UE_LOG(LogNiagaraBaker, Error, TEXT("Null svt resource"));
return;
}
if (RT_SVTRenderResources_B == nullptr)
{
UE_LOG(LogNiagaraBaker, Error, TEXT("Null svt resource"));
return;
}
//
// execute compute shader to output blended result
//
FRDGBuilder GraphBuilder(RHICmdList);
TShaderMapRef<FNiagaraBlendSVTsToDenseBufferCS> ComputeShader(GetGlobalShaderMap(GMaxRHIFeatureLevel));
FNiagaraBlendSVTsToDenseBufferCS::FParameters* PassParameters = GraphBuilder.AllocParameters<FNiagaraBlendSVTsToDenseBufferCS::FParameters>();
FRDGTextureDesc TempTextureDesc = FRDGTextureDesc::Create3D(
RT_VolumeResolution,
RT_VolumeFormat,
FClearValueBinding::Black,
ETextureCreateFlags::ShaderResource | ETextureCreateFlags::RenderTargetable | ETextureCreateFlags::UAV);
FRDGTextureRef TempTexture = GraphBuilder.CreateTexture(TempTextureDesc, TEXT("TempOutput"));
PassParameters->DestinationBuffer = GraphBuilder.CreateUAV(TempTexture);
FRHITexture* PageTableTexture_A = RT_SVTRenderResources_A->GetPageTableTexture();
FRHITexture* TextureA_A = RT_SVTRenderResources_A->GetPhysicalTileDataATexture();
if (PageTableTexture_A == nullptr || TextureA_A == nullptr)
{
UE_LOG(LogNiagaraBaker, Error, TEXT("Null svt texture"));
return;
}
// build pass parameters for the "A" SVT frame
FUintVector4 CurrentPackedUniforms0_A = FUintVector4();
FUintVector4 CurrentPackedUniforms1_A = FUintVector4();
RT_SVTRenderResources_A->GetPackedUniforms(CurrentPackedUniforms0_A, CurrentPackedUniforms1_A);
PassParameters->TileDataTextureSampler_A = TStaticSamplerState<SF_Point, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI();
PassParameters->SparseVolumeTexturePageTable_A = PageTableTexture_A;
PassParameters->SparseVolumeTextureA_A = TextureA_A;
PassParameters->PackedSVTUniforms0_A = CurrentPackedUniforms0_A;
PassParameters->PackedSVTUniforms1_A = CurrentPackedUniforms1_A;
PassParameters->TextureSize_A = RT_VolumeResolution;
PassParameters->MipLevels_A = 0;
// build pass parameters for the "B" SVT frame
FRHITexture* PageTableTexture_B = RT_SVTRenderResources_B->GetPageTableTexture();
FRHITexture* TextureA_B = RT_SVTRenderResources_B->GetPhysicalTileDataATexture();
FUintVector4 CurrentPackedUniforms0_B = FUintVector4();
FUintVector4 CurrentPackedUniforms1_B = FUintVector4();
RT_SVTRenderResources_B->GetPackedUniforms(CurrentPackedUniforms0_B, CurrentPackedUniforms1_B);
PassParameters->TileDataTextureSampler_B = TStaticSamplerState<SF_Point, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI();
PassParameters->SparseVolumeTexturePageTable_B = PageTableTexture_B;
PassParameters->SparseVolumeTextureA_B = TextureA_B;
PassParameters->PackedSVTUniforms0_B = CurrentPackedUniforms0_B;
PassParameters->PackedSVTUniforms1_B = CurrentPackedUniforms1_B;
PassParameters->TextureSize_B = RT_VolumeResolution;
PassParameters->MipLevels_B = 0;
PassParameters->LerpAmount = RT_LerpAmount;
FIntVector ThreadGroupSize = FNiagaraShader::GetDefaultThreadGroupSize(ENiagaraGpuDispatchType::ThreeD);
const FIntVector NumThreadGroups(
FMath::DivideAndRoundUp(RT_VolumeResolution.X, ThreadGroupSize.X),
FMath::DivideAndRoundUp(RT_VolumeResolution.Y, ThreadGroupSize.Y),
FMath::DivideAndRoundUp(RT_VolumeResolution.Z, ThreadGroupSize.Z)
);
GraphBuilder.AddPass(
// Friendly name of the pass for profilers using printf semantics.
RDG_EVENT_NAME("Blend SVTs"),
// Parameters provided to RDG.
PassParameters,
// Issues compute commands.
ERDGPassFlags::Compute,
// This is deferred until Execute. May execute in parallel with other passes.
[PassParameters, ComputeShader, NumThreadGroups](FRDGAsyncTask, FRHIComputeCommandList& RHICmdList)
{
FComputeShaderUtils::Dispatch(RHICmdList, ComputeShader, *PassParameters, NumThreadGroups);
});
//
// Readback dense texture
//
FRHIGPUTextureReadback RenderTargetReadback("ReadVolumeTexture");
AddEnqueueCopyPass(GraphBuilder, &RenderTargetReadback, TempTexture);
// Execute the graph.
GraphBuilder.Execute();
RHICmdList.BlockUntilGPUIdle();
check(RenderTargetReadback.IsReady());
//Lock the readback staging texture
int32 RowPitchInPixels;
int32 BufferHeight;
const uint8* LockedData = (const uint8*)RenderTargetReadback.Lock(RowPitchInPixels, &BufferHeight);
if (LockedData == nullptr)
{
UE_LOG(LogNiagaraBaker, Error, TEXT("Readback failed and returned null locked data"));
return;
}
if (RowPitchInPixels == 0 || BufferHeight == 0)
{
UE_LOG(LogNiagaraBaker, Error, TEXT("Readback failed and returned data with zero pitch/buffer height"));
return;
}
uint32 BlockBytes = GPixelFormats[RT_VolumeFormat].BlockBytes;
int32 Count = RT_VolumeResolution.X * RT_VolumeResolution.Y * RT_VolumeResolution.Z * BlockBytes;
RT_TextureData->AddUninitialized(Count);
if (RT_TextureData->Num() == 0)
{
UE_LOG(LogNiagaraBaker, Error, TEXT("Output looped texture data has no elements"));
return;
}
const uint8* SliceStart = LockedData;
for (int32 Z = 0; Z < RT_VolumeResolution.Z; ++Z)
{
const uint8* RowStart = SliceStart;
for (int32 Y = 0; Y < RT_VolumeResolution.Y; ++Y)
{
int32 Offset = 0 + Y * RT_VolumeResolution.X + Z * RT_VolumeResolution.X * RT_VolumeResolution.Y;
FMemory::Memcpy(RT_TextureData->GetData() + Offset * BlockBytes, RowStart, BlockBytes * RT_VolumeResolution.X);
RowStart += RowPitchInPixels * BlockBytes;
}
SliceStart += BufferHeight * RowPitchInPixels * BlockBytes;
}
//Unlock the staging texture
RenderTargetReadback.Unlock();
}
);
FlushRenderingCommands();
if (TextureData.Num() == 0)
{
UE_LOG(LogNiagaraBaker, Error, TEXT("Readback failed when trying to add looped render target to SVT"));
return;
}
//
// add dense texture to LoopedSVTAsset
//
#if WITH_EDITOR
UE::SVT::FTextureDataCreateInfo SVTCreateInfo;
SVTCreateInfo.VirtualVolumeAABBMin = FIntVector3::ZeroValue;
SVTCreateInfo.VirtualVolumeAABBMax = VolumeResolution;
SVTCreateInfo.FallbackValues[0] = FVector4f(0, 0, 0, 0);
SVTCreateInfo.FallbackValues[1] = FVector4f(0, 0, 0, 0);
SVTCreateInfo.AttributesFormats[0] = VolumeFormat;
SVTCreateInfo.AttributesFormats[1] = PF_Unknown;
UE::SVT::FTextureData SparseTextureData{};
bool Success = SparseTextureData.CreateFromDense(SVTCreateInfo, TArrayView<uint8, int64>((uint8*)TextureData.GetData(), (int64)TextureData.Num() * sizeof(TextureData[0])), TArrayView<uint8>());
if (!Success)
{
UE_LOG(LogNiagaraBaker, Error, TEXT("Cannot create looped SVT output"));
return;
}
FTransform TransformToUse = SparseVolumeTextureFrameA->GetFrameTransform();
if (!LoopedSVTAsset->AppendFrame(SparseTextureData, TransformToUse))
{
UE_LOG(LogNiagaraBaker, Error, TEXT("Cannot append frame to looped SVT"));
return;
}
#endif
}
if (!LoopedSVTAsset->EndInitialize())
{
UE_LOG(LogNiagaraBaker, Error, TEXT("Cannot EndInitialize on creating looped SVT"));
}
LoopedSVTAsset->PostLoad();
}
}