ATOM-15859 AuxGeom rendering in editor is too expensive (#1582)

* ATOM-15859 AuxGeom rendering in editor is too expensive
- The OrphanBuffer calls is the main reason that AuxGeom FP render is slow.
- Switched to use DynamicBuffer for buffers used in DynamicPrimitiveProcessor
- Added some profiling marks.
- Removed DynamicPrimitiveProcessor per view which was added because of OrphanBuffer can only be called once per frame.
This commit is contained in:
Qing Tao
2021-06-25 11:45:07 -07:00
committed by GitHub
parent 185dfeb410
commit 9ccb65aac4
11 changed files with 63 additions and 204 deletions
@@ -61,16 +61,8 @@ namespace AZ
//! Cache a pointer to the AuxGeom draw queue for our scene
RPI::AuxGeomDrawPtr m_sceneDrawQueue = nullptr;
//! Map used to store the AuxGeomDrawQueue & DynamicPrimitiveProcessor for each view
// [GFX TODO][ATOM-4435] remove DynamicPrimitiveProcessor per view if we can get orphan buffers to support multiple
// orphanings per frame.
// Only the DPP suffers from the issue so no need for a per view FixedShapeProcessor.
struct ViewDrawData
{
RPI::AuxGeomDrawPtr m_drawQueue;
AZStd::unique_ptr<DynamicPrimitiveProcessor> m_dynPrimProc;
};
AZStd::map<const RPI::View*, ViewDrawData> m_viewDrawDataMap; // using View* as key to not hold a reference to the view
//! Map used to store the AuxGeomDrawQueue for each view
AZStd::map<const RPI::View*, RPI::AuxGeomDrawPtr> m_viewDrawDataMap; // using View* as key to not hold a reference to the view
//! The object that handles the dynamic primitive geometry data
AZStd::unique_ptr<DynamicPrimitiveProcessor> m_dynamicPrimitiveProcessor;
@@ -564,6 +564,7 @@ namespace AZ
AuxGeomBufferData* AuxGeomDrawQueue::Commit()
{
AZ_ATOM_PROFILE_FUNCTION("AuxGeom", "AuxGeomDrawQueue: Commit");
// get a mutually exclusive lock and then switch to the next buffer, returning a pointer to the current buffer (before the switch)
// grab the lock
@@ -583,6 +584,7 @@ namespace AZ
void AuxGeomDrawQueue::ClearCurrentBufferData()
{
AZ_ATOM_PROFILE_FUNCTION("AuxGeom", "AuxGeomDrawQueue: ClearCurrentBufferData");
// no need for mutex here, this function is only called from a function holding a lock
AuxGeomBufferData& data = m_buffers[m_currentBufferIndex];
@@ -44,7 +44,7 @@ namespace AZ
// initialize the dynamic primitive processor
m_dynamicPrimitiveProcessor = AZStd::make_unique<DynamicPrimitiveProcessor>();
if (!m_dynamicPrimitiveProcessor->Initialize(*rhiSystem->GetDevice(), scene))
if (!m_dynamicPrimitiveProcessor->Initialize(scene))
{
AZ_Error(s_featureProcessorName, false, "Failed to init AuxGeom DynamicPrimitiveProcessor");
return;
@@ -65,11 +65,6 @@ namespace AZ
{
DisableSceneNotification();
// release the per view data
for (auto& viewDD: m_viewDrawDataMap)
{
viewDD.second.m_dynPrimProc->Release();
}
m_viewDrawDataMap.clear();
m_dynamicPrimitiveProcessor->Release();
@@ -84,7 +79,7 @@ namespace AZ
void AuxGeomFeatureProcessor::Render(const FeatureProcessor::RenderPacket& fpPacket)
{
AZ_ATOM_PROFILE_FUNCTION("RPI", "AuxGeomFeatureProcessor: Render");
AZ_ATOM_PROFILE_FUNCTION("AuxGeom", "AuxGeomFeatureProcessor: Render");
// Get the scene data and switch buffers so that other threads can continue to queue requests
AuxGeomBufferData* bufferData = static_cast<AuxGeomDrawQueue*>(m_sceneDrawQueue.get())->Commit();
@@ -106,12 +101,11 @@ namespace AZ
auto it = m_viewDrawDataMap.find(view.get());
if (it != m_viewDrawDataMap.end())
{
bufferData = static_cast<AuxGeomDrawQueue*>(it->second.m_drawQueue.get())->Commit();
bufferData = static_cast<AuxGeomDrawQueue*>(it->second.get())->Commit();
perViewRP.m_views.push_back(view);
// Process the dynamic primitives
it->second.m_dynPrimProc->PrepareFrame();
it->second.m_dynPrimProc->ProcessDynamicPrimitives(bufferData, perViewRP);
m_dynamicPrimitiveProcessor->ProcessDynamicPrimitives(bufferData, perViewRP);
// Process the objects (draw requests using fixed shape buffers)
m_fixedShapeProcessor->ProcessObjects(bufferData, perViewRP);
@@ -129,7 +123,7 @@ namespace AZ
auto drawDataIterator = m_viewDrawDataMap.find(view);
if (drawDataIterator != m_viewDrawDataMap.end())
{
return drawDataIterator->second.m_drawQueue;
return drawDataIterator->second;
}
}
AZ_Warning("AuxGeomFeatureProcessor", false, "Draw Queue requested for unknown view");
@@ -146,23 +140,12 @@ namespace AZ
if (drawQueueIterator == m_viewDrawDataMap.end())
{
AZ::RPI::Scene* scene = GetParentScene();
RHI::RHISystemInterface* rhiSystem = RHI::RHISystemInterface::Get();
// initialize the dynamic primitive processor
ViewDrawData viewDD;
viewDD.m_dynPrimProc = AZStd::make_unique<DynamicPrimitiveProcessor>();
if (!viewDD.m_dynPrimProc->Initialize(*rhiSystem->GetDevice(), scene))
{
AZ_Error(s_featureProcessorName, false, "Failed to init AuxGeom DynamicPrimitiveProcessor for view (%s)", view->GetName().GetCStr());
return RPI::AuxGeomDrawPtr();
}
viewDD.m_drawQueue = RPI::AuxGeomDrawPtr(aznew AuxGeomDrawQueue());
m_viewDrawDataMap.emplace(view, AZStd::move(viewDD));
return m_viewDrawDataMap[view].m_drawQueue;
RPI::AuxGeomDrawPtr drawQueue = RPI::AuxGeomDrawPtr(aznew AuxGeomDrawQueue());
m_viewDrawDataMap.emplace(view, AZStd::move(drawQueue));
return m_viewDrawDataMap[view];
}
return drawQueueIterator->second.m_drawQueue;
return drawQueueIterator->second;
}
void AuxGeomFeatureProcessor::ReleaseDrawQueueForView(const RPI::View* view)
@@ -173,12 +156,6 @@ namespace AZ
void AuxGeomFeatureProcessor::OnSceneRenderPipelinesChanged()
{
m_dynamicPrimitiveProcessor->SetUpdatePipelineStates();
for (auto& viewDrawData : m_viewDrawDataMap)
{
viewDrawData.second.m_dynPrimProc->SetUpdatePipelineStates();
}
m_fixedShapeProcessor->SetUpdatePipelineStates();
}
@@ -8,11 +8,13 @@
#include "DynamicPrimitiveProcessor.h"
#include "AuxGeomDrawProcessorShared.h"
#include <Atom/RHI/Factory.h>
#include <Atom/RHI/CpuProfiler.h>
#include <Atom/RHI/DrawPacketBuilder.h>
#include <Atom/RHI/Factory.h>
#include <Atom/RHI.Reflect/InputStreamLayoutBuilder.h>
#include <Atom/RPI.Reflect/Shader/ShaderAsset.h>
#include <Atom/RPI.Public/DynamicDraw/DynamicDrawInterface.h>
#include <Atom/RPI.Public/RPIUtils.h>
#include <Atom/RPI.Public/Scene.h>
#include <Atom/RPI.Public/Shader/Shader.h>
@@ -35,35 +37,16 @@ namespace AZ
};
}
bool DynamicPrimitiveProcessor::Initialize(AZ::RHI::Device& rhiDevice, const AZ::RPI::Scene* scene)
bool DynamicPrimitiveProcessor::Initialize(const AZ::RPI::Scene* scene)
{
// Note: We use HeapMemoryLevel::Host here so that we can use OrphanBuffer in the update
RHI::BufferPoolDescriptor dynamicPoolDescriptor;
dynamicPoolDescriptor.m_heapMemoryLevel = RHI::HeapMemoryLevel::Host;
dynamicPoolDescriptor.m_bindFlags = RHI::BufferBindFlags::InputAssembly;
dynamicPoolDescriptor.m_largestPooledAllocationSizeInBytes = MaxUploadBufferSize;
m_hostPool = RHI::Factory::Get().CreateBufferPool();
m_hostPool->SetName(Name("AuxGeomDynamicPrimitiveBufferPool"));
RHI::ResultCode resultCode = m_hostPool->Init(rhiDevice, dynamicPoolDescriptor);
if (resultCode != RHI::ResultCode::Success)
{
AZ_Error("DynamicPrimitiveProcessor", false, "Failed to initialize AuxGeom dynamic primitive buffer pool");
return false;
}
for (int primitiveType = 0; primitiveType < PrimitiveType_Count; ++primitiveType)
{
SetupInputStreamLayout(m_inputStreamLayout[primitiveType], PrimitiveTypeToTopology[primitiveType]);
m_streamBufferViewsValidatedForLayout[primitiveType] = false;
}
if (!CreateBuffers())
{
return false;
}
// We have a single stream (position and color are interleaved in the vertex buffer)
m_primitiveBuffers.m_streamBufferViews.resize(1);
m_scene = scene;
InitShader();
@@ -73,13 +56,6 @@ namespace AZ
void DynamicPrimitiveProcessor::Release()
{
DestroyBuffers();
if (m_hostPool)
{
m_hostPool.reset();
}
m_drawPackets.clear();
m_processSrgs.clear();
m_shaderData.m_defaultSRG = nullptr;
@@ -96,6 +72,7 @@ namespace AZ
void DynamicPrimitiveProcessor::PrepareFrame()
{
AZ_ATOM_PROFILE_FUNCTION("AuxGeom", "DynamicPrimitiveProcessor: PrepareFrame");
m_drawPackets.clear();
m_processSrgs.clear();
@@ -113,6 +90,7 @@ namespace AZ
void DynamicPrimitiveProcessor::ProcessDynamicPrimitives(const AuxGeomBufferData* bufferData, const RPI::FeatureProcessor::RenderPacket& fpPacket)
{
AZ_ATOM_PROFILE_FUNCTION("AuxGeom", "DynamicPrimitiveProcessor: ProcessDynamicPrimitives");
RHI::DrawPacketBuilder drawPacketBuilder;
const DynamicPrimitiveData& srcPrimitives = bufferData->m_primitiveData;
@@ -121,8 +99,13 @@ namespace AZ
{
// Update the buffers for all dynamic primitives in this frame's data
// There is just one index buffer and one vertex buffer for all dynamic primitives
UpdateIndexBuffer(srcPrimitives.m_indexBuffer, m_primitiveBuffers);
UpdateVertexBuffer(srcPrimitives.m_vertexBuffer, m_primitiveBuffers);
if (!UpdateIndexBuffer(srcPrimitives.m_indexBuffer, m_primitiveBuffers)
|| !UpdateVertexBuffer(srcPrimitives.m_vertexBuffer, m_primitiveBuffers))
{
// Skip adding render data if failed to update buffers
// Note, the error would be already reported inside the Update* functions
return;
}
// Validate the stream buffer views for all stream layout's if necessary
for (int primitiveType = 0; primitiveType < PrimitiveType_Count; ++primitiveType)
@@ -208,108 +191,34 @@ namespace AZ
}
}
bool DynamicPrimitiveProcessor::CreateBuffers()
{
if (!CreateBufferGroup(m_primitiveBuffers))
{
return false;
}
return true;
}
void DynamicPrimitiveProcessor::DestroyBuffers()
{
DestroyBufferGroup(m_primitiveBuffers);
}
bool DynamicPrimitiveProcessor::CreateBufferGroup(DynamicBufferGroup& group)
{
RHI::ResultCode result = RHI::ResultCode::Fail;
group.m_indexBuffer = RHI::Factory::Get().CreateBuffer();
group.m_vertexBuffer = RHI::Factory::Get().CreateBuffer();
group.m_indexBuffer->SetName(AZ::Name("AuxGeomIndexBuffer"));
group.m_vertexBuffer->SetName(AZ::Name("AuxGeomVertexBuffer"));
AZStd::vector<RHI::Ptr<RHI::Buffer>> buffers = { group.m_indexBuffer , group.m_vertexBuffer };
RHI::BufferInitRequest bufferRequest;
bufferRequest.m_descriptor = RHI::BufferDescriptor{ RHI::BufferBindFlags::InputAssembly, MaxUploadBufferSize };
for (const RHI::Ptr<RHI::Buffer>& buffer : buffers)
{
bufferRequest.m_buffer = buffer.get();
result = m_hostPool->InitBuffer(bufferRequest);
if (result != RHI::ResultCode::Success)
{
AZ_Error("DynamicPrimitiveProcessor", false, "Failed to create GPU buffers for AuxGeom");
return false;
}
}
// We have a single stream (position and color are interleaved in the vertex buffer)
group.m_streamBufferViews.resize(1);
return true;
}
void DynamicPrimitiveProcessor::DestroyBufferGroup(DynamicBufferGroup& group)
{
group.m_indexBuffer.reset();
group.m_vertexBuffer.reset();
group.m_streamBufferViews.clear();
}
void DynamicPrimitiveProcessor::UpdateBuffer(const uint8_t* source, size_t sourceSize, RHI::Ptr<RHI::Buffer> buffer)
{
// This should never happen because of tests in AuxGeomDrawQueue in the functions that increase the source size.
AZ_Assert(sourceSize <= MaxUploadBufferSize, "Max upload buffer size exceeded");
// We use OrphanBuffer currently. If we have issues we may need to add fences or use FrameCountMax buffers
// in a round-robin system.
RHI::ResultCode orphanResult = m_hostPool->OrphanBuffer(*buffer);
AZ_Assert(orphanResult == RHI::ResultCode::Success, "OrphanBuffer failed");
if (orphanResult == RHI::ResultCode::Success)
{
RHI::BufferMapResponse mapResponse;
m_hostPool->MapBuffer(RHI::BufferMapRequest(*buffer, 0, sourceSize), mapResponse);
auto* mappedData = reinterpret_cast<uint8_t*>(mapResponse.m_data);
if (mappedData)
{
memcpy(mappedData, source, sourceSize);
m_hostPool->UnmapBuffer(*buffer);
}
}
}
void DynamicPrimitiveProcessor::UpdateIndexBuffer(const IndexBuffer& source, DynamicBufferGroup& group)
bool DynamicPrimitiveProcessor::UpdateIndexBuffer(const IndexBuffer& source, DynamicBufferGroup& group)
{
const size_t sourceByteSize = source.size() * sizeof(AuxGeomIndex);
auto* sourceBytes = reinterpret_cast<const uint8_t*>(source.data());
UpdateBuffer(sourceBytes, sourceByteSize, group.m_indexBuffer);
group.m_indexBufferView = RHI::IndexBufferView(
*group.m_indexBuffer, 0, static_cast<uint32_t>(sourceByteSize), RHI::IndexFormat::Uint32);
RHI::Ptr<RPI::DynamicBuffer> dynamicBuffer = RPI::DynamicDrawInterface::Get()->GetDynamicBuffer(sourceByteSize);
if (!dynamicBuffer)
{
AZ_WarningOnce("AuxGeom", false, "Failed to allocate dynamic buffer of size %d.", sourceByteSize);
return false;
}
dynamicBuffer->Write(source.data(), sourceByteSize);
group.m_indexBufferView = dynamicBuffer->GetIndexBufferView(RHI::IndexFormat::Uint32);
return true;
}
void DynamicPrimitiveProcessor::UpdateVertexBuffer(const VertexBuffer& source, DynamicBufferGroup& group)
bool DynamicPrimitiveProcessor::UpdateVertexBuffer(const VertexBuffer& source, DynamicBufferGroup& group)
{
const size_t sourceByteSize = source.size() * sizeof(AuxGeomDynamicVertex);
auto* sourceBytes = reinterpret_cast<const uint8_t*>(source.data());
UpdateBuffer(sourceBytes, sourceByteSize, group.m_vertexBuffer);
group.m_streamBufferViews[0] = RHI::StreamBufferView(
*group.m_vertexBuffer, 0, static_cast<uint32_t>(sourceByteSize), static_cast<uint32_t>(sizeof(AuxGeomDynamicVertex)));
RHI::Ptr<RPI::DynamicBuffer> dynamicBuffer = RPI::DynamicDrawInterface::Get()->GetDynamicBuffer(sourceByteSize);
if (!dynamicBuffer)
{
AZ_WarningOnce("AuxGeom", false, "Failed to allocate dynamic buffer of size %d.", sourceByteSize);
return false;
}
dynamicBuffer->Write(source.data(), sourceByteSize);
group.m_streamBufferViews[0] = dynamicBuffer->GetStreamBufferView(sizeof(AuxGeomDynamicVertex));
return true;
}
void DynamicPrimitiveProcessor::ValidateStreamBufferViews(StreamBufferViewsForAllStreams& streamBufferViews, bool* isValidated, int primitiveType)
@@ -58,7 +58,7 @@ namespace AZ
~DynamicPrimitiveProcessor() = default;
//! Initialize the DynamicPrimitiveProcessor and all its buffers, shaders, stream layouts etc
bool Initialize(AZ::RHI::Device& rhiDevice, const AZ::RPI::Scene* scene);
bool Initialize(const AZ::RPI::Scene* scene);
//! Releases the DynamicPrimitiveProcessor and all primitive geometry buffers
void Release();
@@ -78,12 +78,6 @@ namespace AZ
struct DynamicBufferGroup
{
//! The index buffer for this set of primitives
AZ::RHI::Ptr<AZ::RHI::Buffer> m_indexBuffer;
//! The vertices for this set of primitives
AZ::RHI::Ptr<AZ::RHI::Buffer> m_vertexBuffer;
//! The view into the index buffer
AZ::RHI::IndexBufferView m_indexBufferView;
@@ -125,26 +119,11 @@ namespace AZ
RHI::DrawPacketBuilder& drawPacketBuilder,
RHI::DrawItemSortKey sortKey = 0);
// Creates the dynamic buffers
bool CreateBuffers();
// Destroy all the buffers
void DestroyBuffers();
// Creates the dynamic buffers in a group
bool CreateBufferGroup(DynamicBufferGroup& group);
// Destroy all the buffers in a group
void DestroyBufferGroup(DynamicBufferGroup& group);
// Helper function to update a buffer
void UpdateBuffer(const uint8_t* source, size_t sourceSize, RHI::Ptr<RHI::Buffer> buffer);
// Update a dynamic index buffer, given the data from draw requests
void UpdateIndexBuffer(const IndexBuffer& indexSource, DynamicBufferGroup& group);
bool UpdateIndexBuffer(const IndexBuffer& indexSource, DynamicBufferGroup& group);
// Update a dynamic vertex buffer, given the data from draw requests
void UpdateVertexBuffer(const VertexBuffer& source, DynamicBufferGroup& group);
bool UpdateVertexBuffer(const VertexBuffer& source, DynamicBufferGroup& group);
// Validate the given stream buffer views for the layout used for the given prim type (uses isValidated flags to see if necessary)
void ValidateStreamBufferViews(StreamBufferViewsForAllStreams& streamBufferViews, bool* isValidated, int primitiveType);
@@ -170,9 +149,6 @@ namespace AZ
ShaderData m_shaderData;
// The buffer pool that manages all our dynamic index and vertex buffers
RHI::Ptr<AZ::RHI::BufferPool> m_hostPool;
// Buffers for all primitives
DynamicBufferGroup m_primitiveBuffers;
@@ -107,7 +107,8 @@ namespace AZ
}
void FixedShapeProcessor::PrepareFrame()
{
{
AZ_ATOM_PROFILE_FUNCTION("AuxGeom", "FixedShapeProcessor: PrepareFrame");
m_processSrgs.clear();
m_drawPackets.clear();
+3 -1
View File
@@ -6,6 +6,7 @@
*/
#include <Atom/RHI/BufferPool.h>
#include <Atom/RHI/CpuProfiler.h>
#include <Atom/RHI/MemoryStatisticsBuilder.h>
#include <AzCore/Debug/EventTrace.h>
@@ -160,7 +161,8 @@ namespace AZ
{
return ResultCode::InvalidArgument;
}
AZ_ATOM_PROFILE_FUNCTION("RHI", "BufferPool::OrphanBuffer");
return OrphanBufferInternal(buffer);
}
@@ -41,7 +41,7 @@ namespace AZ
public:
//! Write data to the DyanmicBuffer. The write size can't be larger than this buffer's size
bool Write(void* data, uint32_t size);
bool Write(const void* data, uint32_t size);
//! Get the buffer's size
uint32_t GetSize();
@@ -141,11 +141,11 @@ namespace AZ
//! Draw Indexed primitives with vertex and index data and per draw srg
//! The per draw srg need to be provided if it's required by shader.
void DrawIndexed(void* vertexData, uint32_t vertexCount, void* indexData, uint32_t indexCount, RHI::IndexFormat indexFormat, Data::Instance < ShaderResourceGroup> drawSrg = nullptr);
void DrawIndexed(const void* vertexData, uint32_t vertexCount, const void* indexData, uint32_t indexCount, RHI::IndexFormat indexFormat, Data::Instance < ShaderResourceGroup> drawSrg = nullptr);
//! Draw linear indexed primitives with vertex data and per draw srg
//! The per draw srg need to be provided if it's required by shader.
void DrawLinear(void* vertexData, uint32_t vertexCount, Data::Instance<ShaderResourceGroup> drawSrg);
void DrawLinear(const void* vertexData, uint32_t vertexCount, Data::Instance<ShaderResourceGroup> drawSrg);
//! Get per vertex size. The size was evaluated when vertex format was set
uint32_t GetPerVertexDataSize();
@@ -12,7 +12,7 @@ namespace AZ
{
namespace RPI
{
bool DynamicBuffer::Write(void* data, uint32_t size)
bool DynamicBuffer::Write(const void* data, uint32_t size)
{
if (m_size >= size)
{
@@ -394,7 +394,7 @@ namespace AZ
m_currentShaderVariantId = shaderVariantId;
}
void DynamicDrawContext::DrawIndexed(void* vertexData, uint32_t vertexCount, void* indexData, uint32_t indexCount, RHI::IndexFormat indexFormat, Data::Instance < ShaderResourceGroup> drawSrg)
void DynamicDrawContext::DrawIndexed(const void* vertexData, uint32_t vertexCount, const void* indexData, uint32_t indexCount, RHI::IndexFormat indexFormat, Data::Instance < ShaderResourceGroup> drawSrg)
{
if (!m_initialized)
{
@@ -487,7 +487,7 @@ namespace AZ
m_cachedDrawItems.emplace_back(drawItemInfo);
}
void DynamicDrawContext::DrawLinear(void* vertexData, uint32_t vertexCount, Data::Instance<ShaderResourceGroup> drawSrg)
void DynamicDrawContext::DrawLinear(const void* vertexData, uint32_t vertexCount, Data::Instance<ShaderResourceGroup> drawSrg)
{
if (!m_initialized)
{