Signed-off-by: mrieggeramzn <mriegger@amazon.com>
This commit is contained in:
mrieggeramzn
2021-10-01 13:17:28 -07:00
67 changed files with 1890 additions and 398 deletions
+23 -3
View File
@@ -15,6 +15,7 @@
#include "ViewportTitleDlg.h"
// Qt
#include <QLabel>
#include <QInputDialog>
#include <AtomLyIntegration/AtomViewportDisplayInfo/AtomViewportInfoDisplayBus.h>
@@ -39,6 +40,7 @@
#include <AzCore/Asset/AssetSerializer.h>
#include <AzCore/Casting/numeric_cast.h>
#include <AzCore/std/algorithm.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzToolsFramework/Viewport/ViewportMessages.h>
#include <LmbrCentral/Audio/AudioSystemComponentBus.h>
@@ -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);
}
//////////////////////////////////////////////////////////////////////////
+3
View File
@@ -19,6 +19,7 @@
#include <QWidgetAction>
#include <QComboBox>
#include <AzToolsFramework/UI/Prefab/PrefabViewportFocusPathHandler.h>
#include <AzQtComponents/Components/Widgets/SpinBox.h>
#include <HMDBus.h>
@@ -176,6 +177,8 @@ protected:
QWidgetAction* m_gridSizeActionWidget = nullptr;
QWidgetAction* m_angleSizeActionWidget = nullptr;
AzToolsFramework::Prefab::PrefabViewportFocusPathHandler* m_prefabViewportFocusPathHandler = nullptr;
QScopedPointer<Ui::ViewportTitleDlg> m_ui;
};
+86 -76
View File
@@ -28,7 +28,7 @@
<height>29</height>
</size>
</property>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,0,0,0,0,0,0,0,0">
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,0,0,0,0,0,0,0">
<property name="leftMargin">
<number>10</number>
</property>
@@ -42,102 +42,112 @@
<number>0</number>
</property>
<item>
<widget class="QLabel" name="m_titleBtn">
<widget class="QToolButton" name="m_prefabFocusBackButton">
<property name="toolTip">
<string>Up one level</string>
</property>
<property name="icon">
<iconset>
<normaloff>:/Breadcrumb/img/UI20/Breadcrumb/arrow_left-default.svg</normaloff>:/Breadcrumb/img/UI20/Breadcrumb/arrow_left-default.svg</iconset>
</property>
</widget>
</item>
<item>
<widget class="AzQtComponents::BreadCrumbs" name="m_prefabFocusPath" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="contextMenuPolicy">
<enum>Qt::NoContextMenu</enum>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="text">
<string>Static</string>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
<property name="indent">
<number>11</number>
</spacer>
</item>
<item>
<widget class="QToolButton" name="m_cameraMenu">
<property name="toolTip">
<string>Camera settings</string>
</property>
<property name="icon">
<iconset>
<normaloff>:/Menu/camera.svg</normaloff>:/Menu/camera.svg</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="m_cameraMenu">
<property name="icon">
<iconset>
<normaloff>:/Menu/camera.svg</normaloff>:/Menu/camera.svg
</iconset>
</property>
<property name="toolTip">
<string>Camera settings</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="m_debugInformationMenu">
<property name="toolTip">
<string>Debug information</string>
</property>
<property name="icon">
<iconset>
<normaloff>:/Menu/debug.svg</normaloff>:/Menu/debug.svg
</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="m_helpers">
<property name="toolTip">
<string>Toggle viewport helpers</string>
</property>
<property name="icon">
<iconset>
<normaloff>:/Menu/helpers.svg</normaloff>:/Menu/helpers.svg
</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="m_resolutionMenu">
<property name="toolTip">
<string>Viewport resolution</string>
</property>
<property name="icon">
<iconset>
<normaloff>:/Menu/resolution.svg</normaloff>:/Menu/resolution.svg
</iconset>
</property>
</widget>
<widget class="QToolButton" name="m_debugInformationMenu">
<property name="toolTip">
<string>Debug information</string>
</property>
<property name="icon">
<iconset>
<normaloff>:/Menu/debug.svg</normaloff>:/Menu/debug.svg</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="m_overflowBtn">
<property name="toolTip">
<string>Other settings</string>
</property>
<property name="icon">
<iconset>
<normaloff>:/Menu/menu.svg</normaloff>:/Menu/menu.svg
</iconset>
</property>
</widget>
<widget class="QToolButton" name="m_helpers">
<property name="toolTip">
<string>Toggle viewport helpers</string>
</property>
<property name="icon">
<iconset>
<normaloff>:/Menu/helpers.svg</normaloff>:/Menu/helpers.svg</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="m_resolutionMenu">
<property name="toolTip">
<string>Viewport resolution</string>
</property>
<property name="icon">
<iconset>
<normaloff>:/Menu/resolution.svg</normaloff>:/Menu/resolution.svg</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="m_overflowBtn">
<property name="toolTip">
<string>Other settings</string>
</property>
<property name="icon">
<iconset>
<normaloff>:/Menu/menu.svg</normaloff>:/Menu/menu.svg</iconset>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>AzQtComponents::ButtonDivider</class>
<class>AzQtComponents::BreadCrumbs</class>
<extends>QWidget</extends>
<header>AzQtComponents/Components/ButtonDivider.h</header>
<header>AzQtComponents/Components/Widgets/BreadCrumbs.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources>
<include location="../../Framework/AzQtComponents/AzQtComponents/Images/resources.qrc"/>
</resources>
<connections/>
<resources>
<include location="../../Framework/AzQtComponents/AzQtComponents/Images/resources.qrc"/>
</resources>
<connections/>
</ui>
@@ -22,6 +22,7 @@
#include <AzCore/Time/TimeSystemComponent.h>
#include <AzCore/Console/LoggerSystemComponent.h>
#include <AzCore/EBus/EventSchedulerSystemComponent.h>
#include <AzCore/Task/TaskGraphSystemComponent.h>
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<TimeSystemComponent>(),
azrtti_typeid<LoggerSystemComponent>(),
azrtti_typeid<EventSchedulerSystemComponent>(),
azrtti_typeid<TaskGraphSystemComponent>(),
};
}
}
@@ -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<bool> m_active;
AZStd::atomic<bool> m_busy;
AZStd::atomic<bool> 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<TaskExecutor*> 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<TaskExecutor*>("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<TaskExecutor*>(s_executorName, executor);
}
}
TaskExecutor::TaskExecutor(uint32_t threadCount)
@@ -307,14 +325,12 @@ namespace AZ
m_workers = reinterpret_cast<Internal::TaskWorker*>(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
@@ -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;
@@ -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)
{
@@ -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.
@@ -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>(lambdas))... };
}
inline bool TaskGraph::IsEmpty()
{
return m_tasks.empty();
}
inline void TaskGraph::Detach()
{
m_retained = false;
@@ -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 <AzCore/Console/IConsole.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/Task/TaskGraphSystemComponent.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
// 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<TaskGraphActiveInterface>::Get() == nullptr)
{
Interface<TaskGraphActiveInterface>::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<TaskGraphActiveInterface>::Get() == this)
{
Interface<TaskGraphActiveInterface>::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<SerializeContext*>(context))
{
serializeContext->Class<TaskGraphSystemComponent, AZ::Component>()
->Version(1)
;
if (AZ::EditContext* ec = serializeContext->GetEditContext())
{
ec->Class<TaskGraphSystemComponent>
("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
@@ -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 <AzCore/Component/Component.h>
#include <AzCore/Math/Crc.h>
#include <AzCore/Task/TaskExecutor.h>
#include <AzCore/Task/TaskGraph.h>
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;
};
}
@@ -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
+81 -3
View File
@@ -34,7 +34,7 @@ namespace UnitTest
AZ::AllocatorInstance<AZ::PoolAllocator>::Create();
AZ::AllocatorInstance<AZ::ThreadPoolAllocator>::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<int> 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)
@@ -28,6 +28,7 @@
#include <AzCore/NativeUI/NativeUISystemComponent.h>
#include <AzCore/Module/ModuleManagerBus.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/Task/TaskGraphSystemComponent.h>
#include <AzFramework/Asset/SimpleAsset.h>
#include <AzFramework/Asset/AssetBundleManifest.h>
@@ -295,6 +296,7 @@ namespace AzFramework
azrtti_typeid<AZ::ScriptSystemComponent>(),
azrtti_typeid<AZ::JobManagerComponent>(),
azrtti_typeid<AZ::SliceSystemComponent>(),
azrtti_typeid<AZ::TaskGraphSystemComponent>(),
azrtti_typeid<AzFramework::AssetCatalogComponent>(),
azrtti_typeid<AzFramework::CustomAssetTypeComponent>(),
@@ -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()
@@ -8,6 +8,7 @@
#include <AzFramework/XcbApplication.h>
#include <AzFramework/XcbEventHandler.h>
#include <AzFramework/XcbInterface.h>
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<xcb_connection_t, xcb_disconnect> 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_generic_event_t>{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_generic_event_t>{xcb_poll_for_event(xcbConnection)})
{
XcbEventHandlerBus::Broadcast(&XcbEventHandlerBus::Events::HandleXcbEvent, event);
free(event);
XcbEventHandlerBus::Broadcast(&XcbEventHandlerBus::Events::HandleXcbEvent, event.get());
}
}
}
@@ -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 <AzTest/AzTest.h>
AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV);
@@ -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
#
#
@@ -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 <gtest/gtest.h>
#include <gmock/gmock.h>
ACTION_TEMPLATE(ReturnMalloc,
HAS_1_TEMPLATE_PARAMS(typename, T),
AND_1_VALUE_PARAMS(p0)) {
T* value = static_cast<T*>(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<T*>(malloc(sizeof(T)));
*value = T{ p0, p1 };
return value;
}
@@ -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 <AzTest/AzTest.h>
AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV);
@@ -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);
}
}
@@ -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 <gtest/gtest.h>
#include <gmock/gmock.h>
#include <xcb/xcb.h>
#define explicit ExplicitIsACXXKeyword
#include <xcb/xkb.h>
#undef explicit
#include <xkbcommon/xkbcommon.h>
#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;
};
@@ -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 <ostream>
#include <AzFramework/Input/Channels/InputChannel.h>
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
@@ -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 <iosfwd>
#include <AzFramework/Input/Channels/InputChannel.h>
namespace AzFramework
{
void PrintTo(const InputChannel::State& state, std::ostream* os);
} // namespace AzFramework
@@ -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 <gtest/gtest.h>
#include <gmock/gmock.h>
#include <xcb/xcb.h>
#include <AzFramework/XcbApplication.h>
#include <AzFramework/XcbInputDeviceKeyboard.h>
#include "MockXcbInterface.h"
#include "Actions.h"
template<typename T>
xcb_generic_event_t MakeEvent(T event)
{
return *reinterpret_cast<xcb_generic_event_t*>(&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<xcb_xkb_use_extension_reply_t>(
/* .response_type =*/static_cast<uint8_t>(XCB_XKB_USE_EXTENSION),
/* .supported =*/ static_cast<uint8_t>(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<xcb_generic_event_t>(events[0]))
.WillOnce(Return(nullptr))
.WillOnce(ReturnMalloc<xcb_generic_event_t>(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
@@ -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
)
@@ -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()
@@ -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
#
#
@@ -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
#
#
@@ -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
#
#
@@ -7,7 +7,7 @@
#
set(FILES
../../AzCore/Tests/Main.cpp
Main.cpp
Spawnable/SpawnableEntitiesInterfaceTests.cpp
Spawnable/SpawnableEntitiesManagerTests.cpp
ArchiveCompressionTests.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
{
@@ -12,6 +12,8 @@
#include <AzCore/Interface/Interface.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzFramework/Entity/EntityContextBus.h>
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;
@@ -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 <AzCore/Component/EntityId.h>
#include <AzCore/EBus/EBus.h>
#include <AzFramework/Entity/EntityContext.h>
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<FocusModeNotifications>;
} // namespace AzToolsFramework
@@ -9,6 +9,7 @@
#include <AzCore/Component/TransformBus.h>
#include <AzToolsFramework/API/ViewportEditorModeTrackerInterface.h>
#include <AzToolsFramework/FocusMode/FocusModeNotificationBus.h>
#include <AzToolsFramework/FocusMode/FocusModeSystemComponent.h>
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;
}
@@ -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:
@@ -8,9 +8,11 @@
#include <AzToolsFramework/Prefab/PrefabFocusHandler.h>
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
#include <AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <AzToolsFramework/Prefab/Instance/InstanceEntityMapperInterface.h>
#include <AzToolsFramework/Prefab/PrefabFocusNotificationBus.h>
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<PrefabFocusInterface>::Register(this);
}
PrefabFocusHandler::~PrefabFocusHandler()
{
AZ::Interface<PrefabFocusInterface>::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<FocusModeInterface>::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<FocusModeInterface>::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<int>(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<InstanceOptionalReference> 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
@@ -10,6 +10,7 @@
#include <AzCore/Memory/SystemAllocator.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <AzToolsFramework/FocusMode/FocusModeInterface.h>
#include <AzToolsFramework/Prefab/PrefabFocusInterface.h>
#include <AzToolsFramework/Prefab/Template/Template.h>
@@ -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<InstanceOptionalReference> m_instanceFocusVector;
AZ::IO::Path m_instanceFocusPath;
InstanceEntityMapperInterface* m_instanceEntityMapperInterface;
};
@@ -11,6 +11,8 @@
#include <AzCore/Interface/Interface.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzFramework/Entity/EntityContext.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <AzToolsFramework/Prefab/Template/Template.h>
@@ -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
@@ -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 <AzCore/EBus/EBus.h>
#include <AzFramework/Entity/EntityContext.h>
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<PrefabFocusNotifications>;
} // namespace AzToolsFramework::Prefab
@@ -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 <UI/Outliner/moc_EntityOutlinerTreeView.cpp>
@@ -15,6 +15,7 @@
#include <QBasicTimer>
#include <QEvent>
#include <AzToolsFramework/FocusMode/FocusModeNotificationBus.h>
#include <AzQtComponents/Components/Widgets/TreeView.h>
#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;
@@ -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 <AzToolsFramework/UI/Prefab/PrefabViewportFocusPathHandler.h>
#include <AzToolsFramework/Prefab/PrefabFocusInterface.h>
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<PrefabFocusInterface>::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
@@ -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 <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <AzToolsFramework/Prefab/PrefabFocusNotificationBus.h>
#include <AzQtComponents/Components/Widgets/BreadCrumbs.h>
#include <QLayout>
#include <QToolButton>
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
@@ -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
@@ -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();
}
@@ -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);
@@ -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());
}
}
@@ -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);
@@ -72,6 +72,9 @@ namespace UnitTest
m_prefabFocusInterface = AZ::Interface<PrefabFocusInterface>::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<AzToolsFramework::Prefab::Instance> 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());
}
@@ -16,6 +16,7 @@
#include <Atom/Features/MatrixUtility.azsli>
#include <Atom/Features/Decals/DecalTextureUtil.azsli>
#include <Atom/Features/LightCulling/LightCullingTileIterator.azsli>
#include <Atom/RPI/TangentSpace.azsli>
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));
}
}
@@ -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<float4> m_decalTextureArray0;
Texture2DArray<float4> m_decalTextureArray1;
Texture2DArray<float4> m_decalTextureArray2;
Texture2DArray<float4> m_decalTextureArray3;
Texture2DArray<float4> m_decalTextureArray4;
// This must match the variable NumTextureArrays in DecalTextureArrayFeatureProcessor.h
Texture2DArray<float4> m_decalTextureArrayDiffuse0;
Texture2DArray<float4> m_decalTextureArrayDiffuse1;
Texture2DArray<float4> m_decalTextureArrayDiffuse2;
Texture2DArray<float4> m_decalTextureArrayDiffuse3;
Texture2DArray<float4> m_decalTextureArrayDiffuse4;
Texture2DArray<float4> m_decalTextureArrayNormalMaps0;
Texture2DArray<float4> m_decalTextureArrayNormalMaps1;
Texture2DArray<float4> m_decalTextureArrayNormalMaps2;
Texture2DArray<float4> m_decalTextureArrayNormalMaps3;
Texture2DArray<float4> m_decalTextureArrayNormalMaps4;
uint m_decalCount;
}
@@ -5,7 +5,6 @@
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "DecalTextureArray.h"
#include <Atom/RPI.Public/Image/ImageSystemInterface.h>
#include <Atom/RPI.Reflect/Image/StreamingImageAssetCreator.h>
@@ -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<AZ::Name, DecalMapType_Num> 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<AZ::RPI::StreamingImageAsset> 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<AZ::RPI::MaterialAsset>();
return GetStreamingImageAsset(*materialAsset, propertyName);
}
AZ::Data::Asset<AZ::RPI::StreamingImageAsset> GetBaseColorImageAsset(const AZ::Data::Asset<Data::AssetData> 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<AZ::RPI::StreamingImage>& DecalTextureArray::GetPackedTexture() const
const AZ::Data::Instance<AZ::RPI::StreamingImage>& 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<AZ::RPI::ImageMipChainAsset> DecalTextureArray::BuildPackedMipChainAsset(const size_t numTexturesToCreate)
AZ::Data::Asset<AZ::RPI::ImageMipChainAsset> 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<uint16_t>(mipLevels), aznumeric_cast<uint16_t>(numTexturesToCreate));
assetCreator.Begin(Data::AssetId(AZ::Uuid::CreateRandom()), aznumeric_cast<uint16_t>(mipLevels), aznumeric_cast<uint16_t>(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<DecalMapType>(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<uint16_t>(numTexturesToCreate), GetNumMipLevels()));
assetCreator.SetImageViewDescriptor(imageViewDescriptor);
assetCreator.AddMipChainAsset(*mipChainAsset);
Data::Asset<RPI::StreamingImageAsset> 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<uint16_t>(numTexturesToCreate), GetNumMipLevels(mapType)));
assetCreator.SetImageViewDescriptor(imageViewDescriptor);
assetCreator.AddMipChainAsset(*mipChainAsset);
Data::Asset<RPI::StreamingImageAsset> 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<uint8_t> DecalTextureArray::GetRawImageData(int arrayLevel, const int mip) const
AZStd::array_view<uint8_t> 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;
}
}
@@ -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<RPI::StreamingImage>& 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<RPI::StreamingImage>& GetPackedTexture(const DecalMapType mapType) const;
static bool IsValidDecalMaterial(const RPI::MaterialAsset& materialAsset);
@@ -56,22 +70,25 @@ namespace AZ
void OnAssetReady(Data::Asset<Data::AssetData> 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<AZ::RPI::ImageMipChainAsset> BuildPackedMipChainAsset(const size_t numTexturesToCreate);
AZ::Data::Asset<AZ::RPI::ImageMipChainAsset> 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<uint8_t> 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<uint8_t> 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<MaterialData> m_materials;
Data::Instance<RPI::StreamingImage> m_textureArrayPacked;
AZStd::array<Data::Instance<RPI::StreamingImage>, DecalMapType_Num> m_textureArrayPacked;
AZStd::unordered_set<AZ::Data::AssetId> m_assetsCurrentlyLoading;
};
@@ -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<float4> m_decalTextureArrayDiffuse0;
// Texture2DArray<float4> m_decalTextureArrayDiffuse1;
// Texture2DArray<float4> m_decalTextureArrayDiffuse2;
// and
// Texture2DArray<float2> m_decalTextureArrayNormalMaps0;
// Texture2DArray<float2> m_decalTextureArrayNormalMaps1;
// Texture2DArray<float2> m_decalTextureArrayNormalMaps2;
static const AZStd::array<AZStd::string, DecalMapType_Num> 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<DecalMapType>(mapType));
view->GetShaderResourceGroup()->SetImage(m_decalTextureArrayIndices[iter][mapType], packedTexture);
}
iter = m_textureArrayList.next(iter);
}
}
@@ -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<DecalLocation> 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<RHI::ShaderInputImageIndex, NumTextureArrays> m_decalTextureArrayIndices;
AZStd::array<AZStd::array<RHI::ShaderInputImageIndex, DecalMapType_Num>, NumTextureArrays> m_decalTextureArrayIndices;
GpuBufferHandler m_decalBufferHandler;
AsyncLoadTracker<DecalHandle> m_materialLoadTracker;
@@ -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);
}
@@ -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<RHI::Ptr<RayTracingShaderTable>> m_rayTracingShaderTablesToBuild;
AZ::TaskGraphActiveInterface* m_taskGraphActive = nullptr;
};
}
}
@@ -25,9 +25,11 @@
#include <Atom/RHI/RayTracingShaderTable.h>
#include <AzCore/Debug/EventTrace.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/Jobs/Algorithms.h>
#include <AzCore/Jobs/JobCompletion.h>
#include <AzCore/Jobs/JobFunction.h>
#include <AzCore/Task/TaskGraph.h>
namespace AZ
{
@@ -77,6 +79,8 @@ namespace AZ
m_rootScope = m_rootScopeProducer->GetScope();
m_device = &device;
m_taskGraphActive = AZ::Interface<AZ::TaskGraphActiveInterface>::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<decltype(compileGroupsBeginFunction)>(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<decltype(compileIntervalsFunction)>(AZStd::move(compileIntervalsFunction));
if (!taskGraph.IsEmpty())
{
AZ::TaskGraphEvent finishedEvent;
taskGraph.Submit(&finishedEvent);
finishedEvent.Wait();
}
};
resourcePoolDatabase.ForEachShaderResourceGroupPool<decltype(compileIntervalsFunction)>(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<decltype(compileGroupsEndFunction)>(compileGroupsEndFunction);
resourcePoolDatabase.ForEachShaderResourceGroupPool<decltype(compileGroupsBeginFunction)>(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<decltype(compileIntervalsFunction)>(AZStd::move(compileIntervalsFunction));
jobCompletion.StartAndWaitForCompletion();
const auto compileGroupsEndFunction = [](ShaderResourceGroupPool* srgPool)
{
srgPool->CompileGroupsEnd();
};
resourcePoolDatabase.ForEachShaderResourceGroupPool<decltype(compileGroupsEndFunction)>(compileGroupsEndFunction);
}
}
else
{
@@ -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;
@@ -29,6 +29,7 @@
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/Script/ScriptTimePoint.h>
#include <AzCore/Task/TaskGraph.h>
#include <AzFramework/Scene/Scene.h>
#include <AzFramework/Scene/SceneSystemInterface.h>
@@ -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<FeatureProcessorPtr> m_featureProcessors;
// List of pipelines of this scene. Each pipeline has an unique pipeline Id.
AZStd::vector<RenderPipelinePtr> m_pipelines;
// CPU simulation TaskGraphEvent to wait for completion of all the simulation tasks
AZ::TaskGraphEvent m_simulationFinishedTGEvent;
AZStd::atomic_bool m_simulationFinishedWorkActive = false;
// 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;
+220 -61
View File
@@ -23,6 +23,8 @@
#include <AzCore/Jobs/JobFunction.h>
#include <AzCore/Jobs/JobEmpty.h>
#include <AzCore/Task/TaskGraph.h>
#include <AzFramework/Entity/EntityContext.h>
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<AZ::TaskGraphActiveInterface>::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);
}
}
@@ -1,6 +1,6 @@
{
"description": "",
"materialType": "Materials\\Types\\StandardPBR.materialtype",
"materialType": "Materials/Types/StandardPBR.materialtype",
"parentMaterial": "",
"propertyLayoutVersion": 3
}
}
@@ -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"
}
}
}
}
@@ -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());
}
@@ -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<RPI::ModelAsset>& 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<RPI::ModelAsset>{ 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;
@@ -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);
}
}
}