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.
960 lines
45 KiB
C++
960 lines
45 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 <Atom/RPI.Public/AuxGeom/AuxGeomDraw.h>
|
|
#include <Atom/RPI.Public/AuxGeom/AuxGeomFeatureProcessorInterface.h>
|
|
#include <Atom/RPI.Public/Culling.h>
|
|
#include <Atom/RPI.Public/Model/ModelLodUtils.h>
|
|
#include <Atom/RPI.Public/RPISystemInterface.h>
|
|
#include <Atom/RPI.Public/Scene.h>
|
|
#include <Atom/RPI.Public/View.h>
|
|
|
|
#include <AzCore/Math/MatrixUtils.h>
|
|
#include <AzCore/Math/ShapeIntersection.h>
|
|
#include <AzCore/Casting/numeric_cast.h>
|
|
#include <AzCore/std/parallel/lock.h>
|
|
#include <AzCore/Casting/numeric_cast.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
|
|
#include <MaskedOcclusionCulling/MaskedOcclusionCulling.h>
|
|
#endif
|
|
|
|
//Enables more inner-loop profiling scopes (can create high overhead in telemetry if there are many-many objects in a scene)
|
|
//#define AZ_CULL_PROFILE_DETAILED
|
|
|
|
//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
|
|
|
|
namespace AZ
|
|
{
|
|
namespace RPI
|
|
{
|
|
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);
|
|
auxGeom->DrawCylinder(Vector3(.0, .5, .0), Vector3(0, 1, 0), 0.02f, 1.0f, Colors::Green, AuxGeomDraw::DrawStyle::Solid, AuxGeomDraw::DepthTest::Off);
|
|
auxGeom->DrawCylinder(Vector3(.0, .0, .5), Vector3(0, 0, 1), 0.02f, 1.0f, Colors::Blue, AuxGeomDraw::DrawStyle::Solid, AuxGeomDraw::DepthTest::Off);
|
|
|
|
Vector3 axisVerts[] =
|
|
{
|
|
Vector3(0.f, 0.f , 0.f), Vector3(10000.f, 0.f, 0.f),
|
|
Vector3(0.f, 0.f , 0.f), Vector3(0.f, 10000.f, 0.f),
|
|
Vector3(0.f, 0.f , 0.f), Vector3(0.f, 0.f, 10000.f)
|
|
};
|
|
Color colors[] =
|
|
{
|
|
Colors::Red, Colors::Red,
|
|
Colors::Green, Colors::Green,
|
|
Colors::Blue, Colors::Blue
|
|
};
|
|
AuxGeomDraw::AuxGeomDynamicDrawArguments lineArgs;
|
|
lineArgs.m_verts = axisVerts;
|
|
lineArgs.m_vertCount = 6;
|
|
lineArgs.m_colors = colors;
|
|
lineArgs.m_colorCount = lineArgs.m_vertCount;
|
|
lineArgs.m_depthTest = AuxGeomDraw::DepthTest::Off;
|
|
auxGeom->DrawLines(lineArgs);
|
|
}
|
|
|
|
void DebugDrawFrustum(const AZ::Frustum& f, AuxGeomDraw* auxGeom, const AZ::Color color, [[maybe_unused]] AZ::u8 lineWidth = 1)
|
|
{
|
|
using namespace ShapeIntersection;
|
|
|
|
enum CornerIndices {
|
|
NearTopLeft, NearTopRight, NearBottomLeft, NearBottomRight,
|
|
FarTopLeft, FarTopRight, FarBottomLeft, FarBottomRight
|
|
};
|
|
Vector3 corners[8];
|
|
|
|
if (IntersectThreePlanes(f.GetPlane(Frustum::PlaneId::Near), f.GetPlane(Frustum::PlaneId::Top), f.GetPlane(Frustum::PlaneId::Left), corners[NearTopLeft]) &&
|
|
IntersectThreePlanes(f.GetPlane(Frustum::PlaneId::Near), f.GetPlane(Frustum::PlaneId::Top), f.GetPlane(Frustum::PlaneId::Right), corners[NearTopRight]) &&
|
|
IntersectThreePlanes(f.GetPlane(Frustum::PlaneId::Near), f.GetPlane(Frustum::PlaneId::Bottom), f.GetPlane(Frustum::PlaneId::Left), corners[NearBottomLeft]) &&
|
|
IntersectThreePlanes(f.GetPlane(Frustum::PlaneId::Near), f.GetPlane(Frustum::PlaneId::Bottom), f.GetPlane(Frustum::PlaneId::Right), corners[NearBottomRight]) &&
|
|
IntersectThreePlanes(f.GetPlane(Frustum::PlaneId::Far), f.GetPlane(Frustum::PlaneId::Top), f.GetPlane(Frustum::PlaneId::Left), corners[FarTopLeft]) &&
|
|
IntersectThreePlanes(f.GetPlane(Frustum::PlaneId::Far), f.GetPlane(Frustum::PlaneId::Top), f.GetPlane(Frustum::PlaneId::Right), corners[FarTopRight]) &&
|
|
IntersectThreePlanes(f.GetPlane(Frustum::PlaneId::Far), f.GetPlane(Frustum::PlaneId::Bottom), f.GetPlane(Frustum::PlaneId::Left), corners[FarBottomLeft]) &&
|
|
IntersectThreePlanes(f.GetPlane(Frustum::PlaneId::Far), f.GetPlane(Frustum::PlaneId::Bottom), f.GetPlane(Frustum::PlaneId::Right), corners[FarBottomRight]))
|
|
{
|
|
|
|
uint32_t lineIndices[24]{
|
|
//near plane
|
|
NearTopLeft, NearTopRight,
|
|
NearTopRight, NearBottomRight,
|
|
NearBottomRight, NearBottomLeft,
|
|
NearBottomLeft, NearTopLeft,
|
|
|
|
//Far plane
|
|
FarTopLeft, FarTopRight,
|
|
FarTopRight, FarBottomRight,
|
|
FarBottomRight, FarBottomLeft,
|
|
FarBottomLeft, FarTopLeft,
|
|
|
|
//Near-to-Far connecting lines
|
|
NearTopLeft, FarTopLeft,
|
|
NearTopRight, FarTopRight,
|
|
NearBottomLeft, FarBottomLeft,
|
|
NearBottomRight, FarBottomRight
|
|
};
|
|
AuxGeomDraw::AuxGeomDynamicIndexedDrawArguments drawArgs;
|
|
drawArgs.m_verts = corners;
|
|
drawArgs.m_vertCount = 8;
|
|
drawArgs.m_indices = lineIndices;
|
|
drawArgs.m_indexCount = 24;
|
|
drawArgs.m_colors = &color;
|
|
drawArgs.m_colorCount = 1;
|
|
auxGeom->DrawLines(drawArgs);
|
|
|
|
uint32_t triangleIndices[36]{
|
|
//near
|
|
NearBottomLeft, NearTopLeft, NearTopRight,
|
|
NearBottomLeft, NearTopRight, NearBottomRight,
|
|
|
|
//far
|
|
FarBottomRight, FarTopRight, FarTopLeft,
|
|
FarBottomRight, FarTopLeft, FarBottomLeft,
|
|
|
|
//left
|
|
FarBottomLeft, NearBottomLeft, NearTopLeft,
|
|
FarBottomLeft, NearTopLeft, FarTopLeft,
|
|
|
|
//right
|
|
NearBottomRight, NearTopRight, FarTopRight,
|
|
NearBottomRight, FarTopRight, FarBottomRight,
|
|
|
|
//bottom
|
|
FarBottomLeft, NearBottomLeft, NearBottomRight,
|
|
FarBottomLeft, NearBottomRight, FarBottomRight,
|
|
|
|
//top
|
|
NearTopLeft, FarTopLeft, FarTopRight,
|
|
NearTopLeft, FarTopRight, NearTopRight
|
|
};
|
|
Color transparentColor(color.GetR(), color.GetG(), color.GetB(), color.GetA() * 0.3f);
|
|
drawArgs.m_indices = triangleIndices;
|
|
drawArgs.m_indexCount = 36;
|
|
drawArgs.m_colors = &transparentColor;
|
|
auxGeom->DrawTriangles(drawArgs);
|
|
|
|
// plane normals
|
|
Vector3 planeNormals[] =
|
|
{
|
|
//near
|
|
0.25f * (corners[NearBottomLeft] + corners[NearBottomRight] + corners[NearTopLeft] + corners[NearTopRight]),
|
|
0.25f * (corners[NearBottomLeft] + corners[NearBottomRight] + corners[NearTopLeft] + corners[NearTopRight]) + f.GetPlane(Frustum::PlaneId::Near).GetNormal(),
|
|
|
|
//far
|
|
0.25f * (corners[FarBottomLeft] + corners[FarBottomRight] + corners[FarTopLeft] + corners[FarTopRight]),
|
|
0.25f * (corners[FarBottomLeft] + corners[FarBottomRight] + corners[FarTopLeft] + corners[FarTopRight]) + f.GetPlane(Frustum::PlaneId::Far).GetNormal(),
|
|
|
|
//left
|
|
0.5f * (corners[NearBottomLeft] + corners[NearTopLeft]),
|
|
0.5f * (corners[NearBottomLeft] + corners[NearTopLeft]) + f.GetPlane(Frustum::PlaneId::Left).GetNormal(),
|
|
|
|
//right
|
|
0.5f * (corners[NearBottomRight] + corners[NearTopRight]),
|
|
0.5f * (corners[NearBottomRight] + corners[NearTopRight]) + f.GetPlane(Frustum::PlaneId::Right).GetNormal(),
|
|
|
|
//bottom
|
|
0.5f * (corners[NearBottomLeft] + corners[NearBottomRight]),
|
|
0.5f * (corners[NearBottomLeft] + corners[NearBottomRight]) + f.GetPlane(Frustum::PlaneId::Bottom).GetNormal(),
|
|
|
|
//top
|
|
0.5f * (corners[NearTopLeft] + corners[NearTopRight]),
|
|
0.5f * (corners[NearTopLeft] + corners[NearTopRight]) + f.GetPlane(Frustum::PlaneId::Top).GetNormal(),
|
|
};
|
|
Color planeNormalColors[] =
|
|
{
|
|
Colors::Red, Colors::Red, //near
|
|
Colors::Green, Colors::Green, //far
|
|
Colors::Blue, Colors::Blue, //left
|
|
Colors::Orange, Colors::Orange, //right
|
|
Colors::Pink, Colors::Pink, //bottom
|
|
Colors::MediumPurple, Colors::MediumPurple, //top
|
|
};
|
|
AuxGeomDraw::AuxGeomDynamicDrawArguments planeNormalLineArgs;
|
|
planeNormalLineArgs.m_verts = planeNormals;
|
|
planeNormalLineArgs.m_vertCount = 12;
|
|
planeNormalLineArgs.m_colors = planeNormalColors;
|
|
planeNormalLineArgs.m_colorCount = planeNormalLineArgs.m_vertCount;
|
|
planeNormalLineArgs.m_depthTest = AuxGeomDraw::DepthTest::Off;
|
|
auxGeom->DrawLines(planeNormalLineArgs);
|
|
}
|
|
else
|
|
{
|
|
AZ_Assert(false, "invalid frustum, cannot draw");
|
|
}
|
|
}
|
|
#endif //AZ_CULL_DEBUG_ENABLED
|
|
|
|
CullingDebugContext::~CullingDebugContext()
|
|
{
|
|
AZStd::lock_guard<AZStd::mutex> lock(m_perViewCullStatsMutex);
|
|
for (auto& iter : m_perViewCullStats)
|
|
{
|
|
delete iter.second;
|
|
iter.second = nullptr;
|
|
}
|
|
}
|
|
|
|
CullingDebugContext::CullStats& CullingDebugContext::GetCullStatsForView(View* view)
|
|
{
|
|
AZStd::lock_guard<AZStd::mutex> lock(m_perViewCullStatsMutex);
|
|
auto iter = m_perViewCullStats.find(view);
|
|
if (iter != m_perViewCullStats.end())
|
|
{
|
|
AZ_Assert(iter->second->m_name == view->GetName(), "stored view name does not match");
|
|
return *iter->second;
|
|
}
|
|
else
|
|
{
|
|
m_perViewCullStats[view] = aznew CullStats(view->GetName());
|
|
return *m_perViewCullStats[view];
|
|
}
|
|
}
|
|
|
|
void CullingDebugContext::ResetCullStats()
|
|
{
|
|
m_numCullablesInScene = 0;
|
|
|
|
AZStd::lock_guard<AZStd::mutex> lockCullStats(m_perViewCullStatsMutex);
|
|
for (auto& cullStatsPair : m_perViewCullStats)
|
|
{
|
|
cullStatsPair.second->Reset();
|
|
}
|
|
}
|
|
|
|
void CullingScene::RegisterOrUpdateCullable(Cullable& cullable)
|
|
{
|
|
// Multiple threads can call RegisterOrUpdateCullable at the same time
|
|
// since the underlying visScene is thread safe, but if you're inserting or
|
|
// updating between BeginCulling and EndCulling, you'll get non-deterministic
|
|
// results depending on a race condition if you happen to update before or after
|
|
// the culling system starts Enumerating, so use soft_lock_shared here
|
|
m_cullDataConcurrencyCheck.soft_lock_shared();
|
|
m_visScene->InsertOrUpdateEntry(cullable.m_cullData.m_visibilityEntry);
|
|
m_cullDataConcurrencyCheck.soft_unlock_shared();
|
|
}
|
|
|
|
void CullingScene::UnregisterCullable(Cullable& cullable)
|
|
{
|
|
// Multiple threads can call RegisterOrUpdateCullable at the same time
|
|
// since the underlying visScene is thread safe, but if you're inserting or
|
|
// updating between BeginCulling and EndCulling, you'll get non-deterministic
|
|
// results depending on a race condition if you happen to update before or after
|
|
// the culling system starts Enumerating, so use soft_lock_shared here
|
|
m_cullDataConcurrencyCheck.soft_lock_shared();
|
|
m_visScene->RemoveEntry(cullable.m_cullData.m_visibilityEntry);
|
|
m_cullDataConcurrencyCheck.soft_unlock_shared();
|
|
}
|
|
|
|
uint32_t CullingScene::GetNumCullables() const
|
|
{
|
|
return m_visScene->GetEntryCount();
|
|
}
|
|
|
|
|
|
struct WorklistData
|
|
{
|
|
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
|
|
};
|
|
|
|
static AZStd::shared_ptr<WorklistData> MakeWorklistData(
|
|
CullingDebugContext& debugCtx,
|
|
const Scene& scene,
|
|
View& view,
|
|
Frustum& frustum,
|
|
[[maybe_unused]] 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
|
|
worklistData->m_maskedOcclusionCulling = static_cast<MaskedOcclusionCulling*>(maskedOcclusionCulling);
|
|
#endif
|
|
return worklistData;
|
|
}
|
|
|
|
constexpr size_t WorkListCapacity = 5;
|
|
using WorkListType = AZStd::fixed_vector<AzFramework::IVisibilityScene::NodeData, WorkListCapacity>;
|
|
|
|
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
|
|
static MaskedOcclusionCulling::CullingResult TestOcclusionCulling(
|
|
const AZStd::shared_ptr<WorklistData>& worklistData,
|
|
AzFramework::VisibilityEntry* visibleEntry);
|
|
#endif
|
|
|
|
static void ProcessWorklist(const AZStd::shared_ptr<WorklistData>& worklistData, const WorkListType& worklist)
|
|
{
|
|
AZ_PROFILE_SCOPE(RPI, "AddObjectsToViewJob: Process");
|
|
|
|
const View::UsageFlags viewFlags = worklistData->m_view->GetUsageFlags();
|
|
const RHI::DrawListMask drawListMask = worklistData->m_view->GetDrawListMask();
|
|
#ifdef AZ_CULL_DEBUG_ENABLED
|
|
// These variable are only used for the gathering of debug information.
|
|
uint32_t numDrawPackets = 0;
|
|
uint32_t numVisibleCullables = 0;
|
|
#endif
|
|
|
|
AZ_Assert(worklist.size() > 0, "Received empty worklist in ProcessWorklist");
|
|
|
|
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 =
|
|
!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: %ds",
|
|
worklistData->m_view->GetName().GetCStr(), nodeIsContainedInFrustum ? "true" : "false");
|
|
#endif
|
|
|
|
if (nodeIsContainedInFrustum)
|
|
{
|
|
//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 != worklistData->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(worklistData, visibleEntry) == MaskedOcclusionCulling::CullingResult::VISIBLE)
|
|
#endif
|
|
{
|
|
// There are ways to write this without [[maybe_unused]], but they are brittle.
|
|
// For example, using #else could cause a bug where the function's parameter
|
|
// is changed in #ifdef but not in #else.
|
|
[[maybe_unused]] const uint32_t drawPacketCount=AddLodDataToView(c->m_cullData.m_boundingSphere.GetCenter(), c->m_lodData, *worklistData->m_view);
|
|
#ifdef AZ_CULL_DEBUG_ENABLED
|
|
++numVisibleCullables;
|
|
numDrawPackets += drawPacketCount;
|
|
#endif
|
|
|
|
c->m_isVisible = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//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)
|
|
{
|
|
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)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
IntersectResult res = ShapeIntersection::Classify(worklistData->m_frustum, c->m_cullData.m_boundingSphere);
|
|
if (res == IntersectResult::Exterior)
|
|
{
|
|
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
|
|
{
|
|
// There are ways to write this without [[maybe_unused]], but they are brittle.
|
|
// For example, using #else could cause a bug where the function's parameter
|
|
// is changed in #ifdef but not in #else.
|
|
[[maybe_unused]] const uint32_t drawPacketCount=AddLodDataToView(c->m_cullData.m_boundingSphere.GetCenter(), c->m_lodData, *worklistData->m_view);
|
|
#ifdef AZ_CULL_DEBUG_ENABLED
|
|
++numVisibleCullables;
|
|
numDrawPackets += drawPacketCount;
|
|
#endif
|
|
|
|
c->m_isVisible = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#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");
|
|
|
|
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 (worklistData->m_debugCtx->m_drawBoundingBoxes || worklistData->m_debugCtx->m_drawBoundingSpheres || worklistData->m_debugCtx->m_drawLodRadii)
|
|
{
|
|
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 (worklistData->m_debugCtx->m_drawBoundingBoxes)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#ifdef AZ_CULL_DEBUG_ENABLED
|
|
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;
|
|
}
|
|
#endif //AZ_CULL_DEBUG_ENABLED
|
|
}
|
|
|
|
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
|
|
static MaskedOcclusionCulling::CullingResult TestOcclusionCulling(
|
|
const AZStd::shared_ptr<WorklistData>& worklistData,
|
|
AzFramework::VisibilityEntry* visibleEntry)
|
|
{
|
|
if (!worklistData->m_maskedOcclusionCulling)
|
|
{
|
|
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
|
|
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)
|
|
{
|
|
return MaskedOcclusionCulling::CullingResult::VISIBLE;
|
|
}
|
|
|
|
|
|
// 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());
|
|
}
|
|
|
|
// 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::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);
|
|
auto iter = m_debugCtx.m_frozenFrustums.find(&view);
|
|
if (iter != m_debugCtx.m_frozenFrustums.end())
|
|
{
|
|
frustum = iter->second;
|
|
}
|
|
}
|
|
|
|
if (m_debugCtx.m_debugDraw && m_debugCtx.m_drawViewFrustum && view.GetName() == m_debugCtx.m_currentViewSelectionName)
|
|
{
|
|
AuxGeomDrawPtr auxGeomPtr = AuxGeomFeatureProcessorInterface::GetDrawQueueForScene(&scene);
|
|
if (auxGeomPtr)
|
|
{
|
|
DebugDrawFrustum(frustum, auxGeomPtr.get(), AZ::Colors::White);
|
|
}
|
|
}
|
|
|
|
if (m_debugCtx.m_enableStats)
|
|
{
|
|
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();
|
|
if (maskedOcclusionCulling)
|
|
{
|
|
// frustum cull occlusion planes
|
|
using VisibleOcclusionPlane = AZStd::pair<OcclusionPlane, float>;
|
|
AZStd::vector<VisibleOcclusionPlane> visibleOccluders;
|
|
for (const auto& occlusionPlane : m_occlusionPlanes)
|
|
{
|
|
if (ShapeIntersection::Overlaps(frustum, occlusionPlane.m_aabb))
|
|
{
|
|
// occluder is visible, compute view space distance and add to list
|
|
float depth = (view.GetWorldToViewMatrix() * occlusionPlane.m_aabb.GetMin()).GetZ();
|
|
depth = AZStd::min(depth, (view.GetWorldToViewMatrix() * occlusionPlane.m_aabb.GetMax()).GetZ());
|
|
|
|
visibleOccluders.push_back(AZStd::make_pair(occlusionPlane, depth));
|
|
}
|
|
}
|
|
|
|
// sort the occlusion planes by view space distance, front-to-back
|
|
AZStd::sort(visibleOccluders.begin(), visibleOccluders.end(), [](const VisibleOcclusionPlane& LHS, const VisibleOcclusionPlane& RHS)
|
|
{
|
|
return LHS.second > RHS.second;
|
|
});
|
|
|
|
for (const VisibleOcclusionPlane& occlusionPlane: visibleOccluders)
|
|
{
|
|
// convert to clip-space
|
|
Vector4 projectedBL = view.GetWorldToClipMatrix() * Vector4(occlusionPlane.first.m_cornerBL);
|
|
Vector4 projectedTL = view.GetWorldToClipMatrix() * Vector4(occlusionPlane.first.m_cornerTL);
|
|
Vector4 projectedTR = view.GetWorldToClipMatrix() * Vector4(occlusionPlane.first.m_cornerTR);
|
|
Vector4 projectedBR = view.GetWorldToClipMatrix() * Vector4(occlusionPlane.first.m_cornerBR);
|
|
|
|
// store to float array
|
|
float verts[16];
|
|
projectedBL.StoreToFloat4(&verts[0]);
|
|
projectedTL.StoreToFloat4(&verts[4]);
|
|
projectedTR.StoreToFloat4(&verts[8]);
|
|
projectedBR.StoreToFloat4(&verts[12]);
|
|
|
|
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
|
|
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<WorklistData> worklistData = MakeWorklistData(m_debugCtx, scene, view, frustum, maskedOcclusionCulling);
|
|
|
|
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");
|
|
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 job (AddObjectsToViewJob) once the queue is full.
|
|
//This reduces the number of jobs in flight, reducing job-system overhead.
|
|
worklist.emplace_back(AZStd::move(nodeData));
|
|
|
|
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
|
|
AZ::Job* job = AZ::CreateJobFunction(processWorklist, true);
|
|
worklist.clear();
|
|
parentJob.SetContinuation(job);
|
|
job->Start();
|
|
}
|
|
};
|
|
|
|
if (m_debugCtx.m_enableFrustumCulling)
|
|
{
|
|
m_visScene->Enumerate(frustum, nodeVisitorLambda);
|
|
}
|
|
else
|
|
{
|
|
m_visScene->EnumerateNoCull(nodeVisitorLambda);
|
|
}
|
|
|
|
if (worklist.size() > 0)
|
|
{
|
|
// 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
|
|
AZ_PROFILE_SCOPE(RPI, "AddLodDataToView");
|
|
#endif
|
|
|
|
const Matrix4x4& viewToClip = view.GetViewToClipMatrix();
|
|
//the [1][1] element of a perspective projection matrix stores cot(FovY/2) (equal to 2*nearPlaneDistance/nearPlaneHeight),
|
|
//which is used to determine the (vertical) projected size in screen space
|
|
const float yScale = viewToClip.GetElement(1, 1);
|
|
const bool isPerspective = viewToClip.GetElement(3, 3) == 0.f;
|
|
const Vector3 cameraPos = view.GetViewToWorldMatrix().GetTranslation();
|
|
|
|
const float approxScreenPercentage = ModelLodUtils::ApproxScreenPercentage(
|
|
pos, lodData.m_lodSelectionRadius, cameraPos, yScale, isPerspective);
|
|
|
|
uint32_t numVisibleDrawPackets = 0;
|
|
|
|
auto addLodToDrawPacket = [&](const Cullable::LodData::Lod& lod)
|
|
{
|
|
#ifdef AZ_CULL_PROFILE_VERBOSE
|
|
AZ_PROFILE_SCOPE(RPI, "add draw packets: %zu", lod.m_drawPackets.size());
|
|
#endif
|
|
numVisibleDrawPackets += static_cast<uint32_t>(lod.m_drawPackets.size()); //don't want to pay the cost of aznumeric_cast<> here so using static_cast<> instead
|
|
for (const RHI::DrawPacket* drawPacket : lod.m_drawPackets)
|
|
{
|
|
view.AddDrawPacket(drawPacket, pos);
|
|
}
|
|
};
|
|
|
|
switch (lodData.m_lodConfiguration.m_lodType)
|
|
{
|
|
case Cullable::LodType::SpecificLod:
|
|
if (lodData.m_lodConfiguration.m_lodOverride < lodData.m_lods.size())
|
|
{
|
|
addLodToDrawPacket(lodData.m_lods.at(lodData.m_lodConfiguration.m_lodOverride));
|
|
}
|
|
break;
|
|
case Cullable::LodType::ScreenCoverage:
|
|
default:
|
|
for (const Cullable::LodData::Lod& lod : lodData.m_lods)
|
|
{
|
|
// Note that this supports overlapping lod ranges (to suport cross-fading lods, for example)
|
|
if (approxScreenPercentage >= lod.m_screenCoverageMin && approxScreenPercentage <= lod.m_screenCoverageMax)
|
|
{
|
|
addLodToDrawPacket(lod);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
return numVisibleDrawPackets;
|
|
}
|
|
|
|
void CullingScene::Activate(const Scene* parentScene)
|
|
{
|
|
m_parentScene = parentScene;
|
|
|
|
AZ_Assert(m_visScene == nullptr, "IVisibilityScene already created for this RPI::Scene");
|
|
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
|
|
}
|
|
|
|
void CullingScene::Deactivate()
|
|
{
|
|
#ifdef AZ_CULL_DEBUG_ENABLED
|
|
AZ_Assert(CountObjectsInScene() == 0, "All culling entries must be removed from the scene before shutdown.");
|
|
#endif
|
|
if (m_visScene)
|
|
{
|
|
AZ::Interface<AzFramework::IVisibilitySystem>::Get()->DestroyVisibilityScene(m_visScene);
|
|
m_visScene = nullptr;
|
|
}
|
|
}
|
|
|
|
void CullingScene::BeginCullingTaskGraph(const AZStd::vector<ViewPtr>& views)
|
|
{
|
|
AZ::TaskGraph taskGraph;
|
|
AZ::TaskDescriptor beginCullingDescriptor{"RPI_CullingScene_BeginCullingView", "Graphics"};
|
|
for (auto& view : views)
|
|
{
|
|
taskGraph.AddTask(
|
|
beginCullingDescriptor,
|
|
[&view]()
|
|
{
|
|
AZ_PROFILE_SCOPE(RPI, "CullingScene: BeginCullingTaskGraph");
|
|
view->BeginCulling();
|
|
});
|
|
}
|
|
|
|
AZ::TaskGraphEvent waitForCompletion;
|
|
taskGraph.Submit(&waitForCompletion);
|
|
waitForCompletion.Wait();
|
|
}
|
|
|
|
void CullingScene::BeginCullingJobs(const AZStd::vector<ViewPtr>& views)
|
|
{
|
|
AZ::JobCompletion beginCullingCompletion;
|
|
|
|
for (auto& view : views)
|
|
{
|
|
const auto cullingLambda = [&view]()
|
|
{
|
|
AZ_PROFILE_SCOPE(RPI, "CullingScene: BeginCullingJob");
|
|
view->BeginCulling();
|
|
};
|
|
|
|
AZ::Job* cullingJob = AZ::CreateJobFunction(AZStd::move(cullingLambda), true, nullptr);
|
|
cullingJob->SetDependent(&beginCullingCompletion);
|
|
cullingJob->Start();
|
|
}
|
|
|
|
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(views.size() == 1) // avoid job overhead when only 1 job
|
|
{
|
|
views[0]->BeginCulling();
|
|
}
|
|
else if (m_taskGraphActive && m_taskGraphActive->IsTaskGraphActive())
|
|
{
|
|
BeginCullingTaskGraph(views);
|
|
}
|
|
else
|
|
{
|
|
BeginCullingJobs(views);
|
|
}
|
|
|
|
#ifdef AZ_CULL_DEBUG_ENABLED
|
|
AuxGeomDrawPtr auxGeom;
|
|
if (m_debugCtx.m_debugDraw)
|
|
{
|
|
auxGeom = AuxGeomFeatureProcessorInterface::GetDrawQueueForScene(m_parentScene);
|
|
AZ_Assert(auxGeom, "Invalid AuxGeomFeatureProcessorInterface");
|
|
|
|
if (m_debugCtx.m_drawWorldCoordinateAxes)
|
|
{
|
|
DebugDrawWorldCoordinateAxes(auxGeom.get());
|
|
}
|
|
}
|
|
|
|
{
|
|
AZStd::lock_guard<AZStd::mutex> lockFrozenFrustums(m_debugCtx.m_frozenFrustumsMutex);
|
|
if (m_debugCtx.m_freezeFrustums)
|
|
{
|
|
for (const ViewPtr& viewPtr : views)
|
|
{
|
|
auto iter = m_debugCtx.m_frozenFrustums.find(viewPtr.get());
|
|
if (iter == m_debugCtx.m_frozenFrustums.end())
|
|
{
|
|
const Matrix4x4& worldToClip = viewPtr->GetWorldToClipMatrix();
|
|
Frustum frustum = Frustum::CreateFromMatrixColumnMajor(worldToClip, Frustum::ReverseDepth::True);
|
|
m_debugCtx.m_frozenFrustums.insert({ viewPtr.get(), frustum });
|
|
}
|
|
}
|
|
}
|
|
else if(m_debugCtx.m_frozenFrustums.size() > 0)
|
|
{
|
|
m_debugCtx.m_frozenFrustums.clear();
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void CullingScene::EndCulling()
|
|
{
|
|
m_cullDataConcurrencyCheck.soft_unlock();
|
|
}
|
|
|
|
size_t CullingScene::CountObjectsInScene()
|
|
{
|
|
size_t numObjects = 0;
|
|
m_visScene->EnumerateNoCull(
|
|
[this, &numObjects](const AzFramework::IVisibilityScene::NodeData& nodeData)
|
|
{
|
|
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_scene == m_parentScene) //[GFX_TODO][ATOM-13796] once the IVisibilitySystem supports multiple octree scenes, remove this
|
|
{
|
|
++numObjects;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
);
|
|
|
|
return numObjects;
|
|
}
|
|
|
|
} // namespace RPI
|
|
} // namespace AZ
|