diff --git a/Code/Editor/ViewportTitleDlg.cpp b/Code/Editor/ViewportTitleDlg.cpp index 0d05e0bf44..75d16e9a40 100644 --- a/Code/Editor/ViewportTitleDlg.cpp +++ b/Code/Editor/ViewportTitleDlg.cpp @@ -15,6 +15,7 @@ #include "ViewportTitleDlg.h" // Qt +#include #include #include @@ -39,6 +40,7 @@ #include #include #include +#include #include #include @@ -146,6 +148,11 @@ CViewportTitleDlg::~CViewportTitleDlg() AZ::VR::VREventBus::Handler::BusDisconnect(); GetISystem()->GetISystemEventDispatcher()->RemoveListener(this); GetIEditor()->UnregisterNotifyListener(this); + + if (m_prefabViewportFocusPathHandler) + { + delete m_prefabViewportFocusPathHandler; + } } void CViewportTitleDlg::SetupCameraDropdownMenu() @@ -293,8 +300,6 @@ void CViewportTitleDlg::SetViewPane(CLayoutViewPane* pViewPane) ////////////////////////////////////////////////////////////////////////// void CViewportTitleDlg::OnInitDialog() { - m_ui->m_titleBtn->setText(m_title); - // Add a child parented to us that listens for r_displayInfo changes. auto displayInfoHelper = new CViewportTitleDlgDisplayInfoHelper(this); connect(displayInfoHelper, &CViewportTitleDlgDisplayInfoHelper::ViewportInfoStatusUpdated, this, &CViewportTitleDlg::UpdateDisplayInfo); @@ -315,13 +320,28 @@ void CViewportTitleDlg::OnInitDialog() m_cameraSpeed->setFixedWidth(width); + bool isPrefabSystemEnabled = false; + AzFramework::ApplicationRequests::Bus::BroadcastResult(isPrefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled); + + if (isPrefabSystemEnabled) + { + m_prefabViewportFocusPathHandler = new AzToolsFramework::Prefab::PrefabViewportFocusPathHandler(); + m_prefabViewportFocusPathHandler->Initialize(m_ui->m_prefabFocusPath, m_ui->m_prefabFocusBackButton); + } + else + { + m_ui->m_prefabFocusPath->setEnabled(false); + m_ui->m_prefabFocusBackButton->setEnabled(false); + m_ui->m_prefabFocusPath->hide(); + m_ui->m_prefabFocusBackButton->hide(); + } + } ////////////////////////////////////////////////////////////////////////// void CViewportTitleDlg::SetTitle(const QString& title) { m_title = title; - m_ui->m_titleBtn->setText(m_title); } ////////////////////////////////////////////////////////////////////////// diff --git a/Code/Editor/ViewportTitleDlg.h b/Code/Editor/ViewportTitleDlg.h index 4a2a454907..6996fe7750 100644 --- a/Code/Editor/ViewportTitleDlg.h +++ b/Code/Editor/ViewportTitleDlg.h @@ -19,6 +19,7 @@ #include #include +#include #include #include @@ -176,6 +177,8 @@ protected: QWidgetAction* m_gridSizeActionWidget = nullptr; QWidgetAction* m_angleSizeActionWidget = nullptr; + AzToolsFramework::Prefab::PrefabViewportFocusPathHandler* m_prefabViewportFocusPathHandler = nullptr; + QScopedPointer m_ui; }; diff --git a/Code/Editor/ViewportTitleDlg.ui b/Code/Editor/ViewportTitleDlg.ui index f0679dd2a1..7ba6361cf9 100644 --- a/Code/Editor/ViewportTitleDlg.ui +++ b/Code/Editor/ViewportTitleDlg.ui @@ -28,7 +28,7 @@ 29 - + 10 @@ -42,102 +42,112 @@ 0 - + + + Up one level + + + + :/Breadcrumb/img/UI20/Breadcrumb/arrow_left-default.svg:/Breadcrumb/img/UI20/Breadcrumb/arrow_left-default.svg + + + + + 0 0 - - Qt::NoContextMenu + + + + + + Qt::Horizontal - - Static + + + 40 + 20 + - - 11 + + + + + + Camera settings + + + + :/Menu/camera.svg:/Menu/camera.svg - - - - :/Menu/camera.svg:/Menu/camera.svg - - - - Camera settings - - - - - - - Debug information - - - - :/Menu/debug.svg:/Menu/debug.svg - - - - true - - - - - - - Toggle viewport helpers - - - - :/Menu/helpers.svg:/Menu/helpers.svg - - - - true - - - - - - - Viewport resolution - - - - :/Menu/resolution.svg:/Menu/resolution.svg - - - + + + Debug information + + + + :/Menu/debug.svg:/Menu/debug.svg + + + true + + - - - Other settings - - - - :/Menu/menu.svg:/Menu/menu.svg - - - + + + Toggle viewport helpers + + + + :/Menu/helpers.svg:/Menu/helpers.svg + + + true + + + + + + + Viewport resolution + + + + :/Menu/resolution.svg:/Menu/resolution.svg + + + + + + + Other settings + + + + :/Menu/menu.svg:/Menu/menu.svg + + - AzQtComponents::ButtonDivider + AzQtComponents::BreadCrumbs QWidget -
AzQtComponents/Components/ButtonDivider.h
+
AzQtComponents/Components/Widgets/BreadCrumbs.h
1
- - - - + + + + diff --git a/Code/Framework/AzCore/AzCore/AzCoreModule.cpp b/Code/Framework/AzCore/AzCore/AzCoreModule.cpp index c60ea1bd72..ec45335f95 100644 --- a/Code/Framework/AzCore/AzCore/AzCoreModule.cpp +++ b/Code/Framework/AzCore/AzCore/AzCoreModule.cpp @@ -22,6 +22,7 @@ #include #include #include +#include namespace AZ { @@ -41,6 +42,7 @@ namespace AZ TimeSystemComponent::CreateDescriptor(), LoggerSystemComponent::CreateDescriptor(), EventSchedulerSystemComponent::CreateDescriptor(), + TaskGraphSystemComponent::CreateDescriptor(), #if !defined(AZCORE_EXCLUDE_LUA) ScriptSystemComponent::CreateDescriptor(), @@ -55,6 +57,7 @@ namespace AZ azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), + azrtti_typeid(), }; } } diff --git a/Code/Framework/AzCore/AzCore/Task/TaskExecutor.cpp b/Code/Framework/AzCore/AzCore/Task/TaskExecutor.cpp index 2bb88fbfa2..d2d19d1359 100644 --- a/Code/Framework/AzCore/AzCore/Task/TaskExecutor.cpp +++ b/Code/Framework/AzCore/AzCore/Task/TaskExecutor.cpp @@ -190,11 +190,13 @@ namespace AZ class TaskWorker { public: - void Spawn(::AZ::TaskExecutor& executor, size_t id, AZStd::semaphore& initSemaphore, bool affinitize) + static thread_local TaskWorker* t_worker; + + void Spawn(::AZ::TaskExecutor& executor, uint32_t id, AZStd::semaphore& initSemaphore, bool affinitize) { m_executor = &executor; - AZStd::string threadName = AZStd::string::format("TaskWorker %zu", id); + AZStd::string threadName = AZStd::string::format("TaskWorker %u", id); AZStd::thread_desc desc = {}; desc.m_name = threadName.c_str(); if (affinitize) @@ -205,12 +207,29 @@ namespace AZ m_thread = AZStd::thread{ [this, &initSemaphore] { + t_worker = this; initSemaphore.release(); Run(); }, &desc }; } + // Threads that wait on a graph to complete are disqualified from receiving tasks until the wait finishes + void Disable() + { + m_enabled = false; + } + + void Enable() + { + m_enabled = true; + } + + bool Enabled() const + { + return m_enabled; + } + void Join() { m_active.store(false, AZStd::memory_order_release); @@ -222,11 +241,7 @@ namespace AZ { m_queue.Enqueue(task); - if (!m_busy.exchange(true)) - { - // The worker was idle prior to enqueueing the task, release the semaphore - m_semaphore.release(); - } + m_semaphore.release(); } private: @@ -234,7 +249,6 @@ namespace AZ { while (m_active) { - m_busy = false; m_semaphore.acquire(); if (!m_active) @@ -242,8 +256,6 @@ namespace AZ return; } - m_busy = true; - Task* task = m_queue.TryDequeue(); while (task) { @@ -271,12 +283,15 @@ namespace AZ AZStd::thread m_thread; AZStd::atomic m_active; - AZStd::atomic m_busy; + AZStd::atomic m_enabled = true; AZStd::binary_semaphore m_semaphore; ::AZ::TaskExecutor* m_executor; TaskQueue m_queue; + friend class ::AZ::TaskExecutor; }; + + thread_local TaskWorker* TaskWorker::t_worker = nullptr; } // namespace Internal static EnvironmentVariable s_executor; @@ -291,13 +306,16 @@ namespace AZ return **s_executor; } - // TODO: Create the default executor as part of a component (as in TaskManagerComponent) void TaskExecutor::SetInstance(TaskExecutor* executor) { - AZ_Assert(!s_executor, "Attempting to set the global task executor more than once"); - - s_executor = AZ::Environment::CreateVariable("GlobalTaskExecutor"); - s_executor.Set(executor); + if (!executor) + { + s_executor.Reset(); + } + else if (!s_executor) // ignore any calls to set after the first (this happens in unit tests that create new system entities) + { + s_executor = AZ::Environment::CreateVariable(s_executorName, executor); + } } TaskExecutor::TaskExecutor(uint32_t threadCount) @@ -307,14 +325,12 @@ namespace AZ m_workers = reinterpret_cast(azmalloc(m_threadCount * sizeof(Internal::TaskWorker))); - bool affinitize = m_threadCount == AZStd::thread::hardware_concurrency(); - AZStd::semaphore initSemaphore; - for (size_t i = 0; i != m_threadCount; ++i) + for (uint32_t i = 0; i != m_threadCount; ++i) { new (m_workers + i) Internal::TaskWorker{}; - m_workers[i].Spawn(*this, i, initSemaphore, affinitize); + m_workers[i].Spawn(*this, i, initSemaphore, false); } for (size_t i = 0; i != m_threadCount; ++i) @@ -334,9 +350,21 @@ namespace AZ azfree(m_workers); } - void TaskExecutor::Submit(Internal::CompiledTaskGraph& graph) + Internal::TaskWorker* TaskExecutor::GetTaskWorker() + { + if (Internal::TaskWorker::t_worker && Internal::TaskWorker::t_worker->m_executor == this) + { + return Internal::TaskWorker::t_worker; + } + return nullptr; + } + + void TaskExecutor::Submit(Internal::CompiledTaskGraph& graph, TaskGraphEvent* event) { ++m_graphsRemaining; + + event->m_executor = this; // Used to validate event is not waited for inside a job + // Submit all tasks that have no inbound edges for (Internal::Task& task : graph.Tasks()) { @@ -352,11 +380,24 @@ namespace AZ // TODO: Something more sophisticated is likely needed here. // First, we are completely ignoring affinity. // Second, some heuristics on core availability will help distribute work more effectively - m_workers[++m_lastSubmission % m_threadCount].Enqueue(&task); + uint32_t nextWorker = ++m_lastSubmission % m_threadCount; + while (!m_workers[nextWorker].Enabled()) + { + // Graphs that are waiting for the completion of a task graph cannot enqueue tasks onto + // the thread issuing the wait. + nextWorker = ++m_lastSubmission % m_threadCount; + } + + m_workers[nextWorker].Enqueue(&task); } void TaskExecutor::ReleaseGraph() { --m_graphsRemaining; } + + void TaskExecutor::ReactivateTaskWorker() + { + GetTaskWorker()->Enable(); + } } // namespace AZ diff --git a/Code/Framework/AzCore/AzCore/Task/TaskExecutor.h b/Code/Framework/AzCore/AzCore/Task/TaskExecutor.h index dc2fa5a4c8..7e1ff80902 100644 --- a/Code/Framework/AzCore/AzCore/Task/TaskExecutor.h +++ b/Code/Framework/AzCore/AzCore/Task/TaskExecutor.h @@ -72,14 +72,19 @@ namespace AZ explicit TaskExecutor(uint32_t threadCount = 0); ~TaskExecutor(); - void Submit(Internal::CompiledTaskGraph& graph); + // Submit a task graph for execution. Waitable task graphs cannot enqueue work on the task thread + // that is currently active + void Submit(Internal::CompiledTaskGraph& graph, TaskGraphEvent* event); void Submit(Internal::Task& task); private: friend class Internal::TaskWorker; + friend class TaskGraphEvent; + Internal::TaskWorker* GetTaskWorker(); void ReleaseGraph(); + void ReactivateTaskWorker(); Internal::TaskWorker* m_workers; uint32_t m_threadCount = 0; diff --git a/Code/Framework/AzCore/AzCore/Task/TaskGraph.cpp b/Code/Framework/AzCore/AzCore/Task/TaskGraph.cpp index 3fb93903c9..f57b06890a 100644 --- a/Code/Framework/AzCore/AzCore/Task/TaskGraph.cpp +++ b/Code/Framework/AzCore/AzCore/Task/TaskGraph.cpp @@ -14,6 +14,12 @@ namespace AZ { using Internal::CompiledTaskGraph; + void TaskGraphEvent::Wait() + { + AZ_Assert(m_executor->GetTaskWorker() == nullptr, "Waiting in a task is unsupported"); + m_semaphore.acquire(); + } + void TaskToken::PrecedesInternal(TaskToken& comesAfter) { AZ_Assert(!m_parent.m_submitted, "Cannot mutate a TaskGraph that was previously submitted."); @@ -71,7 +77,7 @@ namespace AZ m_compiledTaskGraph->m_tasks[i].Init(); } - executor.Submit(*m_compiledTaskGraph); + executor.Submit(*m_compiledTaskGraph, waitEvent); if (m_retained) { diff --git a/Code/Framework/AzCore/AzCore/Task/TaskGraph.h b/Code/Framework/AzCore/AzCore/Task/TaskGraph.h index 4b454c63de..9553013a4b 100644 --- a/Code/Framework/AzCore/AzCore/Task/TaskGraph.h +++ b/Code/Framework/AzCore/AzCore/Task/TaskGraph.h @@ -22,10 +22,19 @@ namespace AZ namespace Internal { class CompiledTaskGraph; + class TaskWorker; } class TaskExecutor; class TaskGraph; + class TaskGraphActiveInterface + { + public: + AZ_RTTI(TaskGraphActiveInterface, "{08118074-B139-4EF9-B8FD-29F1D6DC9233}"); + + virtual bool IsTaskGraphActive() const = 0; + }; + // A TaskToken is returned each time a Task is added to the TaskGraph. TaskTokens are used to // express dependencies between tasks within the graph, and have no purpose after the graph // is submitted (simply let them go out of scope) @@ -70,9 +79,12 @@ namespace AZ private: friend class ::AZ::Internal::CompiledTaskGraph; friend class TaskGraph; + friend class TaskExecutor; + void Signal(); AZStd::binary_semaphore m_semaphore; + TaskExecutor* m_executor = nullptr; }; // The TaskGraph encapsulates a set of tasks and their interdependencies. After adding @@ -89,6 +101,9 @@ namespace AZ // Reset the state of the task graph to begin recording tasks and edges again // NOTE: Graph must be in a "settled" state (cannot be in-flight) void Reset(); + + // Returns false if 1 or more tasks have been added to the graph + bool IsEmpty(); // Add a task to the graph, retrieiving a token that can be used to express dependencies // between tasks. The first argument specifies the TaskKind, used for tracking the task. diff --git a/Code/Framework/AzCore/AzCore/Task/TaskGraph.inl b/Code/Framework/AzCore/AzCore/Task/TaskGraph.inl index e0ac74ba9d..7b2f0cefdc 100644 --- a/Code/Framework/AzCore/AzCore/Task/TaskGraph.inl +++ b/Code/Framework/AzCore/AzCore/Task/TaskGraph.inl @@ -33,11 +33,6 @@ namespace AZ return m_semaphore.try_acquire_for(AZStd::chrono::milliseconds{ 0 }); } - inline void TaskGraphEvent::Wait() - { - m_semaphore.acquire(); - } - inline void TaskGraphEvent::Signal() { m_semaphore.release(); @@ -59,6 +54,11 @@ namespace AZ return { AddTask(descriptor, AZStd::forward(lambdas))... }; } + inline bool TaskGraph::IsEmpty() + { + return m_tasks.empty(); + } + inline void TaskGraph::Detach() { m_retained = false; diff --git a/Code/Framework/AzCore/AzCore/Task/TaskGraphSystemComponent.cpp b/Code/Framework/AzCore/AzCore/Task/TaskGraphSystemComponent.cpp new file mode 100644 index 0000000000..eed461ecb4 --- /dev/null +++ b/Code/Framework/AzCore/AzCore/Task/TaskGraphSystemComponent.cpp @@ -0,0 +1,88 @@ +/* + * 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 +#include +#include +#include +#include + +// Create a cvar as a central location for experimentation with switching from the Job system to TaskGraph system. +AZ_CVAR(bool, cl_activateTaskGraph, false, nullptr, AZ::ConsoleFunctorFlags::Null, "Flag clients of TaskGraph to switch between jobs/taskgraph (Note does not disable task graph system)"); +static constexpr uint32_t TaskExecutorServiceCrc = AZ_CRC_CE("TaskExecutorService"); + +namespace AZ +{ + void TaskGraphSystemComponent::Activate() + { + AZ_Assert(m_taskExecutor == nullptr, "Error multiple activation of the TaskGraphSystemComponent"); + + if (Interface::Get() == nullptr) + { + Interface::Register(this); + m_taskExecutor = aznew TaskExecutor(); + TaskExecutor::SetInstance(m_taskExecutor); + } + } + + void TaskGraphSystemComponent::Deactivate() + { + if (&TaskExecutor::Instance() == m_taskExecutor) // check that our instance is the global instance (not always true in unit tests) + { + m_taskExecutor->SetInstance(nullptr); + } + if (m_taskExecutor) + { + azdestroy(m_taskExecutor); + m_taskExecutor = nullptr; + } + if (Interface::Get() == this) + { + Interface::Unregister(this); + } + } + + void TaskGraphSystemComponent::GetProvidedServices(ComponentDescriptor::DependencyArrayType& provided) + { + provided.push_back(TaskExecutorServiceCrc); + } + + void TaskGraphSystemComponent::GetIncompatibleServices(ComponentDescriptor::DependencyArrayType& incompatible) + { + incompatible.push_back(TaskExecutorServiceCrc); + } + + void TaskGraphSystemComponent::GetDependentServices([[maybe_unused]] ComponentDescriptor::DependencyArrayType& dependent) + { + } + + void TaskGraphSystemComponent::Reflect(ReflectContext* context) + { + if (SerializeContext* serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(1) + ; + + if (AZ::EditContext* ec = serializeContext->GetEditContext()) + { + ec->Class + ("TaskGraph", "System component to create the default executor") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::Category, "Engine") + ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("System")) + ; + } + } + } + + bool TaskGraphSystemComponent::IsTaskGraphActive() const + { + return cl_activateTaskGraph; + } +} // namespace AZ diff --git a/Code/Framework/AzCore/AzCore/Task/TaskGraphSystemComponent.h b/Code/Framework/AzCore/AzCore/Task/TaskGraphSystemComponent.h new file mode 100644 index 0000000000..a4c6da9539 --- /dev/null +++ b/Code/Framework/AzCore/AzCore/Task/TaskGraphSystemComponent.h @@ -0,0 +1,47 @@ +/* + * 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 + * + */ +#pragma once + +#include +#include +#include +#include + +namespace AZ +{ + class TaskGraphSystemComponent + : public Component + , public TaskGraphActiveInterface + { + public: + AZ_COMPONENT(AZ::TaskGraphSystemComponent, "{5D56B829-1FEB-43D5-A0BD-E33C0497EFE2}") + + TaskGraphSystemComponent() = default; + + // Implement TaskGraphActiveInterface + bool IsTaskGraphActive() const override; + + private: + ////////////////////////////////////////////////////////////////////////// + // Component base + void Activate() override; + void Deactivate() override; + ////////////////////////////////////////////////////////////////////////// + + /// \ref ComponentDescriptor::GetProvidedServices + static void GetProvidedServices(ComponentDescriptor::DependencyArrayType& provided); + /// \ref ComponentDescriptor::GetIncompatibleServices + static void GetIncompatibleServices(ComponentDescriptor::DependencyArrayType& incompatible); + /// \ref ComponentDescriptor::GetDependentServices + static void GetDependentServices(ComponentDescriptor::DependencyArrayType& dependent); + /// \red ComponentDescriptor::Reflect + static void Reflect(ReflectContext* reflection); + + AZ::TaskExecutor* m_taskExecutor = nullptr; + }; +} diff --git a/Code/Framework/AzCore/AzCore/azcore_files.cmake b/Code/Framework/AzCore/AzCore/azcore_files.cmake index aa07959997..14579cbf33 100644 --- a/Code/Framework/AzCore/AzCore/azcore_files.cmake +++ b/Code/Framework/AzCore/AzCore/azcore_files.cmake @@ -633,6 +633,8 @@ set(FILES Task/TaskGraph.cpp Task/TaskGraph.h Task/TaskGraph.inl + Task/TaskGraphSystemComponent.h + Task/TaskGraphSystemComponent.cpp Threading/ThreadSafeDeque.h Threading/ThreadSafeDeque.inl Threading/ThreadSafeObject.h diff --git a/Code/Framework/AzCore/Tests/TaskTests.cpp b/Code/Framework/AzCore/Tests/TaskTests.cpp index e743ab6643..9e839f60ee 100644 --- a/Code/Framework/AzCore/Tests/TaskTests.cpp +++ b/Code/Framework/AzCore/Tests/TaskTests.cpp @@ -34,7 +34,7 @@ namespace UnitTest AZ::AllocatorInstance::Create(); AZ::AllocatorInstance::Create(); - m_executor = aznew TaskExecutor(4); + m_executor = aznew TaskExecutor(); } void TearDown() override @@ -236,6 +236,82 @@ namespace UnitTest EXPECT_EQ(x, 1); } + TEST_F(TaskGraphTestFixture, SingleTask) + { + AZStd::atomic_int32_t x = 0; + + TaskGraph graph; + graph.AddTask( + defaultTD, + [&x] + { + x = 1; + }); + + TaskGraphEvent ev; + graph.SubmitOnExecutor(*m_executor, &ev); + ev.Wait(); + + EXPECT_EQ(1, x); + } + + + TEST_F(TaskGraphTestFixture, SingleTaskChain) + { + AZStd::atomic_int32_t x = 0; + + TaskGraph graph; + auto a = graph.AddTask( + defaultTD, + [&x] + { + x += 1; + }); + auto b = graph.AddTask( + defaultTD, + [&x] + { + x += 1; + }); + b.Precedes(a); + + TaskGraphEvent ev; + graph.SubmitOnExecutor(*m_executor, &ev); + ev.Wait(); + + EXPECT_EQ(2, x); + } + + TEST_F(TaskGraphTestFixture, MultipleIndependentTaskChains) + { + AZStd::atomic_int32_t x = 0; + constexpr int numChains = 5; + + TaskGraph graph; + for( int i = 0; i < numChains; ++i) + { + auto a = graph.AddTask( + defaultTD, + [&x] + { + x += 1; + }); + auto b = graph.AddTask( + defaultTD, + [&x] + { + x += 1; + }); + b.Precedes(a); + } + + TaskGraphEvent ev; + graph.SubmitOnExecutor(*m_executor, &ev); + ev.Wait(); + + EXPECT_EQ(2*numChains, x); + } + TEST_F(TaskGraphTestFixture, VariadicInterface) { int x = 0; @@ -388,6 +464,7 @@ namespace UnitTest EXPECT_EQ(3, x); } + // Waiting inside a task is disallowed , test that it fails correctly TEST_F(TaskGraphTestFixture, SpawnSubgraph) { AZStd::atomic x = 0; @@ -434,7 +511,10 @@ namespace UnitTest f.Precedes(g); TaskGraphEvent ev; subgraph.SubmitOnExecutor(*m_executor, &ev); + // TaskGraphEvent::Wait asserts if called on a worker thread, suppress & validate assert + AZ_TEST_START_TRACE_SUPPRESSION; ev.Wait(); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); }); auto d = graph.AddTask( defaultTD, @@ -464,8 +544,6 @@ namespace UnitTest TaskGraphEvent ev; graph.SubmitOnExecutor(*m_executor, &ev); ev.Wait(); - - EXPECT_EQ(3 | 0b100000, x); } TEST_F(TaskGraphTestFixture, RetainedGraph) diff --git a/Code/Framework/AzFramework/AzFramework/Application/Application.cpp b/Code/Framework/AzFramework/AzFramework/Application/Application.cpp index 12974d03cf..5c7398a95c 100644 --- a/Code/Framework/AzFramework/AzFramework/Application/Application.cpp +++ b/Code/Framework/AzFramework/AzFramework/Application/Application.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -295,6 +296,7 @@ namespace AzFramework azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), + azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), diff --git a/Code/Framework/AzFramework/CMakeLists.txt b/Code/Framework/AzFramework/CMakeLists.txt index f6851f0ab8..59393d90a1 100644 --- a/Code/Framework/AzFramework/CMakeLists.txt +++ b/Code/Framework/AzFramework/CMakeLists.txt @@ -76,6 +76,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) NAMESPACE AZ FILES_CMAKE Tests/frameworktests_files.cmake + ${pal_dir}/platform_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake INCLUDE_DIRECTORIES PRIVATE Tests @@ -93,6 +94,8 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) NAME AZ::AzFramework.Tests ) + include(${pal_dir}/platform_specific_test_targets.cmake) + endif() endif() diff --git a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbApplication.cpp b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbApplication.cpp index 7cbd9ae0e2..f57f4a89ac 100644 --- a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbApplication.cpp +++ b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbApplication.cpp @@ -8,6 +8,7 @@ #include #include +#include namespace AzFramework { @@ -17,8 +18,8 @@ namespace AzFramework { public: XcbConnectionManagerImpl() + : m_xcbConnection(xcb_connect(nullptr, nullptr)) { - m_xcbConnection = xcb_connect(nullptr, nullptr); AZ_Error("Application", m_xcbConnection != nullptr, "Unable to connect to X11 Server."); XcbConnectionManagerBus::Handler::BusConnect(); } @@ -26,16 +27,15 @@ namespace AzFramework ~XcbConnectionManagerImpl() override { XcbConnectionManagerBus::Handler::BusDisconnect(); - xcb_disconnect(m_xcbConnection); } xcb_connection_t* GetXcbConnection() const override { - return m_xcbConnection; + return m_xcbConnection.get(); } private: - xcb_connection_t* m_xcbConnection = nullptr; + XcbUniquePtr m_xcbConnection = nullptr; }; //////////////////////////////////////////////////////////////////////////////////////////////// @@ -65,10 +65,9 @@ namespace AzFramework { if (xcb_connection_t* xcbConnection = m_xcbConnectionManager->GetXcbConnection()) { - if (xcb_generic_event_t* event = xcb_poll_for_event(xcbConnection)) + if (auto event = XcbStdFreePtr{xcb_poll_for_event(xcbConnection)}) { - XcbEventHandlerBus::Broadcast(&XcbEventHandlerBus::Events::HandleXcbEvent, event); - free(event); + XcbEventHandlerBus::Broadcast(&XcbEventHandlerBus::Events::HandleXcbEvent, event.get()); } } } @@ -78,10 +77,9 @@ namespace AzFramework { if (xcb_connection_t* xcbConnection = m_xcbConnectionManager->GetXcbConnection()) { - while (xcb_generic_event_t* event = xcb_poll_for_event(xcbConnection)) + while (auto event = XcbStdFreePtr{xcb_poll_for_event(xcbConnection)}) { - XcbEventHandlerBus::Broadcast(&XcbEventHandlerBus::Events::HandleXcbEvent, event); - free(event); + XcbEventHandlerBus::Broadcast(&XcbEventHandlerBus::Events::HandleXcbEvent, event.get()); } } } diff --git a/Code/Framework/AzFramework/Tests/Main.cpp b/Code/Framework/AzFramework/Tests/Main.cpp new file mode 100644 index 0000000000..c88648458a --- /dev/null +++ b/Code/Framework/AzFramework/Tests/Main.cpp @@ -0,0 +1,9 @@ +/* + * 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 +AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV); diff --git a/Code/Framework/AzFramework/Tests/Platform/Android/platform_specific_test_targets.cmake b/Code/Framework/AzFramework/Tests/Platform/Android/platform_specific_test_targets.cmake new file mode 100644 index 0000000000..7a325ca97e --- /dev/null +++ b/Code/Framework/AzFramework/Tests/Platform/Android/platform_specific_test_targets.cmake @@ -0,0 +1,7 @@ +# +# 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 +# +# diff --git a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/Actions.h b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/Actions.h new file mode 100644 index 0000000000..1650ff4f8d --- /dev/null +++ b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/Actions.h @@ -0,0 +1,27 @@ +/* + * 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 + * + */ + +#pragma once + +#include +#include + +ACTION_TEMPLATE(ReturnMalloc, + HAS_1_TEMPLATE_PARAMS(typename, T), + AND_1_VALUE_PARAMS(p0)) { + T* value = static_cast(malloc(sizeof(T))); + *value = T{ p0 }; + return value; +} +ACTION_TEMPLATE(ReturnMalloc, + HAS_1_TEMPLATE_PARAMS(typename, T), + AND_2_VALUE_PARAMS(p0, p1)) { + T* value = static_cast(malloc(sizeof(T))); + *value = T{ p0, p1 }; + return value; +} diff --git a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/Main.cpp b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/Main.cpp new file mode 100644 index 0000000000..c88648458a --- /dev/null +++ b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/Main.cpp @@ -0,0 +1,9 @@ +/* + * 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 +AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV); diff --git a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/MockXcbInterface.cpp b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/MockXcbInterface.cpp new file mode 100644 index 0000000000..64c3967fdc --- /dev/null +++ b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/MockXcbInterface.cpp @@ -0,0 +1,81 @@ +/* + * 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 "MockXcbInterface.h" + +// The functions defined in this file will take precedence over those defined +// in the real libxcb.so, allowing the test code to use a mock implementation + +extern "C" +{ + +// ---------------------------------------------------------------------------- +// xcb +xcb_connection_t* xcb_connect(const char* displayname, int* screenp) +{ + return MockXcbInterface::Instance()->xcb_connect(displayname, screenp); +} +void xcb_disconnect(xcb_connection_t* c) +{ + return MockXcbInterface::Instance()->xcb_disconnect(c); +} +xcb_generic_event_t* xcb_poll_for_event(xcb_connection_t* c) +{ + return MockXcbInterface::Instance()->xcb_poll_for_event(c); +} + +// ---------------------------------------------------------------------------- +// xcb-xkb +xcb_xkb_use_extension_cookie_t xcb_xkb_use_extension(xcb_connection_t* c, uint16_t wantedMajor, uint16_t wantedMinor) +{ + return MockXcbInterface::Instance()->xcb_xkb_use_extension(c, wantedMajor, wantedMinor); +} +xcb_xkb_use_extension_reply_t* xcb_xkb_use_extension_reply(xcb_connection_t* c, xcb_xkb_use_extension_cookie_t cookie, xcb_generic_error_t** e) +{ + return MockXcbInterface::Instance()->xcb_xkb_use_extension_reply(c, cookie, e); +} + +// ---------------------------------------------------------------------------- +// xkb-x11 +int32_t xkb_x11_get_core_keyboard_device_id(xcb_connection_t* connection) +{ + return MockXcbInterface::Instance()->xkb_x11_get_core_keyboard_device_id(connection); +} +struct xkb_keymap* xkb_x11_keymap_new_from_device(struct xkb_context* context, xcb_connection_t* connection, int32_t device_id, enum xkb_keymap_compile_flags flags) +{ + return MockXcbInterface::Instance()->xkb_x11_keymap_new_from_device(context, connection, device_id, flags); +} +xkb_state* xkb_x11_state_new_from_device(xkb_keymap* keymap, xcb_connection_t* connection, int32_t device_id) +{ + return MockXcbInterface::Instance()->xkb_x11_state_new_from_device(keymap, connection, device_id); +} + +// ---------------------------------------------------------------------------- +// xkbcommon +xkb_context* xkb_context_new(enum xkb_context_flags flags) +{ + return MockXcbInterface::Instance()->xkb_context_new(flags); +} +void xkb_context_unref(xkb_context *context) +{ + return MockXcbInterface::Instance()->xkb_context_unref(context); +} +void xkb_keymap_unref(xkb_keymap *keymap) +{ + return MockXcbInterface::Instance()->xkb_keymap_unref(keymap); +} +void xkb_state_unref(xkb_state *state) +{ + return MockXcbInterface::Instance()->xkb_state_unref(state); +} +xkb_keysym_t xkb_state_key_get_one_sym(xkb_state *state, xkb_keycode_t key) +{ + return MockXcbInterface::Instance()->xkb_state_key_get_one_sym(state, key); +} + +} diff --git a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/MockXcbInterface.h b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/MockXcbInterface.h new file mode 100644 index 0000000000..ecda005830 --- /dev/null +++ b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/MockXcbInterface.h @@ -0,0 +1,81 @@ +/* + * 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 + * + */ + +#pragma once + +#include +#include + +#include + +#define explicit ExplicitIsACXXKeyword +#include +#undef explicit +#include + +#include "Printers.h" + +// xcb / xkb do not provide definitions of these structs, but the tests need some concrete value for them. +struct xcb_connection_t +{ +}; + +struct xkb_context +{ +}; + +struct xkb_keymap +{ +}; + +struct xkb_state +{ +}; + +class MockXcbInterface +{ +public: + MockXcbInterface() + { + self = this; + } + MockXcbInterface(const MockXcbInterface&) = delete; + MockXcbInterface(MockXcbInterface&&) = delete; + MockXcbInterface& operator=(const MockXcbInterface&) = delete; + MockXcbInterface& operator=(MockXcbInterface&&) = delete; + ~MockXcbInterface() + { + self = nullptr; + } + + static MockXcbInterface* Instance() { return self; } + + // xcb + MOCK_CONST_METHOD2(xcb_connect, xcb_connection_t*(const char* displayname, int* screenp)); + MOCK_CONST_METHOD1(xcb_disconnect, void(xcb_connection_t* c)); + MOCK_CONST_METHOD1(xcb_poll_for_event, xcb_generic_event_t*(xcb_connection_t* c)); + + // xcb-xkb + MOCK_CONST_METHOD3(xcb_xkb_use_extension, xcb_xkb_use_extension_cookie_t(xcb_connection_t* c, uint16_t wantedMajor, uint16_t wantedMinor)); + MOCK_CONST_METHOD3(xcb_xkb_use_extension_reply, xcb_xkb_use_extension_reply_t*(xcb_connection_t* c, xcb_xkb_use_extension_cookie_t cookie, xcb_generic_error_t** e)); + + // xkb-x11 + MOCK_CONST_METHOD1(xkb_x11_get_core_keyboard_device_id, int32_t(xcb_connection_t* connection)); + MOCK_CONST_METHOD4(xkb_x11_keymap_new_from_device, xkb_keymap*(xkb_context* context, xcb_connection_t* connection, int32_t device_id, xkb_keymap_compile_flags flags)); + MOCK_CONST_METHOD3(xkb_x11_state_new_from_device, xkb_state*(xkb_keymap* keymap, xcb_connection_t* connection, int32_t device_id)); + + // xkbcommon + MOCK_CONST_METHOD1(xkb_context_new, xkb_context*(xkb_context_flags flags)); + MOCK_CONST_METHOD1(xkb_context_unref, void(xkb_context* context)); + MOCK_CONST_METHOD1(xkb_keymap_unref, void(xkb_keymap* keymap)); + MOCK_CONST_METHOD1(xkb_state_unref, void(xkb_state* state)); + MOCK_CONST_METHOD2(xkb_state_key_get_one_sym, xkb_keysym_t(xkb_state *state, xkb_keycode_t key)); + +private: + static inline MockXcbInterface* self = nullptr; +}; diff --git a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/Printers.cpp b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/Printers.cpp new file mode 100644 index 0000000000..9de17bd776 --- /dev/null +++ b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/Printers.cpp @@ -0,0 +1,33 @@ +/* + * 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 "Printers.h" +#include +#include + +namespace AzFramework +{ + void PrintTo(const InputChannel::State& state, std::ostream* os) + { + switch(state) + { + case InputChannel::State::Began: + *os << "Began"; + break; + case InputChannel::State::Ended: + *os << "Ended"; + break; + case InputChannel::State::Idle: + *os << "Idle"; + break; + case InputChannel::State::Updated: + *os << "Updated"; + break; + } + } +} // namespace AzFramework diff --git a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/Printers.h b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/Printers.h new file mode 100644 index 0000000000..0659ec6eb1 --- /dev/null +++ b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/Printers.h @@ -0,0 +1,17 @@ +/* + * 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 + * + */ + +#pragma once + +#include +#include + +namespace AzFramework +{ + void PrintTo(const InputChannel::State& state, std::ostream* os); +} // namespace AzFramework diff --git a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbInputDeviceKeyboardTests.cpp b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbInputDeviceKeyboardTests.cpp new file mode 100644 index 0000000000..1b494c9525 --- /dev/null +++ b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbInputDeviceKeyboardTests.cpp @@ -0,0 +1,145 @@ +/* + * 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 +#include + +#include + +#include +#include +#include "MockXcbInterface.h" +#include "Actions.h" + +template +xcb_generic_event_t MakeEvent(T event) +{ + return *reinterpret_cast(&event); +} + +namespace AzFramework +{ + TEST(XcbInputDeviceKeyboard, InputChannelsUpdateStateFromXcbEvents) + { + using testing::Return; + using testing::Eq; + using testing::_; + MockXcbInterface interface; + + xcb_connection_t connection{}; + xkb_context xkbContext{}; + xkb_keymap xkbKeymap{}; + xkb_state xkbState{}; + const int32_t coreDeviceId{1}; + + constexpr xcb_keycode_t keycodeForAKey = 38; + + const AZStd::array events + { + MakeEvent(xcb_key_press_event_t{ + /*.response_type = */ XCB_KEY_PRESS, + /*.detail = */ keycodeForAKey, + /*.sequence = */ 0, + /*.time = */ 0, + /*.root = */ 0, + /*.event = */ 0, + /*.child = */ 0, + /*.root_x = */ 0, + /*.root_y = */ 0, + /*.event_x = */ 0, + /*.event_y = */ 0, + /*.state = */ 0, + /*.same_screen = */ 0, + /*.pad0 = */ 0 + }), + MakeEvent(xcb_key_release_event_t{ + /*.response_type = */ XCB_KEY_RELEASE, + /*.detail = */ keycodeForAKey, + /*.sequence = */ 0, + /*.time = */ 0, + /*.root = */ 0, + /*.event = */ 0, + /*.child = */ 0, + /*.root_x = */ 0, + /*.root_y = */ 0, + /*.event_x = */ 0, + /*.event_y = */ 0, + /*.state = */ 0, + /*.same_screen = */ 0, + /*.pad0 = */ 0 + }), + }; + + EXPECT_CALL(interface, xcb_connect(_, _)) + .WillOnce(Return(&connection)); + EXPECT_CALL(interface, xcb_disconnect(&connection)) + .Times(1); + + EXPECT_CALL(interface, xkb_context_new(XKB_CONTEXT_NO_FLAGS)) + .WillOnce(Return(&xkbContext)); + EXPECT_CALL(interface, xkb_context_unref(&xkbContext)) + .Times(1); + + EXPECT_CALL(interface, xkb_x11_keymap_new_from_device(&xkbContext, &connection, coreDeviceId, XKB_KEYMAP_COMPILE_NO_FLAGS)) + .WillOnce(Return(&xkbKeymap)); + EXPECT_CALL(interface, xkb_keymap_unref(&xkbKeymap)) + .Times(1); + + EXPECT_CALL(interface, xkb_x11_state_new_from_device(&xkbKeymap, &connection, coreDeviceId)) + .WillOnce(Return(&xkbState)); + EXPECT_CALL(interface, xkb_state_unref(&xkbState)) + .Times(1); + + EXPECT_CALL(interface, xcb_xkb_use_extension(&connection, 1, 0)); + EXPECT_CALL(interface, xcb_xkb_use_extension_reply(&connection, _, _)) + .WillOnce(ReturnMalloc( + /* .response_type =*/static_cast(XCB_XKB_USE_EXTENSION), + /* .supported =*/ static_cast(1)) + ); + EXPECT_CALL(interface, xkb_x11_get_core_keyboard_device_id(&connection)) + .WillRepeatedly(Return(coreDeviceId)); + + // Set the expectations for the events that will be generated + // nullptr entries represent when the event queue is empty, and will cause + // PumpSystemEventLoopUntilEmpty to return + // event pointers are freed by the calling code, so we malloc new copies + // here + EXPECT_CALL(interface, xcb_poll_for_event(&connection)) + .WillOnce(ReturnMalloc(events[0])) + .WillOnce(Return(nullptr)) + .WillOnce(ReturnMalloc(events[1])) + .WillOnce(Return(nullptr)) + ; + + EXPECT_CALL(interface, xkb_state_key_get_one_sym(&xkbState, keycodeForAKey)) + .WillOnce(Return(XKB_KEY_a)) + .WillOnce(Return(XKB_KEY_a)) + ; + + Application application; + application.Start({}, {}); + + const InputChannel* inputChannel = InputChannelRequests::FindInputChannel(InputDeviceKeyboard::Key::AlphanumericA); + ASSERT_TRUE(inputChannel); + EXPECT_THAT(inputChannel->GetState(), Eq(InputChannel::State::Idle)); + + application.PumpSystemEventLoopUntilEmpty(); + application.TickSystem(); + application.Tick(); + + EXPECT_THAT(inputChannel->GetState(), Eq(InputChannel::State::Began)); + + application.PumpSystemEventLoopUntilEmpty(); + application.TickSystem(); + application.Tick(); + + EXPECT_THAT(inputChannel->GetState(), Eq(InputChannel::State::Ended)); + + application.Stop(); + } +} // namespace AzFramework diff --git a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/azframework_xcb_tests_files.cmake b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/azframework_xcb_tests_files.cmake new file mode 100644 index 0000000000..aebd28222b --- /dev/null +++ b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/azframework_xcb_tests_files.cmake @@ -0,0 +1,17 @@ +# +# 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 +# +# + +set(FILES + Actions.h + Main.cpp + MockXcbInterface.cpp + MockXcbInterface.h + Printers.cpp + Printers.h + XcbInputDeviceKeyboardTests.cpp +) diff --git a/Code/Framework/AzFramework/Tests/Platform/Linux/platform_specific_test_targets.cmake b/Code/Framework/AzFramework/Tests/Platform/Linux/platform_specific_test_targets.cmake new file mode 100644 index 0000000000..e443831462 --- /dev/null +++ b/Code/Framework/AzFramework/Tests/Platform/Linux/platform_specific_test_targets.cmake @@ -0,0 +1,33 @@ +# +# 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 +# +# + +if (${PAL_TRAIT_LINUX_WINDOW_MANAGER} STREQUAL "xcb") + # This library defines local implementations of all the used xcb functions, + # in order to allow tests to run in the absence of a running X server. + # These have to be in a separate library, so that the normal AzFramework + # tests do not need to set up a mock Xcb interface. + ly_add_target( + NAME AzFramework.Xcb.Tests ${PAL_TRAIT_TEST_TARGET_TYPE} + NAMESPACE AZ + FILES_CMAKE + Tests/Platform/Common/Xcb/azframework_xcb_tests_files.cmake + INCLUDE_DIRECTORIES + PRIVATE + Tests + ${pal_dir} + BUILD_DEPENDENCIES + PRIVATE + AZ::AzFramework + AZ::AzTest + AZ::AzTestShared + AZ::AzFrameworkTestShared + ) + ly_add_googletest( + NAME AZ::AzFramework.Xcb.Tests + ) +endif() diff --git a/Code/Framework/AzFramework/Tests/Platform/Mac/platform_specific_test_targets.cmake b/Code/Framework/AzFramework/Tests/Platform/Mac/platform_specific_test_targets.cmake new file mode 100644 index 0000000000..7a325ca97e --- /dev/null +++ b/Code/Framework/AzFramework/Tests/Platform/Mac/platform_specific_test_targets.cmake @@ -0,0 +1,7 @@ +# +# 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 +# +# diff --git a/Code/Framework/AzFramework/Tests/Platform/Windows/platform_specific_test_targets.cmake b/Code/Framework/AzFramework/Tests/Platform/Windows/platform_specific_test_targets.cmake new file mode 100644 index 0000000000..7a325ca97e --- /dev/null +++ b/Code/Framework/AzFramework/Tests/Platform/Windows/platform_specific_test_targets.cmake @@ -0,0 +1,7 @@ +# +# 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 +# +# diff --git a/Code/Framework/AzFramework/Tests/Platform/iOS/platform_specific_test_targets.cmake b/Code/Framework/AzFramework/Tests/Platform/iOS/platform_specific_test_targets.cmake new file mode 100644 index 0000000000..7a325ca97e --- /dev/null +++ b/Code/Framework/AzFramework/Tests/Platform/iOS/platform_specific_test_targets.cmake @@ -0,0 +1,7 @@ +# +# 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 +# +# diff --git a/Code/Framework/AzFramework/Tests/frameworktests_files.cmake b/Code/Framework/AzFramework/Tests/frameworktests_files.cmake index 680155d49d..6c4f611352 100644 --- a/Code/Framework/AzFramework/Tests/frameworktests_files.cmake +++ b/Code/Framework/AzFramework/Tests/frameworktests_files.cmake @@ -7,7 +7,7 @@ # set(FILES - ../../AzCore/Tests/Main.cpp + Main.cpp Spawnable/SpawnableEntitiesInterfaceTests.cpp Spawnable/SpawnableEntitiesManagerTests.cpp ArchiveCompressionTests.cpp diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetBrowserFilterModel.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetBrowserFilterModel.cpp index c6772ea2d7..acf935e6dc 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetBrowserFilterModel.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetBrowserFilterModel.cpp @@ -20,7 +20,7 @@ AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") AZ_POP_DISABLE_WARNING AZ_CVAR( - bool, ed_useNewAssetBrowserTableView, true, nullptr, AZ::ConsoleFunctorFlags::Null, + bool, ed_useNewAssetBrowserTableView, false, nullptr, AZ::ConsoleFunctorFlags::Null, "Use the new AssetBrowser TableView for searching assets."); namespace AzToolsFramework { diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeInterface.h index a2b23fa8d6..75e3bab60f 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeInterface.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeInterface.h @@ -12,6 +12,8 @@ #include #include +#include + namespace AzToolsFramework { //! FocusModeInterface @@ -27,11 +29,11 @@ namespace AzToolsFramework virtual void SetFocusRoot(AZ::EntityId entityId) = 0; //! Clears the Editor focus, allowing the user to select the whole level again. - virtual void ClearFocusRoot() = 0; + virtual void ClearFocusRoot(AzFramework::EntityContextId entityContextId) = 0; //! Returns the entity id of the root of the current Editor focus. //! @return The entity id of the root of the Editor focus, or an invalid entity id if no focus is set. - virtual AZ::EntityId GetFocusRoot() = 0; + virtual AZ::EntityId GetFocusRoot(AzFramework::EntityContextId entityContextId) = 0; //! Returns whether the entity id provided is part of the focused sub-tree. virtual bool IsInFocusSubTree(AZ::EntityId entityId) const = 0; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeNotificationBus.h b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeNotificationBus.h new file mode 100644 index 0000000000..ab8629ac85 --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeNotificationBus.h @@ -0,0 +1,40 @@ +/* + * 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 + * + */ + +#pragma once + +#include +#include + +#include + +namespace AzToolsFramework +{ + //! Used to notify when the editor focus changes. + class FocusModeNotifications + : public AZ::EBusTraits + { + public: + ////////////////////////////////////////////////////////////////////////// + // EBusTraits overrides + static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple; + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById; + using BusIdType = AzFramework::EntityContextId; + ////////////////////////////////////////////////////////////////////////// + + //! Triggered when the editor focus is changed to a different entity. + //! @param entityId The entity the focus has been moved to. + virtual void OnEditorFocusChanged(AZ::EntityId entityId) = 0; + + protected: + ~FocusModeNotifications() = default; + }; + + using FocusModeNotificationBus = AZ::EBus; + +} // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.cpp index e1c04fcb93..d1c4d37821 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.cpp @@ -9,6 +9,7 @@ #include #include +#include #include namespace AzToolsFramework @@ -64,17 +65,23 @@ namespace AzToolsFramework void FocusModeSystemComponent::SetFocusRoot(AZ::EntityId entityId) { + if (m_focusRoot == entityId) + { + return; + } + m_focusRoot = entityId; + FocusModeNotificationBus::Broadcast(&FocusModeNotifications::OnEditorFocusChanged, m_focusRoot); // TODO - If m_focusRoot != AZ::EntityId(), activate focus mode via ViewportEditorModeTrackerInterface; else, deactivate focus mode } - void FocusModeSystemComponent::ClearFocusRoot() + void FocusModeSystemComponent::ClearFocusRoot([[maybe_unused]] AzFramework::EntityContextId entityContextId) { SetFocusRoot(AZ::EntityId()); } - AZ::EntityId FocusModeSystemComponent::GetFocusRoot() + AZ::EntityId FocusModeSystemComponent::GetFocusRoot([[maybe_unused]] AzFramework::EntityContextId entityContextId) { return m_focusRoot; } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.h b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.h index 27ddebe1ed..dabaa6aaf4 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.h @@ -40,8 +40,8 @@ namespace AzToolsFramework // FocusModeInterface overrides ... void SetFocusRoot(AZ::EntityId entityId) override; - void ClearFocusRoot() override; - AZ::EntityId GetFocusRoot() override; + void ClearFocusRoot(AzFramework::EntityContextId entityContextId) override; + AZ::EntityId GetFocusRoot(AzFramework::EntityContextId entityContextId) override; bool IsInFocusSubTree(AZ::EntityId entityId) const override; private: diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp index d69d0004cc..4e58bbdf05 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp @@ -8,9 +8,11 @@ #include +#include #include #include #include +#include namespace AzToolsFramework::Prefab { @@ -23,12 +25,14 @@ namespace AzToolsFramework::Prefab "Instance Entity Mapper Interface could not be found. " "Check that it is being correctly initialized."); + EditorEntityContextNotificationBus::Handler::BusConnect(); AZ::Interface::Register(this); } PrefabFocusHandler::~PrefabFocusHandler() { AZ::Interface::Unregister(this); + EditorEntityContextNotificationBus::Handler::BusDisconnect(); } PrefabFocusOperationResult PrefabFocusHandler::FocusOnOwningPrefab(AZ::EntityId entityId) @@ -53,35 +57,76 @@ namespace AzToolsFramework::Prefab focusedInstance = m_instanceEntityMapperInterface->FindOwningInstance(entityId); } - if (!focusedInstance.has_value()) + return FocusOnPrefabInstance(focusedInstance); + } + + PrefabFocusOperationResult PrefabFocusHandler::FocusOnPathIndex([[maybe_unused]] AzFramework::EntityContextId entityContextId, int index) + { + if (index < 0 || index >= m_instanceFocusVector.size()) { - return AZ::Failure(AZStd::string( - "Prefab Focus Handler: Couldn't find owning instance of entityId provided.")); + return AZ::Failure(AZStd::string("Prefab Focus Handler: Invalid index on FocusOnPathIndex.")); } - m_focusedInstance = focusedInstance; - m_focusedTemplateId = focusedInstance->get().GetTemplateId(); + InstanceOptionalReference focusedInstance = m_instanceFocusVector[index]; - FocusModeInterface* focusModeInterface = AZ::Interface::Get(); - if (focusModeInterface) + return FocusOnPrefabInstance(focusedInstance); + } + + PrefabFocusOperationResult PrefabFocusHandler::FocusOnPrefabInstance(InstanceOptionalReference focusedInstance) + { + if (!focusedInstance.has_value()) { - focusModeInterface->SetFocusRoot(focusedInstance->get().GetContainerEntityId()); + return AZ::Failure(AZStd::string("Prefab Focus Handler: invalid instance to focus on.")); + } + + if (!m_focusedInstance.has_value() || &m_focusedInstance->get() != &focusedInstance->get()) + { + m_focusedInstance = focusedInstance; + m_focusedTemplateId = focusedInstance->get().GetTemplateId(); + + AZ::EntityId containerEntityId; + + if (focusedInstance->get().GetParentInstance() != AZStd::nullopt) + { + containerEntityId = focusedInstance->get().GetContainerEntityId(); + + // Select the container entity + AzToolsFramework::SelectEntity(containerEntityId); + } + else + { + containerEntityId = AZ::EntityId(); + + // Clear the selection + AzToolsFramework::SelectEntities({}); + + } + + // Focus on the descendants of the container entity + if (FocusModeInterface* focusModeInterface = AZ::Interface::Get()) + { + focusModeInterface->SetFocusRoot(containerEntityId); + } + + RefreshInstanceFocusList(); + PrefabFocusNotificationBus::Broadcast(&PrefabFocusNotifications::OnPrefabFocusChanged); } return AZ::Success(); } - TemplateId PrefabFocusHandler::GetFocusedPrefabTemplateId() + TemplateId PrefabFocusHandler::GetFocusedPrefabTemplateId([[maybe_unused]] AzFramework::EntityContextId entityContextId) const { return m_focusedTemplateId; } - InstanceOptionalReference PrefabFocusHandler::GetFocusedPrefabInstance() + InstanceOptionalReference PrefabFocusHandler::GetFocusedPrefabInstance( + [[maybe_unused]] AzFramework::EntityContextId entityContextId) const { return m_focusedInstance; } - bool PrefabFocusHandler::IsOwningPrefabBeingFocused(AZ::EntityId entityId) + bool PrefabFocusHandler::IsOwningPrefabBeingFocused(AZ::EntityId entityId) const { if (!m_focusedInstance.has_value()) { @@ -99,4 +144,44 @@ namespace AzToolsFramework::Prefab return instance.has_value() && (&instance->get() == &m_focusedInstance->get()); } + const AZ::IO::Path& PrefabFocusHandler::GetPrefabFocusPath([[maybe_unused]] AzFramework::EntityContextId entityContextId) const + { + return m_instanceFocusPath; + } + + const int PrefabFocusHandler::GetPrefabFocusPathLength([[maybe_unused]] AzFramework::EntityContextId entityContextId) const + { + return aznumeric_cast(m_instanceFocusVector.size()); + } + + void PrefabFocusHandler::OnEntityStreamLoadSuccess() + { + // Focus on the root prefab (AZ::EntityId() will default to it) + FocusOnOwningPrefab(AZ::EntityId()); + } + + void PrefabFocusHandler::RefreshInstanceFocusList() + { + m_instanceFocusVector.clear(); + m_instanceFocusPath.clear(); + + AZStd::list instanceFocusList; + + // Use a support list to easily push front while traversing the prefab hierarchy + InstanceOptionalReference currentInstance = m_focusedInstance; + while (currentInstance.has_value()) + { + instanceFocusList.push_front(currentInstance); + + currentInstance = currentInstance->get().GetParentInstance(); + } + + // Populate internals using the support list + for (auto& instance : instanceFocusList) + { + m_instanceFocusPath.Append(instance->get().GetContainerEntity()->get().GetName()); + m_instanceFocusVector.emplace_back(instance); + } + } + } // namespace AzToolsFramework::Prefab diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h index fa7727894d..2ccec36882 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h @@ -10,6 +10,7 @@ #include +#include #include #include #include @@ -21,6 +22,7 @@ namespace AzToolsFramework::Prefab //! Handles Prefab Focus mode, determining which prefab file entity changes will target. class PrefabFocusHandler final : private PrefabFocusInterface + , private EditorEntityContextNotificationBus::Handler { public: AZ_CLASS_ALLOCATOR(PrefabFocusHandler, AZ::SystemAllocator, 0); @@ -28,15 +30,26 @@ namespace AzToolsFramework::Prefab PrefabFocusHandler(); ~PrefabFocusHandler(); - // PrefabFocusInterface override ... + // PrefabFocusInterface overrides ... PrefabFocusOperationResult FocusOnOwningPrefab(AZ::EntityId entityId) override; - TemplateId GetFocusedPrefabTemplateId() override; - InstanceOptionalReference GetFocusedPrefabInstance() override; - bool IsOwningPrefabBeingFocused(AZ::EntityId entityId) override; + PrefabFocusOperationResult FocusOnPathIndex(AzFramework::EntityContextId entityContextId, int index) override; + TemplateId GetFocusedPrefabTemplateId(AzFramework::EntityContextId entityContextId) const override; + InstanceOptionalReference GetFocusedPrefabInstance(AzFramework::EntityContextId entityContextId) const override; + bool IsOwningPrefabBeingFocused(AZ::EntityId entityId) const override; + const AZ::IO::Path& GetPrefabFocusPath(AzFramework::EntityContextId entityContextId) const override; + const int GetPrefabFocusPathLength(AzFramework::EntityContextId entityContextId) const override; + + // EditorEntityContextNotificationBus overrides ... + void OnEntityStreamLoadSuccess() override; private: + PrefabFocusOperationResult FocusOnPrefabInstance(InstanceOptionalReference focusedInstance); + void RefreshInstanceFocusList(); + InstanceOptionalReference m_focusedInstance; TemplateId m_focusedTemplateId; + AZStd::vector m_instanceFocusVector; + AZ::IO::Path m_instanceFocusPath; InstanceEntityMapperInterface* m_instanceEntityMapperInterface; }; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusInterface.h index 833a5ef7c8..1c0f4f85e9 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusInterface.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusInterface.h @@ -11,6 +11,8 @@ #include #include +#include + #include #include @@ -28,16 +30,27 @@ namespace AzToolsFramework::Prefab //! @param entityId The entityId of the entity whose owning instance we want the prefab system to focus on. virtual PrefabFocusOperationResult FocusOnOwningPrefab(AZ::EntityId entityId) = 0; + //! Set the focused prefab instance to the instance at position index of the current path. + //! @param index The index of the instance in the current path that we want the prefab system to focus on. + virtual PrefabFocusOperationResult FocusOnPathIndex(AzFramework::EntityContextId entityContextId, int index) = 0; + //! Returns the template id of the instance the prefab system is focusing on. - virtual TemplateId GetFocusedPrefabTemplateId() = 0; + virtual TemplateId GetFocusedPrefabTemplateId(AzFramework::EntityContextId entityContextId) const = 0; //! Returns a reference to the instance the prefab system is focusing on. - virtual InstanceOptionalReference GetFocusedPrefabInstance() = 0; + virtual InstanceOptionalReference GetFocusedPrefabInstance(AzFramework::EntityContextId entityContextId) const = 0; //! Returns whether the entity belongs to the instance that is being focused on, or one of its descendants. //! @param entityId The entityId of the queried entity. //! @return true if the entity belongs to the focused instance or one of its descendants, false otherwise. - virtual bool IsOwningPrefabBeingFocused(AZ::EntityId entityId) = 0; + virtual bool IsOwningPrefabBeingFocused(AZ::EntityId entityId) const = 0; + + //! Returns the path from the root instance to the currently focused instance. + //! @return A path composed from the names of the container entities for the instance path. + virtual const AZ::IO::Path& GetPrefabFocusPath(AzFramework::EntityContextId entityContextId) const = 0; + + //! Returns the size of the path to the currently focused instance. + virtual const int GetPrefabFocusPathLength(AzFramework::EntityContextId entityContextId) const = 0; }; } // namespace AzToolsFramework::Prefab diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusNotificationBus.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusNotificationBus.h new file mode 100644 index 0000000000..31bab719e2 --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusNotificationBus.h @@ -0,0 +1,37 @@ +/* + * 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 + * + */ + +#pragma once + +#include +#include + +namespace AzToolsFramework::Prefab +{ + //! Used to notify when the editor focus changes. + class PrefabFocusNotifications + : public AZ::EBusTraits + { + public: + ////////////////////////////////////////////////////////////////////////// + // EBusTraits overrides + static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple; + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById; + using BusIdType = AzFramework::EntityContextId; + ////////////////////////////////////////////////////////////////////////// + + //! Triggered when the editor focus is changed to a different prefab. + virtual void OnPrefabFocusChanged() = 0; + + protected: + ~PrefabFocusNotifications() = default; + }; + + using PrefabFocusNotificationBus = AZ::EBus; + +} // namespace AzToolsFramework::Prefab diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.cpp index 2d20ac7364..4364ae1efc 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.cpp @@ -39,11 +39,20 @@ namespace AzToolsFramework AZ_Assert((m_editorEntityFrameworkInterface != nullptr), "EntityOutlinerTreeView requires a EditorEntityFrameworkInterface instance on Construction."); + + AzFramework::EntityContextId editorEntityContextId = AzFramework::EntityContextId::CreateNull(); + AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult( + editorEntityContextId, &AzToolsFramework::EditorEntityContextRequestBus::Events::GetEditorEntityContextId); + + FocusModeNotificationBus::Handler::BusConnect(editorEntityContextId); + viewport()->setMouseTracking(true); } EntityOutlinerTreeView::~EntityOutlinerTreeView() { + FocusModeNotificationBus::Handler::BusDisconnect(); + ClearQueuedMouseEvent(); } @@ -303,6 +312,11 @@ namespace AzToolsFramework StyledTreeView::StartCustomDrag(indexListSorted, supportedActions); } + + void EntityOutlinerTreeView::OnEditorFocusChanged([[maybe_unused]] AZ::EntityId entityId) + { + viewport()->repaint(); + } } #include diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.hxx b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.hxx index 5b0134366b..2ddbaaafa9 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.hxx +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.hxx @@ -15,6 +15,7 @@ #include #include +#include #include #endif @@ -35,6 +36,7 @@ namespace AzToolsFramework //! of other entities. If the selection updates instantly, this would never be possible. class EntityOutlinerTreeView : public AzQtComponents::StyledTreeView + , private FocusModeNotificationBus::Handler { Q_OBJECT; public: @@ -61,6 +63,9 @@ namespace AzToolsFramework void dropEvent(QDropEvent* event) override; void leaveEvent(QEvent* event) override; + // FocusModeNotificationBus overrides ... + void OnEditorFocusChanged(AZ::EntityId entityId) override; + //! Renders the left side of the item: appropriate background, branch lines, icons. void drawBranches(QPainter* painter, const QRect& rect, const QModelIndex& index) const override; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabViewportFocusPathHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabViewportFocusPathHandler.cpp new file mode 100644 index 0000000000..6b0de5dc53 --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabViewportFocusPathHandler.cpp @@ -0,0 +1,71 @@ +/* + * 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 + +#include + +namespace AzToolsFramework::Prefab +{ + PrefabViewportFocusPathHandler::PrefabViewportFocusPathHandler() + { + // Get default EntityContextId + AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult( + m_editorEntityContextId, &AzToolsFramework::EditorEntityContextRequestBus::Events::GetEditorEntityContextId); + + // Connect to Prefab Focus Notifications + PrefabFocusNotificationBus::Handler::BusConnect(m_editorEntityContextId); + } + + PrefabViewportFocusPathHandler::~PrefabViewportFocusPathHandler() + { + // Disconnect from Prefab Focus Notifications + PrefabFocusNotificationBus::Handler::BusDisconnect(); + } + + void PrefabViewportFocusPathHandler::Initialize(AzQtComponents::BreadCrumbs* breadcrumbsWidget, QToolButton* backButton) + { + // Get reference to the PrefabFocusInterface handler + m_prefabFocusInterface = AZ::Interface::Get(); + if (m_prefabFocusInterface == nullptr) + { + AZ_Assert(false, "Prefab - could not get PrefabFocusInterface on PrefabViewportFocusPathHandler construction."); + return; + } + + // Initialize Widgets + m_breadcrumbsWidget = breadcrumbsWidget; + m_backButton = backButton; + + // If a part of the path is clicked, focus on that instance + connect(m_breadcrumbsWidget, &AzQtComponents::BreadCrumbs::linkClicked, this, + [&](const QString&, int linkIndex) + { + m_prefabFocusInterface->FocusOnPathIndex(m_editorEntityContextId, linkIndex); + } + ); + + // The back button will allow user to go one level up + connect(m_backButton, &QToolButton::clicked, this, + [&]() + { + if (int length = m_prefabFocusInterface->GetPrefabFocusPathLength(m_editorEntityContextId); length > 1) + { + m_prefabFocusInterface->FocusOnPathIndex(m_editorEntityContextId, length - 2); + } + } + ); + } + + void PrefabViewportFocusPathHandler::OnPrefabFocusChanged() + { + // Push new Path + m_breadcrumbsWidget->pushPath(m_prefabFocusInterface->GetPrefabFocusPath(m_editorEntityContextId).c_str()); + } + +} // namespace AzToolsFramework::Prefab diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabViewportFocusPathHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabViewportFocusPathHandler.h new file mode 100644 index 0000000000..ce7744fb1b --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabViewportFocusPathHandler.h @@ -0,0 +1,45 @@ +/* + * 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 + * + */ + +#pragma once + +#include + +#include + +#include + +#include +#include + +namespace AzToolsFramework::Prefab +{ + class PrefabFocusInterface; + + class PrefabViewportFocusPathHandler + : public PrefabFocusNotificationBus::Handler + , private QObject + { + public: + PrefabViewportFocusPathHandler(); + ~PrefabViewportFocusPathHandler(); + + void Initialize(AzQtComponents::BreadCrumbs* breadcrumbsWidget, QToolButton* backButton); + + // PrefabFocusNotificationBus overrides ... + void OnPrefabFocusChanged() override; + + private: + AzQtComponents::BreadCrumbs* m_breadcrumbsWidget = nullptr; + QToolButton* m_backButton = nullptr; + + AzFramework::EntityContextId m_editorEntityContextId = AzFramework::EntityContextId::CreateNull(); + + PrefabFocusInterface* m_prefabFocusInterface = nullptr; + }; +} // namespace AzToolsFramework::Prefab diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake b/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake index 209eadf59e..f09d4f9b72 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake @@ -151,6 +151,7 @@ set(FILES Fingerprinting/TypeFingerprinter.h Fingerprinting/TypeFingerprinter.cpp FocusMode/FocusModeInterface.h + FocusMode/FocusModeNotificationBus.h FocusMode/FocusModeSystemComponent.h FocusMode/FocusModeSystemComponent.cpp Logger/TraceLogger.cpp @@ -636,6 +637,7 @@ set(FILES Prefab/PrefabFocusHandler.h Prefab/PrefabFocusHandler.cpp Prefab/PrefabFocusInterface.h + Prefab/PrefabFocusNotificationBus.h Prefab/PrefabIdTypes.h Prefab/PrefabLoader.h Prefab/PrefabLoader.cpp @@ -734,6 +736,8 @@ set(FILES UI/Prefab/PrefabIntegrationInterface.h UI/Prefab/PrefabUiHandler.h UI/Prefab/PrefabUiHandler.cpp + UI/Prefab/PrefabViewportFocusPathHandler.h + UI/Prefab/PrefabViewportFocusPathHandler.cpp PythonTerminal/ScriptHelpDialog.cpp PythonTerminal/ScriptHelpDialog.h PythonTerminal/ScriptHelpDialog.ui diff --git a/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeFixture.cpp b/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeFixture.cpp index bc026a4baf..c309261a27 100644 --- a/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeFixture.cpp +++ b/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeFixture.cpp @@ -27,6 +27,9 @@ namespace AzToolsFramework // register a simple component implementing BoundsRequestBus and EditorComponentSelectionRequestsBus GetApplication()->RegisterComponentDescriptor(UnitTest::BoundsTestComponent::CreateDescriptor()); + AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult( + m_editorEntityContextId, &AzToolsFramework::EditorEntityContextRequestBus::Events::GetEditorEntityContextId); + GenerateTestHierarchy(); } diff --git a/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeFixture.h b/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeFixture.h index a461b45e1b..f038408012 100644 --- a/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeFixture.h +++ b/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeFixture.h @@ -32,6 +32,8 @@ namespace AzToolsFramework FocusModeInterface* m_focusModeInterface = nullptr; public: + AzFramework::EntityContextId m_editorEntityContextId = AzFramework::EntityContextId::CreateNull(); + AzFramework::CameraState m_cameraState; inline static const AZ::Vector3 CameraPosition = AZ::Vector3(10.0f, 15.0f, 10.0f); diff --git a/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeSelectionTests.cpp b/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeSelectionTests.cpp index 750137814d..968bb3f293 100644 --- a/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeSelectionTests.cpp +++ b/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeSelectionTests.cpp @@ -58,7 +58,7 @@ namespace AzToolsFramework TEST_F(EditorFocusModeSelectionFixture, EditorFocusModeSelectionTests_SelectEntityWithFocusOnLevel) { // Clear the focus, disabling focus mode - m_focusModeInterface->ClearFocusRoot(); + m_focusModeInterface->ClearFocusRoot(AzFramework::EntityContextId::CreateNull()); // Clear selection ClearSelectedEntities(); @@ -85,6 +85,9 @@ namespace AzToolsFramework auto selectedEntitiesAfter = GetSelectedEntities(); EXPECT_EQ(selectedEntitiesAfter.size(), 1); EXPECT_EQ(selectedEntitiesAfter.front(), m_entityMap[CarEntityName]); + + // Clear the focus, disabling focus mode + m_focusModeInterface->ClearFocusRoot(AzFramework::EntityContextId::CreateNull()); } TEST_F(EditorFocusModeSelectionFixture, EditorFocusModeSelectionTests_SelectEntityWithFocusOnItself) @@ -101,6 +104,9 @@ namespace AzToolsFramework auto selectedEntitiesAfter = GetSelectedEntities(); EXPECT_EQ(selectedEntitiesAfter.size(), 1); EXPECT_EQ(selectedEntitiesAfter.front(), m_entityMap[CarEntityName]); + + // Clear the focus, disabling focus mode + m_focusModeInterface->ClearFocusRoot(AzFramework::EntityContextId::CreateNull()); } TEST_F(EditorFocusModeSelectionFixture, EditorFocusModeSelectionTests_SelectEntityWithFocusOnSibling) @@ -113,9 +119,12 @@ namespace AzToolsFramework // Click on Car Entity ClickAtWorldPositionOnViewport(CarEntityPosition); - // entity is selected + // Verify entity is selected auto selectedEntitiesAfter = GetSelectedEntities(); EXPECT_EQ(selectedEntitiesAfter.size(), 0); + + // Clear the focus, disabling focus mode + m_focusModeInterface->ClearFocusRoot(AzFramework::EntityContextId::CreateNull()); } TEST_F(EditorFocusModeSelectionFixture, EditorFocusModeSelectionTests_SelectEntityWithFocusOnDescendant) @@ -128,8 +137,11 @@ namespace AzToolsFramework // Click on Car Entity ClickAtWorldPositionOnViewport(CarEntityPosition); - // entity is selected + // Verify entity is selected auto selectedEntitiesAfter = GetSelectedEntities(); EXPECT_EQ(selectedEntitiesAfter.size(), 0); + + // Clear the focus, disabling focus mode + m_focusModeInterface->ClearFocusRoot(AzFramework::EntityContextId::CreateNull()); } } diff --git a/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeTests.cpp b/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeTests.cpp index 7dfdff655a..22bc7a494c 100644 --- a/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeTests.cpp +++ b/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeTests.cpp @@ -14,10 +14,10 @@ namespace AzToolsFramework { // When an entity is set as the focus root, GetFocusRoot should return its EntityId. m_focusModeInterface->SetFocusRoot(m_entityMap[CarEntityName]); - EXPECT_EQ(m_focusModeInterface->GetFocusRoot(), m_entityMap[CarEntityName]); + EXPECT_EQ(m_focusModeInterface->GetFocusRoot(m_editorEntityContextId), m_entityMap[CarEntityName]); // Restore default expected focus. - m_focusModeInterface->ClearFocusRoot(); + m_focusModeInterface->ClearFocusRoot(m_editorEntityContextId); } TEST_F(EditorFocusModeFixture, EditorFocusModeTests_ClearFocus) @@ -26,8 +26,8 @@ namespace AzToolsFramework m_focusModeInterface->SetFocusRoot(m_entityMap[CarEntityName]); // Calling ClearFocusRoot restores the default focus root (which is an invalid EntityId). - m_focusModeInterface->ClearFocusRoot(); - EXPECT_EQ(m_focusModeInterface->GetFocusRoot(), AZ::EntityId()); + m_focusModeInterface->ClearFocusRoot(m_editorEntityContextId); + EXPECT_EQ(m_focusModeInterface->GetFocusRoot(m_editorEntityContextId), AZ::EntityId()); } TEST_F(EditorFocusModeFixture, EditorFocusModeTests_IsInFocusSubTree_AncestorsDescendants) @@ -45,7 +45,7 @@ namespace AzToolsFramework } // Restore default expected focus. - m_focusModeInterface->ClearFocusRoot(); + m_focusModeInterface->ClearFocusRoot(m_editorEntityContextId); } TEST_F(EditorFocusModeFixture, EditorFocusModeTests_IsInFocusSubTree_Siblings) @@ -63,7 +63,7 @@ namespace AzToolsFramework } // Restore default expected focus. - m_focusModeInterface->ClearFocusRoot(); + m_focusModeInterface->ClearFocusRoot(m_editorEntityContextId); } TEST_F(EditorFocusModeFixture, EditorFocusModeTests_IsInFocusSubTree_Leaf) @@ -81,7 +81,7 @@ namespace AzToolsFramework } // Restore default expected focus. - m_focusModeInterface->ClearFocusRoot(); + m_focusModeInterface->ClearFocusRoot(m_editorEntityContextId); } TEST_F(EditorFocusModeFixture, EditorFocusModeTests_IsInFocusSubTree_Clear) @@ -91,7 +91,7 @@ namespace AzToolsFramework // When the focus is cleared, the whole level is in the focus subtree; so we expect all entities to return true. { - m_focusModeInterface->ClearFocusRoot(); + m_focusModeInterface->ClearFocusRoot(m_editorEntityContextId); EXPECT_EQ(m_focusModeInterface->IsInFocusSubTree(m_entityMap[CityEntityName]), true); EXPECT_EQ(m_focusModeInterface->IsInFocusSubTree(m_entityMap[StreetEntityName]), true); diff --git a/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabFocus/PrefabFocusTests.cpp b/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabFocus/PrefabFocusTests.cpp index d3c3a2250b..14eee84b7d 100644 --- a/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabFocus/PrefabFocusTests.cpp +++ b/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabFocus/PrefabFocusTests.cpp @@ -72,6 +72,9 @@ namespace UnitTest m_prefabFocusInterface = AZ::Interface::Get(); ASSERT_TRUE(m_prefabFocusInterface != nullptr); + AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult( + m_editorEntityContextId, &AzToolsFramework::EditorEntityContextRequestBus::Events::GetEditorEntityContextId); + GenerateTestHierarchy(); } @@ -88,6 +91,7 @@ namespace UnitTest AZStd::unique_ptr m_rootInstance; PrefabFocusInterface* m_prefabFocusInterface = nullptr; + AzFramework::EntityContextId m_editorEntityContextId = AzFramework::EntityContextId::CreateNull(); inline static const char* CityEntityName = "City"; inline static const char* StreetEntityName = "Street"; @@ -102,9 +106,11 @@ namespace UnitTest // Verify FocusOnOwningPrefab works when passing the container entity of the root prefab. { m_prefabFocusInterface->FocusOnOwningPrefab(m_instanceMap[CityEntityName]->GetContainerEntityId()); - EXPECT_EQ(m_prefabFocusInterface->GetFocusedPrefabTemplateId(), m_instanceMap[CityEntityName]->GetTemplateId()); + EXPECT_EQ( + m_prefabFocusInterface->GetFocusedPrefabTemplateId(m_editorEntityContextId), + m_instanceMap[CityEntityName]->GetTemplateId()); - auto instance = m_prefabFocusInterface->GetFocusedPrefabInstance(); + auto instance = m_prefabFocusInterface->GetFocusedPrefabInstance(m_editorEntityContextId); EXPECT_TRUE(instance.has_value()); EXPECT_EQ(&instance->get(), m_instanceMap[CityEntityName]); } @@ -115,9 +121,11 @@ namespace UnitTest // Verify FocusOnOwningPrefab works when passing a nested entity of the root prefab. { m_prefabFocusInterface->FocusOnOwningPrefab(m_entityMap[CityEntityName]->GetId()); - EXPECT_EQ(m_prefabFocusInterface->GetFocusedPrefabTemplateId(), m_instanceMap[CityEntityName]->GetTemplateId()); + EXPECT_EQ( + m_prefabFocusInterface->GetFocusedPrefabTemplateId(m_editorEntityContextId), + m_instanceMap[CityEntityName]->GetTemplateId()); - auto instance = m_prefabFocusInterface->GetFocusedPrefabInstance(); + auto instance = m_prefabFocusInterface->GetFocusedPrefabInstance(m_editorEntityContextId); EXPECT_TRUE(instance.has_value()); EXPECT_EQ(&instance->get(), m_instanceMap[CityEntityName]); } @@ -128,9 +136,10 @@ namespace UnitTest // Verify FocusOnOwningPrefab works when passing the container entity of a nested prefab. { m_prefabFocusInterface->FocusOnOwningPrefab(m_instanceMap[CarEntityName]->GetContainerEntityId()); - EXPECT_EQ(m_prefabFocusInterface->GetFocusedPrefabTemplateId(), m_instanceMap[CarEntityName]->GetTemplateId()); + EXPECT_EQ( + m_prefabFocusInterface->GetFocusedPrefabTemplateId(m_editorEntityContextId), m_instanceMap[CarEntityName]->GetTemplateId()); - auto instance = m_prefabFocusInterface->GetFocusedPrefabInstance(); + auto instance = m_prefabFocusInterface->GetFocusedPrefabInstance(m_editorEntityContextId); EXPECT_TRUE(instance.has_value()); EXPECT_EQ(&instance->get(), m_instanceMap[CarEntityName]); } @@ -141,9 +150,10 @@ namespace UnitTest // Verify FocusOnOwningPrefab works when passing a nested entity of the a nested prefab. { m_prefabFocusInterface->FocusOnOwningPrefab(m_entityMap[Passenger1EntityName]->GetId()); - EXPECT_EQ(m_prefabFocusInterface->GetFocusedPrefabTemplateId(), m_instanceMap[CarEntityName]->GetTemplateId()); + EXPECT_EQ( + m_prefabFocusInterface->GetFocusedPrefabTemplateId(m_editorEntityContextId), m_instanceMap[CarEntityName]->GetTemplateId()); - auto instance = m_prefabFocusInterface->GetFocusedPrefabInstance(); + auto instance = m_prefabFocusInterface->GetFocusedPrefabInstance(m_editorEntityContextId); EXPECT_TRUE(instance.has_value()); EXPECT_EQ(&instance->get(), m_instanceMap[CarEntityName]); } @@ -160,9 +170,10 @@ namespace UnitTest EXPECT_TRUE(rootPrefabInstance.has_value()); m_prefabFocusInterface->FocusOnOwningPrefab(AZ::EntityId()); - EXPECT_EQ(m_prefabFocusInterface->GetFocusedPrefabTemplateId(), rootPrefabInstance->get().GetTemplateId()); + EXPECT_EQ( + m_prefabFocusInterface->GetFocusedPrefabTemplateId(m_editorEntityContextId), rootPrefabInstance->get().GetTemplateId()); - auto instance = m_prefabFocusInterface->GetFocusedPrefabInstance(); + auto instance = m_prefabFocusInterface->GetFocusedPrefabInstance(m_editorEntityContextId); EXPECT_TRUE(instance.has_value()); EXPECT_EQ(&instance->get(), &rootPrefabInstance->get()); } diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Decals.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Decals.azsli index 1d7a2d6dd0..f9ead72ce4 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Decals.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Decals.azsli @@ -16,6 +16,7 @@ #include #include #include +#include void ApplyDecal(uint currDecalIndex, inout Surface surface); @@ -47,9 +48,10 @@ void ApplyDecal(uint currDecalIndex, inout Surface surface) ViewSrg::Decal decal = ViewSrg::m_decals[currDecalIndex]; float3x3 decalRot = MatrixFromQuaternion(decal.m_quaternion); - + decalRot = transpose(decalRot); + float3 localPos = surface.position - decal.m_position; - localPos = mul(localPos, decalRot); + localPos = mul(decalRot, localPos); float3 decalUVW = localPos * rcp(decal.m_halfSize); if(decalUVW.x >= -1.0f && decalUVW.x <= 1.0f && @@ -63,33 +65,41 @@ void ApplyDecal(uint currDecalIndex, inout Surface surface) decalUVW.y *= -1; float3 decalUV = float3(decalUVW.xy * 0.5f + 0.5f, textureIndex); - + float3 decalSample; float4 baseMap = 0; - + float2 normalMap = 0; // Each texture array handles a size permutation. // e.g. it could be that tex array 0 handles 256x256 and tex array 1 handles 512x64, etc. switch(textureArrayIndex) { case 0: - baseMap = ViewSrg::m_decalTextureArray0.Sample(PassSrg::LinearSampler, decalUV); + baseMap = ViewSrg::m_decalTextureArrayDiffuse0.Sample(PassSrg::LinearSampler, decalUV); + normalMap = ViewSrg::m_decalTextureArrayNormalMaps0.Sample(PassSrg::LinearSampler, decalUV); break; case 1: - baseMap = ViewSrg::m_decalTextureArray1.Sample(PassSrg::LinearSampler, decalUV); + baseMap = ViewSrg::m_decalTextureArrayDiffuse1.Sample(PassSrg::LinearSampler, decalUV); + normalMap = ViewSrg::m_decalTextureArrayNormalMaps1.Sample(PassSrg::LinearSampler, decalUV); break; case 2: - baseMap = ViewSrg::m_decalTextureArray2.Sample(PassSrg::LinearSampler, decalUV); + baseMap = ViewSrg::m_decalTextureArrayDiffuse2.Sample(PassSrg::LinearSampler, decalUV); + normalMap = ViewSrg::m_decalTextureArrayNormalMaps2.Sample(PassSrg::LinearSampler, decalUV); break; case 3: - baseMap = ViewSrg::m_decalTextureArray3.Sample(PassSrg::LinearSampler, decalUV); + baseMap = ViewSrg::m_decalTextureArrayDiffuse3.Sample(PassSrg::LinearSampler, decalUV); + normalMap = ViewSrg::m_decalTextureArrayNormalMaps3.Sample(PassSrg::LinearSampler, decalUV); break; case 4: - baseMap = ViewSrg::m_decalTextureArray4.Sample(PassSrg::LinearSampler, decalUV); - break; + baseMap = ViewSrg::m_decalTextureArrayDiffuse4.Sample(PassSrg::LinearSampler, decalUV); + normalMap = ViewSrg::m_decalTextureArrayNormalMaps4.Sample(PassSrg::LinearSampler, decalUV); + break; } float opacity = baseMap.a * decal.m_opacity * GetDecalAttenuation(surface.normal, decalRot[2], decal.m_angleAttenuation); - surface.albedo = lerp(surface.albedo, baseMap.rgb, opacity); + surface.albedo = lerp(surface.albedo, baseMap.rgb, opacity); + + float3 normalMapWS = GetWorldSpaceNormal(normalMap, decalRot[2], decalRot[0], decalRot[1], 1.0f); + surface.normal = normalize(lerp(surface.normal, normalMapWS, opacity)); } } diff --git a/Gems/Atom/Feature/Common/Assets/ShaderResourceGroups/Decals/ViewSrg.azsli b/Gems/Atom/Feature/Common/Assets/ShaderResourceGroups/Decals/ViewSrg.azsli index 16b23432e1..260614b0f7 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderResourceGroups/Decals/ViewSrg.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderResourceGroups/Decals/ViewSrg.azsli @@ -31,12 +31,18 @@ partial ShaderResourceGroup ViewSrg // e.g. m_decalTextureArray0 might store 24 textures @128x128, // m_decalTextureArray1 might store 16 * 256x256 // and m_decalTextureArray2 might store 4 @ 512x512 - - Texture2DArray m_decalTextureArray0; - Texture2DArray m_decalTextureArray1; - Texture2DArray m_decalTextureArray2; - Texture2DArray m_decalTextureArray3; - Texture2DArray m_decalTextureArray4; + // This must match the variable NumTextureArrays in DecalTextureArrayFeatureProcessor.h + Texture2DArray m_decalTextureArrayDiffuse0; + Texture2DArray m_decalTextureArrayDiffuse1; + Texture2DArray m_decalTextureArrayDiffuse2; + Texture2DArray m_decalTextureArrayDiffuse3; + Texture2DArray m_decalTextureArrayDiffuse4; + + Texture2DArray m_decalTextureArrayNormalMaps0; + Texture2DArray m_decalTextureArrayNormalMaps1; + Texture2DArray m_decalTextureArrayNormalMaps2; + Texture2DArray m_decalTextureArrayNormalMaps3; + Texture2DArray m_decalTextureArrayNormalMaps4; uint m_decalCount; } diff --git a/Gems/Atom/Feature/Common/Code/Source/Decals/DecalTextureArray.cpp b/Gems/Atom/Feature/Common/Code/Source/Decals/DecalTextureArray.cpp index ebdc884ecf..87ac0a9679 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Decals/DecalTextureArray.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Decals/DecalTextureArray.cpp @@ -5,7 +5,6 @@ * SPDX-License-Identifier: Apache-2.0 OR MIT * */ - #include "DecalTextureArray.h" #include #include @@ -24,7 +23,16 @@ namespace AZ { namespace { - static const char* BaseColorTextureMapName = "baseColor.textureMap"; + static AZ::Name GetMapName(const DecalMapType mapType) + { + // Using local static to avoid cost of creating AZ::Name. Also so that this can be called from other static functions + static AZStd::array mapNames = + { + AZ::Name("baseColor.textureMap"), + AZ::Name("normal.textureMap") + }; + return mapNames[mapType]; + } static AZ::Data::AssetId GetImagePoolId() { @@ -40,6 +48,7 @@ namespace AZ return asset; } + // Extract exactly which texture asset we need to load from the given material and map type (diffuse, normal, etc). static AZ::Data::Asset GetStreamingImageAsset(const AZ::RPI::MaterialAsset& materialAsset, const AZ::Name& propertyName) { if (!materialAsset.IsReady()) @@ -78,11 +87,6 @@ namespace AZ const AZ::RPI::MaterialAsset* materialAsset = materialAssetData.GetAs(); return GetStreamingImageAsset(*materialAsset, propertyName); } - - AZ::Data::Asset GetBaseColorImageAsset(const AZ::Data::Asset materialAssetData) - { - return GetStreamingImageAsset(materialAssetData, AZ::Name(BaseColorTextureMapName)); - } } int DecalTextureArray::FindMaterial(const AZ::Data::AssetId materialAssetId) const @@ -103,7 +107,7 @@ namespace AZ { AZ_Error("DecalTextureArray", FindMaterial(materialAssetId) == -1, "Adding material when it already exists in the array"); // Invalidate the existing texture array, as we need to repack it taking into account the new material. - m_textureArrayPacked = nullptr; + AZStd::fill(m_textureArrayPacked.begin(), m_textureArrayPacked.end(), nullptr); MaterialData materialData; materialData.m_materialAssetId = materialAssetId; @@ -122,42 +126,42 @@ namespace AZ return m_materials[index].m_materialAssetId; } - RHI::Size DecalTextureArray::GetImageDimensions() const + RHI::Size DecalTextureArray::GetImageDimensions(const DecalMapType mapType) const { AZ_Assert(m_materials.size() > 0, "GetImageDimensions() cannot be called until at least one material has been added"); const int iter = m_materials.begin(); // All textures in a texture array must have the same size, so just pick the first const MaterialData& firstMaterial = m_materials[iter]; - const auto& baseColorAsset = GetBaseColorImageAsset(firstMaterial.m_materialAssetData); + const auto& baseColorAsset = GetStreamingImageAsset(firstMaterial.m_materialAssetData, GetMapName(mapType)); return baseColorAsset->GetImageDescriptor().m_size; } - const AZ::Data::Instance& DecalTextureArray::GetPackedTexture() const + const AZ::Data::Instance& DecalTextureArray::GetPackedTexture(const DecalMapType mapType) const { - return m_textureArrayPacked; + return m_textureArrayPacked[mapType]; } bool DecalTextureArray::IsValidDecalMaterial(const AZ::RPI::MaterialAsset& materialAsset) { - return GetStreamingImageAsset(materialAsset, AZ::Name(BaseColorTextureMapName)).IsReady(); + return GetStreamingImageAsset(materialAsset, GetMapName(DecalMapType_Diffuse)).IsReady(); } - AZ::Data::Asset DecalTextureArray::BuildPackedMipChainAsset(const size_t numTexturesToCreate) + AZ::Data::Asset DecalTextureArray::BuildPackedMipChainAsset(const DecalMapType mapType, const size_t numTexturesToCreate) { RPI::ImageMipChainAssetCreator assetCreator; - const uint32_t mipLevels = GetNumMipLevels(); + const uint32_t mipLevels = GetNumMipLevels(mapType); - assetCreator.Begin(Data::AssetId(AZ::Uuid::CreateRandom()), static_cast(mipLevels), aznumeric_cast(numTexturesToCreate)); + assetCreator.Begin(Data::AssetId(AZ::Uuid::CreateRandom()), aznumeric_cast(mipLevels), aznumeric_cast(numTexturesToCreate)); for (uint32_t mipLevel = 0; mipLevel < mipLevels; ++mipLevel) { - const auto& layout = GetLayout(mipLevel); + const auto& layout = GetLayout(mapType, mipLevel); assetCreator.BeginMip(layout); for (int i = 0; i < m_materials.array_size(); ++i) { - const auto rawData = GetRawImageData(i, mipLevel); - assetCreator.AddSubImage(rawData.data(), rawData.size()); + const auto imageData = GetRawImageData(GetMapName(mapType), i, mipLevel); + assetCreator.AddSubImage(imageData.data(), imageData.size()); } assetCreator.EndMip(); @@ -169,10 +173,12 @@ namespace AZ return AZStd::move(asset); } - RHI::ImageDescriptor DecalTextureArray::CreatePackedImageDescriptor(const uint16_t arraySize, const uint16_t mipLevels) const + RHI::ImageDescriptor DecalTextureArray::CreatePackedImageDescriptor( + const DecalMapType mapType, const uint16_t arraySize, const uint16_t mipLevels) const { - const RHI::Size imageDimensions = GetImageDimensions(); - RHI::ImageDescriptor imageDescriptor = RHI::ImageDescriptor::Create2DArray(RHI::ImageBindFlags::ShaderRead, imageDimensions.m_width, imageDimensions.m_height, arraySize, GetFormat()); + const RHI::Size imageDimensions = GetImageDimensions(mapType); + RHI::ImageDescriptor imageDescriptor = RHI::ImageDescriptor::Create2DArray( + RHI::ImageBindFlags::ShaderRead, imageDimensions.m_width, imageDimensions.m_height, arraySize, GetFormat(mapType)); imageDescriptor.m_mipLevels = mipLevels; return imageDescriptor; } @@ -189,21 +195,34 @@ namespace AZ } const size_t numTexturesToCreate = m_materials.array_size(); - const auto mipChainAsset = BuildPackedMipChainAsset(numTexturesToCreate); - RHI::ImageViewDescriptor imageViewDescriptor; - imageViewDescriptor.m_isArray = true; + for (int i = 0; i < DecalMapType_Num; ++i) + { + const DecalMapType mapType = aznumeric_cast(i); + if (!AreAllTextureMapsPresent(mapType)) + { + AZ_Warning("DecalTextureArray", true, "Missing decal texture maps for %s. Please make sure all maps of this type are present.\n", GetMapName(mapType).GetCStr()); + m_textureArrayPacked[i] = nullptr; + continue; + } - RPI::StreamingImageAssetCreator assetCreator; - assetCreator.Begin(Data::AssetId(Uuid::CreateRandom())); - assetCreator.SetPoolAssetId(GetImagePoolId()); - assetCreator.SetFlags(RPI::StreamingImageFlags::None); - assetCreator.SetImageDescriptor(CreatePackedImageDescriptor(aznumeric_cast(numTexturesToCreate), GetNumMipLevels())); - assetCreator.SetImageViewDescriptor(imageViewDescriptor); - assetCreator.AddMipChainAsset(*mipChainAsset); - Data::Asset packedAsset; - const bool createdOk = assetCreator.End(packedAsset); - AZ_Error("TextureArrayData", createdOk, "Pack() call failed."); - m_textureArrayPacked = createdOk ? RPI::StreamingImage::FindOrCreate(packedAsset) : nullptr; + const auto mipChainAsset = BuildPackedMipChainAsset(mapType, numTexturesToCreate); + RHI::ImageViewDescriptor imageViewDescriptor; + imageViewDescriptor.m_isArray = true; + + RPI::StreamingImageAssetCreator assetCreator; + assetCreator.Begin(Data::AssetId(Uuid::CreateRandom())); + assetCreator.SetPoolAssetId(GetImagePoolId()); + assetCreator.SetFlags(RPI::StreamingImageFlags::None); + assetCreator.SetImageDescriptor( + CreatePackedImageDescriptor(mapType, aznumeric_cast(numTexturesToCreate), GetNumMipLevels(mapType))); + assetCreator.SetImageViewDescriptor(imageViewDescriptor); + assetCreator.AddMipChainAsset(*mipChainAsset); + Data::Asset packedAsset; + const bool createdOk = assetCreator.End(packedAsset); + AZ_Error("TextureArrayData", createdOk, "Pack() call failed."); + m_textureArrayPacked[i] = createdOk ? RPI::StreamingImage::FindOrCreate(packedAsset) : nullptr; + + } // Free unused memory ClearAssets(); @@ -225,29 +244,30 @@ namespace AZ } } - uint16_t DecalTextureArray::GetNumMipLevels() const + uint16_t DecalTextureArray::GetNumMipLevels(const DecalMapType mapType) const { AZ_Assert(m_materials.size() > 0, "GetNumMipLevels() cannot be called until at least one material has been added"); // All decals in a texture array must have the same number of mips, so just pick the first const int iter = m_materials.begin(); const MaterialData& firstMaterial = m_materials[iter]; - const auto& baseColorAsset = GetBaseColorImageAsset(firstMaterial.m_materialAssetData); - return baseColorAsset->GetImageDescriptor().m_mipLevels; + const auto& imageAsset = GetStreamingImageAsset(firstMaterial.m_materialAssetData, GetMapName(mapType)); + return imageAsset->GetImageDescriptor().m_mipLevels; } - RHI::ImageSubresourceLayout DecalTextureArray::GetLayout(int mip) const + RHI::ImageSubresourceLayout DecalTextureArray::GetLayout(const DecalMapType mapType, int mip) const { AZ_Assert(m_materials.size() > 0, "GetLayout() cannot be called unless at least one material has been added"); const int iter = m_materials.begin(); - const auto& descriptor = GetBaseColorImageAsset(m_materials[iter].m_materialAssetData)->GetImageDescriptor(); + const auto& descriptor = + GetStreamingImageAsset(m_materials[iter].m_materialAssetData, GetMapName(mapType))->GetImageDescriptor(); RHI::Size mipSize = descriptor.m_size; mipSize.m_width >>= mip; mipSize.m_height >>= mip; return AZ::RHI::GetImageSubresourceLayout(mipSize, descriptor.m_format); } - AZStd::array_view DecalTextureArray::GetRawImageData(int arrayLevel, const int mip) const + AZStd::array_view DecalTextureArray::GetRawImageData(const AZ::Name& mapName, int arrayLevel, const int mip) const { // We always want to provide valid data to the AssetCreator for each texture. // If this spot in the array is empty, just provide some random image as filler. @@ -257,17 +277,20 @@ namespace AZ { arrayLevel = m_materials.begin(); } - - const auto image = GetBaseColorImageAsset(m_materials[arrayLevel].m_materialAssetData); + const auto image = GetStreamingImageAsset(m_materials[arrayLevel].m_materialAssetData, mapName); + if (!image) + { + return {}; + } const auto srcData = image->GetSubImageData(mip, 0); return srcData; } - AZ::RHI::Format DecalTextureArray::GetFormat() const + AZ::RHI::Format DecalTextureArray::GetFormat(const DecalMapType mapType) const { AZ_Assert(m_materials.size() > 0, "GetFormat() can only be called after at least one material has been added."); const int iter = m_materials.begin(); - const auto& baseColorAsset = GetBaseColorImageAsset(m_materials[iter].m_materialAssetData); + const auto& baseColorAsset = GetStreamingImageAsset(m_materials[iter].m_materialAssetData, GetMapName(mapType)); return baseColorAsset->GetImageDescriptor().m_format; } @@ -290,6 +313,25 @@ namespace AZ return id.IsValid() && materialData.m_materialAssetData.IsReady(); } + bool DecalTextureArray::AreAllTextureMapsPresent(const DecalMapType mapType) const + { + int iter = m_materials.begin(); + while (iter != -1) + { + if (!IsTextureMapPresentInMaterial(m_materials[iter], mapType)) + { + return false; + } + iter = m_materials.next(iter); + } + return true; + } + + bool DecalTextureArray::IsTextureMapPresentInMaterial(const MaterialData& materialData, const DecalMapType mapType) const + { + return GetStreamingImageAsset(materialData.m_materialAssetData, GetMapName(mapType)).IsReady(); + } + void DecalTextureArray::ClearAssets() { int iter = m_materials.begin(); @@ -330,7 +372,8 @@ namespace AZ if (m_materials.size() == 0) return false; - return m_textureArrayPacked == nullptr; + // We pack all diffuse/normal/etc in one go, so just check to see if the diffusemaps need packing + return m_textureArrayPacked[DecalMapType_Diffuse] == nullptr; } } diff --git a/Gems/Atom/Feature/Common/Code/Source/Decals/DecalTextureArray.h b/Gems/Atom/Feature/Common/Code/Source/Decals/DecalTextureArray.h index eb0f825e08..97bd8b9cbe 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Decals/DecalTextureArray.h +++ b/Gems/Atom/Feature/Common/Code/Source/Decals/DecalTextureArray.h @@ -28,8 +28,18 @@ namespace AZ namespace Render { + enum DecalMapType : uint32_t + { + DecalMapType_Diffuse, + DecalMapType_Normal, + DecalMapType_Num + }; + //! Helper class used by DecalTextureArrayFeatureProcessor. //! Given a set of images (all with the same dimensions and format), it can pack them together into a single textureArray that can be sent to the GPU. + //! Note that once textures are packed, this class will release any material references + //! This might free memory if nothing else is holding onto them + //! The class DOES keep note of which material asset ids were added, so it can load them again if necessary if the whole thing needs to be repacked class DecalTextureArray : public Data::AssetBus::MultiHandler { public: @@ -40,8 +50,12 @@ namespace AZ AZ::Data::AssetId GetMaterialAssetId(const int index) const; + // Packs all the added materials into one texture array per DecalMapType. void Pack(); - const Data::Instance& GetPackedTexture() const; + + // Note that we pack each type into a separate texture array. This is because formats are + // often different (BC5 for normals, BC7 for diffuse, etc) + const Data::Instance& GetPackedTexture(const DecalMapType mapType) const; static bool IsValidDecalMaterial(const RPI::MaterialAsset& materialAsset); @@ -56,22 +70,25 @@ namespace AZ void OnAssetReady(Data::Asset asset) override; + // Returns the index of the material in the m_materials container. -1 if not present. int FindMaterial(const AZ::Data::AssetId materialAssetId) const; // packs the contents of the source images into a texture array readable by the GPU and returns it - AZ::Data::Asset BuildPackedMipChainAsset(const size_t numTexturesToCreate); + AZ::Data::Asset BuildPackedMipChainAsset(const DecalMapType mapType, const size_t numTexturesToCreate); + RHI::ImageDescriptor CreatePackedImageDescriptor(const DecalMapType mapType, const uint16_t arraySize, const uint16_t mipLevels) const; - RHI::ImageDescriptor CreatePackedImageDescriptor(const uint16_t arraySize, const uint16_t mipLevels) const; - - uint16_t GetNumMipLevels() const; - RHI::Size GetImageDimensions() const; - RHI::Format GetFormat() const; - RHI::ImageSubresourceLayout GetLayout(int mip) const; - AZStd::array_view GetRawImageData(int arrayLevel, int mip) const; + uint16_t GetNumMipLevels(const DecalMapType mapType) const; + RHI::Size GetImageDimensions(const DecalMapType mapType) const; + RHI::Format GetFormat(const DecalMapType mapType) const; + RHI::ImageSubresourceLayout GetLayout(const DecalMapType mapType, int mip) const; + AZStd::array_view GetRawImageData(const AZ::Name& mapName, int arrayLevel, int mip) const; bool AreAllAssetsReady() const; bool IsAssetReady(const MaterialData& materialData) const; + bool AreAllTextureMapsPresent(const DecalMapType mapType) const; + bool IsTextureMapPresentInMaterial(const MaterialData& materialData, const DecalMapType mapType) const; + void ClearAssets(); void ClearAsset(MaterialData& materialData); @@ -81,7 +98,7 @@ namespace AZ bool NeedsPacking() const; IndexableList m_materials; - Data::Instance m_textureArrayPacked; + AZStd::array, DecalMapType_Num> m_textureArrayPacked; AZStd::unordered_set m_assetsCurrentlyLoading; }; diff --git a/Gems/Atom/Feature/Common/Code/Source/Decals/DecalTextureArrayFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/Decals/DecalTextureArrayFeatureProcessor.cpp index 3975df52b9..46b00ef47f 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Decals/DecalTextureArrayFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Decals/DecalTextureArrayFeatureProcessor.cpp @@ -301,36 +301,72 @@ namespace AZ return; } - if (material.IsValid()) + if (GetMaterialUsedByDecal(handle) == material) + return; + + const auto decalIndex = handle.GetIndex(); + + const bool isValidMaterialBeingUsedCurrently = m_decalData.GetData(decalIndex).m_textureArrayIndex != DecalData::UnusedIndex; + if (isValidMaterialBeingUsedCurrently) { AZ_Assert( - m_decalData.GetData(handle.GetIndex()).m_textureArrayIndex == DecalData::UnusedIndex || GetMaterialUsedByDecal(handle) == material, - "Setting Material on a decal more than once is not currently supported."); + AZ_Assert(m_decalData.GetData(handle.GetIndex()).m_textureArrayIndex == DecalData::UnusedIndex, "Setting Material on a decal more than once is not currently supported."); - const auto iter = m_materialToTextureArrayLookupTable.find(material); - if (iter != m_materialToTextureArrayLookupTable.end()) - { - // This material is already loaded and registered with this feature processor - iter->second.m_useCount++; - SetDecalTextureLocation(handle, iter->second.m_location); - return; - } + if (!material.IsValid()) + return; - // Material not loaded so queue it up for loading. - QueueMaterialLoadForDecal(material, handle); + const auto iter = m_materialToTextureArrayLookupTable.find(material); + if (iter != m_materialToTextureArrayLookupTable.end()) + { + // This material is already loaded and registered with this feature processor + iter->second.m_useCount++; + SetDecalTextureLocation(handle, iter->second.m_location); return; } + + // Material not loaded so queue it up for loading. + QueueMaterialLoadForDecal(material, handle); + } + + void DecalTextureArrayFeatureProcessor::RemoveMaterialFromDecal(const uint16_t decalIndex) + { + DecalLocation decalLocation; + decalLocation.textureArrayIndex = m_decalData.GetData(decalIndex).m_textureArrayIndex; + decalLocation.textureIndex = m_decalData.GetData(decalIndex).m_textureIndex; + RemoveDecalFromTextureArrays(decalLocation); + + m_decalData.GetData(decalIndex).m_textureArrayIndex = DecalData::UnusedIndex; + m_decalData.GetData(decalIndex).m_textureIndex = DecalData::UnusedIndex; + + m_deviceBufferNeedsUpdate = true; } void DecalTextureArrayFeatureProcessor::CacheShaderIndices() { - for (int i = 0; i < NumTextureArrays; ++i) - { - const RHI::ShaderResourceGroupLayout* viewSrgLayout = RPI::RPISystemInterface::Get()->GetViewSrgLayout().get(); - const AZStd::string baseName = "m_decalTextureArray" + AZStd::to_string(i); + // The azsl shader should define several texture arrays such as: + // Texture2DArray m_decalTextureArrayDiffuse0; + // Texture2DArray m_decalTextureArrayDiffuse1; + // Texture2DArray m_decalTextureArrayDiffuse2; + // and + // Texture2DArray m_decalTextureArrayNormalMaps0; + // Texture2DArray m_decalTextureArrayNormalMaps1; + // Texture2DArray m_decalTextureArrayNormalMaps2; + static const AZStd::array ShaderNames = { "m_decalTextureArrayDiffuse", + "m_decalTextureArrayNormalMaps" }; - m_decalTextureArrayIndices[i] = viewSrgLayout->FindShaderInputImageIndex(Name(baseName.c_str())); - AZ_Warning("DecalTextureArrayFeatureProcessor", m_decalTextureArrayIndices[i].IsValid(), "Unable to find %s in decal shader.", baseName.c_str()); + for (int mapType = 0; mapType < DecalMapType_Num; ++mapType) + { + for (int texArrayIdx = 0; texArrayIdx < NumTextureArrays; ++texArrayIdx) + { + const RHI::ShaderResourceGroupLayout* viewSrgLayout = RPI::RPISystemInterface::Get()->GetViewSrgLayout().get(); + const AZStd::string baseName = ShaderNames[mapType] + AZStd::to_string(texArrayIdx); + + m_decalTextureArrayIndices[texArrayIdx][mapType] = viewSrgLayout->FindShaderInputImageIndex(Name(baseName.c_str())); + AZ_Warning( + "DecalTextureArrayFeatureProcessor", m_decalTextureArrayIndices[texArrayIdx][mapType].IsValid(), + "Unable to find %s in decal shader.", + baseName.c_str()); + } } } @@ -413,8 +449,11 @@ namespace AZ int iter = m_textureArrayList.begin(); while (iter != -1) { - const auto& packedTexture = m_textureArrayList[iter].second.GetPackedTexture(); - view->GetShaderResourceGroup()->SetImage(m_decalTextureArrayIndices[iter], packedTexture); + for (int mapType = 0 ; mapType < DecalMapType_Num ; ++mapType) + { + const auto& packedTexture = m_textureArrayList[iter].second.GetPackedTexture(aznumeric_cast(mapType)); + view->GetShaderResourceGroup()->SetImage(m_decalTextureArrayIndices[iter][mapType], packedTexture); + } iter = m_textureArrayList.next(iter); } } diff --git a/Gems/Atom/Feature/Common/Code/Source/Decals/DecalTextureArrayFeatureProcessor.h b/Gems/Atom/Feature/Common/Code/Source/Decals/DecalTextureArrayFeatureProcessor.h index 57c5c69e0d..fd535bbe64 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Decals/DecalTextureArrayFeatureProcessor.h +++ b/Gems/Atom/Feature/Common/Code/Source/Decals/DecalTextureArrayFeatureProcessor.h @@ -89,6 +89,7 @@ namespace AZ private: // Number of size and format permutations + // This number should match the number of texture arrays in Decals/ViewSrg.azsli static constexpr int NumTextureArrays = 5; static constexpr const char* FeatureProcessorName = "DecalTextureArrayFeatureProcessor"; @@ -113,6 +114,7 @@ namespace AZ AZStd::optional AddMaterialToTextureArrays(const AZ::RPI::MaterialAsset* materialAsset); int FindTextureArrayWithSize(const RHI::Size& size) const; + void RemoveMaterialFromDecal(const uint16_t decalIndex); void SetDecalTextureLocation(const DecalHandle& handle, const DecalLocation location); void QueueMaterialLoadForDecal(const AZ::Data::AssetId material, const DecalHandle handle); bool RemoveDecalFromTextureArrays(const DecalLocation decalLocation); @@ -128,7 +130,7 @@ namespace AZ // 4 textures @ 512x512 IndexableList < AZStd::pair < AZ::RHI::Size, DecalTextureArray>> m_textureArrayList; - AZStd::array m_decalTextureArrayIndices; + AZStd::array, NumTextureArrays> m_decalTextureArrayIndices; GpuBufferHandler m_decalBufferHandler; AsyncLoadTracker m_materialLoadTracker; diff --git a/Gems/Atom/Feature/Common/Code/Tests/Decals/DecalTextureArrayTests.cpp b/Gems/Atom/Feature/Common/Code/Tests/Decals/DecalTextureArrayTests.cpp index fcbcec0256..84c2b88507 100644 --- a/Gems/Atom/Feature/Common/Code/Tests/Decals/DecalTextureArrayTests.cpp +++ b/Gems/Atom/Feature/Common/Code/Tests/Decals/DecalTextureArrayTests.cpp @@ -42,7 +42,7 @@ namespace UnitTest { AZ::Render::DecalTextureArray decalTextureArray; decalTextureArray.Pack(); - auto nothing = decalTextureArray.GetPackedTexture(); + auto nothing = decalTextureArray.GetPackedTexture(AZ::Render::DecalMapType_Diffuse); EXPECT_EQ(nothing, nullptr); } diff --git a/Gems/Atom/RHI/Code/Include/Atom/RHI/FrameScheduler.h b/Gems/Atom/RHI/Code/Include/Atom/RHI/FrameScheduler.h index ddcee53e69..48e7e0f339 100644 --- a/Gems/Atom/RHI/Code/Include/Atom/RHI/FrameScheduler.h +++ b/Gems/Atom/RHI/Code/Include/Atom/RHI/FrameScheduler.h @@ -23,6 +23,7 @@ namespace AZ { class Job; + class TaskGraphActiveInterface; namespace RHI { @@ -228,6 +229,8 @@ namespace AZ // list of RayTracingShaderTables that should be built this frame AZStd::vector> m_rayTracingShaderTablesToBuild; + + AZ::TaskGraphActiveInterface* m_taskGraphActive = nullptr; }; } } diff --git a/Gems/Atom/RHI/Code/Source/RHI/FrameScheduler.cpp b/Gems/Atom/RHI/Code/Source/RHI/FrameScheduler.cpp index 5d2feb1e34..3363675d0e 100644 --- a/Gems/Atom/RHI/Code/Source/RHI/FrameScheduler.cpp +++ b/Gems/Atom/RHI/Code/Source/RHI/FrameScheduler.cpp @@ -25,9 +25,11 @@ #include #include +#include #include #include #include +#include namespace AZ { @@ -77,6 +79,8 @@ namespace AZ m_rootScope = m_rootScopeProducer->GetScope(); m_device = &device; + m_taskGraphActive = AZ::Interface::Get(); + m_lastFrameEndTime = AZStd::GetTimeNowTicks(); return ResultCode::Success; @@ -85,6 +89,7 @@ namespace AZ void FrameScheduler::Shutdown() { m_device = nullptr; + m_taskGraphActive = nullptr; m_rootScopeProducer = nullptr; m_rootScope = nullptr; m_frameGraphExecuter = nullptr; @@ -258,50 +263,98 @@ namespace AZ if (m_compileRequest.m_jobPolicy == JobPolicy::Parallel) { - const auto compileGroupsBeginFunction = [](ShaderResourceGroupPool* srgPool) - { - srgPool->CompileGroupsBegin(); - }; - - resourcePoolDatabase.ForEachShaderResourceGroupPool(compileGroupsBeginFunction); - // Iterate over each SRG pool and fork jobs to compile SRGs. const uint32_t compilesPerJob = m_compileRequest.m_shaderResourceGroupCompilesPerJob; - AZ::JobCompletion jobCompletion; - - const auto compileIntervalsFunction = [compilesPerJob, &jobCompletion](ShaderResourceGroupPool* srgPool) + if (m_taskGraphActive && m_taskGraphActive->IsTaskGraphActive()) { - const uint32_t compilesInPool = srgPool->GetGroupsToCompileCount(); - const uint32_t jobCount = DivideByMultiple(compilesInPool, compilesPerJob); + AZ::TaskGraph taskGraph; - for (uint32_t i = 0; i < jobCount; ++i) + const auto compileIntervalsFunction = [compilesPerJob, &taskGraph](ShaderResourceGroupPool* srgPool) { - Interval interval; - interval.m_min = i * compilesPerJob; - interval.m_max = AZStd::min(interval.m_min + compilesPerJob, compilesInPool); + srgPool->CompileGroupsBegin(); + const uint32_t compilesInPool = srgPool->GetGroupsToCompileCount(); + const uint32_t jobCount = DivideByMultiple(compilesInPool, compilesPerJob); + AZ::TaskDescriptor srgCompileDesc{"SrgCompile", "Graphics"}; + AZ::TaskDescriptor srgCompileEndDesc{"SrgCompileEnd", "Graphics"}; - const auto compileGroupsForIntervalLambda = [srgPool, interval]() + auto srgCompileEndTask = taskGraph.AddTask( + srgCompileEndDesc, + [srgPool]() + { + srgPool->CompileGroupsEnd(); + }); + + for (uint32_t i = 0; i < jobCount; ++i) { - AZ_PROFILE_SCOPE(RHI, "FrameScheduler : compileGroupsForIntervalLambda"); - srgPool->CompileGroupsForInterval(interval); - }; + Interval interval; + interval.m_min = i * compilesPerJob; + interval.m_max = AZStd::min(interval.m_min + compilesPerJob, compilesInPool); - AZ::Job* executeGroupJob = AZ::CreateJobFunction(AZStd::move(compileGroupsForIntervalLambda), true, nullptr); - executeGroupJob->SetDependent(&jobCompletion); - executeGroupJob->Start(); + auto compileTask = taskGraph.AddTask( + srgCompileDesc, + [srgPool, interval]() + { + AZ_PROFILE_SCOPE(RHI, "FrameScheduler : compileGroupsForIntervalLambda"); + srgPool->CompileGroupsForInterval(interval); + }); + compileTask.Precedes(srgCompileEndTask); + } + }; + + resourcePoolDatabase.ForEachShaderResourceGroupPool(AZStd::move(compileIntervalsFunction)); + if (!taskGraph.IsEmpty()) + { + AZ::TaskGraphEvent finishedEvent; + taskGraph.Submit(&finishedEvent); + finishedEvent.Wait(); } - }; - - resourcePoolDatabase.ForEachShaderResourceGroupPool(AZStd::move(compileIntervalsFunction)); - - jobCompletion.StartAndWaitForCompletion(); - - const auto compileGroupsEndFunction = [](ShaderResourceGroupPool* srgPool) + } + else // use Job system { - srgPool->CompileGroupsEnd(); - }; + const auto compileGroupsBeginFunction = [](ShaderResourceGroupPool* srgPool) + { + srgPool->CompileGroupsBegin(); + }; - resourcePoolDatabase.ForEachShaderResourceGroupPool(compileGroupsEndFunction); + resourcePoolDatabase.ForEachShaderResourceGroupPool(compileGroupsBeginFunction); + + // Iterate over each SRG pool and fork jobs to compile SRGs. + AZ::JobCompletion jobCompletion; + + const auto compileIntervalsFunction = [compilesPerJob, &jobCompletion](ShaderResourceGroupPool* srgPool) + { + const uint32_t compilesInPool = srgPool->GetGroupsToCompileCount(); + const uint32_t jobCount = DivideByMultiple(compilesInPool, compilesPerJob); + + for (uint32_t i = 0; i < jobCount; ++i) + { + Interval interval; + interval.m_min = i * compilesPerJob; + interval.m_max = AZStd::min(interval.m_min + compilesPerJob, compilesInPool); + + const auto compileGroupsForIntervalLambda = [srgPool, interval]() + { + AZ_PROFILE_SCOPE(RHI, "FrameScheduler : compileGroupsForIntervalLambda"); + srgPool->CompileGroupsForInterval(interval); + }; + + AZ::Job* executeGroupJob = AZ::CreateJobFunction(AZStd::move(compileGroupsForIntervalLambda), true, nullptr); + executeGroupJob->SetDependent(&jobCompletion); + executeGroupJob->Start(); + } + }; + + resourcePoolDatabase.ForEachShaderResourceGroupPool(AZStd::move(compileIntervalsFunction)); + + jobCompletion.StartAndWaitForCompletion(); + + const auto compileGroupsEndFunction = [](ShaderResourceGroupPool* srgPool) + { + srgPool->CompileGroupsEnd(); + }; + + resourcePoolDatabase.ForEachShaderResourceGroupPool(compileGroupsEndFunction); + } } else { diff --git a/Gems/Atom/RHI/Metal/Code/Source/RHI/AsyncUploadQueue.cpp b/Gems/Atom/RHI/Metal/Code/Source/RHI/AsyncUploadQueue.cpp index bd163f1419..eadd571452 100644 --- a/Gems/Atom/RHI/Metal/Code/Source/RHI/AsyncUploadQueue.cpp +++ b/Gems/Atom/RHI/Metal/Code/Source/RHI/AsyncUploadQueue.cpp @@ -33,6 +33,7 @@ namespace AZ // Use separate work submission queue from the hw copy queue to avoid the per frame sync. m_copyQueue = CommandQueue::Create(); + m_copyQueue->SetName(AZ::Name("AsyncUpload Queue")); RHI::CommandQueueDescriptor commandQueueDescriptor; commandQueueDescriptor.m_hardwareQueueClass = RHI::HardwareQueueClass::Copy; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Scene.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Scene.h index b1c6aac92d..fc81368331 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Scene.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Scene.h @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -194,6 +195,9 @@ namespace AZ // This function is called every time scene's render pipelines change. void RebuildPipelineStatesLookup(); + // Helper function to wait for end of TaskGraph + void WaitTGEvent(AZ::TaskGraphEvent& completionTGEvent, AZStd::atomic_bool* workToWaitOn = nullptr); + // Helper function for wait and clean up a completion job void WaitAndCleanCompletionJob(AZ::JobCompletion*& completionJob); @@ -204,12 +208,26 @@ namespace AZ // This happens in UpdateSrgs() void PrepareSceneSrg(); + // Implementation functions that allow scene to switch between using Jobs or TaskGraphs + void SimulateTaskGraph(); + void SimulateJobs(); + + void CollectDrawPacketsTaskGraph(); + void CollectDrawPacketsJobs(); + + void FinalizeDrawListsTaskGraph(); + void FinalizeDrawListsJobs(); + // List of feature processors that are active for this scene AZStd::vector m_featureProcessors; // List of pipelines of this scene. Each pipeline has an unique pipeline Id. AZStd::vector m_pipelines; + // CPU simulation TaskGraphEvent to wait for completion of all the simulation tasks + AZ::TaskGraphEvent m_simulationFinishedTGEvent; + AZStd::atomic_bool m_simulationFinishedWorkActive = false; + // CPU simulation job completion for track all feature processors' simulation jobs AZ::JobCompletion* m_simulationCompletion = nullptr; @@ -228,6 +246,7 @@ namespace AZ SceneId m_id; bool m_activated = false; + bool m_taskGraphActive = false; // update during tick, to ensure it only changes on frame boundaries RenderPipelinePtr m_defaultPipeline; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Scene.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Scene.cpp index 646cba1999..c5d82c6f29 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Scene.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Scene.cpp @@ -23,6 +23,8 @@ #include #include +#include + #include namespace AZ @@ -92,7 +94,14 @@ namespace AZ Scene::~Scene() { - WaitAndCleanCompletionJob(m_simulationCompletion); + if (m_taskGraphActive) + { + WaitTGEvent(m_simulationFinishedTGEvent, &m_simulationFinishedWorkActive); + } + else + { + WaitAndCleanCompletionJob(m_simulationCompletion); + } SceneRequestBus::Handler::BusDisconnect(); // Remove all the render pipelines. Need to process queued changes with pass system before and after remove render pipelines @@ -346,6 +355,47 @@ namespace AZ return nullptr; } + void Scene::SimulateTaskGraph() + { + static const AZ::TaskDescriptor simulationTGDesc{"RPI::Scene::Simulate", "Graphics"}; + AZ::TaskGraph simulationTG; + + for (FeatureProcessorPtr& fp : m_featureProcessors) + { + FeatureProcessor* featureProcessor = fp.get(); + simulationTG.AddTask( + simulationTGDesc, + [this, featureProcessor]() + { + featureProcessor->Simulate(m_simulatePacket); + }); + } + simulationTG.Detach(); + m_simulationFinishedWorkActive = true; + simulationTG.Submit(&m_simulationFinishedTGEvent); + } + + void Scene::SimulateJobs() + { + // Create a new job to track completion. + m_simulationCompletion = aznew AZ::JobCompletion(); + + for (FeatureProcessorPtr& fp : m_featureProcessors) + { + FeatureProcessor* featureProcessor = fp.get(); + const auto jobLambda = [this, featureProcessor]() + { + + featureProcessor->Simulate(m_simulatePacket); + }; + + AZ::Job* simulationJob = AZ::CreateJobFunction(AZStd::move(jobLambda), true, nullptr); //auto-deletes + simulationJob->SetDependent(m_simulationCompletion); + simulationJob->Start(); + } + //[GFX TODO]: the completion job should start here + } + void Scene::Simulate([[maybe_unused]] const TickTimeInfo& tickInfo, RHI::JobPolicy jobPolicy) { AZ_PROFILE_SCOPE(RPI, "Scene: Simulate"); @@ -353,7 +403,17 @@ namespace AZ m_simulationTime = tickInfo.m_currentGameTime; // If previous simulation job wasn't done, wait for it to finish. - WaitAndCleanCompletionJob(m_simulationCompletion); + if (m_taskGraphActive) + { + WaitTGEvent(m_simulationFinishedTGEvent, &m_simulationFinishedWorkActive); + } + else + { + WaitAndCleanCompletionJob(m_simulationCompletion); + } + + auto taskGraphActiveInterface = AZ::Interface::Get(); + m_taskGraphActive = taskGraphActiveInterface && taskGraphActiveInterface->IsTaskGraphActive(); if (jobPolicy == RHI::JobPolicy::Serial) { @@ -364,22 +424,27 @@ namespace AZ } else { - // Create a new job to track completion. - m_simulationCompletion = aznew AZ::JobCompletion(); - - for (FeatureProcessorPtr& fp : m_featureProcessors) + if (m_taskGraphActive) { - FeatureProcessor* featureProcessor = fp.get(); - const auto jobLambda = [this, featureProcessor]() - { - featureProcessor->Simulate(m_simulatePacket); - }; - - AZ::Job* simulationJob = AZ::CreateJobFunction(AZStd::move(jobLambda), true, nullptr); //auto-deletes - simulationJob->SetDependent(m_simulationCompletion); - simulationJob->Start(); + SimulateTaskGraph(); } - //[GFX TODO]: the completion job should start here + else + { + SimulateJobs(); + } + } + } + + void Scene::WaitTGEvent(AZ::TaskGraphEvent& completionTGEvent, AZStd::atomic_bool* workToWaitOn ) + { + AZ_PROFILE_SCOPE(RPI, "Scene: WaitAndCleanCompletionJob"); + if (!workToWaitOn || workToWaitOn->load()) + { + completionTGEvent.Wait(); + } + if (workToWaitOn) + { + workToWaitOn->store(false); } } @@ -394,7 +459,7 @@ namespace AZ completionJob = nullptr; } } - + void Scene::ConnectEvent(PrepareSceneSrgEvent::Handler& handler) { handler.Connect(m_prepareSrgEvent); @@ -418,12 +483,139 @@ namespace AZ } } + void Scene::CollectDrawPacketsTaskGraph() + { + AZ_PROFILE_SCOPE(RPI, "CollectDrawPackets"); + AZ::TaskGraphEvent collectDrawPacketsTGEvent; + static const AZ::TaskDescriptor collectDrawPacketsTGDesc{"RPI_Scene_PrepareRender_CollectDrawPackets", "Graphics"}; + + AZ::TaskGraph collectDrawPacketsTG; + // Launch FeatureProcessor::Render() jobs + for (auto& fp : m_featureProcessors) + { + collectDrawPacketsTG.AddTask( + collectDrawPacketsTGDesc, + [this, &fp]() + { + fp->Render(m_renderPacket); + }); + + } + collectDrawPacketsTG.Submit(&collectDrawPacketsTGEvent); + + // Launch CullingSystem::ProcessCullables() jobs (will run concurrently with FeatureProcessor::Render() jobs if m_parallelOctreeTraversal) + bool parallelOctreeTraversal = m_cullingScene->GetDebugContext().m_parallelOctreeTraversal; + m_cullingScene->BeginCulling(m_renderPacket.m_views); + AZ::JobCompletion processCullablesCompletion; + for (ViewPtr& viewPtr : m_renderPacket.m_views) + { + AZ::Job* processCullablesJob = AZ::CreateJobFunction([this, &viewPtr](AZ::Job& thisJob) + { + m_cullingScene->ProcessCullables(*this, *viewPtr, thisJob); // can't call directly because ProcessCullables needs a parent job + }, + true, nullptr); //auto-deletes + if (parallelOctreeTraversal) + { + processCullablesJob->SetDependent(&processCullablesCompletion); + processCullablesJob->Start(); + } + else + { + processCullablesJob->StartAndWaitForCompletion(); + } + } + + WaitTGEvent(collectDrawPacketsTGEvent); + processCullablesCompletion.StartAndWaitForCompletion(); + } + + void Scene::CollectDrawPacketsJobs() + { + AZ_PROFILE_SCOPE(RPI, "CollectDrawPackets"); + AZ::JobCompletion* collectDrawPacketsCompletion = aznew AZ::JobCompletion(); + + // Launch FeatureProcessor::Render() jobs + for (auto& fp : m_featureProcessors) + { + const auto renderLambda = [this, &fp]() + { + fp->Render(m_renderPacket); + }; + + AZ::Job* renderJob = AZ::CreateJobFunction(AZStd::move(renderLambda), true, nullptr); //auto-deletes + renderJob->SetDependent(collectDrawPacketsCompletion); + renderJob->Start(); + } + + // Launch CullingSystem::ProcessCullables() jobs (will run concurrently with FeatureProcessor::Render() jobs) + m_cullingScene->BeginCulling(m_renderPacket.m_views); + for (ViewPtr& viewPtr : m_renderPacket.m_views) + { + AZ::Job* processCullablesJob = AZ::CreateJobFunction([this, &viewPtr](AZ::Job& thisJob) + { + m_cullingScene->ProcessCullables(*this, *viewPtr, thisJob); // can't call directly because ProcessCullables needs a parent job + }, + true, nullptr); //auto-deletes + if (m_cullingScene->GetDebugContext().m_parallelOctreeTraversal) + { + processCullablesJob->SetDependent(collectDrawPacketsCompletion); + processCullablesJob->Start(); + } + else + { + processCullablesJob->StartAndWaitForCompletion(); + } + } + + WaitAndCleanCompletionJob(collectDrawPacketsCompletion); + } + + void Scene::FinalizeDrawListsTaskGraph() + { + AZ::TaskGraphEvent finalizeDrawListsTGEvent; + static const AZ::TaskDescriptor finalizeDrawListsTGDesc{"RPI_Scene_PrepareRender_FinalizeDrawLists", "Graphics"}; + + AZ::TaskGraph finalizeDrawListsTG; + for (auto& view : m_renderPacket.m_views) + { + finalizeDrawListsTG.AddTask( + finalizeDrawListsTGDesc, + [view]() + { + view->FinalizeDrawLists(); + }); + } + finalizeDrawListsTG.Submit(&finalizeDrawListsTGEvent); + WaitTGEvent(finalizeDrawListsTGEvent); + } + + void Scene::FinalizeDrawListsJobs() + { + AZ::JobCompletion* finalizeDrawListsCompletion = aznew AZ::JobCompletion(); + for (auto& view : m_renderPacket.m_views) + { + const auto finalizeDrawListsLambda = [view]() + { + view->FinalizeDrawLists(); + }; + + AZ::Job* finalizeDrawListsJob = AZ::CreateJobFunction(AZStd::move(finalizeDrawListsLambda), true, nullptr); //auto-deletes + finalizeDrawListsJob->SetDependent(finalizeDrawListsCompletion); + finalizeDrawListsJob->Start(); + } + WaitAndCleanCompletionJob(finalizeDrawListsCompletion); + } + void Scene::PrepareRender(const TickTimeInfo& tickInfo, RHI::JobPolicy jobPolicy) { AZ_PROFILE_SCOPE(RPI, "Scene: PrepareRender"); + if (m_taskGraphActive) + { + WaitTGEvent(m_simulationFinishedTGEvent, &m_simulationFinishedWorkActive); + } + else { - AZ_PROFILE_SCOPE(RPI, "WaitForSimulationCompletion"); WaitAndCleanCompletionJob(m_simulationCompletion); } @@ -496,44 +688,16 @@ namespace AZ } { - AZ_PROFILE_SCOPE(RPI, "CollectDrawPackets"); - AZ::JobCompletion* collectDrawPacketsCompletion = aznew AZ::JobCompletion(); - // Launch FeatureProcessor::Render() jobs - for (auto& fp : m_featureProcessors) + if (m_taskGraphActive) { - const auto renderLambda = [this, &fp]() - { - fp->Render(m_renderPacket); - }; - - AZ::Job* renderJob = AZ::CreateJobFunction(AZStd::move(renderLambda), true, nullptr); //auto-deletes - renderJob->SetDependent(collectDrawPacketsCompletion); - renderJob->Start(); + CollectDrawPacketsTaskGraph(); } - - // Launch CullingSystem::ProcessCullables() jobs (will run concurrently with FeatureProcessor::Render() jobs) - m_cullingScene->BeginCulling(m_renderPacket.m_views); - for (ViewPtr& viewPtr : m_renderPacket.m_views) + else { - AZ::Job* processCullablesJob = AZ::CreateJobFunction([this, &viewPtr](AZ::Job& thisJob) - { - m_cullingScene->ProcessCullables(*this, *viewPtr, thisJob); - }, - true, nullptr); //auto-deletes - if (m_cullingScene->GetDebugContext().m_parallelOctreeTraversal) - { - processCullablesJob->SetDependent(collectDrawPacketsCompletion); - processCullablesJob->Start(); - } - else - { - processCullablesJob->StartAndWaitForCompletion(); - } + CollectDrawPacketsJobs(); } - WaitAndCleanCompletionJob(collectDrawPacketsCompletion); - m_cullingScene->EndCulling(); // Add dynamic draw data for all the views @@ -556,20 +720,15 @@ namespace AZ } else { - AZ::JobCompletion* finalizeDrawListsCompletion = aznew AZ::JobCompletion(); - for (auto& view : m_renderPacket.m_views) + if (m_taskGraphActive) { - const auto finalizeDrawListsLambda = [view]() - { - view->FinalizeDrawLists(); - }; - - AZ::Job* finalizeDrawListsJob = AZ::CreateJobFunction(AZStd::move(finalizeDrawListsLambda), true, nullptr); //auto-deletes - finalizeDrawListsJob->SetDependent(finalizeDrawListsCompletion); - finalizeDrawListsJob->Start(); + FinalizeDrawListsTaskGraph(); + } + else + { + FinalizeDrawListsJobs(); } AZ_PROFILE_END(RPI); - WaitAndCleanCompletionJob(finalizeDrawListsCompletion); } } diff --git a/Gems/Atom/TestData/TestData/Materials/StandardPbrTestCases/001_DefaultWhite.material b/Gems/Atom/TestData/TestData/Materials/StandardPbrTestCases/001_DefaultWhite.material index 8fe732cb09..2e4eee7f8e 100644 --- a/Gems/Atom/TestData/TestData/Materials/StandardPbrTestCases/001_DefaultWhite.material +++ b/Gems/Atom/TestData/TestData/Materials/StandardPbrTestCases/001_DefaultWhite.material @@ -1,6 +1,6 @@ { "description": "", - "materialType": "Materials\\Types\\StandardPBR.materialtype", + "materialType": "Materials/Types/StandardPBR.materialtype", "parentMaterial": "", "propertyLayoutVersion": 3 -} +} \ No newline at end of file diff --git a/Gems/Atom/TestData/TestData/Materials/StandardPbrTestCases/002_BaseColorLerp.material b/Gems/Atom/TestData/TestData/Materials/StandardPbrTestCases/002_BaseColorLerp.material index 8509e08d78..f8214e1b2e 100644 --- a/Gems/Atom/TestData/TestData/Materials/StandardPbrTestCases/002_BaseColorLerp.material +++ b/Gems/Atom/TestData/TestData/Materials/StandardPbrTestCases/002_BaseColorLerp.material @@ -1,6 +1,6 @@ { "description": "", - "materialType": "Materials\\Types\\StandardPBR.materialtype", + "materialType": "Materials/Types/StandardPBR.materialtype", "parentMaterial": "", "propertyLayoutVersion": 3, "properties": { @@ -16,4 +16,4 @@ "textureMap": "TestData/Textures/TextureHaven/4k_castle_brick_02_red/4k_castle_brick_02_red_hp_bc.png" } } -} +} \ No newline at end of file diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Decals/DecalComponentController.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Decals/DecalComponentController.cpp index 5d16164d92..dd8266c1ca 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Decals/DecalComponentController.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Decals/DecalComponentController.cpp @@ -230,7 +230,7 @@ namespace AZ { DecalNotificationBus::Event(m_entityId, &DecalNotifications::OnMaterialChanged, m_configuration.m_materialAsset); - if (m_featureProcessor && m_configuration.m_materialAsset.GetId().IsValid()) + if (m_featureProcessor) { m_featureProcessor->SetDecalMaterial(m_handle, m_configuration.m_materialAsset.GetId()); } diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.cpp index 2dc3b9134a..6cdb44a9e1 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.cpp @@ -194,41 +194,13 @@ namespace AZ incompatible.push_back(AZ_CRC("MeshService", 0x71d8a455)); } - // [GFX TODO] [ATOM-13339] Remove the ModelAsset id fix up function in MeshComponentController - // Model id was changed due to fix for [ATOM-13312]. We can remove this code when all the levels are updated. - void FixUpModelAsset(Data::Asset& modelAsset) - { - Data::AssetId assetId; - Data::AssetCatalogRequestBus::BroadcastResult( - assetId, - &Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, - modelAsset.GetHint().c_str(), - AZ::RPI::ModelAsset::TYPEINFO_Uuid(), - false); - if (assetId != modelAsset.GetId()) - { - if (assetId.IsValid()) - { - modelAsset = Data::Asset{ assetId, AZ::RPI::ModelAsset::TYPEINFO_Uuid(), modelAsset.GetHint().c_str() }; - modelAsset.SetAutoLoadBehavior(AZ::Data::AssetLoadBehavior::QueueLoad); - } - else - { - AZ_Error("MeshComponentController", false, "Failed to find asset id for [%s] ", modelAsset.GetHint().c_str()); - } - } - } - MeshComponentController::MeshComponentController(const MeshComponentConfig& config) : m_configuration(config) { - FixUpModelAsset(m_configuration.m_modelAsset); } void MeshComponentController::Activate(const AZ::EntityComponentIdPair& entityComponentIdPair) { - FixUpModelAsset(m_configuration.m_modelAsset); - const AZ::EntityId entityId = entityComponentIdPair.GetEntityId(); m_entityComponentIdPair = entityComponentIdPair; diff --git a/Gems/ScriptCanvas/Code/Builder/ScriptCanvasBuilder.cpp b/Gems/ScriptCanvas/Code/Builder/ScriptCanvasBuilder.cpp index 3c168855d8..0eb748b2f1 100644 --- a/Gems/ScriptCanvas/Code/Builder/ScriptCanvasBuilder.cpp +++ b/Gems/ScriptCanvas/Code/Builder/ScriptCanvasBuilder.cpp @@ -182,12 +182,15 @@ namespace ScriptCanvasBuilder continue; } - // copy to override unused list for editor display - m_overridesUnused.push_back(*graphVariable); - auto& overrideValue = m_overridesUnused.back(); - overrideValue.DeepCopy(*graphVariable); - overrideValue.SetScriptInputControlVisibility(AZ::Edit::PropertyVisibility::Hide); - overrideValue.SetAllowSignalOnChange(false); + if (graphVariable->IsComponentProperty()) + { + // copy to override unused list for editor display + m_overridesUnused.push_back(*graphVariable); + auto& overrideValue = m_overridesUnused.back(); + overrideValue.DeepCopy(*graphVariable); + overrideValue.SetScriptInputControlVisibility(AZ::Edit::PropertyVisibility::Hide); + overrideValue.SetAllowSignalOnChange(false); + } } }