Part 2 of enabling Atom to use TaskGraph

Enable Atom CullingScene to use TaskGraph
Enable Atom PNG save to use TaskGraph for red/blue color channel swap on save out.

Signed-off-by: rgba16f <82187279+rgba16f@users.noreply.github.com>
monroegm-disable-blank-issue-2
rgba16f [Amazon] 4 years ago committed by GitHub
parent cf1d13fa43
commit 5c072a5d51
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -363,7 +363,11 @@ namespace AZ
{
++m_graphsRemaining;
event->m_executor = this; // Used to validate event is not waited for inside a job
if (event)
{
event->IncWaitCount();
event->m_executor = this; // Used to validate event is not waited for inside a job
}
// Submit all tasks that have no inbound edges
for (Internal::Task& task : graph.Tasks())

@ -20,6 +20,46 @@ namespace AZ
m_semaphore.acquire();
}
void TaskGraphEvent::IncWaitCount()
{
// guess zero to optimize for single task graph using an event, if multiple are using it then this will take 2+ comp_exch calls
int expectedValue = 0;
while(!m_waitCount.compare_exchange_weak(expectedValue, expectedValue + 1))
{
// value will be negative once event is ready to signal or has been signaled. Shouldn't happen.
AZ_Assert(expectedValue >= 0, "Called TaskGraphEvent::IncWaitCount on a signalled event");
if (expectedValue < 0) // event already signaled, skip
{
return;
}
};
}
void TaskGraphEvent::Signal()
{
// guess one to optimize for single task graph using an event, if multiple are using it then this will take 2+ comp_exch calls
int expectedValue = 1;
while(!m_waitCount.compare_exchange_weak(expectedValue, expectedValue - 1))
{
// It's an error for Signal to be called if no one is waiting, or the event has already been signaled
AZ_Assert(expectedValue > 0, "Called TaskGraphEvent::Signal when event is either signaled or unused");
if (expectedValue < 0) // return if already signaled
{
return;
}
};
if (expectedValue == 1) // This call to Signal decremented the value to 0.
{
expectedValue = 0;
// validate no one incremented the wait count and mark signalling state
if (m_waitCount.compare_exchange_strong(expectedValue, -1))
{
m_semaphore.release();
}
}
}
void TaskToken::PrecedesInternal(TaskToken& comesAfter)
{
AZ_Assert(!m_parent.m_submitted, "Cannot mutate a TaskGraph that was previously submitted.");

@ -61,14 +61,14 @@ namespace AZ
uint32_t m_index;
};
// A TaskGraphEvent may be used to block until a task graph has finished executing. Usage
// A TaskGraphEvent may be used to block until one or more task graphs has finished executing. Usage
// is NOT recommended for the majority of tasks (prefer to simply containing expanding/contracting
// the graph without synchronization over the course of the frame). However, the event
// is useful for the edges of the computation graph.
//
// You are responsible for ensuring the event object lifetime exceeds the task graph lifetime.
//
// After the TaskGraphEvent is signaled, you are allowed to reuse the same TaskGraphEvent
// After the TaskGraphEvent is signaled, you are NOT allowed to reuse the same TaskGraphEvent
// for a future submission.
class TaskGraphEvent
{
@ -81,10 +81,12 @@ namespace AZ
friend class TaskGraph;
friend class TaskExecutor;
void IncWaitCount();
void Signal();
AZStd::binary_semaphore m_semaphore;
TaskExecutor* m_executor = nullptr;
AZStd::atomic_int m_waitCount = 0;
TaskExecutor* m_executor = nullptr;
};
// The TaskGraph encapsulates a set of tasks and their interdependencies. After adding

@ -33,11 +33,6 @@ namespace AZ
return m_semaphore.try_acquire_for(AZStd::chrono::milliseconds{ 0 });
}
inline void TaskGraphEvent::Signal()
{
m_semaphore.release();
}
template<typename Lambda>
TaskToken TaskGraph::AddTask(TaskDescriptor const& desc, Lambda&& lambda)
{

@ -610,15 +610,16 @@ namespace UnitTest
g.Follows(e, f);
g.Precedes(d);
TaskGraphEvent ev;
graph.SubmitOnExecutor(*m_executor, &ev);
ev.Wait();
TaskGraphEvent ev1;
graph.SubmitOnExecutor(*m_executor, &ev1);
ev1.Wait();
EXPECT_EQ(3 | 0b100000, x);
x = 0;
graph.SubmitOnExecutor(*m_executor, &ev);
ev.Wait();
TaskGraphEvent ev2;
graph.SubmitOnExecutor(*m_executor, &ev2);
ev2.Wait();
EXPECT_EQ(3 | 0b100000, x);
}

@ -29,6 +29,8 @@
#include <AzCore/Script/ScriptContextAttributes.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Task/TaskGraph.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <AzFramework/StringFunc/StringFunc.h>
@ -50,6 +52,15 @@ namespace AZ
"Sets the compression level for saving png screenshots. Valid values are from 0 to 8"
);
AZ_CVAR(int,
r_pngCompressionNumThreads,
8, // Number of threads to use for the png r<->b channel data swap
nullptr,
ConsoleFunctorFlags::Null,
"Sets the number of threads for saving png screenshots. Valid values are from 1 to 128, although less than or equal the number of hw threads is recommended"
);
FrameCaptureOutputResult PngFrameCaptureOutput(
const AZStd::string& outputFilePath, const AZ::RPI::AttachmentReadback::ReadbackResult& readbackResult)
{
@ -65,33 +76,67 @@ namespace AZ
buffer = AZStd::make_shared<AZStd::vector<uint8_t>>(readbackResult.m_dataBuffer->size());
AZStd::copy(readbackResult.m_dataBuffer->begin(), readbackResult.m_dataBuffer->end(), buffer->begin());
AZ::JobCompletion jobCompletion;
const int numThreads = 8;
const int numThreads = r_pngCompressionNumThreads;
const int numPixelsPerThread = static_cast<int>(buffer->size() / numChannels / numThreads);
for (int i = 0; i < numThreads; ++i)
AZ::TaskGraphActiveInterface* taskGraphActiveInterface = AZ::Interface<AZ::TaskGraphActiveInterface>::Get();
bool taskGraphActive = taskGraphActiveInterface && taskGraphActiveInterface->IsTaskGraphActive();
if (taskGraphActive)
{
static const AZ::TaskDescriptor pngTaskDescriptor{"PngWriteOutChannelSwap", "Graphics"};
AZ::TaskGraph taskGraph;
for (int i = 0; i < numThreads; ++i)
{
int startPixel = i * numPixelsPerThread;
taskGraph.AddTask(
pngTaskDescriptor,
[&, startPixel]()
{
for (int pixelOffset = 0; pixelOffset < numPixelsPerThread; ++pixelOffset)
{
if (startPixel * numChannels + numChannels < buffer->size())
{
AZStd::swap(
buffer->data()[(startPixel + pixelOffset) * numChannels],
buffer->data()[(startPixel + pixelOffset) * numChannels + 2]
);
}
}
});
}
AZ::TaskGraphEvent taskGraphFinishedEvent;
taskGraph.Submit(&taskGraphFinishedEvent);
taskGraphFinishedEvent.Wait();
}
else
{
int startPixel = i * numPixelsPerThread;
AZ::JobCompletion jobCompletion;
for (int i = 0; i < numThreads; ++i)
{
int startPixel = i * numPixelsPerThread;
AZ::Job* job = AZ::CreateJobFunction(
[&, startPixel, numPixelsPerThread]()
{
for (int pixelOffset = 0; pixelOffset < numPixelsPerThread; ++pixelOffset)
AZ::Job* job = AZ::CreateJobFunction(
[&, startPixel]()
{
if (startPixel * numChannels + numChannels < buffer->size())
for (int pixelOffset = 0; pixelOffset < numPixelsPerThread; ++pixelOffset)
{
AZStd::swap(
buffer->data()[(startPixel + pixelOffset) * numChannels],
buffer->data()[(startPixel + pixelOffset) * numChannels + 2]
);
if (startPixel * numChannels + numChannels < buffer->size())
{
AZStd::swap(
buffer->data()[(startPixel + pixelOffset) * numChannels],
buffer->data()[(startPixel + pixelOffset) * numChannels + 2]
);
}
}
}
}, true, nullptr);
}, true, nullptr);
job->SetDependent(&jobCompletion);
job->Start();
job->SetDependent(&jobCompletion);
job->Start();
}
jobCompletion.StartAndWaitForCompletion();
}
jobCompletion.StartAndWaitForCompletion();
}
Utils::PngFile image = Utils::PngFile::Create(readbackResult.m_imageDescriptor.m_size, format, *buffer);

@ -38,6 +38,8 @@
namespace AZ
{
class Job;
class TaskGraphActiveInterface;
class TaskGraph;
namespace RHI
{
@ -256,7 +258,13 @@ namespace AZ
//! Must be called between BeginCulling() and EndCulling(), once for each active scene/view pair.
//! Will create child jobs under the parentJob to do the processing in parallel.
//! Can be called in parallel (i.e. to perform culling on multiple views at the same time).
void ProcessCullables(const Scene& scene, View& view, AZ::Job& parentJob);
void ProcessCullablesJobs(const Scene& scene, View& view, AZ::Job& parentJob);
//! Performs render culling and lod selection for a View, then adds the visible renderpackets to that View.
//! Must be called between BeginCulling() and EndCulling(), once for each active scene/view pair.
//! Will create child task graphs that signal the TaskGraphEvent to do the processing in parallel.
//! Can be called in parallel (i.e. to perform culling on multiple views at the same time).
void ProcessCullablesTG(const Scene& scene, View& view, AZ::TaskGraph& taskGraph);
//! Adds a Cullable to the underlying visibility system(s).
//! Must be called at least once on initialization and whenever a Cullable's position or bounds is changed.
@ -276,17 +284,20 @@ namespace AZ
return m_debugCtx;
}
static const size_t WorkListCapacity = 5;
using WorkListType = AZStd::fixed_vector<AzFramework::IVisibilityScene::NodeData, WorkListCapacity>;
protected:
size_t CountObjectsInScene();
private:
void BeginCullingTaskGraph(const AZStd::vector<ViewPtr>& views);
void BeginCullingJobs(const AZStd::vector<ViewPtr>& views);
void ProcessCullablesCommon(const Scene& scene, View& view, AZ::Frustum& frustum, void*& maskedOcclusionCulling);
const Scene* m_parentScene = nullptr;
AzFramework::IVisibilityScene* m_visScene = nullptr;
CullingDebugContext m_debugCtx;
AZStd::concurrency_checker m_cullDataConcurrencyCheck;
OcclusionPlaneVector m_occlusionPlanes;
AZ::TaskGraphActiveInterface* m_taskGraphActive = nullptr;
};

@ -200,8 +200,8 @@ namespace AZ
// This function is called every time scene's render pipelines change.
void RebuildPipelineStatesLookup();
// Helper function to wait for end of TaskGraph
void WaitTGEvent(AZ::TaskGraphEvent& completionTGEvent, AZStd::atomic_bool* workToWaitOn = nullptr);
// Helper function to wait for end of TaskGraph and then delete the TaskGraphEvent
void WaitAndCleanTGEvent(AZStd::unique_ptr<AZ::TaskGraphEvent>&& completionTGEvent);
// Helper function for wait and clean up a completion job
void WaitAndCleanCompletionJob(AZ::JobCompletion*& completionJob);
@ -230,8 +230,7 @@ namespace AZ
AZStd::vector<RenderPipelinePtr> m_pipelines;
// CPU simulation TaskGraphEvent to wait for completion of all the simulation tasks
AZ::TaskGraphEvent m_simulationFinishedTGEvent;
AZStd::atomic_bool m_simulationFinishedWorkActive = false;
AZStd::unique_ptr<AZ::TaskGraphEvent> m_simulationFinishedTGEvent;
// CPU simulation job completion for track all feature processors' simulation jobs
AZ::JobCompletion* m_simulationCompletion = nullptr;

@ -23,6 +23,8 @@
#include <AzCore/Debug/Timer.h>
#include <AzCore/Jobs/JobFunction.h>
#include <AzCore/Jobs/Job.h>
#include <AzCore/Task/TaskGraph.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <Atom_RPI_Traits_Platform.h>
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
@ -265,89 +267,71 @@ namespace AZ
return m_visScene->GetEntryCount();
}
class AddObjectsToViewJob final
: public Job
struct WorklistData
{
public:
AZ_CLASS_ALLOCATOR(AddObjectsToViewJob, ThreadPoolAllocator, 0);
CullingDebugContext* m_debugCtx = nullptr;
const Scene* m_scene = nullptr;
View* m_view = nullptr;
Frustum m_frustum;
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
MaskedOcclusionCulling* m_maskedOcclusionCulling = nullptr;
#endif
};
struct JobData
{
CullingDebugContext* m_debugCtx = nullptr;
const Scene* m_scene = nullptr;
View* m_view = nullptr;
Frustum m_frustum;
static AZStd::shared_ptr<WorklistData> MakeWorklistData(
CullingDebugContext& debugCtx,
const Scene& scene,
View& view,
Frustum& frustum,
void* maskedOcclusionCulling)
{
AZStd::shared_ptr<WorklistData> worklistData = AZStd::make_shared<WorklistData>();
worklistData->m_debugCtx = &debugCtx;
worklistData->m_scene = &scene;
worklistData->m_view = &view;
worklistData->m_frustum = frustum;
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
MaskedOcclusionCulling* m_maskedOcclusionCulling = nullptr;
worklistData->m_maskedOcclusionCulling = static_cast<MaskedOcclusionCulling*>(maskedOcclusionCulling);
#endif
};
return worklistData;
}
constexpr size_t WorkListCapacity = 5;
using WorkListType = AZStd::fixed_vector<AzFramework::IVisibilityScene::NodeData, WorkListCapacity>;
private:
const AZStd::shared_ptr<JobData> m_jobData;
CullingScene::WorkListType m_worklist;
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
static MaskedOcclusionCulling::CullingResult TestOcclusionCulling(
const AZStd::shared_ptr<WorklistData>& worklistData,
AzFramework::VisibilityEntry* visibleEntry);
#endif
public:
AddObjectsToViewJob(const AZStd::shared_ptr<AddObjectsToViewJob::JobData>& jobData, CullingScene::WorkListType& worklist)
: Job(true, nullptr) //auto-deletes, no JobContext
, m_jobData(jobData)
, m_worklist(worklist)
{
}
static void ProcessWorklist(const AZStd::shared_ptr<WorklistData>& worklistData, const WorkListType& worklist)
{
AZ_PROFILE_SCOPE(RPI, "AddObjectsToViewJob: Process");
//work function
void Process() override
{
AZ_PROFILE_SCOPE(RPI, "AddObjectsToViewJob: Process");
const View::UsageFlags viewFlags = worklistData->m_view->GetUsageFlags();
const RHI::DrawListMask drawListMask = worklistData->m_view->GetDrawListMask();
uint32_t numDrawPackets = 0;
uint32_t numVisibleCullables = 0;
const View::UsageFlags viewFlags = m_jobData->m_view->GetUsageFlags();
const RHI::DrawListMask drawListMask = m_jobData->m_view->GetDrawListMask();
uint32_t numDrawPackets = 0;
uint32_t numVisibleCullables = 0;
AZ_Assert(worklist.size() > 0, "Received empty worklist in ProcessWorklist");
for (const AzFramework::IVisibilityScene::NodeData& nodeData : m_worklist)
{
//If a node is entirely contained within the frustum, then we can skip the fine grained culling.
bool nodeIsContainedInFrustum = ShapeIntersection::Contains(m_jobData->m_frustum, nodeData.m_bounds);
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);
#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: %d",
m_view->GetName().GetCStr(), nodeIsContainedInFrustum ? 1 : 0);
#endif
if (nodeIsContainedInFrustum || !m_jobData->m_debugCtx->m_enableFrustumCulling)
{
//Add all objects within this node to the view, without any extra culling
for (AzFramework::VisibilityEntry* visibleEntry : nodeData.m_entries)
{
{
if (visibleEntry->m_typeFlags & AzFramework::VisibilityEntry::TYPE_RPI_Cullable)
{
Cullable* c = static_cast<Cullable*>(visibleEntry->m_userData);
if ((c->m_cullData.m_drawListMask & drawListMask).none() ||
c->m_cullData.m_hideFlags & viewFlags ||
c->m_cullData.m_scene != m_jobData->m_scene || //[GFX_TODO][ATOM-13796] once the IVisibilitySystem supports multiple octree scenes, remove this
c->m_isHidden)
{
continue;
}
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
if (TestOcclusionCulling(visibleEntry) == MaskedOcclusionCulling::CullingResult::VISIBLE)
#endif
{
numDrawPackets += AddLodDataToView(c->m_cullData.m_boundingSphere.GetCenter(), c->m_lodData, *m_jobData->m_view);
++numVisibleCullables;
c->m_isVisible = true;
}
}
}
}
}
else
if (nodeIsContainedInFrustum || !worklistData->m_debugCtx->m_enableFrustumCulling)
{
//Add all objects within this node to the view, without any extra culling
for (AzFramework::VisibilityEntry* visibleEntry : nodeData.m_entries)
{
//Do fine-grained culling before adding objects to the view
for (AzFramework::VisibilityEntry* visibleEntry : nodeData.m_entries)
{
if (visibleEntry->m_typeFlags & AzFramework::VisibilityEntry::TYPE_RPI_Cullable)
{
@ -355,160 +339,188 @@ namespace AZ
if ((c->m_cullData.m_drawListMask & drawListMask).none() ||
c->m_cullData.m_hideFlags & viewFlags ||
c->m_cullData.m_scene != m_jobData->m_scene || //[GFX_TODO][ATOM-13796] once the IVisibilitySystem supports multiple octree scenes, remove this
c->m_cullData.m_scene != worklistData->m_scene || //[GFX_TODO][ATOM-13796] once the IVisibilitySystem supports multiple octree scenes, remove this
c->m_isHidden)
{
continue;
}
IntersectResult res = ShapeIntersection::Classify(m_jobData->m_frustum, c->m_cullData.m_boundingSphere);
if (res == IntersectResult::Exterior)
{
continue;
}
else if (res == IntersectResult::Interior || ShapeIntersection::Overlaps(m_jobData->m_frustum, c->m_cullData.m_boundingObb))
{
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
if (TestOcclusionCulling(visibleEntry) == MaskedOcclusionCulling::CullingResult::VISIBLE)
if (TestOcclusionCulling(worklistData, visibleEntry) == MaskedOcclusionCulling::CullingResult::VISIBLE)
#endif
{
numDrawPackets += AddLodDataToView(c->m_cullData.m_boundingSphere.GetCenter(), c->m_lodData, *m_jobData->m_view);
++numVisibleCullables;
c->m_isVisible = true;
}
{
numDrawPackets += AddLodDataToView(c->m_cullData.m_boundingSphere.GetCenter(), c->m_lodData, *worklistData->m_view);
++numVisibleCullables;
c->m_isVisible = true;
}
}
}
}
if (m_jobData->m_debugCtx->m_debugDraw && (m_jobData->m_view->GetName() == m_jobData->m_debugCtx->m_currentViewSelectionName))
}
else
{
//Do fine-grained culling before adding objects to the view
for (AzFramework::VisibilityEntry* visibleEntry : nodeData.m_entries)
{
AZ_PROFILE_SCOPE(RPI, "debug draw culling");
AuxGeomDrawPtr auxGeomPtr = AuxGeomFeatureProcessorInterface::GetDrawQueueForScene(m_jobData->m_scene);
if (auxGeomPtr)
if (visibleEntry->m_typeFlags & AzFramework::VisibilityEntry::TYPE_RPI_Cullable)
{
//Draw the node bounds
// "Fully visible" nodes are nodes that are fully inside the frustum. "Partially visible" nodes intersect the edges of the frustum.
// Since the nodes of an octree have lots of overlapping boxes with coplanar edges, it's easier to view these separately, so
// we have a few debug booleans to toggle which ones to draw.
if (nodeIsContainedInFrustum && m_jobData->m_debugCtx->m_drawFullyVisibleNodes)
Cullable* c = static_cast<Cullable*>(visibleEntry->m_userData);
if ((c->m_cullData.m_drawListMask & drawListMask).none() ||
c->m_cullData.m_hideFlags & viewFlags ||
c->m_cullData.m_scene != worklistData->m_scene || //[GFX_TODO][ATOM-13796] once the IVisibilitySystem supports multiple octree scenes, remove this
c->m_isHidden)
{
auxGeomPtr->DrawAabb(nodeData.m_bounds, Colors::Lime, RPI::AuxGeomDraw::DrawStyle::Line, RPI::AuxGeomDraw::DepthTest::Off);
continue;
}
else if (!nodeIsContainedInFrustum && m_jobData->m_debugCtx->m_drawPartiallyVisibleNodes)
IntersectResult res = ShapeIntersection::Classify(worklistData->m_frustum, c->m_cullData.m_boundingSphere);
if (res == IntersectResult::Exterior)
{
auxGeomPtr->DrawAabb(nodeData.m_bounds, Colors::Yellow, RPI::AuxGeomDraw::DrawStyle::Line, RPI::AuxGeomDraw::DepthTest::Off);
continue;
}
else if (res == IntersectResult::Interior || ShapeIntersection::Overlaps(worklistData->m_frustum, c->m_cullData.m_boundingObb))
{
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
if (TestOcclusionCulling(worklistData, visibleEntry) == MaskedOcclusionCulling::CullingResult::VISIBLE)
#endif
{
numDrawPackets += AddLodDataToView(c->m_cullData.m_boundingSphere.GetCenter(), c->m_lodData, *worklistData->m_view);
++numVisibleCullables;
c->m_isVisible = true;
}
}
}
}
}
if (worklistData->m_debugCtx->m_debugDraw && (worklistData->m_view->GetName() == worklistData->m_debugCtx->m_currentViewSelectionName))
{
AZ_PROFILE_SCOPE(RPI, "debug draw culling");
AuxGeomDrawPtr auxGeomPtr = AuxGeomFeatureProcessorInterface::GetDrawQueueForScene(worklistData->m_scene);
if (auxGeomPtr)
{
//Draw the node bounds
// "Fully visible" nodes are nodes that are fully inside the frustum. "Partially visible" nodes intersect the edges of the frustum.
// Since the nodes of an octree have lots of overlapping boxes with coplanar edges, it's easier to view these separately, so
// we have a few debug booleans to toggle which ones to draw.
if (nodeIsContainedInFrustum && worklistData->m_debugCtx->m_drawFullyVisibleNodes)
{
auxGeomPtr->DrawAabb(nodeData.m_bounds, Colors::Lime, RPI::AuxGeomDraw::DrawStyle::Line, RPI::AuxGeomDraw::DepthTest::Off);
}
else if (!nodeIsContainedInFrustum && worklistData->m_debugCtx->m_drawPartiallyVisibleNodes)
{
auxGeomPtr->DrawAabb(nodeData.m_bounds, Colors::Yellow, RPI::AuxGeomDraw::DrawStyle::Line, RPI::AuxGeomDraw::DepthTest::Off);
}
//Draw bounds on individual objects
if (m_jobData->m_debugCtx->m_drawBoundingBoxes || m_jobData->m_debugCtx->m_drawBoundingSpheres || m_jobData->m_debugCtx->m_drawLodRadii)
//Draw bounds on individual objects
if (worklistData->m_debugCtx->m_drawBoundingBoxes || worklistData->m_debugCtx->m_drawBoundingSpheres || worklistData->m_debugCtx->m_drawLodRadii)
{
for (AzFramework::VisibilityEntry* visibleEntry : nodeData.m_entries)
{
for (AzFramework::VisibilityEntry* visibleEntry : nodeData.m_entries)
if (visibleEntry->m_typeFlags & AzFramework::VisibilityEntry::TYPE_RPI_Cullable)
{
if (visibleEntry->m_typeFlags & AzFramework::VisibilityEntry::TYPE_RPI_Cullable)
Cullable* c = static_cast<Cullable*>(visibleEntry->m_userData);
if (worklistData->m_debugCtx->m_drawBoundingBoxes)
{
Cullable* c = static_cast<Cullable*>(visibleEntry->m_userData);
if (m_jobData->m_debugCtx->m_drawBoundingBoxes)
{
auxGeomPtr->DrawObb(c->m_cullData.m_boundingObb, Matrix3x4::Identity(),
nodeIsContainedInFrustum ? Colors::Lime : Colors::Yellow, AuxGeomDraw::DrawStyle::Line);
}
if (m_jobData->m_debugCtx->m_drawBoundingSpheres)
{
auxGeomPtr->DrawSphere(c->m_cullData.m_boundingSphere.GetCenter(), c->m_cullData.m_boundingSphere.GetRadius(),
Color(0.5f, 0.5f, 0.5f, 0.3f), AuxGeomDraw::DrawStyle::Shaded);
}
if (m_jobData->m_debugCtx->m_drawLodRadii)
{
auxGeomPtr->DrawSphere(c->m_cullData.m_boundingSphere.GetCenter(),
c->m_lodData.m_lodSelectionRadius,
Color(1.0f, 0.5f, 0.0f, 0.3f), RPI::AuxGeomDraw::DrawStyle::Shaded);
}
auxGeomPtr->DrawObb(c->m_cullData.m_boundingObb, Matrix3x4::Identity(),
nodeIsContainedInFrustum ? Colors::Lime : Colors::Yellow, AuxGeomDraw::DrawStyle::Line);
}
if (worklistData->m_debugCtx->m_drawBoundingSpheres)
{
auxGeomPtr->DrawSphere(c->m_cullData.m_boundingSphere.GetCenter(), c->m_cullData.m_boundingSphere.GetRadius(),
Color(0.5f, 0.5f, 0.5f, 0.3f), AuxGeomDraw::DrawStyle::Shaded);
}
if (worklistData->m_debugCtx->m_drawLodRadii)
{
auxGeomPtr->DrawSphere(c->m_cullData.m_boundingSphere.GetCenter(),
c->m_lodData.m_lodSelectionRadius,
Color(1.0f, 0.5f, 0.0f, 0.3f), RPI::AuxGeomDraw::DrawStyle::Shaded);
}
}
}
}
}
}
}
if (m_jobData->m_debugCtx->m_enableStats)
{
CullingDebugContext::CullStats& cullStats = m_jobData->m_debugCtx->GetCullStatsForView(m_jobData->m_view);
if (worklistData->m_debugCtx->m_enableStats)
{
CullingDebugContext::CullStats& cullStats = worklistData->m_debugCtx->GetCullStatsForView(worklistData->m_view);
//no need for mutex here since these are all atomics
cullStats.m_numVisibleDrawPackets += numDrawPackets;
cullStats.m_numVisibleCullables += numVisibleCullables;
++cullStats.m_numJobs;
}
//no need for mutex here since these are all atomics
cullStats.m_numVisibleDrawPackets += numDrawPackets;
cullStats.m_numVisibleCullables += numVisibleCullables;
++cullStats.m_numJobs;
}
}
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
MaskedOcclusionCulling::CullingResult TestOcclusionCulling(AzFramework::VisibilityEntry* visibleEntry)
static MaskedOcclusionCulling::CullingResult TestOcclusionCulling(
const AZStd::shared_ptr<WorklistData>& worklistData,
AzFramework::VisibilityEntry* visibleEntry)
{
if (!worklistData->m_maskedOcclusionCulling)
{
if (!m_jobData->m_maskedOcclusionCulling)
{
return MaskedOcclusionCulling::CullingResult::VISIBLE;
}
return MaskedOcclusionCulling::CullingResult::VISIBLE;
}
if (visibleEntry->m_boundingVolume.Contains(m_jobData->m_view->GetCameraTransform().GetTranslation()))
if (visibleEntry->m_boundingVolume.Contains(worklistData->m_view->GetCameraTransform().GetTranslation()))
{
// camera is inside bounding volume
return MaskedOcclusionCulling::CullingResult::VISIBLE;
}
const Vector3& minBound = visibleEntry->m_boundingVolume.GetMin();
const Vector3& maxBound = visibleEntry->m_boundingVolume.GetMax();
// compute bounding volume corners
Vector4 corners[8];
corners[0] = worklistData->m_view->GetWorldToClipMatrix() * Vector4(minBound.GetX(), minBound.GetY(), minBound.GetZ(), 1.0f);
corners[1] = worklistData->m_view->GetWorldToClipMatrix() * Vector4(minBound.GetX(), minBound.GetY(), maxBound.GetZ(), 1.0f);
corners[2] = worklistData->m_view->GetWorldToClipMatrix() * Vector4(maxBound.GetX(), minBound.GetY(), maxBound.GetZ(), 1.0f);
corners[3] = worklistData->m_view->GetWorldToClipMatrix() * Vector4(maxBound.GetX(), minBound.GetY(), minBound.GetZ(), 1.0f);
corners[4] = worklistData->m_view->GetWorldToClipMatrix() * Vector4(minBound.GetX(), maxBound.GetY(), minBound.GetZ(), 1.0f);
corners[5] = worklistData->m_view->GetWorldToClipMatrix() * Vector4(minBound.GetX(), maxBound.GetY(), maxBound.GetZ(), 1.0f);
corners[6] = worklistData->m_view->GetWorldToClipMatrix() * Vector4(maxBound.GetX(), maxBound.GetY(), maxBound.GetZ(), 1.0f);
corners[7] = worklistData->m_view->GetWorldToClipMatrix() * Vector4(maxBound.GetX(), maxBound.GetY(), minBound.GetZ(), 1.0f);
// find min clip-space depth and NDC min/max
float minDepth = FLT_MAX;
float ndcMinX = FLT_MAX;
float ndcMinY = FLT_MAX;
float ndcMaxX = -FLT_MAX;
float ndcMaxY = -FLT_MAX;
for (uint32_t index = 0; index < 8; ++index)
{
minDepth = AZStd::min(minDepth, corners[index].GetW());
if (minDepth < 0.00000001f)
{
// camera is inside bounding volume
return MaskedOcclusionCulling::CullingResult::VISIBLE;
}
const Vector3& minBound = visibleEntry->m_boundingVolume.GetMin();
const Vector3& maxBound = visibleEntry->m_boundingVolume.GetMax();
// compute bounding volume corners
Vector4 corners[8];
corners[0] = m_jobData->m_view->GetWorldToClipMatrix() * Vector4(minBound.GetX(), minBound.GetY(), minBound.GetZ(), 1.0f);
corners[1] = m_jobData->m_view->GetWorldToClipMatrix() * Vector4(minBound.GetX(), minBound.GetY(), maxBound.GetZ(), 1.0f);
corners[2] = m_jobData->m_view->GetWorldToClipMatrix() * Vector4(maxBound.GetX(), minBound.GetY(), maxBound.GetZ(), 1.0f);
corners[3] = m_jobData->m_view->GetWorldToClipMatrix() * Vector4(maxBound.GetX(), minBound.GetY(), minBound.GetZ(), 1.0f);
corners[4] = m_jobData->m_view->GetWorldToClipMatrix() * Vector4(minBound.GetX(), maxBound.GetY(), minBound.GetZ(), 1.0f);
corners[5] = m_jobData->m_view->GetWorldToClipMatrix() * Vector4(minBound.GetX(), maxBound.GetY(), maxBound.GetZ(), 1.0f);
corners[6] = m_jobData->m_view->GetWorldToClipMatrix() * Vector4(maxBound.GetX(), maxBound.GetY(), maxBound.GetZ(), 1.0f);
corners[7] = m_jobData->m_view->GetWorldToClipMatrix() * Vector4(maxBound.GetX(), maxBound.GetY(), minBound.GetZ(), 1.0f);
// find min clip-space depth and NDC min/max
float minDepth = FLT_MAX;
float ndcMinX = FLT_MAX;
float ndcMinY = FLT_MAX;
float ndcMaxX = -FLT_MAX;
float ndcMaxY = -FLT_MAX;
for (uint32_t index = 0; index < 8; ++index)
{
minDepth = AZStd::min(minDepth, corners[index].GetW());
// convert to NDC
corners[index] /= corners[index].GetW();
ndcMinX = AZStd::min(ndcMinX, corners[index].GetX());
ndcMinY = AZStd::min(ndcMinY, corners[index].GetY());
ndcMaxX = AZStd::max(ndcMaxX, corners[index].GetX());
ndcMaxY = AZStd::max(ndcMaxY, corners[index].GetY());
}
// convert to NDC
corners[index] /= corners[index].GetW();
if (minDepth < 0.00000001f)
{
return MaskedOcclusionCulling::VISIBLE;
}
// test against the occlusion buffer, which contains only the manually placed occlusion planes
return m_jobData->m_maskedOcclusionCulling->TestRect(ndcMinX, ndcMinY, ndcMaxX, ndcMaxY, minDepth);
ndcMinX = AZStd::min(ndcMinX, corners[index].GetX());
ndcMinY = AZStd::min(ndcMinY, corners[index].GetY());
ndcMaxX = AZStd::max(ndcMaxX, corners[index].GetX());
ndcMaxY = AZStd::max(ndcMaxY, corners[index].GetY());
}
// test against the occlusion buffer, which contains only the manually placed occlusion planes
return worklistData->m_maskedOcclusionCulling->TestRect(ndcMinX, ndcMinY, ndcMaxX, ndcMaxY, minDepth);
}
#endif
};
void CullingScene::ProcessCullables(const Scene& scene, View& view, AZ::Job& parentJob)
void CullingScene::ProcessCullablesCommon(const Scene& scene, View& view, AZ::Frustum& frustum, void*& maskedOcclusionCulling)
{
AZ_PROFILE_SCOPE(RPI, "CullingScene::ProcessCullables() - %s", view.GetName().GetCStr());
AZ_PROFILE_SCOPE(RPI, "CullingScene::ProcessCullablesCommon() - %s", view.GetName().GetCStr());
const Matrix4x4& worldToClip = view.GetWorldToClipMatrix();
Frustum frustum = Frustum::CreateFromMatrixColumnMajor(worldToClip);
if (m_debugCtx.m_freezeFrustums)
{
AZStd::lock_guard<AZStd::mutex> lock(m_debugCtx.m_frozenFrustumsMutex);
@ -536,7 +548,7 @@ namespace AZ
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
// setup occlusion culling, if necessary
MaskedOcclusionCulling* maskedOcclusionCulling = m_occlusionPlanes.empty() ? nullptr : view.GetMaskedOcclusionCulling();
maskedOcclusionCulling = m_occlusionPlanes.empty() ? nullptr : view.GetMaskedOcclusionCulling();
if (maskedOcclusionCulling)
{
// frustum cull occlusion planes
@ -578,23 +590,27 @@ namespace AZ
static uint32_t indices[6] = { 0, 1, 2, 2, 3, 0 };
// render into the occlusion buffer, specifying BACKFACE_NONE so it functions as a double-sided occluder
maskedOcclusionCulling->RenderTriangles((float*)verts, indices, 2, nullptr, MaskedOcclusionCulling::BACKFACE_NONE);
static_cast<MaskedOcclusionCulling*>(maskedOcclusionCulling)->RenderTriangles(verts, indices, 2, nullptr, MaskedOcclusionCulling::BACKFACE_NONE);
}
}
#endif
}
void CullingScene::ProcessCullablesJobs(const Scene& scene, View& view, AZ::Job& parentJob)
{
AZ_PROFILE_SCOPE(RPI, "CullingScene::ProcessCullablesJobs() - %s", view.GetName().GetCStr());
const Matrix4x4& worldToClip = view.GetWorldToClipMatrix();
AZ::Frustum frustum = Frustum::CreateFromMatrixColumnMajor(worldToClip);
void* maskedOcclusionCulling = nullptr;
ProcessCullablesCommon(scene, view, frustum, maskedOcclusionCulling);
WorkListType worklist;
AZStd::shared_ptr<AddObjectsToViewJob::JobData> jobData = AZStd::make_shared<AddObjectsToViewJob::JobData>();
jobData->m_debugCtx = &m_debugCtx;
jobData->m_scene = &scene;
jobData->m_view = &view;
jobData->m_frustum = frustum;
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
jobData->m_maskedOcclusionCulling = maskedOcclusionCulling;
#endif
AZStd::shared_ptr<WorklistData> worklistData = MakeWorklistData(m_debugCtx, scene, view, frustum, maskedOcclusionCulling);
auto nodeVisitorLambda = [jobData, &parentJob, &worklist](const AzFramework::IVisibilityScene::NodeData& nodeData) -> void
auto nodeVisitorLambda = [worklistData, &parentJob, &worklist](const AzFramework::IVisibilityScene::NodeData& nodeData) -> void
{
AZ_PROFILE_SCOPE(RPI, "nodeVisitorLambda()");
AZ_Assert(nodeData.m_entries.size() > 0, "should not get called with 0 entries");
@ -606,8 +622,13 @@ namespace AZ
if (worklist.size() == worklist.capacity())
{
// capture worklistData & worklist by value
auto processWorklist = [worklistData, worklist]()
{
ProcessWorklist(worklistData, worklist);
};
//Kick off a job to process the (full) worklist
AddObjectsToViewJob* job = aznew AddObjectsToViewJob(jobData, worklist); //pool allocated (cheap), auto-deletes when job finishes
AZ::Job* job = AZ::CreateJobFunction(processWorklist, true);
worklist.clear();
parentJob.SetContinuation(job);
job->Start();
@ -616,7 +637,7 @@ namespace AZ
if (m_debugCtx.m_enableFrustumCulling)
{
m_visScene->Enumerate(frustum, nodeVisitorLambda);
m_visScene->Enumerate(frustum, nodeVisitorLambda);
}
else
{
@ -625,21 +646,76 @@ namespace AZ
if (worklist.size() > 0)
{
AZStd::shared_ptr<AddObjectsToViewJob::JobData> remainingJobData = AZStd::make_shared<AddObjectsToViewJob::JobData>();
remainingJobData->m_debugCtx = &m_debugCtx;
remainingJobData->m_scene = &scene;
remainingJobData->m_view = &view;
remainingJobData->m_frustum = frustum;
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
remainingJobData->m_maskedOcclusionCulling = maskedOcclusionCulling;
#endif
//Kick off a job to process any remaining workitems
AddObjectsToViewJob* job = aznew AddObjectsToViewJob(remainingJobData, worklist); //pool allocated (cheap), auto-deletes when job finishes
// capture worklistData & worklist by value
auto processWorklist = [worklistData, worklist]()
{
ProcessWorklist(worklistData, worklist);
};
//Kick off a job to process the (full) worklist
AZ::Job* job = AZ::CreateJobFunction(processWorklist, true);
parentJob.SetContinuation(job);
job->Start();
}
}
void CullingScene::ProcessCullablesTG(const Scene& scene, View& view, AZ::TaskGraph& taskGraph)
{
AZ_PROFILE_SCOPE(RPI, "CullingScene::ProcessCullablesTG() - %s", view.GetName().GetCStr());
const Matrix4x4& worldToClip = view.GetWorldToClipMatrix();
AZ::Frustum frustum = Frustum::CreateFromMatrixColumnMajor(worldToClip);
void* maskedOcclusionCulling = nullptr;
ProcessCullablesCommon(scene, view, frustum, maskedOcclusionCulling);
AZStd::unique_ptr<WorkListType> worklist = AZStd::make_unique<WorkListType>();
AZStd::shared_ptr<WorklistData> worklistData = MakeWorklistData(m_debugCtx, scene, view, frustum, maskedOcclusionCulling);
static const AZ::TaskDescriptor descriptor{ "AZ::RPI::ProcessWorklist", "Graphics" };
auto nodeVisitorLambda = [worklistData, &taskGraph, &worklist](const AzFramework::IVisibilityScene::NodeData& nodeData) -> void
{
AZ_PROFILE_SCOPE(RPI, "nodeVisitorLambda()");
AZ_Assert(nodeData.m_entries.size() > 0, "should not get called with 0 entries");
AZ_Assert(worklist->size() < worklist->capacity(), "we should always have room to push a node on the queue");
//Queue up a small list of work items (NodeData*) which will be pushed to a worker task once the queue is full.
//This reduces the number of tasks in flight, reducing task-system overhead.
worklist->emplace_back(AZStd::move(nodeData));
if (worklist->size() == worklist->capacity())
{
//Task takes ownership of the worklist unique ptr
taskGraph.AddTask( descriptor, [worklistData, worklist = AZStd::move(worklist)]()
{
ProcessWorklist(worklistData, *worklist.get());
// allow worklist to go out of scope and be deleted
});
worklist = AZStd::make_unique<WorkListType>();
}
};
if (m_debugCtx.m_enableFrustumCulling)
{
m_visScene->Enumerate(frustum, nodeVisitorLambda);
}
else
{
m_visScene->EnumerateNoCull(nodeVisitorLambda);
}
if (worklist->size() > 0)
{
//Task takes ownership of the worklist unique ptr
taskGraph.AddTask( descriptor, [worklistData, worklist = AZStd::move(worklist)]()
{
ProcessWorklist(worklistData, *worklist.get());
// allow worklist to go out of scope and be deleted
});
}
}
uint32_t AddLodDataToView(const Vector3& pos, const Cullable::LodData& lodData, RPI::View& view)
{
#ifdef AZ_CULL_PROFILE_DETAILED
@ -702,6 +778,8 @@ namespace AZ
AZ::Name visSceneName(AZStd::string::format("RenderCullScene[%s]", m_parentScene->GetName().GetCStr()));
m_visScene = AZ::Interface<AzFramework::IVisibilitySystem>::Get()->CreateVisibilityScene(visSceneName);
m_taskGraphActive = AZ::Interface<AZ::TaskGraphActiveInterface>::Get();
#ifdef AZ_CULL_DEBUG_ENABLED
AZ_Assert(CountObjectsInScene() == 0, "The culling system should start with 0 entries in this scene.");
#endif
@ -719,13 +797,27 @@ namespace AZ
}
}
void CullingScene::BeginCulling(const AZStd::vector<ViewPtr>& views)
void CullingScene::BeginCullingTaskGraph(const AZStd::vector<ViewPtr>& views)
{
AZ_PROFILE_SCOPE(RPI, "CullingScene: BeginCulling");
m_cullDataConcurrencyCheck.soft_lock();
AZ::TaskGraph taskGraph;
AZ::TaskDescriptor beginCullingDescriptor{"RPI_CullingScene_BeginCullingView", "Graphics"};
for (auto& view : views)
{
taskGraph.AddTask(
beginCullingDescriptor,
[&view]()
{
view->BeginCulling();
});
}
m_debugCtx.ResetCullStats();
m_debugCtx.m_numCullablesInScene = GetNumCullables();
AZ::TaskGraphEvent waitForCompletion;
taskGraph.Submit(&waitForCompletion);
waitForCompletion.Wait();
}
void CullingScene::BeginCullingJobs(const AZStd::vector<ViewPtr>& views)
{
AZ::JobCompletion beginCullingCompletion;
for (auto& view : views)
@ -741,6 +833,26 @@ namespace AZ
}
beginCullingCompletion.StartAndWaitForCompletion();
}
void CullingScene::BeginCulling(const AZStd::vector<ViewPtr>& views)
{
AZ_PROFILE_SCOPE(RPI, "CullingScene: BeginCulling");
m_cullDataConcurrencyCheck.soft_lock();
m_debugCtx.ResetCullStats();
m_debugCtx.m_numCullablesInScene = GetNumCullables();
m_taskGraphActive = AZ::Interface<AZ::TaskGraphActiveInterface>::Get();
if (m_taskGraphActive && m_taskGraphActive->IsTaskGraphActive())
{
BeginCullingTaskGraph(views);
}
else
{
BeginCullingJobs(views);
}
AuxGeomDrawPtr auxGeom;
if (m_debugCtx.m_debugDraw)

@ -111,7 +111,7 @@ namespace AZ
{
if (m_taskGraphActive)
{
WaitTGEvent(m_simulationFinishedTGEvent, &m_simulationFinishedWorkActive);
WaitAndCleanTGEvent(AZStd::move(m_simulationFinishedTGEvent));
}
else
{
@ -385,8 +385,8 @@ namespace AZ
});
}
simulationTG.Detach();
m_simulationFinishedWorkActive = true;
simulationTG.Submit(&m_simulationFinishedTGEvent);
m_simulationFinishedTGEvent = AZStd::make_unique<TaskGraphEvent>();
simulationTG.Submit(m_simulationFinishedTGEvent.get());
}
void Scene::SimulateJobs()
@ -419,7 +419,7 @@ namespace AZ
// If previous simulation job wasn't done, wait for it to finish.
if (m_taskGraphActive)
{
WaitTGEvent(m_simulationFinishedTGEvent, &m_simulationFinishedWorkActive);
WaitAndCleanTGEvent(AZStd::move(m_simulationFinishedTGEvent));
}
else
{
@ -449,17 +449,14 @@ namespace AZ
}
}
void Scene::WaitTGEvent(AZ::TaskGraphEvent& completionTGEvent, AZStd::atomic_bool* workToWaitOn )
void Scene::WaitAndCleanTGEvent(AZStd::unique_ptr<AZ::TaskGraphEvent>&& completionTGEvent)
{
AZ_PROFILE_SCOPE(RPI, "Scene: WaitAndCleanCompletionJob");
if (!workToWaitOn || workToWaitOn->load())
AZ_PROFILE_SCOPE(RPI, "Scene: WaitAndCleanTGEvent");
if (completionTGEvent)
{
completionTGEvent.Wait();
}
if (workToWaitOn)
{
workToWaitOn->store(false);
completionTGEvent->Wait();
}
// allow completionTGEvent to go out of scope and be deleted
}
void Scene::WaitAndCleanCompletionJob(AZ::JobCompletion*& completionJob)
@ -499,12 +496,12 @@ namespace AZ
void Scene::CollectDrawPacketsTaskGraph()
{
AZ_PROFILE_SCOPE(RPI, "CollectDrawPackets");
AZ_PROFILE_SCOPE(RPI, "CollectDrawPacketsTaskGraph");
AZ::TaskGraphEvent collectDrawPacketsTGEvent;
static const AZ::TaskDescriptor collectDrawPacketsTGDesc{"RPI_Scene_PrepareRender_CollectDrawPackets", "Graphics"};
AZ::TaskGraph collectDrawPacketsTG;
// Launch FeatureProcessor::Render() jobs
// Launch FeatureProcessor::Render() taskgraphs
for (auto& fp : m_featureProcessors)
{
collectDrawPacketsTG.AddTask(
@ -520,32 +517,48 @@ namespace AZ
// Launch CullingSystem::ProcessCullables() jobs (will run concurrently with FeatureProcessor::Render() jobs if m_parallelOctreeTraversal)
bool parallelOctreeTraversal = m_cullingScene->GetDebugContext().m_parallelOctreeTraversal;
m_cullingScene->BeginCulling(m_renderPacket.m_views);
AZ::JobCompletion processCullablesCompletion;
for (ViewPtr& viewPtr : m_renderPacket.m_views)
static const AZ::TaskDescriptor processCullablesDescriptor{"AZ::RPI::Scene::ProcessCullables", "Graphics"};
AZ::TaskGraphEvent processCullablesTGEvent;
AZ::TaskGraph processCullablesTG;
if (parallelOctreeTraversal)
{
AZ::Job* processCullablesJob = AZ::CreateJobFunction([this, &viewPtr](AZ::Job& thisJob)
{
m_cullingScene->ProcessCullables(*this, *viewPtr, thisJob); // can't call directly because ProcessCullables needs a parent job
},
true, nullptr); //auto-deletes
if (parallelOctreeTraversal)
for (ViewPtr& viewPtr : m_renderPacket.m_views)
{
processCullablesJob->SetDependent(&processCullablesCompletion);
processCullablesJob->Start();
processCullablesTG.AddTask(processCullablesDescriptor, [this, &viewPtr, &processCullablesTGEvent]()
{
AZ::TaskGraph subTaskGraph;
m_cullingScene->ProcessCullablesTG(*this, *viewPtr, subTaskGraph);
if (!subTaskGraph.IsEmpty())
{
subTaskGraph.Detach();
subTaskGraph.Submit(&processCullablesTGEvent);
}
});
}
else
}
else
{
for (ViewPtr& viewPtr : m_renderPacket.m_views)
{
processCullablesJob->StartAndWaitForCompletion();
m_cullingScene->ProcessCullablesTG(*this, *viewPtr, processCullablesTG);
}
}
bool processCullablesHasWork = !processCullablesTG.IsEmpty();
if (processCullablesHasWork)
{
processCullablesTG.Submit(&processCullablesTGEvent);
}
WaitTGEvent(collectDrawPacketsTGEvent);
processCullablesCompletion.StartAndWaitForCompletion();
collectDrawPacketsTGEvent.Wait();
if (processCullablesHasWork) // skip the wait if there is no work to do
{
processCullablesTGEvent.Wait();
}
}
void Scene::CollectDrawPacketsJobs()
{
AZ_PROFILE_SCOPE(RPI, "CollectDrawPackets");
AZ_PROFILE_SCOPE(RPI, "CollectDrawPacketsJobs");
AZ::JobCompletion* collectDrawPacketsCompletion = aznew AZ::JobCompletion();
// Launch FeatureProcessor::Render() jobs
@ -567,7 +580,7 @@ namespace AZ
{
AZ::Job* processCullablesJob = AZ::CreateJobFunction([this, &viewPtr](AZ::Job& thisJob)
{
m_cullingScene->ProcessCullables(*this, *viewPtr, thisJob); // can't call directly because ProcessCullables needs a parent job
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)
@ -600,7 +613,7 @@ namespace AZ
});
}
finalizeDrawListsTG.Submit(&finalizeDrawListsTGEvent);
WaitTGEvent(finalizeDrawListsTGEvent);
finalizeDrawListsTGEvent.Wait();
}
void Scene::FinalizeDrawListsJobs()
@ -626,7 +639,7 @@ namespace AZ
if (m_taskGraphActive)
{
WaitTGEvent(m_simulationFinishedTGEvent, &m_simulationFinishedWorkActive);
WaitAndCleanTGEvent(AZStd::move(m_simulationFinishedTGEvent));
}
else
{

Loading…
Cancel
Save