You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshInputBuffers.cpp

686 lines
39 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <Atom/Feature/SkinnedMesh/SkinnedMeshInputBuffers.h>
#include <Atom/Feature/MorphTargets/MorphTargetInputBuffers.h>
#include <SkinnedMesh/SkinnedMeshOutputStreamManager.h>
#include <Atom/RPI.Reflect/ResourcePoolAssetCreator.h>
#include <Atom/RPI.Reflect/Buffer/BufferAssetCreator.h>
#include <Atom/RPI.Reflect/Model/ModelAssetCreator.h>
#include <Atom/RPI.Reflect/Model/ModelLodAssetCreator.h>
#include <Atom/RPI.Reflect/Model/MorphTargetMetaAsset.h>
#include <Atom/RPI.Reflect/Model/MorphTargetDelta.h>
#include <Atom/RPI.Public/Shader/ShaderResourceGroup.h>
#include <Atom/RPI.Public/Model/Model.h>
#include <Atom/RHI/Factory.h>
#include <AzCore/std/algorithm.h>
#include <AzCore/Math/PackedVector3.h>
namespace AZ
{
namespace Render
{
Data::Asset<RPI::BufferAsset> CreateBufferAsset(const void* data, const RHI::BufferViewDescriptor& viewDescriptor, RHI::BufferBindFlags bindFlags, Data::Asset<RPI::ResourcePoolAsset> resourcePoolAsset, const char* bufferName)
{
const uint32_t bufferSize = viewDescriptor.m_elementCount * viewDescriptor.m_elementSize;
Data::Asset<RPI::BufferAsset> asset;
{
RHI::BufferDescriptor bufferDescriptor;
bufferDescriptor.m_bindFlags = bindFlags;
bufferDescriptor.m_byteCount = bufferSize;
bufferDescriptor.m_alignment = viewDescriptor.m_elementSize;
RPI::BufferAssetCreator creator;
Uuid uuid = Uuid::CreateRandom();
creator.Begin(uuid);
creator.SetPoolAsset(resourcePoolAsset);
creator.SetBuffer(data, bufferDescriptor.m_byteCount, bufferDescriptor);
// Create a unique buffer name by combining the given, friendly buffer name with the uuid. Use isBrackents=false and isDashes=false to make it look less like some kind of AssetId that has any meaning.
creator.SetBufferName(AZStd::string::format("%s_%s", bufferName, uuid.ToString<AZStd::string>(false,false).c_str()));
creator.SetBufferViewDescriptor(viewDescriptor);
creator.End(asset);
}
return asset;
}
void SkinnedMeshInputLod::SetIndexCount(uint32_t indexCount)
{
m_indexCount = indexCount;
}
void SkinnedMeshInputLod::SetVertexCount(uint32_t vertexCount)
{
m_vertexCount = vertexCount;
}
uint32_t SkinnedMeshInputLod::GetVertexCount() const
{
return m_vertexCount;
}
void SkinnedMeshInputLod::SetIndexBufferAsset(const Data::Asset<RPI::BufferAsset> bufferAsset)
{
m_indexBufferAsset = bufferAsset;
m_indexBuffer = RPI::Buffer::FindOrCreate(bufferAsset);
}
void SkinnedMeshInputLod::CreateIndexBuffer(const uint32_t* data, const AZStd::string& bufferNamePrefix)
{
AZ_Assert(m_indexCount > 0, "SkinnedMeshInputLod::CreateIndexBuffer called with a index count of 0. Make sure SetIndexCount has been called before trying to create any buffers.");
RHI::BufferViewDescriptor indexBufferViewDescriptor = RHI::BufferViewDescriptor::CreateTyped(0, m_indexCount, AZ::RHI::Format::R32_UINT);
// Use the user-specified buffer name if it exits, or a default one otherwise.
const char* bufferName = !bufferNamePrefix.empty() ? bufferNamePrefix.c_str() : "SkinnedMeshStaticIndexBuffer";
Data::Asset<RPI::BufferAsset> bufferAsset = CreateBufferAsset(data, indexBufferViewDescriptor, RHI::BufferBindFlags::InputAssembly, SkinnedMeshVertexStreamPropertyInterface::Get()->GetStaticStreamResourcePool(), bufferName);
m_indexBufferAsset = bufferAsset;
m_indexBuffer = RPI::Buffer::FindOrCreate(bufferAsset);
}
void SkinnedMeshInputLod::CreateSkinningInputBuffer(void* data, SkinnedMeshInputVertexStreams inputStream, const AZStd::string& bufferNamePrefix)
{
AZ_Assert(m_vertexCount > 0, "SkinnedMeshInputLod::CreateSkinningInputBuffer called with a vertex count of 0. Make sure SetVertexCount has been called before trying to create any buffers.");
const SkinnedMeshVertexStreamInfo& streamInfo = SkinnedMeshVertexStreamPropertyInterface::Get()->GetInputStreamInfo(inputStream);
RHI::BufferViewDescriptor viewDescriptor = RHI::BufferViewDescriptor::CreateRaw(0, m_vertexCount * streamInfo.m_elementSize);
// Use the user-specified buffer name if it exits, or a default one from the streamInfo otherwise.
const char* bufferName = !bufferNamePrefix.empty() ? bufferNamePrefix.c_str() : streamInfo.m_bufferName.GetCStr();
Data::Asset<RPI::BufferAsset> bufferAsset = CreateBufferAsset(data, viewDescriptor, RHI::BufferBindFlags::ShaderRead, SkinnedMeshVertexStreamPropertyInterface::Get()->GetInputStreamResourcePool(), bufferName);
m_inputBufferAssets[static_cast<uint8_t>(inputStream)] = bufferAsset;
m_inputBuffers[static_cast<uint8_t>(inputStream)] = RPI::Buffer::FindOrCreate(bufferAsset);
}
void SkinnedMeshInputLod::SetModelLodAsset(const Data::Asset<RPI::ModelLodAsset>& modelLodAsset)
{
m_modelLodAsset = modelLodAsset;
}
void SkinnedMeshInputLod::SetSkinningInputBufferAsset(const Data::Asset<RPI::BufferAsset> bufferAsset, SkinnedMeshInputVertexStreams inputStream)
{
if (inputStream == SkinnedMeshInputVertexStreams::Color)
{
AZ_Assert(!m_hasStaticColors, "Attempting to set colors as skinning input (meaning they are dynamic) when they already exist as a static stream");
m_hasDynamicColors = true;
}
m_inputBufferAssets[static_cast<uint8_t>(inputStream)] = bufferAsset;
Data::Instance<RPI::Buffer> buffer = RPI::Buffer::FindOrCreate(bufferAsset);
m_inputBuffers[static_cast<uint8_t>(inputStream)] = buffer;
// Create a buffer view to use as input to the skinning shader
AZ::RHI::Ptr<AZ::RHI::BufferView> bufferView = RHI::Factory::Get().CreateBufferView();
bufferView->SetName(Name{ AZStd::string(buffer->GetBufferView()->GetName().GetStringView()) + "_SkinningInputBufferView" });
RHI::BufferViewDescriptor bufferViewDescriptor = bufferAsset->GetBufferViewDescriptor();
// 3-component float buffers are not supported on metal for non-input assembly buffer views, so use a float view instead
if (bufferViewDescriptor.m_elementFormat == RHI::Format::R32G32B32_FLOAT)
{
// Use one float per element, with 3x as many elements
bufferViewDescriptor = RHI::BufferViewDescriptor::CreateTyped(
bufferViewDescriptor.m_elementOffset * 3, bufferViewDescriptor.m_elementCount * 3, RHI::Format::R32_FLOAT);
}
[[maybe_unused]] RHI::ResultCode resultCode =
bufferView->Init(*buffer->GetRHIBuffer(), bufferViewDescriptor);
AZ_Error(
"SkinnedMeshInputBuffers", resultCode == RHI::ResultCode::Success,
"Failed to initialize buffer view for skinned mesh input.");
m_bufferViews[static_cast<uint8_t>(inputStream)] = bufferView;
}
void SkinnedMeshInputLod::SetStaticBufferAsset(const Data::Asset<RPI::BufferAsset> bufferAsset, SkinnedMeshStaticVertexStreams staticStream)
{
if (staticStream == SkinnedMeshStaticVertexStreams::Color)
{
AZ_Assert(!m_hasDynamicColors, "Attempting to set colors as a static stream on a skinned mesh, when they already exist as a dynamic stream");
m_hasStaticColors = true;
}
m_staticBufferAssets[static_cast<uint8_t>(staticStream)] = bufferAsset;
m_staticBuffers[static_cast<uint8_t>(staticStream)] = RPI::Buffer::FindOrCreate(bufferAsset);
}
void SkinnedMeshInputLod::CreateStaticBuffer(void* data, SkinnedMeshStaticVertexStreams staticStream, const AZStd::string& bufferNamePrefix)
{
AZ_Assert(m_vertexCount > 0, "SkinnedMeshInputLod::CreateStaticBuffer called with a vertex count of 0. Make sure SetVertexCount has been called before trying to create any buffers.");
const SkinnedMeshVertexStreamInfo& streamInfo = SkinnedMeshVertexStreamPropertyInterface::Get()->GetStaticStreamInfo(staticStream);
RHI::BufferViewDescriptor viewDescriptor = RHI::BufferViewDescriptor::CreateTyped(0, m_vertexCount, streamInfo.m_elementFormat);
// Use the user-specified buffer name if it exits, or a default one from the streamInfo otherwise.
const char* bufferName = !bufferNamePrefix.empty() ? bufferNamePrefix.c_str() : streamInfo.m_bufferName.GetCStr();
Data::Asset<RPI::BufferAsset> bufferAsset = CreateBufferAsset(data, viewDescriptor, RHI::BufferBindFlags::InputAssembly, SkinnedMeshVertexStreamPropertyInterface::Get()->GetStaticStreamResourcePool(), bufferName);
m_staticBufferAssets[static_cast<uint8_t>(staticStream)] = bufferAsset;
m_staticBuffers[static_cast<uint8_t>(staticStream)] = RPI::Buffer::FindOrCreate(bufferAsset);
}
void SkinnedMeshInputLod::SetSubMeshProperties(const AZStd::vector<SkinnedSubMeshProperties>& subMeshProperties)
{
m_subMeshProperties = subMeshProperties;
CreateSharedSubMeshBufferViews();
}
const AZStd::vector<SkinnedSubMeshProperties>& SkinnedMeshInputLod::GetSubMeshProperties() const
{
return m_subMeshProperties;
}
const Data::Asset<RPI::BufferAsset>& SkinnedMeshInputLod::GetSkinningInputBufferAsset(SkinnedMeshInputVertexStreams stream) const
{
return m_inputBufferAssets[static_cast<uint8_t>(stream)];
}
void SkinnedMeshInputLod::WaitForUpload()
{
m_indexBuffer->WaitForUpload();
for (const Data::Instance<RPI::Buffer>& inputBuffer : m_inputBuffers)
{
if (inputBuffer)
{
inputBuffer->WaitForUpload();
}
}
for (const Data::Instance<RPI::Buffer>& staticBuffer : m_staticBuffers)
{
if (staticBuffer)
{
staticBuffer->WaitForUpload();
}
}
}
void SkinnedMeshInputLod::CreateSharedSubMeshBufferViews()
{
AZStd::array_view<RPI::ModelLodAsset::Mesh> meshes = m_modelLodAsset->GetMeshes();
m_sharedSubMeshViews.resize(meshes.size());
// The index and static buffer views will be shared by all instances that use the same SkinnedMeshInputBuffers, so set them here
for (size_t i = 0; i < meshes.size(); ++i)
{
// Set the view into the index buffer
m_sharedSubMeshViews[i].m_indexBufferView = meshes[i].GetIndexBufferAssetView();
// Set the views into the static buffers
for (uint8_t staticStreamIndex = 0; staticStreamIndex < static_cast<uint8_t>(SkinnedMeshStaticVertexStreams::NumVertexStreams); ++staticStreamIndex)
{
// Skip colors if they don't exist or are dynamic
if (staticStreamIndex == static_cast<uint8_t>(SkinnedMeshStaticVertexStreams::Color) && !m_hasStaticColors)
{
continue;
}
const SkinnedMeshVertexStreamInfo& streamInfo = SkinnedMeshVertexStreamPropertyInterface::Get()->GetStaticStreamInfo(static_cast<SkinnedMeshStaticVertexStreams>(staticStreamIndex));
const RPI::BufferAssetView* bufferView = meshes[i].GetSemanticBufferAssetView(streamInfo.m_semantic.m_name);
if (bufferView)
{
m_sharedSubMeshViews[i].m_staticStreamViews[staticStreamIndex] = bufferView;
}
}
}
}
void SkinnedMeshInputLod::AddMorphTarget(const RPI::MorphTargetMetaAsset::MorphTarget& morphTarget, const Data::Asset<RPI::BufferAsset>& morphBufferAsset, const AZStd::string& bufferNamePrefix, float minWeight = 0.0f, float maxWeight = 1.0f)
{
m_morphTargetMetaDatas.push_back(MorphTargetMetaData{ minWeight, maxWeight, morphTarget.m_minPositionDelta, morphTarget.m_maxPositionDelta, morphTarget.m_numVertices, morphTarget.m_startIndex });
// Create a view into the larger per-lod morph buffer for this particular morph
RHI::BufferViewDescriptor morphView = RHI::BufferViewDescriptor::CreateStructured(morphTarget.m_startIndex, morphTarget.m_numVertices, sizeof(RPI::PackedCompressedMorphTargetDelta));
RPI::BufferAssetView morphTargetDeltaView{ morphBufferAsset, morphView };
m_morphTargetInputBuffers.push_back(aznew MorphTargetInputBuffers{ morphTargetDeltaView, bufferNamePrefix });
// If colors are going to be morphed, the SkinnedMeshInputLod needs to know so that it allocates memory for the dynamically updated colors
if (morphTarget.m_hasColorDeltas)
{
m_hasDynamicColors = true;
}
}
bool SkinnedMeshInputLod::HasDynamicColors() const
{
return m_hasDynamicColors;
}
const AZStd::vector<MorphTargetMetaData>& SkinnedMeshInputLod::GetMorphTargetMetaDatas() const
{
return m_morphTargetMetaDatas;
}
const AZStd::vector<AZStd::intrusive_ptr<MorphTargetInputBuffers>>& SkinnedMeshInputLod::GetMorphTargetInputBuffers() const
{
return m_morphTargetInputBuffers;
}
SkinnedMeshInputBuffers::SkinnedMeshInputBuffers() = default;
SkinnedMeshInputBuffers::~SkinnedMeshInputBuffers() = default;
void SkinnedMeshInputBuffers::SetAssetId(Data::AssetId assetId)
{
m_assetId = assetId;
}
void SkinnedMeshInputBuffers::SetLodCount(size_t lodCount)
{
AZ_Assert(lodCount <= RPI::ModelLodAsset::LodCountMax, "Attempting to set lod count of %d in SkinnedMeshInputBuffers, "
"which exceeds the maximum count of %d", lodCount, RPI::ModelLodAsset::LodCountMax);
m_lods.resize(lodCount);
}
size_t SkinnedMeshInputBuffers::GetLodCount() const
{
return m_lods.size();
}
void SkinnedMeshInputBuffers::SetLod(size_t lodIndex, const SkinnedMeshInputLod& lod)
{
AZ_Assert(lodIndex < m_lods.size(), "Attempting to set lod at index %d in SkinnedMeshInputBuffers, which is outside the range of %zu. "
"Make sure SetLodCount has been called before calling SetLod.", lodIndex, m_lods.size());
m_lods[lodIndex] = lod;
m_isUploadPending = true;
}
const SkinnedMeshInputLod& SkinnedMeshInputBuffers::GetLod(size_t lodIndex) const
{
AZ_Assert(lodIndex < m_lods.size(), "Attempting to get lod at index %d in SkinnedMeshInputBuffers, which is outside the range of %zu.", lodIndex, m_lods.size());
return m_lods[lodIndex];
}
AZStd::array_view<AZ::RHI::Ptr<RHI::BufferView>> SkinnedMeshInputBuffers::GetInputBufferViews(size_t lodIndex) const
{
return m_lods[lodIndex].m_bufferViews;
}
AZ::RHI::Ptr<const RHI::BufferView> SkinnedMeshInputBuffers::GetInputBufferView(size_t lodIndex, uint8_t inputStream) const
{
return m_lods[lodIndex].m_inputBuffers[inputStream]->GetBufferView();
}
uint32_t SkinnedMeshInputBuffers::GetVertexCount(size_t lodIndex) const
{
return m_lods[lodIndex].m_vertexCount;
}
void SkinnedMeshInputBuffers::SetBufferViewsOnShaderResourceGroup(size_t lodIndex, const Data::Instance<RPI::ShaderResourceGroup>& perInstanceSRG)
{
// Get the SRG indices for each input stream
for (uint8_t inputStream = 0; inputStream < static_cast<uint8_t>(SkinnedMeshInputVertexStreams::NumVertexStreams); ++inputStream)
{
// Skip colors if they don't exist or are not being morphed
if (inputStream == static_cast<uint8_t>(SkinnedMeshInputVertexStreams::Color) && !m_lods[lodIndex].m_hasDynamicColors)
{
continue;
}
const SkinnedMeshVertexStreamInfo& streamInfo = SkinnedMeshVertexStreamPropertyInterface::Get()->GetInputStreamInfo(static_cast<SkinnedMeshInputVertexStreams>(inputStream));
RHI::ShaderInputBufferIndex srgIndex = perInstanceSRG->FindShaderInputBufferIndex(streamInfo.m_shaderResourceGroupName);
AZ_Error("SkinnedMeshInputBuffers", srgIndex.IsValid(), "Failed to find shader input index for '%s' in the skinning compute shader per-instance SRG.", streamInfo.m_shaderResourceGroupName.GetCStr());
[[maybe_unused]] bool success = false;
if (m_lods[lodIndex].m_inputBuffers[inputStream])
{
success = perInstanceSRG->SetBufferView(srgIndex, m_lods[lodIndex].m_bufferViews[inputStream].get());
}
AZ_Error("SkinnedMeshInputBuffers", success, "Failed to bind buffer view for %s", streamInfo.m_bufferName.GetCStr());
}
// Set the vertex count
RHI::ShaderInputConstantIndex numVerticesIndex;
numVerticesIndex = perInstanceSRG->FindShaderInputConstantIndex(Name{ "m_numVertices" });
AZ_Error("SkinnedMeshInputBuffers", numVerticesIndex.IsValid(), "Failed to find shader input index for m_numVerticies in the skinning compute shader per-instance SRG.");
perInstanceSRG->SetConstant(numVerticesIndex, m_lods[lodIndex].m_vertexCount);
}
// Create a resource view that has a different type than the data it is viewing
static RHI::BufferViewDescriptor CreateResourceViewWithDifferentFormat(size_t offsetInBytes, uint32_t realElementCount, uint32_t realElementSize, RHI::Format format, RHI::BufferBindFlags overrideBindFlags)
{
RHI::BufferViewDescriptor viewDescriptor;
size_t elementOffset = offsetInBytes / aznumeric_cast<size_t>(RHI::GetFormatSize(format));
AZ_Assert(elementOffset <= std::numeric_limits<uint32_t>().max(), "The offset in bytes from the start of the SkinnedMeshOutputStream buffer is too large to be expressed as a uint32_t element offset in the BufferViewDescriptor.");
viewDescriptor.m_elementOffset = aznumeric_cast<uint32_t>(elementOffset);
viewDescriptor.m_elementCount = realElementCount * (realElementSize / RHI::GetFormatSize(format));
viewDescriptor.m_elementFormat = format;
viewDescriptor.m_elementSize = RHI::GetFormatSize(format);
viewDescriptor.m_overrideBindFlags = overrideBindFlags;
return viewDescriptor;
}
static bool AllocateLodStream(
uint8_t outputStreamIndex,
size_t vertexCount,
AZStd::intrusive_ptr<SkinnedMeshInstance> instance,
AZStd::vector<uint32_t>& streamOffsetsFromBufferStart,
AZStd::vector<AZStd::intrusive_ptr<SkinnedMeshOutputStreamAllocation>>& lodAllocations)
{
const SkinnedMeshOutputVertexStreamInfo& outputStreamInfo = SkinnedMeshVertexStreamPropertyInterface::Get()->GetOutputStreamInfo(static_cast<SkinnedMeshOutputVertexStreams>(outputStreamIndex));
// Positions use 2x the number of vertices to hold both the current frame and previous frame's data
size_t positionMultiplier = static_cast<SkinnedMeshOutputVertexStreams>(outputStreamIndex) == SkinnedMeshOutputVertexStreams::Position ? 2u : 1u;
AZStd::intrusive_ptr<SkinnedMeshOutputStreamAllocation> allocation = SkinnedMeshOutputStreamManagerInterface::Get()->Allocate(vertexCount * static_cast<size_t>(outputStreamInfo.m_elementSize) * positionMultiplier);
if (!allocation)
{
// Suppress the OnMemoryFreed signal when releasing the previous successful allocations
// The memory was already free before this function was called, so it's not really newly available memory
AZ_Error("SkinnedMeshInputBuffers", false, "Out of memory to create a skinned mesh instance. Consider increasing r_skinnedMeshInstanceMemoryPoolSize");
instance->m_allocations.push_back(lodAllocations);
instance->SuppressSignalOnDeallocate();
return false;
}
lodAllocations.push_back(allocation);
streamOffsetsFromBufferStart.push_back(aznumeric_cast<uint32_t>(allocation->GetVirtualAddress().m_ptr));
return true;
}
static bool AllocateMorphTargetsForLod(const SkinnedMeshInputLod& lod, size_t vertexCount, AZStd::intrusive_ptr<SkinnedMeshInstance> instance, AZStd::vector<AZStd::intrusive_ptr<SkinnedMeshOutputStreamAllocation>>& lodAllocations)
{
// If this skinned mesh lod has morph targets, allocate a buffer for the accumulated deltas that come from the morph target pass
if (lod.GetMorphTargetMetaDatas().size() > 0)
{
// Naively, we're going to allocate enough memory to store the accumulated delta for every vertex.
// This makes it simple for the skinning shader to index into the buffer, but the memory cost
// could be reduced by keeping a buffer that maps from vertexId to morph target delta offset ATOM-14427
// We're also using the skinned mesh output buffer, since it gives us a read-write pool of memory that can be
// used for dependency tracking between passes. This can be switched to a transient memory pool so that the memory is free
// later in the frame once skinning is finished ATOM-14429
size_t perVertexSizeInBytes = static_cast<size_t>(MorphTargetConstants::s_unpackedMorphTargetDeltaSizeInBytes) * MorphTargetConstants::s_morphTargetDeltaTypeCount;
if (lod.HasDynamicColors())
{
// Naively, if colors are morphed by any of the morph targets,
// we'll allocate enough memory to store the accumulated color deltas for every vertex in the lod.
// This could be reduced by ATOM-14427
// We assume that the model has been padded to include colors even for the meshes which don't use them
// this could be reduced by dispatching the skinning shade
// for one mesh at a time instead of the entire lod at once ATOM-15078
// Add four floats for colors
perVertexSizeInBytes += 4 * sizeof(float);
}
AZStd::intrusive_ptr<SkinnedMeshOutputStreamAllocation> allocation = SkinnedMeshOutputStreamManagerInterface::Get()->Allocate(vertexCount * perVertexSizeInBytes);
if (!allocation)
{
// Suppress the OnMemoryFreed signal when releasing the previous successful allocations
// The memory was already free before this function was called, so it's not really newly available memory
AZ_Error("SkinnedMeshInputBuffers", false, "Out of memory to create a skinned mesh instance. Consider increasing r_skinnedMeshInstanceMemoryPoolSize");
instance->m_allocations.push_back(lodAllocations);
instance->SuppressSignalOnDeallocate();
return false;
}
else
{
// We're using an offset into a global buffer to be able to access the morph target offsets in a bindless manner.
// The offset can at most be a 32-bit uint until AZSL supports 64-bit uints. This gives us a 4GB limit for where the
// morph target deltas can live. In practice, the offsets could end up outside that range even if less that 4GB is used
// if the memory becomes fragmented. To address it, we can split morph target deltas into their own buffer, allocate
// memory in pages with a buffer for each page, or create and bind a buffer view
// so we are not doing an offset from the beginning of the buffer
AZ_Error("SkinnedMeshInputBuffers", allocation->GetVirtualAddress().m_ptr < static_cast<uintptr_t>(std::numeric_limits<uint32_t>::max()), "Morph target deltas allocated from the skinned mesh memory pool are outside the range that can be accessed from the skinning shader");
MorphTargetInstanceMetaData instanceMetaData;
// Positions start at the beginning of the allocation
instanceMetaData.m_accumulatedPositionDeltaOffsetInBytes = allocation->GetVirtualAddress().m_ptr;
uint32_t deltaStreamSizeInBytes = vertexCount * MorphTargetConstants::s_unpackedMorphTargetDeltaSizeInBytes;
// Followed by normals, tangents, and bitangents
instanceMetaData.m_accumulatedNormalDeltaOffsetInBytes = instanceMetaData.m_accumulatedPositionDeltaOffsetInBytes + deltaStreamSizeInBytes;
instanceMetaData.m_accumulatedTangentDeltaOffsetInBytes = instanceMetaData.m_accumulatedNormalDeltaOffsetInBytes + deltaStreamSizeInBytes;
instanceMetaData.m_accumulatedBitangentDeltaOffsetInBytes = instanceMetaData.m_accumulatedTangentDeltaOffsetInBytes + deltaStreamSizeInBytes;
// Followed by colors
if (lod.HasDynamicColors())
{
instanceMetaData.m_accumulatedColorDeltaOffsetInBytes = instanceMetaData.m_accumulatedBitangentDeltaOffsetInBytes + deltaStreamSizeInBytes;
}
else
{
instanceMetaData.m_accumulatedColorDeltaOffsetInBytes = MorphTargetConstants::s_invalidDeltaOffset;
}
// Track both the allocation and the metadata in the instance
instance->m_morphTargetInstanceMetaData.push_back(instanceMetaData);
lodAllocations.push_back(allocation);
}
}
else
{
// No morph targets for this lod
MorphTargetInstanceMetaData instanceMetaData{ MorphTargetConstants::s_invalidDeltaOffset, MorphTargetConstants::s_invalidDeltaOffset, MorphTargetConstants::s_invalidDeltaOffset, MorphTargetConstants::s_invalidDeltaOffset };
instance->m_morphTargetInstanceMetaData.push_back(instanceMetaData);
}
return true;
}
static void AddSubMeshViewToModelLodCreator(
uint8_t outputStreamIndex,
uint32_t lodVertexCount,
uint32_t submeshVertexCount,
Data::Asset<RPI::BufferAsset> skinnedMeshOutputBufferAsset,
const AZStd::vector<uint32_t>& streamOffsetsFromBufferStart,
AZStd::vector<size_t>& subMeshOffsetsFromStreamStart,
RPI::ModelLodAssetCreator& modelLodCreator)
{
const SkinnedMeshOutputVertexStreamInfo& outputStreamInfo = SkinnedMeshVertexStreamPropertyInterface::Get()->GetOutputStreamInfo(static_cast<SkinnedMeshOutputVertexStreams>(outputStreamIndex));
// For the purpose of the model, which is fed to the static mesh feature processor, these buffer views are only going to be used as input assembly.
// The underlying buffer is still writable and will be written to by the skinning shader.
RHI::BufferViewDescriptor viewDescriptor = CreateResourceViewWithDifferentFormat(static_cast<size_t>(streamOffsetsFromBufferStart[outputStreamIndex]) + static_cast<size_t>(subMeshOffsetsFromStreamStart[outputStreamIndex]), submeshVertexCount, outputStreamInfo.m_elementSize, outputStreamInfo.m_elementFormat, RHI::BufferBindFlags::InputAssembly);
AZ_Assert(streamOffsetsFromBufferStart[outputStreamIndex] % outputStreamInfo.m_elementSize == 0, "The SkinnedMeshOutputStreamManager is supposed to guarantee that offsets can always align.");
RPI::BufferAssetView bufferView{ skinnedMeshOutputBufferAsset, viewDescriptor };
modelLodCreator.AddMeshStreamBuffer(outputStreamInfo.m_semantic, AZ::Name(), bufferView);
if (static_cast<SkinnedMeshOutputVertexStreams>(outputStreamIndex) == SkinnedMeshOutputVertexStreams::Position)
{
// Add stream buffer for position history
size_t positionHistoryBufferOffsetInBytes = streamOffsetsFromBufferStart[outputStreamIndex] + subMeshOffsetsFromStreamStart[outputStreamIndex] + lodVertexCount * outputStreamInfo.m_elementSize;
viewDescriptor.m_elementOffset = aznumeric_cast<uint32_t>(positionHistoryBufferOffsetInBytes / outputStreamInfo.m_elementSize);
bufferView = { skinnedMeshOutputBufferAsset, viewDescriptor };
modelLodCreator.AddMeshStreamBuffer(RHI::ShaderSemantic{ Name{"POSITIONT"} }, AZ::Name(), bufferView);
}
subMeshOffsetsFromStreamStart[outputStreamIndex] += viewDescriptor.m_elementCount * viewDescriptor.m_elementSize;
}
AZStd::intrusive_ptr<SkinnedMeshInstance> SkinnedMeshInputBuffers::CreateSkinnedMeshInstance() const
{
// This function creates a SkinnedMeshInstance which describes all the buffer views needed to write the output of the skinned mesh compute shader
// and a model which can be rendered by the MeshFeatureProcessor
// Static data that doesn't get modified during skinning (e.g. index buffer, uvs) is shared between all instances that use the same SkinnedMeshInputBuffers
// The buffers for this static data and the per sub-mesh views into these buffers were created when the SkinnedMeshInputBuffers was created.
// This function adds those views to the model when creating it
// For the output of the skinned mesh shader, each instance has unique vertex data that exists in a single buffer managed by the SkinnedMeshOutputStreamManager
// For a given stream all of the vertices for an entire lod is contiguous in memory, allowing the entire lod to be skinned at once in as part of a single dispatch
// The streams are de-interleaved, and each stream may reside independently within the output buffer as determined by the best fit allocator
// E.g. the positions may or may not be adjacent to normals, but all of the positions for a single lod with be contiguous
// To support multiple sub-meshes, views into each stream for each lod are created for the sub-meshes
// SkinnedMeshOutputBuffer[.....................................................................................................................................]
// lod0 Positions[^ ^] lod0Normals[^ ^] lod1Positions[^ ^] lod1Normals[^ ^]
// lod0 subMesh0+1 Positions[^ ^^ ^] lod0 subMesh0+1 Normals[^ ^^ ^] lod1 sm0+1 pos[^ ^^ ^] lod1 sm0+1 norm[^ ^^ ^]
AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender);
AZStd::intrusive_ptr<SkinnedMeshInstance> instance = aznew SkinnedMeshInstance;
// Each model gets a unique, random ID, so if the same source model is used for multiple instances, multiple target models will be created.
RPI::ModelAssetCreator modelCreator;
modelCreator.Begin(Uuid::CreateRandom());
// Using the filename as a name for the model
AZStd::string assetPath;
Data::AssetCatalogRequestBus::BroadcastResult(assetPath, &Data::AssetCatalogRequests::GetAssetPathById, m_assetId);
AZStd::string fullFileName;
AzFramework::StringFunc::Path::GetFullFileName(assetPath.c_str(), fullFileName);
modelCreator.SetName(fullFileName + "_SkinnedMeshOutput");
Data::Asset<RPI::BufferAsset> skinnedMeshOutputBufferAsset = SkinnedMeshOutputStreamManagerInterface::Get()->GetBufferAsset();
size_t lodIndex = 0;
for (const SkinnedMeshInputLod& lod : m_lods)
{
RPI::ModelLodAssetCreator modelLodCreator;
modelLodCreator.Begin(Data::AssetId(Uuid::CreateRandom()));
//
// Lod
//
// Add a reference to the shared index buffer
modelLodCreator.AddLodStreamBuffer(lod.m_indexBufferAsset);
// There is only one underlying buffer that houses all of the skinned mesh output streams for all skinned mesh instances
modelLodCreator.AddLodStreamBuffer(skinnedMeshOutputBufferAsset);
// Add references to the shared static buffers
// Only uv0 for now
modelLodCreator.AddLodStreamBuffer(lod.m_staticBufferAssets[static_cast<uint8_t>(SkinnedMeshStaticVertexStreams::UV_0)]);
// Track offsets for each stream, so that the sub-meshes know where to begin
AZStd::vector<uint32_t> streamOffsetsFromBufferStart;
AZStd::vector<AZStd::intrusive_ptr<SkinnedMeshOutputStreamAllocation>> lodAllocations;
// The skinning shader doesn't differentiate between sub-meshes, it just writes all the vertices at once.
// So we want to pack all the positions for each sub-mesh together, all the normals together, etc.
for (uint8_t outputStreamIndex = 0; outputStreamIndex < static_cast<uint8_t>(SkinnedMeshOutputVertexStreams::NumVertexStreams); ++outputStreamIndex)
{
// Skip colors if they don't exist or are not being morphed
if (outputStreamIndex == static_cast<uint8_t>(SkinnedMeshOutputVertexStreams::Color) && !lod.m_hasDynamicColors)
{
continue;
}
if (!AllocateLodStream(outputStreamIndex, aznumeric_cast<size_t>(lod.m_vertexCount), instance, streamOffsetsFromBufferStart, lodAllocations))
{
return nullptr;
}
}
if (!AllocateMorphTargetsForLod(lod, lod.m_vertexCount, instance, lodAllocations))
{
return nullptr;
}
instance->m_outputStreamOffsetsInBytes.push_back(streamOffsetsFromBufferStart);
instance->m_allocations.push_back(lodAllocations);
//
// Submesh
//
AZStd::vector<size_t> subMeshOffsetsFromStreamStart(streamOffsetsFromBufferStart.size(), 0);
// Iterate over each sub-mesh for the lod to create views into the buffers
AZ_Assert(lod.m_subMeshProperties.size() == lod.m_sharedSubMeshViews.size(), "Skinned sub-mesh property and view vectors are mis-matched in size.")
for (size_t i = 0; i < lod.m_subMeshProperties.size(); ++i)
{
modelLodCreator.BeginMesh();
// Set the index buffer view
modelLodCreator.SetMeshIndexBuffer(lod.m_sharedSubMeshViews[i].m_indexBufferView);
// Create and set the views into the skinning output buffers
for (uint8_t outputStreamIndex = 0; outputStreamIndex < static_cast<uint8_t>(SkinnedMeshOutputVertexStreams::NumVertexStreams); ++outputStreamIndex)
{
// Skip colors if they don't exist or are not being morphed
if (outputStreamIndex == static_cast<uint8_t>(SkinnedMeshOutputVertexStreams::Color) && !lod.m_hasDynamicColors)
{
continue;
}
AddSubMeshViewToModelLodCreator(outputStreamIndex, lod.m_vertexCount, lod.m_subMeshProperties[i].m_vertexCount, skinnedMeshOutputBufferAsset, streamOffsetsFromBufferStart, subMeshOffsetsFromStreamStart, modelLodCreator);
}
// Set the views into the static buffers
for (uint8_t staticStreamIndex = 0; staticStreamIndex < static_cast<uint8_t>(SkinnedMeshStaticVertexStreams::NumVertexStreams); ++staticStreamIndex)
{
// Skip colors if they don't exist or are dynamic
if (!lod.m_sharedSubMeshViews[i].m_staticStreamViews[staticStreamIndex]
|| (staticStreamIndex == static_cast<uint8_t>(SkinnedMeshStaticVertexStreams::Color) && !lod.m_hasStaticColors))
{
continue;
}
const SkinnedMeshVertexStreamInfo& staticStreamInfo = SkinnedMeshVertexStreamPropertyInterface::Get()->GetStaticStreamInfo(static_cast<SkinnedMeshStaticVertexStreams>(staticStreamIndex));
modelLodCreator.AddMeshStreamBuffer(staticStreamInfo.m_semantic, AZ::Name(), *lod.m_sharedSubMeshViews[i].m_staticStreamViews[staticStreamIndex]);
}
Aabb localAabb = lod.m_subMeshProperties[i].m_aabb;
modelLodCreator.SetMeshAabb(AZStd::move(localAabb));
modelCreator.AddMaterialSlot(lod.m_subMeshProperties[i].m_materialSlot);
modelLodCreator.SetMeshMaterialSlot(lod.m_subMeshProperties[i].m_materialSlot.m_stableId);
modelLodCreator.EndMesh();
}
Data::Asset<RPI::ModelLodAsset> lodAsset;
modelLodCreator.End(lodAsset);
if (!lodAsset.IsReady())
{
// [GFX TODO] During mesh reload the modelLodCreator could report errors and result in the lodAsset not ready.
return nullptr;
}
modelCreator.AddLodAsset(AZStd::move(lodAsset));
lodIndex++;
}
Data::Asset<RPI::ModelAsset> modelAsset;
modelCreator.End(modelAsset);
instance->m_model = RPI::Model::FindOrCreate(modelAsset);
return instance;
}
void SkinnedMeshInputBuffers::WaitForUpload()
{
if (m_isUploadPending)
{
for (SkinnedMeshInputLod& lod : m_lods)
{
lod.WaitForUpload();
}
m_isUploadPending = false;
}
}
bool SkinnedMeshInputBuffers::IsUploadPending() const
{
return m_isUploadPending;
}
} // namespace Render
}// namespace AZ