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/SkinnedMeshOutputStreamMana...

200 lines
7.1 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 <SkinnedMesh/SkinnedMeshOutputStreamManager.h>
#include <AzCore/Console/IConsole.h>
#include <Atom/Feature/SkinnedMesh/SkinnedMeshVertexStreams.h>
#include <Atom/Feature/SkinnedMesh/SkinnedMeshFeatureProcessorBus.h>
#include <Atom/RPI.Reflect/Buffer/BufferAssetCreator.h>
#include <numeric>
namespace AZ
{
namespace Render
{
SkinnedMeshOutputStreamManager::SkinnedMeshOutputStreamManager()
{
Init();
}
SkinnedMeshOutputStreamManager::~SkinnedMeshOutputStreamManager()
{
m_bufferAsset = {};
}
void SkinnedMeshOutputStreamManager::CalculateAlignment()
{
m_alignment = 1;
for (uint8_t outputStreamIndex = 0; outputStreamIndex < static_cast<uint8_t>(SkinnedMeshOutputVertexStreams::NumVertexStreams); ++outputStreamIndex)
{
// Using the least common multiple enables resource views to be typed and ensures they can get an offset in bytes that is a multiple of an element count
const SkinnedMeshOutputVertexStreamInfo& outputStreamInfo = SkinnedMeshVertexStreamPropertyInterface::Get()->GetOutputStreamInfo(static_cast<SkinnedMeshOutputVertexStreams>(outputStreamIndex));
m_alignment = std::lcm(m_alignment, outputStreamInfo.m_elementSize);
}
}
void SkinnedMeshOutputStreamManager::CreateBufferAsset()
{
RHI::FreeListAllocator::Descriptor allocatorDescriptor;
allocatorDescriptor.m_alignmentInBytes = m_alignment;
allocatorDescriptor.m_capacityInBytes = m_sizeInBytes;
allocatorDescriptor.m_policy = RHI::FreeListAllocatorPolicy::BestFit;
allocatorDescriptor.m_garbageCollectLatency = 0;
m_freeListAllocator.Init(allocatorDescriptor);
// Create the actual buffer
RPI::BufferAssetCreator creator;
Uuid uuid = Uuid::CreateRandom();
creator.Begin(uuid);
creator.SetBufferName("SkinnedMeshOutputStream");
creator.SetPoolAsset(SkinnedMeshVertexStreamPropertyInterface::Get()->GetOutputStreamResourcePool());
RHI::BufferDescriptor bufferDescriptor;
bufferDescriptor.m_bindFlags = RHI::BufferBindFlags::InputAssembly | RHI::BufferBindFlags::ShaderReadWrite;
bufferDescriptor.m_byteCount = m_sizeInBytes;
bufferDescriptor.m_alignment = m_alignment;
creator.SetBuffer(nullptr, 0, bufferDescriptor);
RHI::BufferViewDescriptor viewDescriptor;
viewDescriptor.m_elementFormat = RHI::Format::Unknown;
viewDescriptor.m_elementSize = sizeof(float);
viewDescriptor.m_elementCount = aznumeric_cast<uint32_t>(m_sizeInBytes) / viewDescriptor.m_elementSize;
viewDescriptor.m_elementOffset = 0;
creator.SetBufferViewDescriptor(viewDescriptor);
creator.End(m_bufferAsset);
}
// default value of 256mb supports roughly 42 character instances at 100,000 vertices per character x 64 bytes per vertex (12 byte position + 12 byte previous frame position + 12 byte normal + 16 byte tangent + 12 byte bitangent)
// This includes only the output of the skinning compute shader, not the input buffers or bone transforms
AZ_CVAR(
int,
r_skinnedMeshInstanceMemoryPoolSize,
256,
nullptr,
AZ::ConsoleFunctorFlags::NeedsReload,
"The amount of memory in Mb available for all actor skinning data. Note that this must only be set once at application startup"
);
void SkinnedMeshOutputStreamManager::Init()
{
}
void SkinnedMeshOutputStreamManager::EnsureInit()
{
if (!m_needsInit)
{
return;
}
m_needsInit = false;
AZ::u64 sizeInMb{};
if (auto console = AZ::Interface<AZ::IConsole>::Get(); console != nullptr)
{
console->GetCvarValue("r_skinnedMeshInstanceMemoryPoolSize", sizeInMb);
}
m_sizeInBytes = sizeInMb * (1024u * 1024u);
CalculateAlignment();
CreateBufferAsset();
SystemTickBus::Handler::BusConnect();
}
AZStd::intrusive_ptr<SkinnedMeshOutputStreamAllocation> SkinnedMeshOutputStreamManager::Allocate(size_t byteCount)
{
RHI::VirtualAddress result;
{
AZStd::lock_guard<AZStd::mutex> lock(m_allocatorMutex);
EnsureInit();
result = m_freeListAllocator.Allocate(byteCount, m_alignment);
}
if (result.IsValid())
{
return aznew SkinnedMeshOutputStreamAllocation(result);
}
return nullptr;
}
void SkinnedMeshOutputStreamManager::DeAllocate(RHI::VirtualAddress allocation)
{
if (allocation.IsValid())
{
{
AZStd::lock_guard<AZStd::mutex> lock(m_allocatorMutex);
m_freeListAllocator.DeAllocate(allocation);
}
m_memoryWasFreed = true;
m_broadcastMemoryAvailableEvent = true;
}
}
void SkinnedMeshOutputStreamManager::DeAllocateNoSignal(RHI::VirtualAddress allocation)
{
if (allocation.IsValid())
{
{
AZStd::lock_guard<AZStd::mutex> lock(m_allocatorMutex);
m_freeListAllocator.DeAllocate(allocation);
}
m_memoryWasFreed = true;
}
}
Data::Asset<RPI::BufferAsset> SkinnedMeshOutputStreamManager::GetBufferAsset()
{
EnsureInit();
return m_bufferAsset;
}
Data::Instance<RPI::Buffer> SkinnedMeshOutputStreamManager::GetBuffer()
{
EnsureInit();
if (!m_buffer)
{
m_buffer = RPI::Buffer::FindOrCreate(m_bufferAsset);
}
return m_buffer;
}
void SkinnedMeshOutputStreamManager::OnSystemTick()
{
GarbageCollect();
}
void SkinnedMeshOutputStreamManager::GarbageCollect()
{
if (m_memoryWasFreed)
{
m_memoryWasFreed = false;
{
AZStd::lock_guard<AZStd::mutex> lock(m_allocatorMutex);
m_freeListAllocator.GarbageCollect();
}
if (m_broadcastMemoryAvailableEvent)
{
SkinnedMeshOutputStreamNotificationBus::Broadcast(&SkinnedMeshOutputStreamNotificationBus::Events::OnSkinnedMeshOutputStreamMemoryAvailable);
m_broadcastMemoryAvailableEvent = false;
}
}
}
}// namespace Render
}// namespace AZ