From 17e9c17f311bca70b61b3295c4aef49d23a2dd46 Mon Sep 17 00:00:00 2001 From: Doug McDiarmid Date: Wed, 26 May 2021 19:18:18 -0700 Subject: [PATCH] Added Occlusion Culling Planes and RPI Culling support for Masked Occlusion Culling --- ...ionCullingPlaneFeatureProcessorInterface.h | 41 +++++ .../Code/Source/CommonSystemComponent.cpp | 4 + .../OcclusionCullingPlaneFeatureProcessor.cpp | 96 ++++++++++ .../OcclusionCullingPlaneFeatureProcessor.h | 75 ++++++++ .../Code/atom_feature_common_files.cmake | 2 + .../atom_feature_common_public_files.cmake | 1 + .../Code/Include/Atom/RPI.Public/Culling.h | 12 +- .../RPI/Code/Include/Atom/RPI.Public/View.h | 12 +- .../Source/Platform/Windows/PAL_windows.cmake | 13 ++ .../RPI/Code/Source/RPI.Public/Culling.cpp | 171 +++++++++++++----- .../Atom/RPI/Code/Source/RPI.Public/Scene.cpp | 3 +- Gems/Atom/RPI/Code/Source/RPI.Public/View.cpp | 68 ++++++- .../Atom/RPI/Code/atom_rpi_public_files.cmake | 4 + .../CommonFeatures/Code/Source/Module.cpp | 4 + .../EditorOcclusionCullingPlaneComponent.cpp | 91 ++++++++++ .../EditorOcclusionCullingPlaneComponent.h | 43 +++++ .../OcclusionCullingPlaneComponent.cpp | 43 +++++ .../OcclusionCullingPlaneComponent.h | 37 ++++ .../OcclusionCullingPlaneComponentConstants.h | 22 +++ ...clusionCullingPlaneComponentController.cpp | 137 ++++++++++++++ ...OcclusionCullingPlaneComponentController.h | 78 ++++++++ ...egration_commonfeatures_editor_files.cmake | 2 + ...omlyintegration_commonfeatures_files.cmake | 4 + 23 files changed, 913 insertions(+), 50 deletions(-) create mode 100644 Gems/Atom/Feature/Common/Code/Include/Atom/Feature/OcclusionCullingPlane/OcclusionCullingPlaneFeatureProcessorInterface.h create mode 100644 Gems/Atom/Feature/Common/Code/Source/OcclusionCullingPlane/OcclusionCullingPlaneFeatureProcessor.cpp create mode 100644 Gems/Atom/Feature/Common/Code/Source/OcclusionCullingPlane/OcclusionCullingPlaneFeatureProcessor.h create mode 100644 Gems/AtomLyIntegration/CommonFeatures/Code/Source/OcclusionCullingPlane/EditorOcclusionCullingPlaneComponent.cpp create mode 100644 Gems/AtomLyIntegration/CommonFeatures/Code/Source/OcclusionCullingPlane/EditorOcclusionCullingPlaneComponent.h create mode 100644 Gems/AtomLyIntegration/CommonFeatures/Code/Source/OcclusionCullingPlane/OcclusionCullingPlaneComponent.cpp create mode 100644 Gems/AtomLyIntegration/CommonFeatures/Code/Source/OcclusionCullingPlane/OcclusionCullingPlaneComponent.h create mode 100644 Gems/AtomLyIntegration/CommonFeatures/Code/Source/OcclusionCullingPlane/OcclusionCullingPlaneComponentConstants.h create mode 100644 Gems/AtomLyIntegration/CommonFeatures/Code/Source/OcclusionCullingPlane/OcclusionCullingPlaneComponentController.cpp create mode 100644 Gems/AtomLyIntegration/CommonFeatures/Code/Source/OcclusionCullingPlane/OcclusionCullingPlaneComponentController.h diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/OcclusionCullingPlane/OcclusionCullingPlaneFeatureProcessorInterface.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/OcclusionCullingPlane/OcclusionCullingPlaneFeatureProcessorInterface.h new file mode 100644 index 0000000000..07a6179e78 --- /dev/null +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/OcclusionCullingPlane/OcclusionCullingPlaneFeatureProcessorInterface.h @@ -0,0 +1,41 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#include +#include +#include + +namespace AZ +{ + namespace Render + { + class OcclusionCullingPlane; + + using OcclusionCullingPlaneHandle = AZStd::shared_ptr; + + // OcclusionCullingPlaneFeatureProcessorInterface provides an interface to the feature processor for code outside of Atom + class OcclusionCullingPlaneFeatureProcessorInterface + : public RPI::FeatureProcessor + { + public: + AZ_RTTI(AZ::Render::OcclusionCullingPlaneFeatureProcessorInterface, "{50F6B45E-A622-44EC-B962-DE25FBD44095}"); + + virtual OcclusionCullingPlaneHandle AddOcclusionCullingPlane(const AZ::Transform& transform) = 0; + virtual void RemoveOcclusionCullingPlane(OcclusionCullingPlaneHandle& handle) = 0; + virtual bool IsValidOcclusionCullingPlaneHandle(const OcclusionCullingPlaneHandle& occlusionCullingPlane) const = 0; + virtual void SetTransform(const OcclusionCullingPlaneHandle& occlusionCullingPlane, const AZ::Transform& transform) = 0; + virtual void SetEnabled(const OcclusionCullingPlaneHandle& occlusionCullingPlane, bool enabled) = 0; + }; + } // namespace Render +} // namespace AZ diff --git a/Gems/Atom/Feature/Common/Code/Source/CommonSystemComponent.cpp b/Gems/Atom/Feature/Common/Code/Source/CommonSystemComponent.cpp index 089a6168b1..614e614c06 100644 --- a/Gems/Atom/Feature/Common/Code/Source/CommonSystemComponent.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/CommonSystemComponent.cpp @@ -102,6 +102,7 @@ #include #include #include +#include namespace AZ { @@ -137,6 +138,7 @@ namespace AZ ModelPreset::Reflect(context); DiffuseProbeGridFeatureProcessor::Reflect(context); RayTracingFeatureProcessor::Reflect(context); + OcclusionCullingPlaneFeatureProcessor::Reflect(context); if (SerializeContext* serialize = azrtti_cast(context)) { @@ -193,6 +195,7 @@ namespace AZ AZ::RPI::FeatureProcessorFactory::Get()->RegisterFeatureProcessor(); AZ::RPI::FeatureProcessorFactory::Get()->RegisterFeatureProcessor(); AZ::RPI::FeatureProcessorFactory::Get()->RegisterFeatureProcessor(); + AZ::RPI::FeatureProcessorFactory::Get()->RegisterFeatureProcessor(); // Add SkyBox pass auto* passSystem = RPI::PassSystemInterface::Get(); @@ -295,6 +298,7 @@ namespace AZ AZ::RPI::FeatureProcessorFactory::Get()->UnregisterFeatureProcessor(); AZ::RPI::FeatureProcessorFactory::Get()->UnregisterFeatureProcessor(); AZ::RPI::FeatureProcessorFactory::Get()->UnregisterFeatureProcessor(); + AZ::RPI::FeatureProcessorFactory::Get()->UnregisterFeatureProcessor(); } void CommonSystemComponent::LoadPassTemplateMappings() diff --git a/Gems/Atom/Feature/Common/Code/Source/OcclusionCullingPlane/OcclusionCullingPlaneFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/OcclusionCullingPlane/OcclusionCullingPlaneFeatureProcessor.cpp new file mode 100644 index 0000000000..d4a1a37521 --- /dev/null +++ b/Gems/Atom/Feature/Common/Code/Source/OcclusionCullingPlane/OcclusionCullingPlaneFeatureProcessor.cpp @@ -0,0 +1,96 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include +#include +#include +#include +#include + +namespace AZ +{ + namespace Render + { + void OcclusionCullingPlaneFeatureProcessor::Reflect(ReflectContext* context) + { + if (auto* serializeContext = azrtti_cast(context)) + { + serializeContext + ->Class() + ->Version(0); + } + } + + void OcclusionCullingPlaneFeatureProcessor::Activate() + { + m_occlusionCullingPlanes.reserve(InitialOcclusionCullingPlanesAllocationSize); + + EnableSceneNotification(); + } + + void OcclusionCullingPlaneFeatureProcessor::Deactivate() + { + AZ_Warning("OcclusionCullingPlaneFeatureProcessor", m_occlusionCullingPlanes.size() == 0, + "Deactivating the OcclusionCullingPlaneFeatureProcessor, but there are still outstanding occlusion planes. Components\n" + "using OcclusionCullingPlaneHandles should free them before the OcclusionCullingPlaneFeatureProcessor is deactivated.\n" + ); + + DisableSceneNotification(); + } + + void OcclusionCullingPlaneFeatureProcessor::Simulate([[maybe_unused]] const FeatureProcessor::SimulatePacket& packet) + { + AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender); + + AZStd::vector occlusionCullingPlanes; + for (auto& occlusionCullingPlane : m_occlusionCullingPlanes) + { + occlusionCullingPlanes.push_back(occlusionCullingPlane->GetTransform()); + } + GetParentScene()->GetCullingScene()->SetOcclusionCullingPlanes(occlusionCullingPlanes); + } + + OcclusionCullingPlaneHandle OcclusionCullingPlaneFeatureProcessor::AddOcclusionCullingPlane(const AZ::Transform& transform) + { + AZStd::shared_ptr occlusionCullingPlane = AZStd::make_shared(); + occlusionCullingPlane->SetTransform(transform); + m_occlusionCullingPlanes.push_back(occlusionCullingPlane); + return occlusionCullingPlane; + } + + void OcclusionCullingPlaneFeatureProcessor::RemoveOcclusionCullingPlane(OcclusionCullingPlaneHandle& occlusionCullingPlane) + { + AZ_Assert(occlusionCullingPlane.get(), "RemoveOcclusionCullingPlane called with an invalid handle"); + + auto itEntry = AZStd::find_if(m_occlusionCullingPlanes.begin(), m_occlusionCullingPlanes.end(), [&](AZStd::shared_ptr const& entry) + { + return (entry == occlusionCullingPlane); + }); + + AZ_Assert(itEntry != m_occlusionCullingPlanes.end(), "RemoveOcclusionCullingPlane called with an occlusion plane that is not in the occlusion plane list"); + m_occlusionCullingPlanes.erase(itEntry); + occlusionCullingPlane = nullptr; + } + + void OcclusionCullingPlaneFeatureProcessor::SetTransform(const OcclusionCullingPlaneHandle& occlusionCullingPlane, const AZ::Transform& transform) + { + AZ_Assert(occlusionCullingPlane.get(), "SetTransform called with an invalid handle"); + occlusionCullingPlane->SetTransform(transform); + } + + void OcclusionCullingPlaneFeatureProcessor::SetEnabled(const OcclusionCullingPlaneHandle& occlusionCullingPlane, bool enabled) + { + AZ_Assert(occlusionCullingPlane.get(), "Enable called with an invalid handle"); + occlusionCullingPlane->SetEnabled(enabled); + } + } // namespace Render +} // namespace AZ diff --git a/Gems/Atom/Feature/Common/Code/Source/OcclusionCullingPlane/OcclusionCullingPlaneFeatureProcessor.h b/Gems/Atom/Feature/Common/Code/Source/OcclusionCullingPlane/OcclusionCullingPlaneFeatureProcessor.h new file mode 100644 index 0000000000..c54c816bfd --- /dev/null +++ b/Gems/Atom/Feature/Common/Code/Source/OcclusionCullingPlane/OcclusionCullingPlaneFeatureProcessor.h @@ -0,0 +1,75 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#include + +namespace AZ +{ + namespace Render + { + //! This class represents an OcclusionCullingPlane which is used to cull meshes that are inside the view frustum + class OcclusionCullingPlane final + { + public: + OcclusionCullingPlane() = default; + ~OcclusionCullingPlane() = default; + + void SetTransform(const AZ::Transform& transform) { m_transform = transform; } + const AZ::Transform& GetTransform() const { return m_transform; } + + void SetEnabled(bool enabled) { m_enabled = enabled; } + bool GetEnabled() const { return m_enabled; } + + private: + AZ::Transform m_transform; + bool m_enabled = true; + }; + + //! This class manages OcclusionCullingPlanes which are used to cull meshes that are inside the view frustum + class OcclusionCullingPlaneFeatureProcessor final + : public OcclusionCullingPlaneFeatureProcessorInterface + { + public: + AZ_RTTI(AZ::Render::OcclusionCullingPlaneFeatureProcessor, "{C3DE91D7-EF7A-4A82-A55F-E22BC52074EA}", OcclusionCullingPlaneFeatureProcessorInterface); + + static void Reflect(AZ::ReflectContext* context); + + OcclusionCullingPlaneFeatureProcessor() = default; + virtual ~OcclusionCullingPlaneFeatureProcessor() = default; + + // OcclusionCullingPlaneFeatureProcessorInterface overrides + OcclusionCullingPlaneHandle AddOcclusionCullingPlane(const AZ::Transform& transform) override; + void RemoveOcclusionCullingPlane(OcclusionCullingPlaneHandle& handle) override; + bool IsValidOcclusionCullingPlaneHandle(const OcclusionCullingPlaneHandle& occlusionCullingPlane) const override { return (occlusionCullingPlane.get() != nullptr); } + void SetTransform(const OcclusionCullingPlaneHandle& occlusionCullingPlane, const AZ::Transform& transform) override; + void SetEnabled(const OcclusionCullingPlaneHandle& occlusionCullingPlane, bool enable) override; + + // FeatureProcessor overrides + void Activate() override; + void Deactivate() override; + void Simulate(const FeatureProcessor::SimulatePacket& packet) override; + + // retrieve the full list of occlusion planes + using OcclusionCullingPlaneVector = AZStd::vector>; + OcclusionCullingPlaneVector& GetOcclusionCullingPlanes() { return m_occlusionCullingPlanes; } + + private: + AZ_DISABLE_COPY_MOVE(OcclusionCullingPlaneFeatureProcessor); + + // list of occlusion planes + const size_t InitialOcclusionCullingPlanesAllocationSize = 64; + OcclusionCullingPlaneVector m_occlusionCullingPlanes; + }; + } // namespace Render +} // namespace AZ diff --git a/Gems/Atom/Feature/Common/Code/atom_feature_common_files.cmake b/Gems/Atom/Feature/Common/Code/atom_feature_common_files.cmake index 8926b0c19f..f3ee1b4757 100644 --- a/Gems/Atom/Feature/Common/Code/atom_feature_common_files.cmake +++ b/Gems/Atom/Feature/Common/Code/atom_feature_common_files.cmake @@ -175,6 +175,8 @@ set(FILES Source/MorphTargets/MorphTargetComputePass.h Source/MorphTargets/MorphTargetDispatchItem.cpp Source/MorphTargets/MorphTargetDispatchItem.h + Source/OcclusionCullingPlane/OcclusionCullingPlaneFeatureProcessor.h + Source/OcclusionCullingPlane/OcclusionCullingPlaneFeatureProcessor.cpp Source/PostProcess/PostProcessBase.cpp Source/PostProcess/PostProcessBase.h Source/PostProcess/PostProcessFeatureProcessor.cpp diff --git a/Gems/Atom/Feature/Common/Code/atom_feature_common_public_files.cmake b/Gems/Atom/Feature/Common/Code/atom_feature_common_public_files.cmake index 9034859707..3df15f9a70 100644 --- a/Gems/Atom/Feature/Common/Code/atom_feature_common_public_files.cmake +++ b/Gems/Atom/Feature/Common/Code/atom_feature_common_public_files.cmake @@ -44,6 +44,7 @@ set(FILES Include/Atom/Feature/ParamMacros/StartParamFunctionsVirtual.inl Include/Atom/Feature/ParamMacros/StartParamMembers.inl Include/Atom/Feature/ParamMacros/StartParamSerializeContext.inl + Include/Atom/Feature/OcclusionCullingPlane/OcclusionCullingPlaneFeatureProcessorInterface.h Include/Atom/Feature/PostProcess/PostProcessFeatureProcessorInterface.h Include/Atom/Feature/PostProcess/PostProcessParams.inl Include/Atom/Feature/PostProcess/PostProcessSettings.inl diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Culling.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Culling.h index c7e9cc4706..17e0a1f82d 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Culling.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Culling.h @@ -31,7 +31,7 @@ #include #include - +#include #include #include @@ -213,8 +213,11 @@ namespace AZ void Activate(const class Scene* parentScene); void Deactivate(); + //! Sets a list of occlusion planes to be used during the culling process. + void SetOcclusionCullingPlanes(const AZStd::vector& occlusionCullingPlanes) { m_occlusionCullingPlanes = occlusionCullingPlanes; } + //! Notifies the CullingScene that culling will begin for this frame. - void BeginCulling(const AZStd::vector& views); + void BeginCulling(const AZStd::vector& views, const AZStd::vector& activePipelines); //! Notifies the CullingScene that the culling is done for this frame. void EndCulling(); @@ -251,12 +254,9 @@ namespace AZ const Scene* m_parentScene = nullptr; AzFramework::IVisibilityScene* m_visScene = nullptr; - CullingDebugContext m_debugCtx; - AZStd::concurrency_checker m_cullDataConcurrencyCheck; - - AZStd::mutex m_mutex; + AZStd::vector m_occlusionCullingPlanes; }; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/View.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/View.h index aad099dc23..74c841d2a5 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/View.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/View.h @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -57,7 +58,7 @@ namespace AZ //! Only use this function to create a new view object. And force using smart pointer to manage view's life time static ViewPtr CreateView(const AZ::Name& name, UsageFlags usage); - ~View() = default; + ~View(); void SetDrawListMask(const RHI::DrawListMask& drawListMask); RHI::DrawListMask GetDrawListMask() const { return m_drawListMask; } @@ -126,6 +127,12 @@ namespace AZ //! Notifies consumers when the world to clip matrix has changed. void ConnectWorldToClipMatrixChangedHandler(MatrixChangedEvent::Handler& handler); + //! Prepare for view culling + void BeginCulling(const AZStd::vector& activePipelines); + + //! Returns the masked occlusion culling interface + MaskedOcclusionCulling* GetMaskedOcclusionCulling(); + private: View() = delete; View(const AZ::Name& name, UsageFlags usage); @@ -193,6 +200,9 @@ namespace AZ MatrixChangedEvent m_onWorldToClipMatrixChange; MatrixChangedEvent m_onWorldToViewMatrixChange; + + // Software occlusion culling + MaskedOcclusionCulling* m_maskedOcclusionCulling = nullptr; }; AZ_DEFINE_ENUM_BITWISE_OPERATORS(View::UsageFlags); diff --git a/Gems/Atom/RPI/Code/Source/Platform/Windows/PAL_windows.cmake b/Gems/Atom/RPI/Code/Source/Platform/Windows/PAL_windows.cmake index c060b8bbaa..51e42d5216 100644 --- a/Gems/Atom/RPI/Code/Source/Platform/Windows/PAL_windows.cmake +++ b/Gems/Atom/RPI/Code/Source/Platform/Windows/PAL_windows.cmake @@ -10,3 +10,16 @@ # set (PAL_TRAIT_BUILD_ATOM_RPI_ASSETS_SUPPORTED TRUE) + +ly_add_source_properties( + SOURCES Source/RPI.Public/External/MaskedOcclusionCulling/MaskedOcclusionCullingAVX2.cpp + PROPERTY COMPILE_OPTIONS + VALUES /arch:AVX2 /W3 +) +ly_add_source_properties( + SOURCES + Source/RPI.Public/External/MaskedOcclusionCulling/MaskedOcclusionCullingAVX512.cpp + Source/RPI.Public/External/MaskedOcclusionCulling/MaskedOcclusionCulling.cpp + PROPERTY COMPILE_OPTIONS + VALUES /W3 +) \ No newline at end of file diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Culling.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Culling.cpp index c64f08e4f8..ab25fb14fb 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Culling.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Culling.cpp @@ -262,21 +262,24 @@ namespace AZ public: AZ_CLASS_ALLOCATOR(AddObjectsToViewJob, ThreadPoolAllocator, 0); + struct JobData + { + CullingDebugContext* m_debugCtx = nullptr; + MaskedOcclusionCulling* m_maskedOcclusionCulling = nullptr; + const Scene* m_scene = nullptr; + View* m_view = nullptr; + Frustum m_frustum; + }; + private: - CullingDebugContext* m_debugCtx; - const Scene* m_scene; - View* m_view; - Frustum m_frustum; + const AZStd::shared_ptr m_jobData; CullingScene::WorkListType m_worklist; public: - AddObjectsToViewJob(CullingDebugContext& debugCtx, const Scene& scene, View& view, Frustum& frustum, CullingScene::WorkListType& worklist) + AddObjectsToViewJob(const AZStd::shared_ptr& jobData, CullingScene::WorkListType& worklist) : Job(true, nullptr) //auto-deletes, no JobContext - , m_debugCtx(&debugCtx) - , m_scene(&scene) - , m_view(&view) - , m_frustum(frustum) //capture by value - , m_worklist(AZStd::move(worklist)) //capture by value + , m_jobData(jobData) + , m_worklist(worklist) { } @@ -285,37 +288,40 @@ namespace AZ { AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender); - const View::UsageFlags viewFlags = m_view->GetUsageFlags(); - const RHI::DrawListMask drawListMask = m_view->GetDrawListMask(); + 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; 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_frustum, nodeData.m_bounds); + bool nodeIsContainedInFrustum = ShapeIntersection::Contains(m_jobData->m_frustum, nodeData.m_bounds); #ifdef AZ_CULL_PROFILE_VERBOSE AZ_PROFILE_SCOPE_DYNAMIC(Debug::ProfileCategory::AzRender, "process node (view: %s, skip fine cull: %d", m_view->GetName().GetCStr(), nodeIsContainedInFrustum ? 1 : 0); #endif - if (nodeIsContainedInFrustum || !m_debugCtx->m_enableFrustumCulling) + 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) + if (TestOcclusionCulling(visibleEntry) == MaskedOcclusionCulling::CullingResult::VISIBLE) { - Cullable* c = static_cast(visibleEntry->m_userData); - if ((c->m_cullData.m_drawListMask & drawListMask).none() || - c->m_cullData.m_hideFlags & viewFlags || - c->m_cullData.m_scene != m_scene) //[GFX_TODO][ATOM-13796] once the IVisibilitySystem supports multiple octree scenes, remove this + if (visibleEntry->m_typeFlags & AzFramework::VisibilityEntry::TYPE_RPI_Cullable) { - continue; + Cullable* c = static_cast(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 + { + continue; + } + numDrawPackets += AddLodDataToView(c->m_cullData.m_boundingSphere.GetCenter(), c->m_lodData, *m_jobData->m_view); + ++numVisibleCullables; } - numDrawPackets += AddLodDataToView(c->m_cullData.m_boundingSphere.GetCenter(), c->m_lodData, *m_view); - ++numVisibleCullables; } } } @@ -329,66 +335,69 @@ namespace AZ Cullable* c = static_cast(visibleEntry->m_userData); if ((c->m_cullData.m_drawListMask & drawListMask).none() || c->m_cullData.m_hideFlags & viewFlags || - c->m_cullData.m_scene != m_scene) //[GFX_TODO][ATOM-13796] once the IVisibilitySystem supports multiple octree scenes, remove this + c->m_cullData.m_scene != m_jobData->m_scene) //[GFX_TODO][ATOM-13796] once the IVisibilitySystem supports multiple octree scenes, remove this { continue; } - IntersectResult res = ShapeIntersection::Classify(m_frustum, c->m_cullData.m_boundingSphere); + 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_frustum, c->m_cullData.m_boundingObb)) + else if (res == IntersectResult::Interior || ShapeIntersection::Overlaps(m_jobData->m_frustum, c->m_cullData.m_boundingObb)) { - numDrawPackets += AddLodDataToView(c->m_cullData.m_boundingSphere.GetCenter(), c->m_lodData, *m_view); - ++numVisibleCullables; + if (TestOcclusionCulling(visibleEntry) == MaskedOcclusionCulling::CullingResult::VISIBLE) + { + numDrawPackets += AddLodDataToView(c->m_cullData.m_boundingSphere.GetCenter(), c->m_lodData, *m_jobData->m_view); + ++numVisibleCullables; + } } } } } - if (m_debugCtx->m_debugDraw && (m_view->GetName() == m_debugCtx->m_currentViewSelectionName)) + if (m_jobData->m_debugCtx->m_debugDraw && (m_jobData->m_view->GetName() == m_jobData->m_debugCtx->m_currentViewSelectionName)) { AZ_PROFILE_SCOPE(Debug::ProfileCategory::AzRender, "debug draw culling"); - AuxGeomDrawPtr auxGeomPtr = AuxGeomFeatureProcessorInterface::GetDrawQueueForScene(m_scene); + AuxGeomDrawPtr auxGeomPtr = AuxGeomFeatureProcessorInterface::GetDrawQueueForScene(m_jobData->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 && m_debugCtx->m_drawFullyVisibleNodes) + if (nodeIsContainedInFrustum && m_jobData->m_debugCtx->m_drawFullyVisibleNodes) { auxGeomPtr->DrawAabb(nodeData.m_bounds, Colors::Lime, RPI::AuxGeomDraw::DrawStyle::Line, RPI::AuxGeomDraw::DepthTest::Off); } - else if (!nodeIsContainedInFrustum && m_debugCtx->m_drawPartiallyVisibleNodes) + else if (!nodeIsContainedInFrustum && m_jobData->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_debugCtx->m_drawBoundingBoxes || m_debugCtx->m_drawBoundingSpheres || m_debugCtx->m_drawLodRadii) + if (m_jobData->m_debugCtx->m_drawBoundingBoxes || m_jobData->m_debugCtx->m_drawBoundingSpheres || m_jobData->m_debugCtx->m_drawLodRadii) { for (AzFramework::VisibilityEntry* visibleEntry : nodeData.m_entries) { if (visibleEntry->m_typeFlags & AzFramework::VisibilityEntry::TYPE_RPI_Cullable) { Cullable* c = static_cast(visibleEntry->m_userData); - if (m_debugCtx->m_drawBoundingBoxes) + 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_debugCtx->m_drawBoundingSpheres) + 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_debugCtx->m_drawLodRadii) + if (m_jobData->m_debugCtx->m_drawLodRadii) { auxGeomPtr->DrawSphere(c->m_cullData.m_boundingSphere.GetCenter(), c->m_lodData.m_lodSelectionRadius, @@ -401,9 +410,9 @@ namespace AZ } } - if (m_debugCtx->m_enableStats) + if (m_jobData->m_debugCtx->m_enableStats) { - CullingDebugContext::CullStats& cullStats = m_debugCtx->GetCullStatsForView(m_view); + CullingDebugContext::CullStats& cullStats = m_jobData->m_debugCtx->GetCullStatsForView(m_jobData->m_view); //no need for mutex here since these are all atomics cullStats.m_numVisibleDrawPackets += numDrawPackets; @@ -411,6 +420,29 @@ namespace AZ ++cullStats.m_numJobs; } } + + MaskedOcclusionCulling::CullingResult TestOcclusionCulling(AzFramework::VisibilityEntry* visibleEntry) + { + if (!m_jobData->m_maskedOcclusionCulling) + { + return MaskedOcclusionCulling::CullingResult::VISIBLE; + } + + // convert the bounding box of the visibility entry to NDC + AZ::Vector4 clipSpaceMin = m_jobData->m_view->GetWorldToClipMatrix() * Vector4(visibleEntry->m_boundingVolume.GetMin()); + float depth = clipSpaceMin.GetW(); + AZ::Vector4 ndcMin = clipSpaceMin / clipSpaceMin.GetW(); + + AZ::Vector4 clipSpaceMax = m_jobData->m_view->GetWorldToClipMatrix() * Vector4(visibleEntry->m_boundingVolume.GetMax()); + depth = AZStd::min(depth, clipSpaceMax.GetW()); + AZ::Vector4 ndcMax = clipSpaceMax / clipSpaceMax.GetW(); + + Vector2 rectMin(AZStd::min(ndcMin.GetX(), ndcMax.GetX()), AZStd::min(ndcMin.GetY(), ndcMax.GetY())); + Vector2 rectMax(AZStd::max(ndcMin.GetX(), ndcMax.GetX()), AZStd::max(ndcMin.GetY(), ndcMax.GetY())); + + // test against the occlusion buffer, which contains only the manually placed occlusion planes + return m_jobData->m_maskedOcclusionCulling->TestRect(rectMin.GetX(), rectMin.GetY(), rectMax.GetX(), rectMax.GetY(), depth); + } }; void CullingScene::ProcessCullables(const Scene& scene, View& view, AZ::Job& parentJob) @@ -444,8 +476,53 @@ namespace AZ cullStats.m_cameraViewToWorld = view.GetViewToWorldMatrix(); } + // setup occlusion culling, if necessary + MaskedOcclusionCulling* maskedOcclusionCulling = m_occlusionCullingPlanes.empty() ? nullptr : view.GetMaskedOcclusionCulling(); + if (maskedOcclusionCulling) + { + for (const AZ::Transform& transform : m_occlusionCullingPlanes) + { + // find the corners of the plane + static const Vector3 BL = Vector3(-0.5f, -0.5f, 0.0f); + static const Vector3 BR = Vector3(0.5f, -0.5f, 0.0f); + static const Vector3 TL = Vector3(-0.5f, 0.5f, 0.0f); + static const Vector3 TR = Vector3(0.5f, 0.5f, 0.0f); + + Vector3 planeBL = transform.TransformPoint(BL); + Vector3 planeBR = transform.TransformPoint(BR); + Vector3 planeTL = transform.TransformPoint(TL); + Vector3 planeTR = transform.TransformPoint(TR); + + // convert to clip-space + Vector4 projectedBL = view.GetWorldToClipMatrix() * Vector4(planeBL); + Vector4 projectedBR = view.GetWorldToClipMatrix() * Vector4(planeBR); + Vector4 projectedTL = view.GetWorldToClipMatrix() * Vector4(planeTL); + Vector4 projectedTR = view.GetWorldToClipMatrix() * Vector4(planeTR); + + // store to float array + float verts[16]; + projectedBL.StoreToFloat4(&verts[0]); + projectedBR.StoreToFloat4(&verts[4]); + projectedTL.StoreToFloat4(&verts[8]); + projectedTR.StoreToFloat4(&verts[12]); + + static uint32_t indices[6] = { 0, 2, 1, 2, 3, 1 }; + + // 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); + } + } + WorkListType worklist; - auto nodeVisitorLambda = [this, &scene, &view, &parentJob, &frustum, &worklist](const AzFramework::IVisibilityScene::NodeData& nodeData) -> void + + AZStd::shared_ptr jobData = AZStd::make_shared(); + jobData->m_debugCtx = &m_debugCtx; + jobData->m_maskedOcclusionCulling = maskedOcclusionCulling; + jobData->m_scene = &scene; + jobData->m_view = &view; + jobData->m_frustum = frustum; + + auto nodeVisitorLambda = [this, jobData, &parentJob, &frustum, &worklist](const AzFramework::IVisibilityScene::NodeData& nodeData) -> void { AZ_PROFILE_SCOPE(Debug::ProfileCategory::AzRender, "nodeVisitorLambda()"); AZ_Assert(nodeData.m_entries.size() > 0, "should not get called with 0 entries"); @@ -458,7 +535,7 @@ namespace AZ if (worklist.size() == worklist.capacity()) { //Kick off a job to process the (full) worklist - AddObjectsToViewJob* job = aznew AddObjectsToViewJob(m_debugCtx, scene, view, frustum, worklist); //pool allocated (cheap), auto-deletes when job finishes + AddObjectsToViewJob* job = aznew AddObjectsToViewJob(jobData, worklist); //pool allocated (cheap), auto-deletes when job finishes worklist.clear(); parentJob.SetContinuation(job); job->Start(); @@ -476,8 +553,15 @@ namespace AZ if (worklist.size() > 0) { + AZStd::shared_ptr remainingJobData = AZStd::make_shared(); + remainingJobData->m_debugCtx = &m_debugCtx; + remainingJobData->m_maskedOcclusionCulling = maskedOcclusionCulling; + remainingJobData->m_scene = &scene; + remainingJobData->m_view = &view; + remainingJobData->m_frustum = frustum; + //Kick off a job to process any remaining workitems - AddObjectsToViewJob* job = aznew AddObjectsToViewJob(m_debugCtx, scene, view, frustum, worklist); //pool allocated (cheap), auto-deletes when job finishes + AddObjectsToViewJob* job = aznew AddObjectsToViewJob(remainingJobData, worklist); //pool allocated (cheap), auto-deletes when job finishes parentJob.SetContinuation(job); job->Start(); } @@ -559,13 +643,18 @@ namespace AZ } } - void CullingScene::BeginCulling(const AZStd::vector& views) + void CullingScene::BeginCulling(const AZStd::vector& views, const AZStd::vector& activePipelines) { m_cullDataConcurrencyCheck.soft_lock(); m_debugCtx.ResetCullStats(); m_debugCtx.m_numCullablesInScene = GetNumCullables(); + for (auto& view : views) + { + view->BeginCulling(activePipelines); + } + AuxGeomDrawPtr auxGeom; if (m_debugCtx.m_debugDraw) { diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Scene.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Scene.cpp index 7e33750eb5..16c2189a00 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Scene.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Scene.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -499,7 +500,7 @@ namespace AZ } // Launch CullingSystem::ProcessCullables() jobs (will run concurrently with FeatureProcessor::Render() jobs) - m_cullingScene->BeginCulling(m_renderPacket.m_views); + m_cullingScene->BeginCulling(m_renderPacket.m_views, activePipelines); for (ViewPtr& viewPtr : m_renderPacket.m_views) { AZ::Job* processCullablesJob = AZ::CreateJobFunction([this, &viewPtr](AZ::Job& thisJob) diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/View.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/View.cpp index 21a46693d5..3060fc34a3 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/View.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/View.cpp @@ -15,7 +15,8 @@ #include #include #include - +#include +#include #include #include @@ -51,6 +52,18 @@ namespace AZ { m_shaderResourceGroup = ShaderResourceGroup::Create(viewSrgAsset); } + + m_maskedOcclusionCulling = MaskedOcclusionCulling::Create(); + m_maskedOcclusionCulling->SetNearClipPlane(0.1f); + } + + View::~View() + { + if (m_maskedOcclusionCulling) + { + MaskedOcclusionCulling::Destroy(m_maskedOcclusionCulling); + m_maskedOcclusionCulling = nullptr; + } } void View::SetDrawListMask(const RHI::DrawListMask& drawListMask) @@ -374,5 +387,58 @@ namespace AZ m_shaderResourceGroup->Compile(); m_needBuildSrg = false; } + + void View::BeginCulling(const AZStd::vector& activePipelines) + { + // retrieve current resolution + Vector2 resolution(0.0f, 0.0f); + for (auto& pipeline : activePipelines) + { + ViewPtr pipelineView = pipeline->GetDefaultView(); + if (pipelineView.get() == this) + { + RPI::SwapChainPass* pass = AZ::RPI::PassSystemInterface::Get()->FindSwapChainPass(pipeline->GetWindowHandle()); + if (pass) + { + const RHI::Viewport& viewport = pass->GetViewport(); + resolution.SetX(viewport.m_maxX); + resolution.SetY(viewport.m_maxY); + } + break; + } + } + + // calculate culling resolution based on required tile size for MaskedOcclusionCulling + static const uint32_t MaskedOcclusionCullingSubTileWidth = 8; + static const uint32_t MaskedOcclusionCullingSubTileHeight = 4; + + uint32_t cullingWidth = RHI::AlignUp(resolution.GetX(), MaskedOcclusionCullingSubTileWidth); + uint32_t cullingHeight = RHI::AlignUp(resolution.GetY(), MaskedOcclusionCullingSubTileHeight); + + m_maskedOcclusionCulling->SetResolution(cullingWidth, cullingHeight); + + if (cullingWidth > 0 && cullingHeight > 0) + { + m_maskedOcclusionCulling->ClearBuffer(); + } + } + + MaskedOcclusionCulling* View::GetMaskedOcclusionCulling() + { + if (m_maskedOcclusionCulling) + { + uint32_t width = 0; + uint32_t height = 0; + + m_maskedOcclusionCulling->GetResolution(width, height); + if (width > 0 && height > 0) + { + return m_maskedOcclusionCulling; + } + } + + return nullptr; + } + } // namespace RPI } // namespace AZ diff --git a/Gems/Atom/RPI/Code/atom_rpi_public_files.cmake b/Gems/Atom/RPI/Code/atom_rpi_public_files.cmake index 0d5c19758b..5ae72fe42f 100644 --- a/Gems/Atom/RPI/Code/atom_rpi_public_files.cmake +++ b/Gems/Atom/RPI/Code/atom_rpi_public_files.cmake @@ -101,6 +101,7 @@ set(FILES Include/Atom/RPI.Public/GpuQuery/Query.h Include/Atom/RPI.Public/GpuQuery/QueryPool.h Include/Atom/RPI.Public/GpuQuery/TimestampQueryPool.h + Include/Atom/RPI.Public/External/MaskedOcclusionCulling/MaskedOcclusionCulling.h Source/RPI.Public/Culling.cpp Source/RPI.Public/FeatureProcessor.cpp Source/RPI.Public/FeatureProcessorFactory.cpp @@ -178,4 +179,7 @@ set(FILES Source/RPI.Public/GpuQuery/Query.cpp Source/RPI.Public/GpuQuery/QueryPool.cpp Source/RPI.Public/GpuQuery/TimestampQueryPool.cpp + Source/RPI.Public/External/MaskedOcclusionCulling/MaskedOcclusionCulling.cpp + Source/RPI.Public/External/MaskedOcclusionCulling/MaskedOcclusionCullingAVX2.cpp + Source/RPI.Public/External/MaskedOcclusionCulling/MaskedOcclusionCullingAVX512.cpp ) diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Module.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Module.cpp index 2ef4e1e229..368d8e76e7 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Module.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Module.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -55,6 +56,7 @@ #include #include #include +#include #include #include #include @@ -114,6 +116,7 @@ namespace AZ DeferredFogComponent::CreateDescriptor(), SurfaceData::SurfaceDataMeshComponent::CreateDescriptor(), AttachmentComponent::CreateDescriptor(), + OcclusionCullingPlaneComponent::CreateDescriptor(), #ifdef ATOMLYINTEGRATION_FEATURE_COMMON_EDITOR EditorAreaLightComponent::CreateDescriptor(), @@ -145,6 +148,7 @@ namespace AZ EditorDeferredFogComponent::CreateDescriptor(), SurfaceData::EditorSurfaceDataMeshComponent::CreateDescriptor(), EditorAttachmentComponent::CreateDescriptor(), + EditorOcclusionCullingPlaneComponent::CreateDescriptor(), #endif }); } diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/OcclusionCullingPlane/EditorOcclusionCullingPlaneComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/OcclusionCullingPlane/EditorOcclusionCullingPlaneComponent.cpp new file mode 100644 index 0000000000..9a655727d6 --- /dev/null +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/OcclusionCullingPlane/EditorOcclusionCullingPlaneComponent.cpp @@ -0,0 +1,91 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include +#include +#include +#include +#include +#include + +namespace AZ +{ + namespace Render + { + void EditorOcclusionCullingPlaneComponent::Reflect(AZ::ReflectContext* context) + { + BaseClass::Reflect(context); + + if (AZ::SerializeContext* serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(1, ConvertToEditorRenderComponentAdapter<1>) + ; + + if (AZ::EditContext* editContext = serializeContext->GetEditContext()) + { + editContext->Class( + "Occlusion Culling Plane", "The OcclusionCullingPlane component is used to cull meshes that are inside the view frustum and behind the occlusion plane") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::Category, "Atom") + ->Attribute(AZ::Edit::Attributes::Icon, "Icons/Components/Component_Placeholder.svg") + ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/Component_Placeholder.png") + ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c)) + ->Attribute(AZ::Edit::Attributes::AutoExpand, true) + ; + + editContext->Class( + "OcclusionCullingPlaneComponentController", "") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::AutoExpand, true) + ->DataElement(AZ::Edit::UIHandlers::Default, &OcclusionCullingPlaneComponentController::m_configuration, "Configuration", "") + ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) + ; + + editContext->Class( + "OcclusionCullingPlaneComponentConfig", "") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::AutoExpand, true) + ; + } + } + + if (auto behaviorContext = azrtti_cast(context)) + { + behaviorContext->ConstantProperty("EditorOcclusionCullingPlaneComponentTypeId", BehaviorConstant(Uuid(EditorOcclusionCullingPlaneComponentTypeId))) + ->Attribute(AZ::Script::Attributes::Module, "render") + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation); + } + } + + EditorOcclusionCullingPlaneComponent::EditorOcclusionCullingPlaneComponent() + { + } + + EditorOcclusionCullingPlaneComponent::EditorOcclusionCullingPlaneComponent(const OcclusionCullingPlaneComponentConfig& config) + : BaseClass(config) + { + } + + void EditorOcclusionCullingPlaneComponent::Activate() + { + BaseClass::Activate(); + AzFramework::EntityDebugDisplayEventBus::Handler::BusConnect(GetEntityId()); + } + + void EditorOcclusionCullingPlaneComponent::Deactivate() + { + AzFramework::EntityDebugDisplayEventBus::Handler::BusDisconnect(); + BaseClass::Deactivate(); + } + } // namespace Render +} // namespace AZ diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/OcclusionCullingPlane/EditorOcclusionCullingPlaneComponent.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/OcclusionCullingPlane/EditorOcclusionCullingPlaneComponent.h new file mode 100644 index 0000000000..8070c1d553 --- /dev/null +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/OcclusionCullingPlane/EditorOcclusionCullingPlaneComponent.h @@ -0,0 +1,43 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#include +#include +#include +#include +#include + +namespace AZ +{ + namespace Render + { + class EditorOcclusionCullingPlaneComponent final + : public EditorRenderComponentAdapter + , private AzFramework::EntityDebugDisplayEventBus::Handler + { + public: + using BaseClass = EditorRenderComponentAdapter; + AZ_EDITOR_COMPONENT(AZ::Render::EditorOcclusionCullingPlaneComponent, EditorOcclusionCullingPlaneComponentTypeId, BaseClass); + + static void Reflect(AZ::ReflectContext* context); + + EditorOcclusionCullingPlaneComponent(); + EditorOcclusionCullingPlaneComponent(const OcclusionCullingPlaneComponentConfig& config); + + // AZ::Component overrides + void Activate() override; + void Deactivate() override; + }; + } // namespace Render +} // namespace AZ diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/OcclusionCullingPlane/OcclusionCullingPlaneComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/OcclusionCullingPlane/OcclusionCullingPlaneComponent.cpp new file mode 100644 index 0000000000..567809266d --- /dev/null +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/OcclusionCullingPlane/OcclusionCullingPlaneComponent.cpp @@ -0,0 +1,43 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include + +namespace AZ +{ + namespace Render + { + OcclusionCullingPlaneComponent::OcclusionCullingPlaneComponent(const OcclusionCullingPlaneComponentConfig& config) + : BaseClass(config) + { + } + + void OcclusionCullingPlaneComponent::Reflect(AZ::ReflectContext* context) + { + BaseClass::Reflect(context); + + if (auto serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(0) + ; + } + + if (auto behaviorContext = azrtti_cast(context)) + { + behaviorContext->ConstantProperty("OcclusionCullingPlaneComponentTypeId", BehaviorConstant(Uuid(OcclusionCullingPlaneComponentTypeId))) + ->Attribute(AZ::Script::Attributes::Module, "render") + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common); + } + } + } // namespace Render +} // namespace AZ diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/OcclusionCullingPlane/OcclusionCullingPlaneComponent.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/OcclusionCullingPlane/OcclusionCullingPlaneComponent.h new file mode 100644 index 0000000000..7e7b48bd45 --- /dev/null +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/OcclusionCullingPlane/OcclusionCullingPlaneComponent.h @@ -0,0 +1,37 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#include +#include +#include + +namespace AZ +{ + namespace Render + { + class OcclusionCullingPlaneComponent final + : public AzFramework::Components::ComponentAdapter + { + public: + using BaseClass = AzFramework::Components::ComponentAdapter; + AZ_COMPONENT(AZ::Render::OcclusionCullingPlaneComponent, OcclusionCullingPlaneComponentTypeId, BaseClass); + + OcclusionCullingPlaneComponent() = default; + OcclusionCullingPlaneComponent(const OcclusionCullingPlaneComponentConfig& config); + + static void Reflect(AZ::ReflectContext* context); + }; + + } // namespace Render +} // namespace AZ diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/OcclusionCullingPlane/OcclusionCullingPlaneComponentConstants.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/OcclusionCullingPlane/OcclusionCullingPlaneComponentConstants.h new file mode 100644 index 0000000000..59276de9ee --- /dev/null +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/OcclusionCullingPlane/OcclusionCullingPlaneComponentConstants.h @@ -0,0 +1,22 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +namespace AZ +{ + namespace Render + { + static constexpr const char* const OcclusionCullingPlaneComponentTypeId = "{F7537387-15A8-48F0-A1F3-D19C5886B886}"; + static constexpr const char* const EditorOcclusionCullingPlaneComponentTypeId = "{BE7CC17B-32EB-49B0-BAD9-D26E3A059012}"; + } // namespace Render +} // namespace AZ diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/OcclusionCullingPlane/OcclusionCullingPlaneComponentController.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/OcclusionCullingPlane/OcclusionCullingPlaneComponentController.cpp new file mode 100644 index 0000000000..bd03bec0f4 --- /dev/null +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/OcclusionCullingPlane/OcclusionCullingPlaneComponentController.cpp @@ -0,0 +1,137 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include + +namespace AZ +{ + namespace Render + { + void OcclusionCullingPlaneComponentConfig::Reflect(ReflectContext* context) + { + if (auto* serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(0) + ; + } + } + + void OcclusionCullingPlaneComponentController::Reflect(ReflectContext* context) + { + OcclusionCullingPlaneComponentConfig::Reflect(context); + + if (auto* serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(0) + ->Field("Configuration", &OcclusionCullingPlaneComponentController::m_configuration); + } + } + + void OcclusionCullingPlaneComponentController::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent) + { + dependent.push_back(AZ_CRC("TransformService", 0x8ee22c50)); + } + + void OcclusionCullingPlaneComponentController::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) + { + provided.push_back(AZ_CRC("OcclusionCullingPlaneService", 0x9123f33d)); + } + + void OcclusionCullingPlaneComponentController::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) + { + incompatible.push_back(AZ_CRC("OcclusionCullingPlaneService", 0x9123f33d)); + } + + void OcclusionCullingPlaneComponentController::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required) + { + required.push_back(AZ_CRC("TransformService")); + } + + OcclusionCullingPlaneComponentController::OcclusionCullingPlaneComponentController(const OcclusionCullingPlaneComponentConfig& config) + : m_configuration(config) + { + } + + void OcclusionCullingPlaneComponentController::Activate(AZ::EntityId entityId) + { + m_entityId = entityId; + + TransformNotificationBus::Handler::BusConnect(m_entityId); + + m_featureProcessor = RPI::Scene::GetFeatureProcessorForEntity(entityId); + AZ_Assert(m_featureProcessor, "OcclusionCullingPlaneComponentController was unable to find a OcclusionCullingPlaneFeatureProcessor on the EntityContext provided."); + + m_transformInterface = TransformBus::FindFirstHandler(entityId); + AZ_Assert(m_transformInterface, "Unable to attach to a TransformBus handler"); + if (!m_transformInterface) + { + return; + } + + // add this occlusion plane to the feature processor + const AZ::Transform& transform = m_transformInterface->GetWorldTM(); + m_handle = m_featureProcessor->AddOcclusionCullingPlane(transform); + } + + void OcclusionCullingPlaneComponentController::Deactivate() + { + if (m_featureProcessor) + { + m_featureProcessor->RemoveOcclusionCullingPlane(m_handle); + } + + Data::AssetBus::MultiHandler::BusDisconnect(); + TransformNotificationBus::Handler::BusDisconnect(); + + m_transformInterface = nullptr; + m_featureProcessor = nullptr; + } + + void OcclusionCullingPlaneComponentController::SetConfiguration(const OcclusionCullingPlaneComponentConfig& config) + { + m_configuration = config; + } + + const OcclusionCullingPlaneComponentConfig& OcclusionCullingPlaneComponentController::GetConfiguration() const + { + return m_configuration; + } + + void OcclusionCullingPlaneComponentController::OnTransformChanged([[maybe_unused]] const AZ::Transform& local, const AZ::Transform& world) + { + if (!m_featureProcessor) + { + return; + } + + m_featureProcessor->SetTransform(m_handle, world); + } + } // namespace Render +} // namespace AZ diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/OcclusionCullingPlane/OcclusionCullingPlaneComponentController.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/OcclusionCullingPlane/OcclusionCullingPlaneComponentController.h new file mode 100644 index 0000000000..5f0be5315f --- /dev/null +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/OcclusionCullingPlane/OcclusionCullingPlaneComponentController.h @@ -0,0 +1,78 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace AZ +{ + namespace Render + { + class OcclusionCullingPlaneComponentConfig final + : public AZ::ComponentConfig + { + public: + AZ_RTTI(AZ::Render::OcclusionCullingPlaneComponentConfig, "{D0E107CA-5AFB-4675-BC97-94BCA5F248DB}", ComponentConfig); + AZ_CLASS_ALLOCATOR(OcclusionCullingPlaneComponentConfig, SystemAllocator, 0); + static void Reflect(AZ::ReflectContext* context); + + OcclusionCullingPlaneComponentConfig() = default; + }; + + class OcclusionCullingPlaneComponentController final + : public Data::AssetBus::MultiHandler + , private TransformNotificationBus::Handler + { + public: + friend class EditorOcclusionCullingPlaneComponent; + + AZ_CLASS_ALLOCATOR(OcclusionCullingPlaneComponentController, AZ::SystemAllocator, 0); + AZ_RTTI(AZ::Render::OcclusionCullingPlaneComponentController, "{8EDA3C7D-5171-4843-9969-4D84DB13F221}"); + + static void Reflect(AZ::ReflectContext* context); + static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent); + static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided); + static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible); + static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required); + + OcclusionCullingPlaneComponentController() = default; + OcclusionCullingPlaneComponentController(const OcclusionCullingPlaneComponentConfig& config); + + void Activate(AZ::EntityId entityId); + void Deactivate(); + void SetConfiguration(const OcclusionCullingPlaneComponentConfig& config); + const OcclusionCullingPlaneComponentConfig& GetConfiguration() const; + + private: + + AZ_DISABLE_COPY(OcclusionCullingPlaneComponentController); + + // TransformNotificationBus overrides + void OnTransformChanged(const AZ::Transform& local, const AZ::Transform& world) override; + + // handle for this occlusion plane in the feature processor + OcclusionCullingPlaneHandle m_handle; + + OcclusionCullingPlaneFeatureProcessorInterface* m_featureProcessor = nullptr; + TransformInterface* m_transformInterface = nullptr; + AZ::EntityId m_entityId; + OcclusionCullingPlaneComponentConfig m_configuration; + }; + } // namespace Render +} // namespace AZ diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/atomlyintegration_commonfeatures_editor_files.cmake b/Gems/AtomLyIntegration/CommonFeatures/Code/atomlyintegration_commonfeatures_editor_files.cmake index e58f72a121..360511aaea 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/atomlyintegration_commonfeatures_editor_files.cmake +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/atomlyintegration_commonfeatures_editor_files.cmake @@ -53,6 +53,8 @@ set(FILES Source/Mesh/EditorMeshSystemComponent.h Source/Mesh/MeshThumbnail.h Source/Mesh/MeshThumbnail.cpp + Source/OcclusionCullingPlane/EditorOcclusionCullingPlaneComponent.h + Source/OcclusionCullingPlane/EditorOcclusionCullingPlaneComponent.cpp Source/PostProcess/EditorPostFxLayerComponent.cpp Source/PostProcess/EditorPostFxLayerComponent.h Source/PostProcess/Bloom/EditorBloomComponent.cpp diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/atomlyintegration_commonfeatures_files.cmake b/Gems/AtomLyIntegration/CommonFeatures/Code/atomlyintegration_commonfeatures_files.cmake index e13d1d37d6..deb8ab1b74 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/atomlyintegration_commonfeatures_files.cmake +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/atomlyintegration_commonfeatures_files.cmake @@ -66,6 +66,10 @@ set(FILES Source/Mesh/MeshComponent.cpp Source/Mesh/MeshComponentController.h Source/Mesh/MeshComponentController.cpp + Source/OcclusionCullingPlane/OcclusionCullingPlaneComponent.h + Source/OcclusionCullingPlane/OcclusionCullingPlaneComponent.cpp + Source/OcclusionCullingPlane/OcclusionCullingPlaneComponentController.h + Source/OcclusionCullingPlane/OcclusionCullingPlaneComponentController.cpp Source/PostProcess/PostFxLayerComponent.cpp Source/PostProcess/PostFxLayerComponent.h Source/PostProcess/PostFxLayerComponentConfig.cpp