/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include namespace AZ { namespace Render { Data::Asset CreateBufferAsset(const void* data, const RHI::BufferViewDescriptor& viewDescriptor, RHI::BufferBindFlags bindFlags, Data::Asset resourcePoolAsset, const char* bufferName) { const uint32_t bufferSize = viewDescriptor.m_elementCount * viewDescriptor.m_elementSize; Data::Asset 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(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 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 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 bufferAsset = CreateBufferAsset(data, viewDescriptor, RHI::BufferBindFlags::ShaderRead, SkinnedMeshVertexStreamPropertyInterface::Get()->GetInputStreamResourcePool(), bufferName); m_inputBufferAssets[static_cast(inputStream)] = bufferAsset; m_inputBuffers[static_cast(inputStream)] = RPI::Buffer::FindOrCreate(bufferAsset); } void SkinnedMeshInputLod::SetModelLodAsset(const Data::Asset& modelLodAsset) { m_modelLodAsset = modelLodAsset; } void SkinnedMeshInputLod::SetSkinningInputBufferAsset(const Data::Asset 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(inputStream)] = bufferAsset; Data::Instance buffer = RPI::Buffer::FindOrCreate(bufferAsset); m_inputBuffers[static_cast(inputStream)] = buffer; // Create a buffer view to use as input to the skinning shader AZ::RHI::Ptr 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(inputStream)] = bufferView; } void SkinnedMeshInputLod::SetStaticBufferAsset(const Data::Asset 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(staticStream)] = bufferAsset; m_staticBuffers[static_cast(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 bufferAsset = CreateBufferAsset(data, viewDescriptor, RHI::BufferBindFlags::InputAssembly, SkinnedMeshVertexStreamPropertyInterface::Get()->GetStaticStreamResourcePool(), bufferName); m_staticBufferAssets[static_cast(staticStream)] = bufferAsset; m_staticBuffers[static_cast(staticStream)] = RPI::Buffer::FindOrCreate(bufferAsset); } void SkinnedMeshInputLod::SetSubMeshProperties(const AZStd::vector& subMeshProperties) { m_subMeshProperties = subMeshProperties; CreateSharedSubMeshBufferViews(); } const AZStd::vector& SkinnedMeshInputLod::GetSubMeshProperties() const { return m_subMeshProperties; } const Data::Asset& SkinnedMeshInputLod::GetSkinningInputBufferAsset(SkinnedMeshInputVertexStreams stream) const { return m_inputBufferAssets[static_cast(stream)]; } void SkinnedMeshInputLod::WaitForUpload() { m_indexBuffer->WaitForUpload(); for (const Data::Instance& inputBuffer : m_inputBuffers) { if (inputBuffer) { inputBuffer->WaitForUpload(); } } for (const Data::Instance& staticBuffer : m_staticBuffers) { if (staticBuffer) { staticBuffer->WaitForUpload(); } } } void SkinnedMeshInputLod::CreateSharedSubMeshBufferViews() { AZStd::array_view 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(SkinnedMeshStaticVertexStreams::NumVertexStreams); ++staticStreamIndex) { // Skip colors if they don't exist or are dynamic if (staticStreamIndex == static_cast(SkinnedMeshStaticVertexStreams::Color) && !m_hasStaticColors) { continue; } const SkinnedMeshVertexStreamInfo& streamInfo = SkinnedMeshVertexStreamPropertyInterface::Get()->GetStaticStreamInfo(static_cast(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& 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& SkinnedMeshInputLod::GetMorphTargetMetaDatas() const { return m_morphTargetMetaDatas; } const AZStd::vector>& 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> SkinnedMeshInputBuffers::GetInputBufferViews(size_t lodIndex) const { return m_lods[lodIndex].m_bufferViews; } AZ::RHI::Ptr 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& perInstanceSRG) { // Get the SRG indices for each input stream for (uint8_t inputStream = 0; inputStream < static_cast(SkinnedMeshInputVertexStreams::NumVertexStreams); ++inputStream) { // Skip colors if they don't exist or are not being morphed if (inputStream == static_cast(SkinnedMeshInputVertexStreams::Color) && !m_lods[lodIndex].m_hasDynamicColors) { continue; } const SkinnedMeshVertexStreamInfo& streamInfo = SkinnedMeshVertexStreamPropertyInterface::Get()->GetInputStreamInfo(static_cast(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(RHI::GetFormatSize(format)); AZ_Assert(elementOffset <= std::numeric_limits().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(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 instance, AZStd::vector& streamOffsetsFromBufferStart, AZStd::vector>& lodAllocations) { const SkinnedMeshOutputVertexStreamInfo& outputStreamInfo = SkinnedMeshVertexStreamPropertyInterface::Get()->GetOutputStreamInfo(static_cast(outputStreamIndex)); // Positions use 2x the number of vertices to hold both the current frame and previous frame's data size_t positionMultiplier = static_cast(outputStreamIndex) == SkinnedMeshOutputVertexStreams::Position ? 2u : 1u; AZStd::intrusive_ptr allocation = SkinnedMeshOutputStreamManagerInterface::Get()->Allocate(vertexCount * static_cast(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(allocation->GetVirtualAddress().m_ptr)); return true; } static bool AllocateMorphTargetsForLod(const SkinnedMeshInputLod& lod, size_t vertexCount, AZStd::intrusive_ptr instance, AZStd::vector>& 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(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 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(std::numeric_limits::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 = static_cast(allocation->GetVirtualAddress().m_ptr); uint32_t deltaStreamSizeInBytes = static_cast(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 skinnedMeshOutputBufferAsset, const AZStd::vector& streamOffsetsFromBufferStart, AZStd::vector& subMeshOffsetsFromStreamStart, RPI::ModelLodAssetCreator& modelLodCreator) { const SkinnedMeshOutputVertexStreamInfo& outputStreamInfo = SkinnedMeshVertexStreamPropertyInterface::Get()->GetOutputStreamInfo(static_cast(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(streamOffsetsFromBufferStart[outputStreamIndex]) + static_cast(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(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(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 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 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 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(SkinnedMeshStaticVertexStreams::UV_0)]); // Track offsets for each stream, so that the sub-meshes know where to begin AZStd::vector streamOffsetsFromBufferStart; AZStd::vector> 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(SkinnedMeshOutputVertexStreams::NumVertexStreams); ++outputStreamIndex) { // Skip colors if they don't exist or are not being morphed if (outputStreamIndex == static_cast(SkinnedMeshOutputVertexStreams::Color) && !lod.m_hasDynamicColors) { continue; } if (!AllocateLodStream(outputStreamIndex, aznumeric_cast(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 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(SkinnedMeshOutputVertexStreams::NumVertexStreams); ++outputStreamIndex) { // Skip colors if they don't exist or are not being morphed if (outputStreamIndex == static_cast(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(SkinnedMeshStaticVertexStreams::NumVertexStreams); ++staticStreamIndex) { // Skip colors if they don't exist or are dynamic if (!lod.m_sharedSubMeshViews[i].m_staticStreamViews[staticStreamIndex] || (staticStreamIndex == static_cast(SkinnedMeshStaticVertexStreams::Color) && !lod.m_hasStaticColors)) { continue; } const SkinnedMeshVertexStreamInfo& staticStreamInfo = SkinnedMeshVertexStreamPropertyInterface::Get()->GetStaticStreamInfo(static_cast(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 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 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