@@ -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);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
+17
@@ -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
|
||||
|
||||
+1
-1
@@ -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
-2
@@ -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;
|
||||
}
|
||||
|
||||
+2
-2
@@ -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
|
||||
+14
@@ -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>
|
||||
|
||||
+5
@@ -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;
|
||||
|
||||
|
||||
+71
@@ -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
|
||||
+45
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"description": "",
|
||||
"materialType": "Materials\\Types\\StandardPBR.materialtype",
|
||||
"materialType": "Materials/Types/StandardPBR.materialtype",
|
||||
"parentMaterial": "",
|
||||
"propertyLayoutVersion": 3
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user