Merge branch 'development' of https://github.com/o3de/o3de into Network/olexl/nettransform_local_for_children_cr

monroegm-disable-blank-issue-2
AMZN-Olex 4 years ago
commit 6e68da9382

@ -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>(),

@ -11,6 +11,7 @@
#include <AzToolsFramework/API/ViewportEditorModeTrackerInterface.h>
#include <AzToolsFramework/FocusMode/FocusModeNotificationBus.h>
#include <AzToolsFramework/FocusMode/FocusModeSystemComponent.h>
#include <AzToolsFramework/API/ViewportEditorModeTrackerInterface.h>
namespace AzToolsFramework
{
@ -73,7 +74,18 @@ namespace AzToolsFramework
m_focusRoot = entityId;
FocusModeNotificationBus::Broadcast(&FocusModeNotifications::OnEditorFocusChanged, m_focusRoot);
// TODO - If m_focusRoot != AZ::EntityId(), activate focus mode via ViewportEditorModeTrackerInterface; else, deactivate focus mode
if (auto tracker = AZ::Interface<ViewportEditorModeTrackerInterface>::Get();
tracker != nullptr)
{
if (!m_focusRoot.IsValid() && entityId.IsValid())
{
tracker->ActivateMode({ /* DefaultViewportId */ }, ViewportEditorMode::Focus);
}
else if (m_focusRoot.IsValid() && !entityId.IsValid())
{
tracker->DeactivateMode({ /* DefaultViewportId */ }, ViewportEditorMode::Focus);
}
}
}
void FocusModeSystemComponent::ClearFocusRoot([[maybe_unused]] AzFramework::EntityContextId entityContextId)

@ -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,30 +65,39 @@ 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;
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;
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;
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;
}
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);
RHI::ImageDescriptor CreatePackedImageDescriptor(const uint16_t arraySize, const uint16_t mipLevels) const;
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;
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;
};

@ -322,13 +322,30 @@ namespace AZ
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);
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());
// 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" };
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());
}
}
}
@ -411,8 +428,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";
@ -128,7 +129,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;

@ -128,8 +128,8 @@ namespace AZ
void DiffuseProbeGrid::SetTransform(const AZ::Transform& transform)
{
m_position = transform.GetTranslation();
m_aabbWs = Aabb::CreateCenterHalfExtents(m_position, m_extents / 2.0f);
m_transform = transform;
m_obbWs = Obb::CreateFromPositionRotationAndHalfLengths(m_transform.GetTranslation(), m_transform.GetRotation(), m_extents / 2.0f);
// probes need to be relocated since the grid position changed
m_remainingRelocationIterations = DefaultNumRelocationIterations;
@ -145,7 +145,7 @@ namespace AZ
void DiffuseProbeGrid::SetExtents(const AZ::Vector3& extents)
{
m_extents = extents;
m_aabbWs = Aabb::CreateCenterHalfExtents(m_position, m_extents / 2.0f);
m_obbWs = Obb::CreateFromPositionRotationAndHalfLengths(m_transform.GetTranslation(), m_transform.GetRotation(), m_extents / 2.0f);
// recompute the number of probes since the extents changed
UpdateProbeCount();
@ -467,7 +467,10 @@ namespace AZ
RHI::ShaderInputConstantIndex constantIndex;
constantIndex = srgLayout->FindShaderInputConstantIndex(AZ::Name("m_probeGrid.origin"));
srg->SetConstant(constantIndex, m_position);
srg->SetConstant(constantIndex, m_transform.GetTranslation());
constantIndex = srgLayout->FindShaderInputConstantIndex(AZ::Name("m_probeGrid.rotation"));
srg->SetConstant(constantIndex, m_transform.GetRotation());
constantIndex = srgLayout->FindShaderInputConstantIndex(AZ::Name("m_probeGrid.numRaysPerProbe"));
srg->SetConstant(constantIndex, m_numRaysPerProbe);
@ -760,14 +763,15 @@ namespace AZ
RHI::ShaderInputImageIndex imageIndex;
constantIndex = srgLayout->FindShaderInputConstantIndex(Name("m_modelToWorld"));
AZ::Matrix3x4 modelToWorld = AZ::Matrix3x4::CreateFromMatrix3x3AndTranslation(Matrix3x3::CreateIdentity(), m_position) * AZ::Matrix3x4::CreateScale(m_extents);
AZ::Matrix3x4 modelToWorld = AZ::Matrix3x4::CreateFromTransform(m_transform) * AZ::Matrix3x4::CreateScale(m_extents);
m_renderObjectSrg->SetConstant(constantIndex, modelToWorld);
constantIndex = srgLayout->FindShaderInputConstantIndex(Name("m_aabbMin"));
m_renderObjectSrg->SetConstant(constantIndex, m_aabbWs.GetMin());
constantIndex = srgLayout->FindShaderInputConstantIndex(Name("m_modelToWorldInverse"));
AZ::Matrix3x4 modelToWorldInverse = AZ::Matrix3x4::CreateFromTransform(m_transform).GetInverseFull();
m_renderObjectSrg->SetConstant(constantIndex, modelToWorldInverse);
constantIndex = srgLayout->FindShaderInputConstantIndex(Name("m_aabbMax"));
m_renderObjectSrg->SetConstant(constantIndex, m_aabbWs.GetMax());
constantIndex = srgLayout->FindShaderInputConstantIndex(Name("m_obbHalfLengths"));
m_renderObjectSrg->SetConstant(constantIndex, m_obbWs.GetHalfLengths());
constantIndex = srgLayout->FindShaderInputConstantIndex(Name("m_enableDiffuseGI"));
m_renderObjectSrg->SetConstant(constantIndex, m_enabled);
@ -821,13 +825,14 @@ namespace AZ
lod.m_screenCoverageMax = 1.0f;
// update cullable bounds
Aabb aabbWs = Aabb::CreateFromObb(m_obbWs);
Vector3 center;
float radius;
m_aabbWs.GetAsSphere(center, radius);
aabbWs.GetAsSphere(center, radius);
m_cullable.m_cullData.m_boundingSphere = Sphere(center, radius);
m_cullable.m_cullData.m_boundingObb = m_aabbWs.GetTransformedObb(AZ::Transform::CreateIdentity());
m_cullable.m_cullData.m_visibilityEntry.m_boundingVolume = m_aabbWs;
m_cullable.m_cullData.m_boundingObb = m_obbWs;
m_cullable.m_cullData.m_visibilityEntry.m_boundingVolume = aabbWs;
m_cullable.m_cullData.m_visibilityEntry.m_userData = &m_cullable;
m_cullable.m_cullData.m_visibilityEntry.m_typeFlags = AzFramework::VisibilityEntry::TYPE_RPI_Cullable;

@ -72,7 +72,7 @@ namespace AZ
const AZ::Vector3& GetExtents() const { return m_extents; }
void SetExtents(const AZ::Vector3& extents);
const AZ::Aabb& GetAabbWs() const { return m_aabbWs; }
const AZ::Obb& GetObbWs() const { return m_obbWs; }
bool ValidateProbeSpacing(const AZ::Vector3& newSpacing);
const AZ::Vector3& GetProbeSpacing() const { return m_probeSpacing; }
@ -183,14 +183,14 @@ namespace AZ
// scene
RPI::Scene* m_scene = nullptr;
// probe grid position
AZ::Vector3 m_position = AZ::Vector3(0.0f, 0.0f, 0.0f);
// probe grid transform
AZ::Transform m_transform = AZ::Transform::CreateIdentity();
// extents of the probe grid
AZ::Vector3 m_extents = AZ::Vector3(0.0f, 0.0f, 0.0f);
// probe grid AABB (world space), built from position and extents
AZ::Aabb m_aabbWs = AZ::Aabb::CreateNull();
// probe grid OBB (world space), built from transform and extents
AZ::Obb m_obbWs;
// per-axis spacing of probes in the grid
AZ::Vector3 m_probeSpacing;

@ -154,10 +154,11 @@ namespace AZ
// sort the probes by descending inner volume size, so the smallest volumes are rendered last
auto sortFn = [](AZStd::shared_ptr<DiffuseProbeGrid> const& probe1, AZStd::shared_ptr<DiffuseProbeGrid> const& probe2) -> bool
{
const Aabb& aabb1 = probe1->GetAabbWs();
const Aabb& aabb2 = probe2->GetAabbWs();
float size1 = aabb1.GetXExtent() * aabb1.GetZExtent() * aabb1.GetYExtent();
float size2 = aabb2.GetXExtent() * aabb2.GetZExtent() * aabb2.GetYExtent();
const Obb& obb1 = probe1->GetObbWs();
const Obb& obb2 = probe2->GetObbWs();
float size1 = obb1.GetHalfLengthX() * obb1.GetHalfLengthZ() * obb1.GetHalfLengthY();
float size2 = obb2.GetHalfLengthX() * obb2.GetHalfLengthZ() * obb2.GetHalfLengthY();
return (size1 > size2);
};

@ -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);
const auto compileGroupsForIntervalLambda = [srgPool, interval]()
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"};
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);
auto compileTask = taskGraph.AddTask(
srgCompileDesc,
[srgPool, interval]()
{
AZ_PROFILE_SCOPE(RHI, "FrameScheduler : compileGroupsForIntervalLambda");
srgPool->CompileGroupsForInterval(interval);
});
compileTask.Precedes(srgCompileEndTask);
}
};
AZ::Job* executeGroupJob = AZ::CreateJobFunction(AZStd::move(compileGroupsForIntervalLambda), true, nullptr);
executeGroupJob->SetDependent(&jobCompletion);
executeGroupJob->Start();
resourcePoolDatabase.ForEachShaderResourceGroupPool<decltype(compileIntervalsFunction)>(AZStd::move(compileIntervalsFunction));
if (!taskGraph.IsEmpty())
{
AZ::TaskGraphEvent finishedEvent;
taskGraph.Submit(&finishedEvent);
finishedEvent.Wait();
}
};
}
else // use Job system
{
const auto compileGroupsBeginFunction = [](ShaderResourceGroupPool* srgPool)
{
srgPool->CompileGroupsBegin();
};
resourcePoolDatabase.ForEachShaderResourceGroupPool<decltype(compileIntervalsFunction)>(AZStd::move(compileIntervalsFunction));
resourcePoolDatabase.ForEachShaderResourceGroupPool<decltype(compileGroupsBeginFunction)>(compileGroupsBeginFunction);
jobCompletion.StartAndWaitForCompletion();
// Iterate over each SRG pool and fork jobs to compile SRGs.
AZ::JobCompletion jobCompletion;
const auto compileGroupsEndFunction = [](ShaderResourceGroupPool* srgPool)
{
srgPool->CompileGroupsEnd();
};
const auto compileIntervalsFunction = [compilesPerJob, &jobCompletion](ShaderResourceGroupPool* srgPool)
{
const uint32_t compilesInPool = srgPool->GetGroupsToCompileCount();
const uint32_t jobCount = DivideByMultiple(compilesInPool, compilesPerJob);
resourcePoolDatabase.ForEachShaderResourceGroupPool<decltype(compileGroupsEndFunction)>(compileGroupsEndFunction);
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);
}
}

@ -1,6 +1,6 @@
{
"description": "",
"materialType": "Materials\\Types\\StandardPBR.materialtype",
"materialType": "Materials/Types/StandardPBR.materialtype",
"parentMaterial": "",
"propertyLayoutVersion": 3
}
}

@ -1,6 +1,6 @@
{
"description": "",
"materialType": "Materials\\Types\\StandardPBR.materialtype",
"materialType": "Materials/Types/StandardPBR.materialtype",
"parentMaterial": "",
"propertyLayoutVersion": 3,
"properties": {
@ -16,4 +16,4 @@
"textureMap": "TestData/Textures/TextureHaven/4k_castle_brick_02_red/4k_castle_brick_02_red_hp_bc.png"
}
}
}
}

@ -69,6 +69,9 @@ AZ_PUSH_DISABLE_WARNING(4267, "-Wconversion")
AZ_POP_DISABLE_WARNING
#include <LyViewPaneNames.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <IEditor.h>
namespace EMStudio
{
class SaveDirtyWorkspaceCallback
@ -257,10 +260,10 @@ namespace EMStudio
m_saveWorkspaceCallback = nullptr;
}
// destructor
MainWindow::~MainWindow()
{
DisableUpdatingPlugins();
if (m_nativeEventFilter)
{
QAbstractEventDispatcher::instance()->removeNativeEventFilter(m_nativeEventFilter);
@ -577,6 +580,8 @@ namespace EMStudio
AZ_Assert(!m_nativeEventFilter, "Double initialization?");
m_nativeEventFilter = new NativeEventFilter(this);
QAbstractEventDispatcher::instance()->installNativeEventFilter(m_nativeEventFilter);
EnableUpdatingPlugins();
}
MainWindow::MainWindowCommandManagerCallback::MainWindowCommandManagerCallback()
@ -2813,6 +2818,53 @@ namespace EMStudio
}
}
} // namespace EMStudio
void MainWindow::UpdatePlugins(float timeDelta)
{
EMStudio::PluginManager* pluginManager = EMStudio::GetPluginManager();
if (!pluginManager)
{
return;
}
const size_t numPlugins = pluginManager->GetNumActivePlugins();
for (size_t i = 0; i < numPlugins; ++i)
{
EMStudio::EMStudioPlugin* plugin = pluginManager->GetActivePlugin(i);
plugin->ProcessFrame(timeDelta);
}
}
#include <EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/moc_MainWindow.cpp>
void MainWindow::EnableUpdatingPlugins()
{
AZ::TickBus::Handler::BusConnect();
}
void MainWindow::DisableUpdatingPlugins()
{
AZ::TickBus::Handler::BusDisconnect();
}
void MainWindow::OnTick(float delta, AZ::ScriptTimePoint timePoint)
{
AZ_UNUSED(timePoint);
// Check if we are in game mode.
IEditor* editor = nullptr;
AzToolsFramework::EditorRequestBus::BroadcastResult(editor, &AzToolsFramework::EditorRequests::GetEditor);
const bool inGameMode = editor ? editor->IsInGameMode() : false;
// Update all the animation editor plugins (redraw viewports, timeline, and graph windows etc).
// But only update this when the main window is visible and we are in game mode.
const bool isEditorActive = !visibleRegion().isEmpty() && !inGameMode;
if (isEditorActive)
{
UpdatePlugins(delta);
}
}
int MainWindow::GetTickOrder()
{
return AZ::TICK_UI;
}
} // namespace EMStudio

@ -9,6 +9,7 @@
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzCore/Component/TickBus.h>
#include <EMotionStudio/EMStudioSDK/Source/EMStudioConfig.h>
#include <EMotionStudio/EMStudioSDK/Source/GUIOptions.h>
#include <EMotionStudio/EMStudioSDK/Source/PluginOptionsBus.h>
@ -99,8 +100,9 @@ namespace EMStudio
: public AzQtComponents::DockMainWindow
, private PluginOptionsNotificationsBus::Router
, public EMotionFX::ActorEditorRequestBus::Handler
, private AZ::TickBus::Handler
{
Q_OBJECT
Q_OBJECT // AUTOMOC
MCORE_MEMORYOBJECTCATEGORY(MainWindow, MCore::MCORE_DEFAULT_ALIGNMENT, MEMCATEGORY_EMSTUDIOSDK)
public:
@ -304,6 +306,16 @@ namespace EMStudio
MainWindowCommandManagerCallback m_mainWindowCommandManagerCallback;
private:
// AZ::TickBus::Handler overrides
void OnTick(float delta, AZ::ScriptTimePoint timePoint) override;
int GetTickOrder() override;
void UpdatePlugins(float timeDelta);
void EnableUpdatingPlugins();
void DisableUpdatingPlugins();
public slots:
void OnFileOpenActor();
void OnFileSaveSelectedActors();

@ -62,7 +62,6 @@
#if defined(EMOTIONFXANIMATION_EDITOR) // EMFX tools / editor includes
# include <IEditor.h>
// Qt
# include <QtGui/QSurfaceFormat>
// EMStudio tools and main window registration
@ -603,31 +602,6 @@ namespace EMotionFX
#endif
}
//////////////////////////////////////////////////////////////////////////
#if defined (EMOTIONFXANIMATION_EDITOR)
void SystemComponent::UpdateAnimationEditorPlugins(float delta)
{
if (!EMStudio::GetManager())
{
return;
}
EMStudio::PluginManager* pluginManager = EMStudio::GetPluginManager();
if (!pluginManager)
{
return;
}
// Process the plugins.
const size_t numPlugins = pluginManager->GetNumActivePlugins();
for (size_t i = 0; i < numPlugins; ++i)
{
EMStudio::EMStudioPlugin* plugin = pluginManager->GetActivePlugin(i);
plugin->ProcessFrame(delta);
}
}
#endif
//////////////////////////////////////////////////////////////////////////
void SystemComponent::OnTick(float delta, AZ::ScriptTimePoint timePoint)
{
@ -635,36 +609,9 @@ namespace EMotionFX
#if defined (EMOTIONFXANIMATION_EDITOR)
AZ_UNUSED(delta);
const float realDelta = m_updateTimer.StampAndGetDeltaTimeInSeconds();
// Flush events prior to updating EMotion FX.
ActorNotificationBus::ExecuteQueuedEvents();
if (CVars::emfx_updateEnabled)
{
// Main EMotionFX runtime update.
GetEMotionFX().Update(realDelta);
}
// Check if we are in game mode.
IEditor* editor = nullptr;
EBUS_EVENT_RESULT(editor, AzToolsFramework::EditorRequests::Bus, GetEditor);
const bool inGameMode = editor ? editor->IsInGameMode() : false;
// Update all the animation editor plugins (redraw viewports, timeline, and graph windows etc).
// But only update this when the main window is visible and we are in game mode.
const bool isEditorActive =
EMotionFX::GetEMotionFX().GetIsInEditorMode() &&
EMStudio::GetManager() &&
EMStudio::HasMainWindow() &&
!EMStudio::GetMainWindow()->visibleRegion().isEmpty() &&
!inGameMode;
delta = m_updateTimer.StampAndGetDeltaTimeInSeconds();
#endif
if (isEditorActive)
{
UpdateAnimationEditorPlugins(realDelta);
}
#else
// Flush events prior to updating EMotion FX.
ActorNotificationBus::ExecuteQueuedEvents();
@ -673,9 +620,7 @@ namespace EMotionFX
// Main EMotionFX runtime update.
GetEMotionFX().Update(delta);
}
#endif
const float timeDelta = delta;
const ActorManager* actorManager = GetEMotionFX().GetActorManager();
const size_t numActorInstances = actorManager->GetNumActorInstances();
for (size_t i = 0; i < numActorInstances; ++i)
@ -704,7 +649,7 @@ namespace EMotionFX
// If we have a physics controller.
if (hasCustomMotionExtractionController || hasPhysicsController)
{
const float deltaTimeInv = (timeDelta > 0.0f) ? (1.0f / timeDelta) : 0.0f;
const float deltaTimeInv = (delta > 0.0f) ? (1.0f / delta) : 0.0f;
AZ::Transform currentTransform = AZ::Transform::CreateIdentity();
AZ::TransformBus::EventResult(currentTransform, entityId, &AZ::TransformBus::Events::GetWorldTM);
@ -719,7 +664,7 @@ namespace EMotionFX
}
else if (hasCustomMotionExtractionController)
{
MotionExtractionRequestBus::Event(entityId, &MotionExtractionRequestBus::Events::ExtractMotion, positionDelta, timeDelta);
MotionExtractionRequestBus::Event(entityId, &MotionExtractionRequestBus::Events::ExtractMotion, positionDelta, delta);
AZ::TransformBus::EventResult(currentTransform, entityId, &AZ::TransformBus::Events::GetWorldTM);
}
@ -884,7 +829,7 @@ namespace EMotionFX
// Register EMotionFX window with the main editor.
AzToolsFramework::ViewPaneOptions emotionFXWindowOptions;
emotionFXWindowOptions.isPreview = true;
emotionFXWindowOptions.isPreview = false;
emotionFXWindowOptions.isDeletable = true;
emotionFXWindowOptions.isDockable = false;
#if AZ_TRAIT_EMOTIONFX_MAIN_WINDOW_DETACHED

@ -108,7 +108,6 @@ namespace EMotionFX
void SetMediaRoot(const char* alias);
#if defined (EMOTIONFXANIMATION_EDITOR)
void UpdateAnimationEditorPlugins(float delta);
void NotifyRegisterViews() override;
bool IsSystemActive(EditorAnimationSystemRequests::AnimationSystem systemType) override;

@ -32,6 +32,9 @@ set(_addtional_defines
-dCPACK_RESOURCE_PATH=${CPACK_SOURCE_DIR}/Platform/Windows/Packaging
)
file(REAL_PATH "${CPACK_SOURCE_DIR}/.." _root_path)
file(TO_NATIVE_PATH "${_root_path}/scripts/signer/Platform/Windows/signer.ps1" _sign_script)
if(CPACK_LICENSE_URL)
list(APPEND _addtional_defines -dCPACK_LICENSE_URL=${CPACK_LICENSE_URL})
endif()
@ -55,6 +58,30 @@ set(_light_command
-o "${_bootstrap_output_file}"
)
set(_signing_command
psexec.exe
-accepteula
-nobanner
-s
powershell.exe
-NoLogo
-ExecutionPolicy Bypass
-File ${_sign_script}
)
message(STATUS "Signing package files in ${_cpack_wix_out_dir}")
execute_process(
COMMAND ${_signing_command} -packagePath ${_cpack_wix_out_dir}
RESULT_VARIABLE _signing_result
ERROR_VARIABLE _signing_errors
OUTPUT_VARIABLE _signing_output
ECHO_OUTPUT_VARIABLE
)
if(NOT ${_signing_result} EQUAL 0)
message(FATAL_ERROR "An error occurred during signing package files. ${_signing_errors}")
endif()
message(STATUS "Creating Bootstrap Installer...")
execute_process(
COMMAND ${_candle_command}
@ -80,6 +107,19 @@ file(COPY ${_bootstrap_output_file}
message(STATUS "Bootstrap installer generated to ${CPACK_PACKAGE_DIRECTORY}/${_bootstrap_filename}")
message(STATUS "Signing bootstrap installer in ${CPACK_PACKAGE_DIRECTORY}")
execute_process(
COMMAND ${_signing_command} -bootstrapPath ${CPACK_PACKAGE_DIRECTORY}/${_bootstrap_filename}
RESULT_VARIABLE _signing_result
ERROR_VARIABLE _signing_errors
OUTPUT_VARIABLE _signing_output
ECHO_OUTPUT_VARIABLE
)
if(NOT ${_signing_result} EQUAL 0)
message(FATAL_ERROR "An error occurred during signing bootstrap installer. ${_signing_errors}")
endif()
# use the internal default path if somehow not specified from cpack_configure_downloads
if(NOT CPACK_UPLOAD_DIRECTORY)
set(CPACK_UPLOAD_DIRECTORY ${CPACK_PACKAGE_DIRECTORY}/CPackUploads)
@ -100,11 +140,9 @@ if(NOT CPACK_UPLOAD_URL)
return()
endif()
file(REAL_PATH "${CPACK_SOURCE_DIR}/.." _root_path)
file(TO_NATIVE_PATH "${_cpack_wix_out_dir}" _cpack_wix_out_dir)
file(TO_NATIVE_PATH "${_root_path}/python/python.cmd" _python_cmd)
file(TO_NATIVE_PATH "${_root_path}/scripts/build/tools/upload_to_s3.py" _upload_script)
file(TO_NATIVE_PATH "${_cpack_wix_out_dir}" _cpack_wix_out_dir)
function(upload_to_s3 in_url in_local_path in_file_regex)

@ -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
#
#
file(REAL_PATH "${CPACK_SOURCE_DIR}/.." _root_path)
set(_cpack_wix_out_dir ${CPACK_TOPLEVEL_DIRECTORY})
file(TO_NATIVE_PATH "${_root_path}/scripts/signer/Platform/Windows/signer.ps1" _sign_script)
set(_signing_command
psexec.exe
-accepteula
-nobanner
-s
powershell.exe
-NoLogo
-ExecutionPolicy Bypass
-File ${_sign_script}
)
message(STATUS "Signing executable files in ${_cpack_wix_out_dir}")
execute_process(
COMMAND ${_signing_command} -exePath ${_cpack_wix_out_dir}
RESULT_VARIABLE _signing_result
ERROR_VARIABLE _signing_errors
OUTPUT_VARIABLE _signing_output
ECHO_OUTPUT_VARIABLE
)
if(NOT ${_signing_result} EQUAL 0)
message(FATAL_ERROR "An error occurred during signing executable files. ${_signing_errors}")
endif()
message(STATUS "Signing exes complete!")

@ -108,7 +108,6 @@ set(_raw_text_license [[
]])
if(LY_INSTALLER_DOWNLOAD_URL)
set(WIX_THEME_WARNING_IMAGE ${CPACK_SOURCE_DIR}/Platform/Windows/Packaging/warning.png)
if(LY_INSTALLER_LICENSE_URL)
@ -138,6 +137,10 @@ if(LY_INSTALLER_DOWNLOAD_URL)
# the bootstrapper will at the very least need a different upgrade guid
generate_wix_guid(CPACK_WIX_BOOTSTRAP_UPGRADE_GUID "${_guid_seed_base}_Bootstrap_UpgradeCode")
set(CPACK_PRE_BUILD_SCRIPTS
${CPACK_SOURCE_DIR}/Platform/Windows/PackagingPreBuild.cmake
)
set(CPACK_POST_BUILD_SCRIPTS
${CPACK_SOURCE_DIR}/Platform/Windows/PackagingPostBuild.cmake
)

@ -351,17 +351,21 @@
"CMAKE_NATIVE_BUILD_ARGS": "/m /nologo"
}
},
"windows_installer": {
"installer_vs2019": {
"TAGS": [
"nightly-clean"
"nightly-clean",
"nightly-installer"
],
"PIPELINE_ENV":{
"NODE_LABEL":"windows-packaging"
},
"COMMAND": "build_installer_windows.cmd",
"PARAMETERS": {
"CONFIGURATION": "profile",
"OUTPUT_DIRECTORY": "build\\windows_vs2019",
"CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DLY_DISABLE_TEST_MODULES=TRUE -DLY_VERSION_ENGINE_NAME=o3de-sdk -DLY_INSTALLER_WIX_ROOT=\"!WIX! \"",
"EXTRA_CMAKE_OPTIONS": "-DLY_INSTALLER_AUTO_GEN_TAG=ON -DLY_INSTALLER_DOWNLOAD_URL=https://www.o3debinaries.org -DLY_INSTALLER_LICENSE_URL=https://www.o3debinaries.org/license",
"CPACK_BUCKET": "spectra-prism-staging-us-west-2",
"EXTRA_CMAKE_OPTIONS": "-DLY_INSTALLER_AUTO_GEN_TAG=ON -DLY_INSTALLER_DOWNLOAD_URL=!INSTALLER_DOWNLOAD_URL! -DLY_INSTALLER_LICENSE_URL=!INSTALLER_DOWNLOAD_URL!/license",
"CPACK_BUCKET": "!INSTALLER_BUCKET!",
"CMAKE_LY_PROJECTS": "",
"CMAKE_TARGET": "ALL_BUILD",
"CMAKE_NATIVE_BUILD_ARGS": "/m /nologo"

@ -0,0 +1,99 @@
#
# 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
#
#
param (
[String[]] $exePath,
[String[]] $packagePath,
[String[]] $bootstrapPath,
[String[]] $certificate
)
# Get prerequisites, certs, and paths ready
$tempPath = [System.IO.Path]::GetTempPath() # Order of operations defined here: https://docs.microsoft.com/en-us/dotnet/api/system.io.path.gettemppath?view=net-5.0&tabs=windows#remarks
$certThumbprint = Get-ChildItem -Path Cert:LocalMachine\MY -CodeSigningCert -ErrorAction Stop | Select-Object -ExpandProperty Thumbprint # Grab first certificate from local machine store
if ($certificate) {
Write-Output "Checking certificate thumbprint $certificate"
Get-ChildItem -Path Cert:LocalMachine\MY -ErrorAction SilentlyContinue | Where-Object {$_.Thumbprint -eq $certificate} # Prints certificate Thumbprint and Subject if found
if($?) {
$certThumbprint = $certificate
}
else {
Write-Error "$certificate thumbprint not found, using $certThumbprint thumbprint instead"
}
}
Try {
$signtoolPath = Resolve-Path "C:\Program Files*\Windows Kits\10\bin\*\x64\signtool.exe" -ErrorAction Stop | Select-Object -Last 1 -ExpandProperty Path
$insigniaPath = Resolve-Path "C:\Program Files*\WiX*\bin\insignia.exe" -ErrorAction Stop | Select-Object -Last 1 -ExpandProperty Path
}
Catch {
Write-Error "Signtool or Wix insignia not found! Exiting."
}
function Write-Signature {
param (
$signtool,
$thumbprint,
$filename
)
$attempts = 2
$sleepSec = 5
Do {
$attempts--
Try {
& $signtool sign /tr http://timestamp.digicert.com /td sha256 /fd sha256 /sha1 $thumbprint /sm $filename
& $signtool verify /pa /v $filename
return
}
Catch {
Write-Error $_.Exception.InnerException.Message -ErrorAction Continue
Start-Sleep -Seconds $sleepSec
}
} while ($attempts -lt 0)
throw "Failed to sign $filename" # Bypassed in try block if the command is successful
}
# Looping through each path insteaad of globbing to prevent hitting maximum command string length limit
if ($exePath) {
Write-Output "### Signing EXE files ###"
$files = @(Get-ChildItem $exePath -Recurse *.exe | % { $_.FullName })
foreach ($file in $files) {
Write-Signature -signtool $signtoolPath -thumbprint $certThumbprint -filename $file
}
}
if ($packagePath) {
Write-Output "### Signing CAB files ###"
$files = @(Get-ChildItem $packagePath -Recurse *.cab | % { $_.FullName })
foreach ($file in $files) {
Write-Signature -signtool $signtoolPath -thumbprint $certThumbprint -filename $file
}
Write-Output "### Signing MSI files ###"
$files = @(Get-ChildItem $packagePath -Recurse *.msi | % { $_.FullName })
foreach ($file in $files) {
& $insigniaPath -im $files
Write-Signature -signtool $signtoolPath -thumbprint $certThumbprint -filename $file
}
}
if ($bootstrapPath) {
Write-Output "### Signing bootstrapper EXE ###"
$files = @(Get-ChildItem $bootstrapPath -Recurse *.exe | % { $_.FullName })
foreach ($file in $files) {
& $insigniaPath -ib $file -o $tempPath\engine.exe
Write-Signature -signtool $signtoolPath -thumbprint $certThumbprint -filename $tempPath\engine.exe
& $insigniaPath -ab $tempPath\engine.exe $file -o $file
Write-Signature -signtool $signtoolPath -thumbprint $certThumbprint -filename $file
Remove-Item -Force $tempPath\engine.exe
}
}
Loading…
Cancel
Save