Jobify the draw item list sort in View::FinalizeDrawLists (#6176)

Jobification of the sort saves 0.7ms/frame on the HighInstanceTest on my pc.
Make MeshFeatureProcessor update of the mesh cull bounds part of the job work rather than part of the simulate work. This saves 0.1ms/frame on the HighInstanceTest

Signed-off-by: rgba16f <82187279+rgba16f@users.noreply.github.com>
This commit is contained in:
rgba16f
2021-12-07 10:44:12 -06:00
committed by GitHub
parent 201777c1d1
commit aad16acf1f
13 changed files with 92 additions and 50 deletions
@@ -671,8 +671,6 @@ namespace AZ
AZ::u8 width,
int32_t viewProjOverrideIndex)
{
AZ_PROFILE_SCOPE(AzRender, "AuxGeomDrawQueue: DrawPrimitiveWithSharedVerticesCommon");
// grab a mutex lock for the rest of this function so that a commit cannot happen during it and
// other threads can't add geometry during it
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_buffersWriteLock);
@@ -743,8 +741,6 @@ namespace AZ
AZ::u8 width,
int32_t viewProjOverrideIndex)
{
AZ_PROFILE_SCOPE(AzRender, "AuxGeomDrawQueue: DrawPrimitiveWithSharedVerticesCommon");
AZ_Assert(indexCount >= verticesPerPrimitiveType && (indexCount % verticesPerPrimitiveType == 0),
"Index count must be at least %d and must be a multiple of %d",
verticesPerPrimitiveType, verticesPerPrimitiveType);
@@ -77,8 +77,8 @@ namespace AZ
void MeshFeatureProcessor::Simulate(const FeatureProcessor::SimulatePacket& packet)
{
AZ_PROFILE_SCOPE(RPI, "MeshFeatureProcessor: Simulate");
AZ_UNUSED(packet);
AZ::Job* parentJob = packet.m_parentJob;
AZStd::concurrency_check_scope scopeCheck(m_meshDataChecker);
const auto iteratorRanges = m_modelData.GetParallelRanges();
@@ -87,6 +87,8 @@ namespace AZ
{
const auto jobLambda = [&]() -> void
{
AZ_PROFILE_SCOPE(AzRender, "MeshFeatureProcessor: Simulate: Job");
for (auto meshDataIter = iteratorRange.first; meshDataIter != iteratorRange.second; ++meshDataIter)
{
if (!meshDataIter->m_model)
@@ -114,24 +116,22 @@ namespace AZ
{
meshDataIter->BuildCullable();
}
if (meshDataIter->m_cullBoundsNeedsUpdate)
{
meshDataIter->UpdateCullBounds(m_transformService);
}
}
};
Job* executeGroupJob = aznew JobFunction<decltype(jobLambda)>(jobLambda, true, nullptr); // Auto-deletes
executeGroupJob->SetDependent(&jobCompletion);
executeGroupJob->Start();
parentJob->StartAsChild(executeGroupJob);
}
{
AZ_PROFILE_SCOPE(AzRender, "MeshFeatureProcessor: Simulate: WaitForChildren");
parentJob->WaitForChildren();
}
jobCompletion.StartAndWaitForCompletion();
m_forceRebuildDrawPackets = false;
// CullingSystem::RegisterOrUpdateCullable() is not threadsafe, so need to do those updates in a single thread
for (ModelDataInstance& modelDataInstance : m_modelData)
{
if (modelDataInstance.m_model && modelDataInstance.m_cullBoundsNeedsUpdate)
{
modelDataInstance.UpdateCullBounds(m_transformService);
}
}
}
void MeshFeatureProcessor::OnBeginPrepareRender()
@@ -1038,7 +1038,6 @@ namespace AZ
void ModelDataInstance::UpdateDrawPackets(bool forceUpdate /*= false*/)
{
AZ_PROFILE_SCOPE(AzRender, "ModelDataInstance:: UpdateDrawPackets");
for (auto& drawPacketList : m_drawPacketListsByLod)
{
for (auto& drawPacket : drawPacketList)
@@ -1053,7 +1052,6 @@ namespace AZ
void ModelDataInstance::BuildCullable()
{
AZ_PROFILE_SCOPE(AzRender, "ModelDataInstance: BuildCullable");
AZ_Assert(m_cullableNeedsRebuild, "This function only needs to be called if the cullable to be rebuilt");
AZ_Assert(m_model, "The model has not finished loading yet");
@@ -1130,7 +1128,6 @@ namespace AZ
void ModelDataInstance::UpdateCullBounds(const TransformServiceFeatureProcessor* transformService)
{
AZ_PROFILE_SCOPE(AzRender, "ModelDataInstance: UpdateCullBounds");
AZ_Assert(m_cullBoundsNeedsUpdate, "This function only needs to be called if the culling bounds need to be rebuilt");
AZ_Assert(m_model, "The model has not finished loading yet");
@@ -7,6 +7,7 @@
*/
#include <Atom/RHI/DrawListContext.h>
#include <AzCore/Debug/Profiler.h>
#include <AzCore/std/sort.h>
namespace AZ
@@ -86,6 +87,7 @@ namespace AZ
void DrawListContext::FinalizeLists()
{
AZ_PROFILE_SCOPE(RHI, "DrawListContext: FinalizeLists");
for (size_t i = 0; i < m_mergedListsByTag.size(); ++i)
{
if (m_drawListMask[i])
@@ -233,9 +233,10 @@ namespace AZ
for (ScopeProducer* scopeProducer : m_scopeProducers)
{
AZ_PROFILE_SCOPE(RHI, "FrameScheduler: PrepareProducers: Scope %s", scopeProducer->GetScopeId().GetCStr());
m_frameGraph->BeginScope(*scopeProducer->GetScope());
scopeProducer->SetupFrameGraphDependencies(*m_frameGraph);
// All scopes depend on the root scope.
if (scopeProducer->GetScopeId() != m_rootScopeId)
{
@@ -527,7 +528,10 @@ namespace AZ
parentJob->StartAsChild(AZ::CreateJobFunction(AZStd::move(jobLambda), true, nullptr));
}
parentJob->WaitForChildren();
{
AZ_PROFILE_SCOPE(RHI, "FrameScheduler: ExecuteGroupInternal: WaitForChildren");
parentJob->WaitForChildren();
}
}
m_frameGraphExecuter->EndGroup(groupIndex);
@@ -479,8 +479,7 @@ namespace AZ
return;
}
D3D12_RESOURCE_TRANSITION_BARRIER transition;
memset(&transition, 0, sizeof(D3D12_RESOURCE_TRANSITION_BARRIER)); // C4701 potentially unitialized local variable 'transition' used
D3D12_RESOURCE_TRANSITION_BARRIER transition = {0};
transition.pResource = image.GetMemoryView().GetMemory();
Scope& firstScope = static_cast<Scope&>(scopeAttachment->GetScope());
+2 -2
View File
@@ -94,13 +94,13 @@ namespace AZ
const bool Scope::IsStateSupportedByQueue(D3D12_RESOURCE_STATES state) const
{
const D3D12_RESOURCE_STATES VALID_COMPUTE_QUEUE_RESOURCE_STATES =
constexpr D3D12_RESOURCE_STATES VALID_COMPUTE_QUEUE_RESOURCE_STATES =
(D3D12_RESOURCE_STATE_UNORDERED_ACCESS |
D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE |
D3D12_RESOURCE_STATE_COPY_DEST |
D3D12_RESOURCE_STATE_COPY_SOURCE);
const D3D12_RESOURCE_STATES VALID_GRAPHICS_QUEUE_RESOURCE_STATES =
constexpr D3D12_RESOURCE_STATES VALID_GRAPHICS_QUEUE_RESOURCE_STATES =
(D3D12_RESOURCE_STATES)DX12_RESOURCE_STATE_VALID_API_MASK;
switch (GetHardwareQueueClass())
@@ -22,10 +22,12 @@
#include <AzCore/base.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Component/EntityId.h>
#include <AzCore/Jobs/JobCompletion.h>
namespace AZ
{
// forward declares
class Job;
namespace RPI
{
//! @class FeatureProcessor
@@ -51,6 +53,7 @@ namespace AZ
struct SimulatePacket
{
AZ::Job* m_parentJob = nullptr;
};
struct RenderPacket
@@ -35,7 +35,7 @@
//Enables more detailed profiling descriptions within the culling system, but adds some performance overhead.
//Enable this to more easily see which jobs are associated with which view.
//#define AZ_CULL_PROFILE_VERBOSE
//#define AZ_CULL_PROFILE_VERBOSE
namespace AZ
{
@@ -44,6 +44,7 @@ namespace AZ
AZ_CVAR(bool, r_CullInParallel, true, nullptr, ConsoleFunctorFlags::Null, "");
AZ_CVAR(uint32_t, r_CullWorkPerBatch, 500, nullptr, ConsoleFunctorFlags::Null, "");
#ifdef AZ_CULL_DEBUG_ENABLED
void DebugDrawWorldCoordinateAxes(AuxGeomDraw* auxGeom)
{
auxGeom->DrawCylinder(Vector3(.5, .0, .0), Vector3(1, 0, 0), 0.02f, 1.0f, Colors::Red, AuxGeomDraw::DrawStyle::Solid, AuxGeomDraw::DepthTest::Off);
@@ -199,6 +200,7 @@ namespace AZ
AZ_Assert(false, "invalid frustum, cannot draw");
}
}
#endif //AZ_CULL_DEBUG_ENABLED
CullingDebugContext::~CullingDebugContext()
{
@@ -283,7 +285,7 @@ namespace AZ
const Scene& scene,
View& view,
Frustum& frustum,
[[maybe_unused]]void* maskedOcclusionCulling)
[[maybe_unused]] void* maskedOcclusionCulling)
{
AZStd::shared_ptr<WorklistData> worklistData = AZStd::make_shared<WorklistData>();
worklistData->m_debugCtx = &debugCtx;
@@ -319,14 +321,16 @@ namespace AZ
for (const AzFramework::IVisibilityScene::NodeData& nodeData : worklist)
{
//If a node is entirely contained within the frustum, then we can skip the fine grained culling.
bool nodeIsContainedInFrustum = ShapeIntersection::Contains(worklistData->m_frustum, nodeData.m_bounds);
bool nodeIsContainedInFrustum =
!worklistData->m_debugCtx->m_enableFrustumCulling ||
ShapeIntersection::Contains(worklistData->m_frustum, nodeData.m_bounds);
#ifdef AZ_CULL_PROFILE_VERBOSE
AZ_PROFILE_SCOPE(RPI, "process node (view: %s, skip fine cull: %d",
m_view->GetName().GetCStr(), nodeIsContainedInFrustum ? 1 : 0);
AZ_PROFILE_SCOPE(RPI, "process node (view: %s, skip fine cull: %ds",
worklistData->m_view->GetName().GetCStr(), nodeIsContainedInFrustum ? "true" : "false");
#endif
if (nodeIsContainedInFrustum || !worklistData->m_debugCtx->m_enableFrustumCulling)
if (nodeIsContainedInFrustum)
{
//Add all objects within this node to the view, without any extra culling
for (AzFramework::VisibilityEntry* visibleEntry : nodeData.m_entries)
@@ -392,7 +396,7 @@ namespace AZ
}
}
}
#ifdef AZ_CULL_DEBUG_ENABLED
if (worklistData->m_debugCtx->m_debugDraw && (worklistData->m_view->GetName() == worklistData->m_debugCtx->m_currentViewSelectionName))
{
AZ_PROFILE_SCOPE(RPI, "debug draw culling");
@@ -444,8 +448,10 @@ namespace AZ
}
}
}
#endif
}
#ifdef AZ_CULL_DEBUG_ENABLED
if (worklistData->m_debugCtx->m_enableStats)
{
CullingDebugContext::CullStats& cullStats = worklistData->m_debugCtx->GetCullStatsForView(worklistData->m_view);
@@ -455,6 +461,7 @@ namespace AZ
cullStats.m_numVisibleCullables += numVisibleCullables;
++cullStats.m_numJobs;
}
#endif //AZ_CULL_DEBUG_ENABLED
}
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
@@ -467,6 +474,10 @@ namespace AZ
return MaskedOcclusionCulling::CullingResult::VISIBLE;
}
#ifdef AZ_CULL_PROFILE_VERBOSE
AZ_PROFILE_SCOPE(RPI, "TestOcclusionCulling");
#endif
if (visibleEntry->m_boundingVolume.Contains(worklistData->m_view->GetCameraTransform().GetTranslation()))
{
// camera is inside bounding volume
@@ -516,10 +527,15 @@ namespace AZ
}
#endif
void CullingScene::ProcessCullablesCommon(const Scene& scene, View& view, AZ::Frustum& frustum, [[maybe_unused]]void*& maskedOcclusionCulling)
void CullingScene::ProcessCullablesCommon(
const Scene& scene [[maybe_unused]],
View& view,
AZ::Frustum& frustum [[maybe_unused]],
void*& maskedOcclusionCulling [[maybe_unused]])
{
AZ_PROFILE_SCOPE(RPI, "CullingScene::ProcessCullablesCommon() - %s", view.GetName().GetCStr());
#ifdef AZ_CULL_DEBUG_ENABLED
if (m_debugCtx.m_freezeFrustums)
{
AZStd::lock_guard<AZStd::mutex> lock(m_debugCtx.m_frozenFrustumsMutex);
@@ -544,7 +560,7 @@ namespace AZ
CullingDebugContext::CullStats& cullStats = m_debugCtx.GetCullStatsForView(&view);
cullStats.m_cameraViewToWorld = view.GetViewToWorldMatrix();
}
#endif //AZ_CULL_DEBUG_ENABLED
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
// setup occlusion culling, if necessary
maskedOcclusionCulling = m_occlusionPlanes.empty() ? nullptr : view.GetMaskedOcclusionCulling();
@@ -806,6 +822,7 @@ namespace AZ
beginCullingDescriptor,
[&view]()
{
AZ_PROFILE_SCOPE(RPI, "CullingScene: BeginCullingTaskGraph");
view->BeginCulling();
});
}
@@ -823,6 +840,7 @@ namespace AZ
{
const auto cullingLambda = [&view]()
{
AZ_PROFILE_SCOPE(RPI, "CullingScene: BeginCullingJob");
view->BeginCulling();
};
@@ -844,7 +862,11 @@ namespace AZ
m_taskGraphActive = AZ::Interface<AZ::TaskGraphActiveInterface>::Get();
if (m_taskGraphActive && m_taskGraphActive->IsTaskGraphActive())
if(views.size() == 1) // avoid job overhead when only 1 job
{
views[0]->BeginCulling();
}
else if (m_taskGraphActive && m_taskGraphActive->IsTaskGraphActive())
{
BeginCullingTaskGraph(views);
}
@@ -853,6 +875,7 @@ namespace AZ
BeginCullingJobs(views);
}
#if AZ_CULL_DEBUG_ENABLED
AuxGeomDrawPtr auxGeom;
if (m_debugCtx.m_debugDraw)
{
@@ -885,6 +908,7 @@ namespace AZ
m_debugCtx.m_frozenFrustums.clear();
}
}
#endif
}
void CullingScene::EndCulling()
@@ -1288,6 +1288,7 @@ namespace AZ
void Pass::FrameBegin(FramePrepareParams params)
{
AZ_PROFILE_SCOPE(RPI, "Pass::FrameBegin() - %s", m_path.GetCStr());
AZ_RPI_BREAK_ON_TARGET_PASS;
if (!IsEnabled())
@@ -1310,7 +1311,10 @@ namespace AZ
// FrameBeginInternal needs to be the last function be called in FrameBegin because its implementation expects
// all the attachments are imported to database (for example, ImageAttachmentPreview)
FrameBeginInternal(params);
{
AZ_PROFILE_SCOPE(RPI, "Pass::FrameBeginInternal()");
FrameBeginInternal(params);
}
// readback attachment with output state
UpdateReadbackAttachment(params, false);
@@ -312,7 +312,6 @@ namespace AZ
Pass::FramePrepareParams params{ &frameGraphBuilder };
{
AZ_PROFILE_SCOPE(RPI, "Pass: FrameBegin");
m_rootPass->FrameBegin(params);
}
}
@@ -146,6 +146,7 @@ namespace AZ
void RasterPass::UpdateDrawList()
{
AZ_PROFILE_SCOPE(RPI, "RasterPass::UpdateDrawList");
// DrawLists from dynamic draw
AZStd::vector<RHI::DrawListView> drawLists = DynamicDrawInterface::Get()->GetDrawListsForPass(this);
+10 -10
View File
@@ -400,10 +400,11 @@ namespace AZ
for (FeatureProcessorPtr& fp : m_featureProcessors)
{
FeatureProcessor* featureProcessor = fp.get();
const auto jobLambda = [this, featureProcessor]()
const auto jobLambda = [this, featureProcessor](AZ::Job& owner)
{
featureProcessor->Simulate(m_simulatePacket);
FeatureProcessor::SimulatePacket jobPacket = m_simulatePacket;
jobPacket.m_parentJob = &owner;
featureProcessor->Simulate(jobPacket);
};
AZ::Job* simulationJob = AZ::CreateJobFunction(AZStd::move(jobLambda), true, nullptr); //auto-deletes
@@ -516,7 +517,7 @@ namespace AZ
collectDrawPacketsTG.Submit(&collectDrawPacketsTGEvent);
// Launch CullingSystem::ProcessCullables() jobs (will run concurrently with FeatureProcessor::Render() jobs if m_parallelOctreeTraversal)
bool parallelOctreeTraversal = m_cullingScene->GetDebugContext().m_parallelOctreeTraversal;
const bool parallelOctreeTraversal = m_cullingScene->GetDebugContext().m_parallelOctreeTraversal;
m_cullingScene->BeginCulling(m_renderPacket.m_views);
static const AZ::TaskDescriptor processCullablesDescriptor{"AZ::RPI::Scene::ProcessCullables", "Graphics"};
AZ::TaskGraphEvent processCullablesTGEvent;
@@ -576,6 +577,7 @@ namespace AZ
}
// Launch CullingSystem::ProcessCullables() jobs (will run concurrently with FeatureProcessor::Render() jobs)
const bool parallelOctreeTraversal = m_cullingScene->GetDebugContext().m_parallelOctreeTraversal;
m_cullingScene->BeginCulling(m_renderPacket.m_views);
for (ViewPtr& viewPtr : m_renderPacket.m_views)
{
@@ -584,7 +586,7 @@ namespace AZ
m_cullingScene->ProcessCullablesJobs(*this, *viewPtr, thisJob); // can't call directly because ProcessCullables needs a parent job
},
true, nullptr); //auto-deletes
if (m_cullingScene->GetDebugContext().m_parallelOctreeTraversal)
if (parallelOctreeTraversal)
{
processCullablesJob->SetDependent(collectDrawPacketsCompletion);
processCullablesJob->Start();
@@ -731,20 +733,19 @@ namespace AZ
// Add dynamic draw data for all the views
if (m_dynamicDrawSystem)
{
AZ_PROFILE_SCOPE(RPI, "DynamicDraw SubmitDrawData");
m_dynamicDrawSystem->SubmitDrawData(this, m_renderPacket.m_views);
}
}
{
AZ_PROFILE_BEGIN(RPI, "FinalizeDrawLists");
if (jobPolicy == RHI::JobPolicy::Serial)
AZ_PROFILE_SCOPE(RPI, "FinalizeDrawLists");
if (jobPolicy == RHI::JobPolicy::Serial ||
m_renderPacket.m_views.size() <= 1) // FinalizeDrawListsX both immediately wait for the job to complete, skip job if only 1 job would be generated
{
for (auto& view : m_renderPacket.m_views)
{
view->FinalizeDrawLists();
}
AZ_PROFILE_END(RPI);
}
else
{
@@ -756,7 +757,6 @@ namespace AZ
{
FinalizeDrawListsJobs();
}
AZ_PROFILE_END(RPI);
}
}
+14 -1
View File
@@ -19,6 +19,8 @@
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Math/MatrixUtils.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Jobs/JobCompletion.h>
#include <AzCore/Jobs/JobFunction.h>
#include <Atom_RPI_Traits_Platform.h>
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
@@ -260,15 +262,25 @@ namespace AZ
void View::SortFinalizedDrawLists()
{
AZ_PROFILE_SCOPE(RPI, "View: SortFinalizedDrawLists");
RHI::DrawListsByTag& drawListsByTag = m_drawListContext.GetMergedDrawListsByTag();
AZ::JobCompletion jobCompletion;
for (size_t idx = 0; idx < drawListsByTag.size(); ++idx)
{
if (drawListsByTag[idx].size() > 1)
{
SortDrawList(drawListsByTag[idx], RHI::DrawListTag(idx));
auto jobLambda = [this, &drawListsByTag, idx]()
{
AZ_PROFILE_SCOPE(RPI, "View: SortDrawList Job");
SortDrawList(drawListsByTag[idx], RHI::DrawListTag(idx));
};
Job* jobSortDrawList = aznew JobFunction<decltype(jobLambda)>(jobLambda, true, nullptr); // Auto-deletes
jobSortDrawList->SetDependent(&jobCompletion);
jobSortDrawList->Start();
}
}
jobCompletion.StartAndWaitForCompletion();
}
void View::SortDrawList(RHI::DrawList& drawList, RHI::DrawListTag tag)
@@ -433,6 +445,7 @@ namespace AZ
void View::BeginCulling()
{
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
AZ_PROFILE_SCOPE(RPI, "View: ClearMaskedOcclusionBuffer");
m_maskedOcclusionCulling->ClearBuffer();
#endif
}