// Copyright Epic Games, Inc. All Rights Reserved. #include "AnimBankTransformProvider.h" #include "Animation/AnimBoneCompressionCodec.h" #include "Animation/AnimSequence.h" #include "Animation/AnimSequenceDecompressionContext.h" #include "Animation/Skeleton.h" #include "AnimationRuntime.h" #include "AnimEncoding.h" #include "ComponentRecreateRenderStateContext.h" #include "GlobalRenderResources.h" #include "GlobalShader.h" #include "RenderGraphBuilder.h" #include "RenderGraphResources.h" #include "RenderGraphUtils.h" #include "RenderingThread.h" #include "RenderUtils.h" #include "SceneInterface.h" #include "ScenePrivate.h" #include "SceneView.h" #include "SkeletalRenderPublic.h" #include "SkinningDefinitions.h" static FGuid AnimBankGPUProviderId(ANIM_BANK_GPU_TRANSFORM_PROVIDER_GUID); static FGuid AnimBankCPUProviderId(ANIM_BANK_CPU_TRANSFORM_PROVIDER_GUID); IMPLEMENT_SCENE_EXTENSION(FAnimBankTransformProvider); // Animation is always sampled at 30hz #define ANIM_BANK_SAMPLE_RATE 30.0f static TAutoConsoleVariable CVarAnimBankInterp( TEXT("r.AnimBank.Interpolation"), true, TEXT(""), ECVF_Scalability | ECVF_RenderThreadSafe ); static TAutoConsoleVariable CVarAnimBankTimeScale( TEXT("r.AnimBank.TimeScale"), 1.0f, TEXT(""), ECVF_Scalability | ECVF_RenderThreadSafe ); static TAutoConsoleVariable CVarAnimBankGPU( TEXT("r.AnimBank.GPU"), true, TEXT(""), FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* InVariable) { FGlobalComponentRecreateRenderStateContext Context; }), ECVF_Scalability | ECVF_RenderThreadSafe ); class FAnimBankEvaluateCS : public FGlobalShader { public: static constexpr uint32 BonesPerGroup = 64u; private: DECLARE_GLOBAL_SHADER(FAnimBankEvaluateCS); SHADER_USE_PARAMETER_STRUCT(FAnimBankEvaluateCS, FGlobalShader); static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return DoesPlatformSupportNanite(Parameters.Platform); } static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment); OutEnvironment.SetDefine(TEXT("BONES_PER_GROUP"), 64); OutEnvironment.CompilerFlags.Add(CFLAG_WarningsAsErrors); OutEnvironment.CompilerFlags.Add(CFLAG_HLSL2021); } BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_RDG_BUFFER_SRV(ByteAddressBuffer, HeaderBuffer) SHADER_PARAMETER_RDG_BUFFER_SRV(ByteAddressBuffer, BankBuffer) SHADER_PARAMETER_RDG_BUFFER_UAV(RWByteAddressBuffer, TransformBuffer) END_SHADER_PARAMETER_STRUCT() }; IMPLEMENT_GLOBAL_SHADER(FAnimBankEvaluateCS, "/Engine/Private/Skinning/AnimBankEval.usf", "BankEvaluateCS", SF_Compute); class FAnimBankScatterCS : public FGlobalShader { public: static constexpr uint32 BonesPerGroup = FAnimBankEvaluateCS::BonesPerGroup; private: DECLARE_GLOBAL_SHADER(FAnimBankScatterCS); SHADER_USE_PARAMETER_STRUCT(FAnimBankScatterCS, FGlobalShader); static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return DoesPlatformSupportNanite(Parameters.Platform); } static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment); OutEnvironment.SetDefine(TEXT("BONES_PER_GROUP"), 64); OutEnvironment.CompilerFlags.Add(CFLAG_WarningsAsErrors); OutEnvironment.CompilerFlags.Add(CFLAG_HLSL2021); } BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_RDG_BUFFER_SRV(ByteAddressBuffer, HeaderBuffer) SHADER_PARAMETER_RDG_BUFFER_SRV(ByteAddressBuffer, SrcTransformBuffer) SHADER_PARAMETER_RDG_BUFFER_UAV(RWByteAddressBuffer, TransformBuffer) END_SHADER_PARAMETER_STRUCT() }; IMPLEMENT_GLOBAL_SHADER(FAnimBankScatterCS, "/Engine/Private/Skinning/AnimBankEval.usf", "BankScatterCS", SF_Compute); bool FAnimBankTransformProvider::ShouldCreateExtension(FScene& InScene) { return NaniteSkinnedMeshesSupported() && DoesRuntimeSupportNanite(GetFeatureLevelShaderPlatform(InScene.GetFeatureLevel()), true, true); } void FAnimBankTransformProvider::InitExtension(FScene& InScene) { if (auto TransformProvider = InScene.GetExtensionPtr()) { // Register GPU animation bank transform provider TransformProvider->RegisterProvider( AnimBankGPUProviderId, FSkinningTransformProvider::FOnProvideTransforms::CreateRaw(this, &FAnimBankTransformProvider::ProvideGPUBankTransforms) ); // Register CPU animation bank transform provider TransformProvider->RegisterProvider( AnimBankCPUProviderId, FSkinningTransformProvider::FOnProvideTransforms::CreateRaw(this, &FAnimBankTransformProvider::ProvideCPUBankTransforms) ); } } FAnimBankRecordHandle FAnimBankTransformProvider::RegisterBank(const FAnimBankDesc& Desc) { FAnimBankRecordHandle Handle; const FAnimBankDesc::FDescHash DescHash = BankRecordMap.ComputeHash(Desc); FAnimBankRecord::FRecordId RecordId = BankRecordMap.FindOrAddIdByHash(DescHash, Desc, FAnimBankRecord()); Handle.Id = RecordId.GetIndex(); Handle.Hash = DescHash.AsUInt(); FAnimBankRecord& BankRecord = BankRecordMap.GetByElementId(RecordId).Value; if (BankRecord.ReferenceCount == 0) { // First reference BankRecord.Desc = Desc; BankRecord.RecordId = Handle.Id; // Calculate total number of sequence rot/pos keys const FAnimBankData& BankData = Desc.BankAsset.Get()->GetData(); check(Desc.SequenceIndex < uint32(BankData.Entries.Num())); const FAnimBankEntry& BankEntry = BankData.Entries[Desc.SequenceIndex]; const float SampleInterval = 1.0f / ANIM_BANK_SAMPLE_RATE; const float TrackLength = float(BankEntry.FrameCount - 1) * SampleInterval; BankRecord.KeyOffset = BankAllocator.Allocate(BankEntry.KeyCount); BankRecord.KeyCount = BankEntry.KeyCount; BankRecord.CurrentTime = FMath::Clamp(BankEntry.Position, 0.0f, TrackLength); BankRecord.PreviousTime = BankRecord.CurrentTime; BankRecord.Playing = BankEntry.IsAutoStart() ? 1 : 0; BankRecord.FrameCount = BankEntry.FrameCount; BankRecord.PositionKeys = BankEntry.PositionKeys; // TODO: Avoid copy BankRecord.RotationKeys = BankEntry.RotationKeys; // TODO: Avoid copy BankRecord.AssetMapping = BankData.Mapping; // TODO: Avoid copy check(BankEntry.KeyCount == BankRecord.PositionKeys.Num() && BankEntry.KeyCount == BankRecord.RotationKeys.Num()); } ++BankRecord.ReferenceCount; return Handle; } void FAnimBankTransformProvider::UnregisterBank(const FAnimBankRecordHandle& Handle) { FAnimBankRecord::FRecordId RecordId(Handle.Id); check(RecordId.IsValid()); FAnimBankRecord& BankRecord = BankRecordMap.GetByElementId(RecordId).Value; check(BankRecord.ReferenceCount > 0); --BankRecord.ReferenceCount; if (BankRecord.ReferenceCount == 0) { BankAllocator.Free(BankRecord.KeyOffset, BankRecord.KeyCount); BankRecordMap.RemoveByElementId(RecordId); } } struct FAnimBankGPUData { TArray IdToOffsetMapping; FRDGBufferRef BoneBlockBuffer = nullptr; FRDGBufferRef BankDataBuffer = nullptr; FRDGBufferRef TransformBuffer = nullptr; uint32 RecordCount = 0; uint32 TransformCount = 0; uint32 BlockCount = 0; uint32 KeyCount = 0; bool bValid = false; }; struct FAnimBankCPUData { TArray IdToOffsetMapping; FRDGBufferRef TransformBuffer = nullptr; uint32 TransformCount = 0; uint32 RecordCount = 0; bool bValid = false; }; static FAnimBankGPUData BuildAnimBankGPUData(const FAnimBankRecordMap& BankRecordMap, FRDGBuilder& GraphBuilder) { const uint32 BonesPerGroup = FAnimBankEvaluateCS::BonesPerGroup; FAnimBankGPUData BankData{}; int32 MaxOffsetIndex = INDEX_NONE; for (const auto& RecordPair : BankRecordMap) { const FAnimBankRecord& BankRecord = RecordPair.Value; check(BankRecord.PositionKeys.Num() == BankRecord.RotationKeys.Num()); MaxOffsetIndex = FMath::Max(MaxOffsetIndex, BankRecord.RecordId); if (BankRecord.PositionKeys.Num() == 0 || !BankRecord.Playing) { continue; } ++BankData.RecordCount; const FAnimBankData& AnimBankData = BankRecord.Desc.BankAsset->GetData(); const uint32 BoneCount = AnimBankData.Mapping.BoneCount; BankData.TransformCount += BoneCount; BankData.BlockCount += FMath::DivideAndRoundUp(BoneCount, BonesPerGroup); BankData.KeyCount += BankRecord.PositionKeys.Num(); } BankData.IdToOffsetMapping.SetNumZeroed(MaxOffsetIndex + 1); BankData.bValid = BankData.BlockCount > 0; if (BankData.bValid) { const bool bInterpolating = CVarAnimBankInterp.GetValueOnRenderThread(); // Transform Buffer { const uint32 TransformSize = sizeof(FCompressedBoneTransform) * BankData.TransformCount; BankData.TransformBuffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateByteAddressDesc(TransformSize), TEXT("AnimBank.Transforms")); } // Bank Record Data const uint32 BankHeaderSize = sizeof(UE::HLSL::FBankRecordHeader) * BankData.RecordCount; const uint32 BankMappingSize = sizeof(float) * 7 * BankData.TransformCount; const uint32 BankKeySize = sizeof(float) * 7 * BankData.KeyCount; const uint32 BankDataSize = BankHeaderSize + BankMappingSize + BankKeySize; uint8* BankRecordData = GraphBuilder.AllocPODArray(BankDataSize); // Bone Block Headers UE::HLSL::FBankBlockHeader* BlockHeaders = GraphBuilder.AllocPODArray(BankData.BlockCount); // Construct Headers and Indirections { uint32 BlockWrite = 0; uint32 TransformOffset = 0; uint8* BankRecordWrite = BankRecordData; for (auto& RecordPair : BankRecordMap) { const FAnimBankRecord& BankRecord = RecordPair.Value; if (BankRecord.KeyCount == 0 || !BankRecord.Playing) { continue; } const FAnimBankData& AnimBankData = BankRecord.Desc.BankAsset->GetData(); const uint32 BoneCount = AnimBankData.Mapping.BoneCount; const uint32 BankRecordOffset = UE_PTRDIFF_TO_UINT32(BankRecordWrite - BankRecordData); UE::HLSL::FBankRecordHeader& Header = *reinterpret_cast(BankRecordWrite); BankRecordWrite += sizeof(UE::HLSL::FBankRecordHeader); Header.BoneCount = BoneCount; Header.FrameCount = BankRecord.FrameCount; Header.SampleRate = ANIM_BANK_SAMPLE_RATE; Header.PlayRate = BankRecord.Desc.PlayRate; Header.CurrentTime = BankRecord.CurrentTime; Header.PreviousTime = BankRecord.PreviousTime; Header.TransformOffset = TransformOffset; Header.Playing = BankRecord.Playing; Header.Interpolating = bInterpolating; Header.HasScale = false; checkSlow(Header.TransformOffset == TransformOffset); BankData.IdToOffsetMapping[BankRecord.RecordId] = Header.TransformOffset; TransformOffset += sizeof(FCompressedBoneTransform) * Header.BoneCount; // Full bone blocks uint32 BlockTransformOffset = Header.TransformOffset; const uint32 FullBlockCount = Header.BoneCount / BonesPerGroup; for (uint32 BlockIndex = 0; BlockIndex < FullBlockCount; ++BlockIndex) { BlockHeaders[BlockWrite].BlockLocalIndex = BlockIndex; BlockHeaders[BlockWrite].BlockBoneCount = BonesPerGroup; BlockHeaders[BlockWrite].BlockTransformOffset = BlockTransformOffset; BlockHeaders[BlockWrite].BankRecordOffset = BankRecordOffset; ++BlockWrite; BlockTransformOffset += (sizeof(FCompressedBoneTransform) * BonesPerGroup); } // Partial bone block (remainder) const uint32 PartialBoneCount = Header.BoneCount - (FullBlockCount * BonesPerGroup); if (PartialBoneCount > 0) { BlockHeaders[BlockWrite].BlockLocalIndex = FullBlockCount; BlockHeaders[BlockWrite].BlockBoneCount = PartialBoneCount; BlockHeaders[BlockWrite].BlockTransformOffset = BlockTransformOffset; BlockHeaders[BlockWrite].BankRecordOffset = BankRecordOffset; ++BlockWrite; } // Asset Mapping { const FVector3f* PositionKeys = BankRecord.AssetMapping.PositionKeys.GetData(); const FQuat4f* RotationKeys = BankRecord.AssetMapping.RotationKeys.GetData(); for (uint32 KeyIndex = 0; KeyIndex < Header.BoneCount; ++KeyIndex) { float* KeyWrite = reinterpret_cast(BankRecordWrite); KeyWrite[0] = RotationKeys[KeyIndex].X; KeyWrite[1] = RotationKeys[KeyIndex].Y; KeyWrite[2] = RotationKeys[KeyIndex].Z; KeyWrite[3] = RotationKeys[KeyIndex].W; KeyWrite[4] = PositionKeys[KeyIndex].X; KeyWrite[5] = PositionKeys[KeyIndex].Y; KeyWrite[6] = PositionKeys[KeyIndex].Z; BankRecordWrite += sizeof(float) * 7; } } // Position and Rotation Keys { const FVector3f* PositionKeys = BankRecord.PositionKeys.GetData(); const FQuat4f* RotationKeys = BankRecord.RotationKeys.GetData(); for (uint32 KeyIndex = 0; KeyIndex < BankRecord.KeyCount; ++KeyIndex) { float* KeyWrite = reinterpret_cast(BankRecordWrite); KeyWrite[0] = RotationKeys[KeyIndex].X; KeyWrite[1] = RotationKeys[KeyIndex].Y; KeyWrite[2] = RotationKeys[KeyIndex].Z; KeyWrite[3] = RotationKeys[KeyIndex].W; KeyWrite[4] = PositionKeys[KeyIndex].X; KeyWrite[5] = PositionKeys[KeyIndex].Y; KeyWrite[6] = PositionKeys[KeyIndex].Z; BankRecordWrite += sizeof(float) * 7; } } } check(UE_PTRDIFF_TO_UINT32(BankRecordWrite - BankRecordData) == BankDataSize); } // Create and upload buffers BankData.BoneBlockBuffer = CreateByteAddressBuffer( GraphBuilder, TEXT("AnimBank.BlockHeaders"), FMath::RoundUpToPowerOfTwo(sizeof(UE::HLSL::FBankBlockHeader) * BankData.BlockCount), BlockHeaders, sizeof(UE::HLSL::FBankBlockHeader) * BankData.BlockCount, // The buffer data is allocated above on the RDG timeline ERDGInitialDataFlags::NoCopy ); BankData.BankDataBuffer = CreateByteAddressBuffer( GraphBuilder, TEXT("AnimBank.BankData"), FMath::RoundUpToPowerOfTwo(BankDataSize), BankRecordData, BankDataSize, // The buffer data is allocated above on the RDG timeline ERDGInitialDataFlags::NoCopy ); } return BankData; } static FAnimBankCPUData BuildAnimBankCPUData(const FAnimBankRecordMap& BankRecordMap, FRDGBuilder& GraphBuilder) { FAnimBankCPUData BankData{}; int32 MaxOffsetIndex = INDEX_NONE; for (const auto& RecordPair : BankRecordMap) { const FAnimBankRecord& BankRecord = RecordPair.Value; check(BankRecord.PositionKeys.Num() == BankRecord.RotationKeys.Num()); MaxOffsetIndex = FMath::Max(MaxOffsetIndex, BankRecord.RecordId); if (BankRecord.PositionKeys.Num() == 0 || !BankRecord.Playing) { continue; } const FAnimBankData& AnimBankData = BankRecord.Desc.BankAsset->GetData(); const uint32 BoneCount = AnimBankData.Mapping.BoneCount; ++BankData.RecordCount; BankData.TransformCount += BoneCount; } BankData.IdToOffsetMapping.SetNumZeroed(MaxOffsetIndex + 1); for (auto& RecordPair : BankRecordMap) { const FAnimBankRecord& BankRecord = RecordPair.Value; BankData.IdToOffsetMapping[BankRecord.RecordId] = ~uint32(0); } BankData.bValid = BankData.RecordCount > 0; if (BankData.bValid) { const bool bInterpolating = CVarAnimBankInterp.GetValueOnRenderThread(); FCompressedBoneTransform* Transforms = GraphBuilder.AllocPODArray(BankData.TransformCount); uint32 TransformOffset = 0; for (const auto& RecordPair : BankRecordMap) { const FAnimBankRecord& BankRecord = RecordPair.Value; if (BankRecord.PositionKeys.Num() == 0 || !BankRecord.Playing) { continue; } check(BankRecord.PositionKeys.Num() == BankRecord.RotationKeys.Num()); BankData.IdToOffsetMapping[BankRecord.RecordId] = (TransformOffset * sizeof(FCompressedBoneTransform)); const float SampleInterval = 1.0f / ANIM_BANK_SAMPLE_RATE; const float TrackLength = float(BankRecord.FrameCount - 1) * SampleInterval; double Time = (double)FMath::Fmod(BankRecord.CurrentTime, TrackLength); if (Time < 0.0) { Time += TrackLength; } int32 KeyIndex0, KeyIndex1; float Alpha; FAnimationRuntime::GetKeyIndicesFromTime(KeyIndex0, KeyIndex1, Alpha, Time, BankRecord.FrameCount, TrackLength); if (!bInterpolating) { // Forcing alpha to zero disables pose interpolation (interpolation method is "step") Alpha = 0.0f; } const FAnimBankData& AnimBankData = BankRecord.Desc.BankAsset->GetData(); const uint32 BoneCount = AnimBankData.Mapping.BoneCount; for (uint32 TransformIndex = 0; TransformIndex < BoneCount; ++TransformIndex) { const FTransform InvGlobalRefPoseXform = FTransform(FQuat(BankRecord.AssetMapping.RotationKeys[TransformIndex]), FVector(BankRecord.AssetMapping.PositionKeys[TransformIndex])); FTransform MeshGlobalAnimPoseXform; if (Alpha <= 0.f) { MeshGlobalAnimPoseXform = FTransform(FQuat(BankRecord.RotationKeys[(KeyIndex0 * BoneCount) + TransformIndex]), FVector(BankRecord.PositionKeys[(KeyIndex0 * BoneCount) + TransformIndex])); MeshGlobalAnimPoseXform.NormalizeRotation(); } else if (Alpha >= 1.f) { MeshGlobalAnimPoseXform = FTransform(FQuat(BankRecord.RotationKeys[(KeyIndex1 * BoneCount) + TransformIndex]), FVector(BankRecord.PositionKeys[(KeyIndex1 * BoneCount) + TransformIndex])); MeshGlobalAnimPoseXform.NormalizeRotation(); } else { FTransform MeshGlobalXformA = FTransform(FQuat(BankRecord.RotationKeys[(KeyIndex0 * BoneCount) + TransformIndex]), FVector(BankRecord.PositionKeys[(KeyIndex0 * BoneCount) + TransformIndex])); FTransform MeshGlobalXformB = FTransform(FQuat(BankRecord.RotationKeys[(KeyIndex1 * BoneCount) + TransformIndex]), FVector(BankRecord.PositionKeys[(KeyIndex1 * BoneCount) + TransformIndex])); // Ensure rotations are normalized MeshGlobalXformA.NormalizeRotation(); MeshGlobalXformB.NormalizeRotation(); MeshGlobalAnimPoseXform.Blend(MeshGlobalXformA, MeshGlobalXformB, Alpha); MeshGlobalAnimPoseXform.NormalizeRotation(); } MeshGlobalAnimPoseXform = InvGlobalRefPoseXform * MeshGlobalAnimPoseXform; FMatrix44f Transform = (FMatrix44f)MeshGlobalAnimPoseXform.ToMatrixNoScale(); StoreCompressedBoneTransform(&Transforms[TransformOffset], Transform); ++TransformOffset; } } // Transform Buffer { const uint32 TransformSize = sizeof(FCompressedBoneTransform) * BankData.TransformCount; BankData.TransformBuffer = CreateByteAddressBuffer( GraphBuilder, TEXT("AnimBank.Transforms"), TransformSize, Transforms, TransformSize, // The buffer data is allocated above on the RDG timeline ERDGInitialDataFlags::NoCopy ); } } return BankData; } void FAnimBankTransformProvider::AdvanceAnimation(FSkinningTransformProvider::FProviderContext& Context) { const float GlobalTimeScale = CVarAnimBankTimeScale.GetValueOnRenderThread(); for (auto& RecordPair : BankRecordMap) { FAnimBankRecord& BankRecord = RecordPair.Value; if (!BankRecord.Playing) { continue; } BankRecord.PreviousTime = BankRecord.CurrentTime; BankRecord.CurrentTime += (Context.DeltaTime * BankRecord.Desc.PlayRate) * GlobalTimeScale; } } void FAnimBankTransformProvider::ScatterAnimation( FSkinningTransformProvider::FProviderContext& Context, const TConstArrayView IdToOffsetMapping, FRDGBufferRef TransformBuffer ) { FRDGBuilder& GraphBuilder = Context.GraphBuilder; const uint32 BonesPerGroup = FAnimBankScatterCS::BonesPerGroup; uint32 BlockCount = 0; for (const FUintVector2& Indirection : Context.Indirections) { const FPrimitiveSceneInfo* Primitive = Context.Primitives[Indirection.X]; auto* SkinnedProxy = static_cast(Primitive->Proxy); const uint32 TransformCount = SkinnedProxy->GetMaxBoneTransformCount(); const uint32 AnimationCount = SkinnedProxy->GetUniqueAnimationCount(); BlockCount += FMath::DivideAndRoundUp(TransformCount, BonesPerGroup) * AnimationCount; } UE::HLSL::FBankScatterHeader* BlockHeaders = GraphBuilder.AllocPODArray(BlockCount); uint32 BlockWrite = 0; for (const FUintVector2& Indirection : Context.Indirections) { const FPrimitiveSceneInfo* Primitive = Context.Primitives[Indirection.X]; auto* SkinnedProxy = static_cast(Primitive->Proxy); const uint32 TransformCount = SkinnedProxy->GetMaxBoneTransformCount(); const uint32 AnimationCount = SkinnedProxy->GetUniqueAnimationCount(); bool bIsValid = false; const TConstArrayView BankIds = SkinnedProxy->GetAnimationProviderData(bIsValid); check(bIsValid && BankIds.Num() == SkinnedProxy->GetUniqueAnimationCount()); const uint32 FullBlockCount = TransformCount / BonesPerGroup; const uint32 PartialTransformCount = TransformCount - (FullBlockCount * BonesPerGroup); uint32 DstTransformOffset = Indirection.Y; for (const uint64 BankId : BankIds) { if (BankId == ~uint64(0)) { continue; } check(BankId < uint64(IdToOffsetMapping.Num())); uint32 SrcTransformOffset = IdToOffsetMapping[int32(BankId)]; const bool bIsPending = (SrcTransformOffset == ~uint32(0)); const uint32 BlockStart = BlockWrite; for (uint32 BlockIndex = 0; BlockIndex < FullBlockCount; ++BlockIndex) { BlockHeaders[BlockWrite].BlockLocalIndex = BlockIndex; BlockHeaders[BlockWrite].BlockSrcTransformOffset = SrcTransformOffset; BlockHeaders[BlockWrite].BlockDstTransformOffset = DstTransformOffset; BlockHeaders[BlockWrite].BlockTransformCount = BonesPerGroup; BlockHeaders[BlockWrite].TotalTransformCount = TransformCount; ++BlockWrite; SrcTransformOffset += (BonesPerGroup * sizeof(FCompressedBoneTransform)); DstTransformOffset += (BonesPerGroup * sizeof(FCompressedBoneTransform)); } if (PartialTransformCount > 0) { BlockHeaders[BlockWrite].BlockLocalIndex = FullBlockCount; BlockHeaders[BlockWrite].BlockSrcTransformOffset = SrcTransformOffset; BlockHeaders[BlockWrite].BlockDstTransformOffset = DstTransformOffset; BlockHeaders[BlockWrite].BlockTransformCount = PartialTransformCount; BlockHeaders[BlockWrite].TotalTransformCount = TransformCount; ++BlockWrite; SrcTransformOffset += (PartialTransformCount * sizeof(FCompressedBoneTransform)); DstTransformOffset += (PartialTransformCount * sizeof(FCompressedBoneTransform)); } if (bIsPending) { for (uint32 BlockIndex = 0; BlockIndex < FullBlockCount; ++BlockIndex) { BlockHeaders[BlockStart + BlockIndex].BlockSrcTransformOffset = ~uint32(0); } if (PartialTransformCount > 0) { BlockHeaders[BlockStart + FullBlockCount].BlockSrcTransformOffset = ~uint32(0); } } // Advance past all the previous transforms as well DstTransformOffset += (sizeof(FCompressedBoneTransform) * TransformCount); } } FRDGBufferRef ScatterBlockHeaders = CreateByteAddressBuffer( GraphBuilder, TEXT("AnimBank.ScatterHeaders"), FMath::RoundUpToPowerOfTwo(sizeof(UE::HLSL::FBankScatterHeader) * BlockCount), BlockHeaders, sizeof(UE::HLSL::FBankScatterHeader) * BlockCount, // The buffer data is allocated above on the RDG timeline ERDGInitialDataFlags::NoCopy ); // Can be null if there are banks, but all are pending (so evaluation is skipped) // None of the blocks will be sourcing from this buffer, ref pose will be written FRDGBufferRef SrcTransformBuffer = TransformBuffer ? TransformBuffer : GSystemTextures.GetDefaultByteAddressBuffer(GraphBuilder, 8u); FAnimBankScatterCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->TransformBuffer = GraphBuilder.CreateUAV(Context.TransformBuffer); PassParameters->SrcTransformBuffer = GraphBuilder.CreateSRV(SrcTransformBuffer); PassParameters->HeaderBuffer = GraphBuilder.CreateSRV(ScatterBlockHeaders); auto ComputeShader = GetGlobalShaderMap(GMaxRHIFeatureLevel)->GetShader(); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("AnimBankScatter"), ComputeShader, PassParameters, FIntVector(BlockCount, 1, 1) ); } void FAnimBankTransformProvider::ProvideGPUBankTransforms(FSkinningTransformProvider::FProviderContext& Context) { AdvanceAnimation(Context); FRDGBuilder& GraphBuilder = Context.GraphBuilder; const FAnimBankGPUData BankData = BuildAnimBankGPUData(BankRecordMap, GraphBuilder); if (!BankData.bValid) { return; } // Evaluate animation banks { FAnimBankEvaluateCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->TransformBuffer = GraphBuilder.CreateUAV(BankData.TransformBuffer); PassParameters->BankBuffer = GraphBuilder.CreateSRV(BankData.BankDataBuffer); PassParameters->HeaderBuffer = GraphBuilder.CreateSRV(BankData.BoneBlockBuffer); auto ComputeShader = GetGlobalShaderMap(GMaxRHIFeatureLevel)->GetShader(); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("AnimBankEvaluate"), ComputeShader, PassParameters, FIntVector(BankData.BlockCount, 1, 1) ); } // Scatter animation bank results ScatterAnimation(Context, MakeConstArrayView(BankData.IdToOffsetMapping), BankData.TransformBuffer); } void FAnimBankTransformProvider::ProvideCPUBankTransforms(FSkinningTransformProvider::FProviderContext& Context) { AdvanceAnimation(Context); // Evaluate animation banks on the CPU, and then upload // the results to the GPU as input to the scatter. FRDGBuilder& GraphBuilder = Context.GraphBuilder; const FAnimBankCPUData BankData = BuildAnimBankCPUData(BankRecordMap, GraphBuilder); if (!BankData.bValid) { return; } // Scatter animation bank results ScatterAnimation(Context, MakeConstArrayView(BankData.IdToOffsetMapping), BankData.TransformBuffer); } const FSkinningTransformProvider::FProviderId& GetAnimBankProviderId() { if (CVarAnimBankGPU.GetValueOnRenderThread()) { return AnimBankGPUProviderId; } else { return AnimBankCPUProviderId; } }