Merge branch 'development' into Atom/jromnoa/assert-for-screenshot-comparisons

This commit is contained in:
jromnoa
2021-11-03 09:23:26 -07:00
234 changed files with 6503 additions and 2184 deletions
@@ -25,11 +25,11 @@ namespace AWSGameLift
AWSGameLiftCreateSessionOnQueueRequest() = default;
virtual ~AWSGameLiftCreateSessionOnQueueRequest() = default;
// Name of the queue to use to place the new game session. You can use either the queue name or ARN value.
//! Name of the queue to use to place the new game session. You can use either the queue name or ARN value.
AZStd::string m_queueName;
// A unique identifier to assign to the new game session placement. This value is developer-defined.
// The value must be unique across all Regions and cannot be reused unless you are resubmitting a canceled or timed-out placement request.
//! A unique identifier to assign to the new game session placement. This value is developer-defined.
//! The value must be unique across all Regions and cannot be reused unless you are resubmitting a canceled or timed-out placement request.
AZStd::string m_placementId;
};
} // namespace AWSGameLift
@@ -25,14 +25,14 @@ namespace AWSGameLift
AWSGameLiftCreateSessionRequest() = default;
virtual ~AWSGameLiftCreateSessionRequest() = default;
// A unique identifier for the alias associated with the fleet to create a game session in.
//! A unique identifier for the alias associated with the fleet to create a game session in.
AZStd::string m_aliasId;
// A unique identifier for the fleet to create a game session in.
//! A unique identifier for the fleet to create a game session in.
AZStd::string m_fleetId;
// Custom string that uniquely identifies the new game session request.
// This is useful for ensuring that game session requests with the same idempotency token are processed only once.
//! Custom string that uniquely identifies the new game session request.
//! This is useful for ensuring that game session requests with the same idempotency token are processed only once.
AZStd::string m_idempotencyToken;
};
} // namespace AWSGameLift
@@ -25,13 +25,13 @@ namespace AWSGameLift
AWSGameLiftSearchSessionsRequest() = default;
virtual ~AWSGameLiftSearchSessionsRequest() = default;
// A unique identifier for the alias associated with the fleet to search for active game sessions.
//! A unique identifier for the alias associated with the fleet to search for active game sessions.
AZStd::string m_aliasId;
// A unique identifier for the fleet to search for active game sessions.
//! A unique identifier for the fleet to search for active game sessions.
AZStd::string m_fleetId;
// A fleet location to search for game sessions.
//! A fleet location to search for game sessions.
AZStd::string m_location;
};
} // namespace AWSGameLift
@@ -30,9 +30,10 @@ namespace AWSGameLift
AWSGameLiftStartMatchmakingRequest() = default;
virtual ~AWSGameLiftStartMatchmakingRequest() = default;
// Name of the matchmaking configuration to use for this request
//! Name of the matchmaking configuration to use for this request
AZStd::string m_configurationName;
// Information on each player to be matched
//! Information on each player to be matched
AZStd::vector<AWSGameLiftPlayer> m_players;
};
} // namespace AWSGameLift
@@ -10,6 +10,7 @@
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Component/ComponentApplicationLifecycle.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/NativeUI/NativeUIRequests.h>
#include <AzCore/Serialization/SerializeContext.h>
@@ -132,7 +133,6 @@ namespace AZ
m_createDefaultScene = false;
}
AzFramework::AssetCatalogEventBus::Handler::BusConnect();
TickBus::Handler::BusConnect();
// Listen for window system requests (e.g. requests for default window handle)
@@ -143,6 +143,20 @@ namespace AZ
Render::Bootstrap::DefaultWindowBus::Handler::BusConnect();
Render::Bootstrap::RequestBus::Handler::BusConnect();
// If the settings registry isn't available, something earlier in startup will report that failure.
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
{
// Automatically register the event if it's not registered, because
// this system is initialized before the settings registry has loaded the event list.
AZ::ComponentApplicationLifecycle::RegisterHandler(
*settingsRegistry, m_componentApplicationLifecycleHandler,
[this](AZStd::string_view /*path*/, AZ::SettingsRegistryInterface::Type /*type*/)
{
Initialize();
},
"LegacySystemInterfaceCreated");
}
}
void BootstrapSystemComponent::Deactivate()
@@ -153,7 +167,6 @@ namespace AZ
AzFramework::WindowSystemRequestBus::Handler::BusDisconnect();
AzFramework::WindowSystemNotificationBus::Handler::BusDisconnect();
TickBus::Handler::BusDisconnect();
AzFramework::AssetCatalogEventBus::Handler::BusDisconnect();
m_brdfTexture = nullptr;
RemoveRenderPipeline();
@@ -164,14 +177,14 @@ namespace AZ
m_windowHandle = nullptr;
}
void BootstrapSystemComponent::OnCatalogLoaded(const char* /*catalogFile*/)
void BootstrapSystemComponent::Initialize()
{
if (m_isAssetCatalogLoaded)
if (m_isInitialized)
{
return;
}
m_isAssetCatalogLoaded = true;
m_isInitialized = true;
if (!RPI::RPISystemInterface::Get()->IsInitialized())
{
@@ -216,7 +229,7 @@ namespace AZ
{
m_windowHandle = windowHandle;
if (m_isAssetCatalogLoaded)
if (m_isInitialized)
{
CreateWindowContext();
if (m_createDefaultScene)
@@ -259,6 +272,7 @@ namespace AZ
// Create and register a scene with all available feature processors
RPI::SceneDescriptor sceneDesc;
sceneDesc.m_nameId = AZ::Name("Main");
AZ::RPI::ScenePtr atomScene = RPI::Scene::CreateScene(sceneDesc);
atomScene->EnableAllFeatureProcessors();
atomScene->Activate();
@@ -8,10 +8,10 @@
#pragma once
#include <AzCore/Component/Component.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/Settings/SettingsRegistry.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzFramework/Asset/AssetCatalogBus.h>
#include <AzFramework/Scene/Scene.h>
#include <AzFramework/Scene/SceneSystemInterface.h>
#include <AzFramework/Windowing/NativeWindow.h>
@@ -29,7 +29,6 @@
#include <Atom/Bootstrap/DefaultWindowBus.h>
#include <Atom/Bootstrap/BootstrapRequestBus.h>
namespace AZ
{
namespace Render
@@ -40,7 +39,6 @@ namespace AZ
: public Component
, public TickBus::Handler
, public AzFramework::WindowNotificationBus::Handler
, public AzFramework::AssetCatalogEventBus::Handler
, public AzFramework::WindowSystemNotificationBus::Handler
, public AzFramework::WindowSystemRequestBus::Handler
, public Render::Bootstrap::DefaultWindowBus::Handler
@@ -82,13 +80,12 @@ namespace AZ
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
int GetTickOrder() override;
// AzFramework::AssetCatalogEventBus::Handler overrides ...
void OnCatalogLoaded(const char* catalogFile) override;
// AzFramework::WindowSystemNotificationBus::Handler overrides ...
void OnWindowCreated(AzFramework::NativeWindowHandle windowHandle) override;
private:
void Initialize();
void CreateDefaultRenderPipeline();
void CreateDefaultScene();
void DestroyDefaultScene();
@@ -105,7 +102,7 @@ namespace AZ
RPI::ScenePtr m_defaultScene = nullptr;
AZStd::shared_ptr<AzFramework::Scene> m_defaultFrameworkScene = nullptr;
bool m_isAssetCatalogLoaded = false;
bool m_isInitialized = false;
// The id of the render pipeline created by this component
RPI::RenderPipelineId m_renderPipelineId;
@@ -119,6 +116,8 @@ namespace AZ
// Maps AZ scenes to RPI scene weak pointers to allow looking up a ScenePtr instead of a raw Scene*
AZStd::unordered_map<AzFramework::Scene*, AZStd::weak_ptr<AZ::RPI::Scene>> m_azSceneToAtomSceneMap;
AZ::SettingsRegistryInterface::NotifyEventHandler m_componentApplicationLifecycleHandler;
};
} // namespace Bootstrap
} // namespace Render
@@ -1574,15 +1574,6 @@
"shaderOption": "o_baseColor_useTexture"
}
},
{
"type": "UseTexture",
"args": {
"textureProperty": "metallic.textureMap",
"useTextureProperty": "metallic.useTexture",
"dependentProperties": ["metallic.textureMapUv"],
"shaderOption": "o_metallic_useTexture"
}
},
{
"type": "UseTexture",
"args": {
@@ -1649,6 +1640,12 @@
"file": "StandardPBR_Roughness.lua"
}
},
{
"type": "Lua",
"args": {
"file": "StandardPBR_Metallic.lua"
}
},
{
"type": "Lua",
"args": {
@@ -2698,16 +2698,12 @@
}
},
{
"type": "UseTexture",
"type": "Lua",
"args": {
"textureProperty": "layer1_metallic.textureMap",
"useTextureProperty": "layer1_metallic.useTexture",
"dependentProperties": ["layer1_metallic.textureMapUv"],
"shaderTags": [
"ForwardPass",
"ForwardPass_EDS"
],
"shaderOption": "o_layer1_o_metallic_useTexture"
"file": "StandardPBR_Metallic.lua",
"propertyNamePrefix": "layer1_",
"srgNamePrefix": "m_layer1_",
"optionsNamePrefix": "o_layer1_"
}
},
{
@@ -2835,16 +2831,12 @@
}
},
{
"type": "UseTexture",
"type": "Lua",
"args": {
"textureProperty": "layer2_metallic.textureMap",
"useTextureProperty": "layer2_metallic.useTexture",
"dependentProperties": ["layer2_metallic.textureMapUv"],
"shaderTags": [
"ForwardPass",
"ForwardPass_EDS"
],
"shaderOption": "o_layer2_o_metallic_useTexture"
"file": "StandardPBR_Metallic.lua",
"propertyNamePrefix": "layer2_",
"srgNamePrefix": "m_layer2_",
"optionsNamePrefix": "o_layer2_"
}
},
{
@@ -2963,8 +2955,8 @@
"args": {
"textureProperty": "layer3_baseColor.textureMap",
"useTextureProperty": "layer3_baseColor.useTexture",
"dependentProperties": ["layer3_baseColor.textureMapUv", "layer3_baseColor.textureBlendMode"],
"shaderTags": [
"dependentProperties": [ "layer3_baseColor.textureMapUv", "layer3_baseColor.textureBlendMode" ],
"shaderTags": [
"ForwardPass",
"ForwardPass_EDS"
],
@@ -2972,16 +2964,12 @@
}
},
{
"type": "UseTexture",
"type": "Lua",
"args": {
"textureProperty": "layer3_metallic.textureMap",
"useTextureProperty": "layer3_metallic.useTexture",
"dependentProperties": ["layer3_metallic.textureMapUv"],
"shaderTags": [
"ForwardPass",
"ForwardPass_EDS"
],
"shaderOption": "o_layer3_o_metallic_useTexture"
"file": "StandardPBR_Metallic.lua",
"propertyNamePrefix": "layer3_",
"srgNamePrefix": "m_layer3_",
"optionsNamePrefix": "o_layer3_"
}
},
{
@@ -1104,15 +1104,6 @@
"shaderOption": "o_baseColor_useTexture"
}
},
{
"type": "UseTexture",
"args": {
"textureProperty": "metallic.textureMap",
"useTextureProperty": "metallic.useTexture",
"dependentProperties": ["metallic.textureMapUv"],
"shaderOption": "o_metallic_useTexture"
}
},
{
"type": "UseTexture",
"args": {
@@ -1179,6 +1170,12 @@
"file": "StandardPBR_Roughness.lua"
}
},
{
"type": "Lua",
"args": {
"file": "StandardPBR_Metallic.lua"
}
},
{
"type": "Lua",
"args": {
@@ -0,0 +1,43 @@
--------------------------------------------------------------------------------------
--
-- 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
--
--
--
----------------------------------------------------------------------------------------------------
function GetMaterialPropertyDependencies()
return {"metallic.textureMap", "metallic.useTexture"}
end
function GetShaderOptionDependencies()
return {"o_metallic_useTexture"}
end
function Process(context)
local textureMap = context:GetMaterialPropertyValue_Image("metallic.textureMap")
local useTexture = context:GetMaterialPropertyValue_bool("metallic.useTexture")
context:SetShaderOptionValue_bool("o_metallic_useTexture", useTexture and textureMap ~= nil)
end
function ProcessEditor(context)
local textureMap = context:GetMaterialPropertyValue_Image("metallic.textureMap")
local useTexture = context:GetMaterialPropertyValue_bool("metallic.useTexture")
if(nil == textureMap) then
context:SetMaterialPropertyVisibility("metallic.useTexture", MaterialPropertyVisibility_Hidden)
context:SetMaterialPropertyVisibility("metallic.textureMapUv", MaterialPropertyVisibility_Hidden)
context:SetMaterialPropertyVisibility("metallic.factor", MaterialPropertyVisibility_Enabled)
elseif(not useTexture) then
context:SetMaterialPropertyVisibility("metallic.useTexture", MaterialPropertyVisibility_Enabled)
context:SetMaterialPropertyVisibility("metallic.textureMapUv", MaterialPropertyVisibility_Disabled)
context:SetMaterialPropertyVisibility("metallic.factor", MaterialPropertyVisibility_Enabled)
else
context:SetMaterialPropertyVisibility("metallic.useTexture", MaterialPropertyVisibility_Enabled)
context:SetMaterialPropertyVisibility("metallic.textureMapUv", MaterialPropertyVisibility_Enabled)
context:SetMaterialPropertyVisibility("metallic.factor", MaterialPropertyVisibility_Hidden)
end
end
@@ -19,7 +19,6 @@
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/EditContextConstants.inl>
#include <AzCore/Script/ScriptAsset.h>
#include <AssetBuilderSDK/AssetBuilderSDK.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
@@ -101,13 +100,6 @@ namespace AZ
materialFunctorRegistration->RegisterMaterialFunctor("ConvertEmissiveUnit", azrtti_typeid<ConvertEmissiveUnitFunctorSourceData>());
materialFunctorRegistration->RegisterMaterialFunctor("HandleSubsurfaceScatteringParameters", azrtti_typeid<SubsurfaceTransmissionParameterFunctorSourceData>());
materialFunctorRegistration->RegisterMaterialFunctor("Lua", azrtti_typeid<RPI::LuaMaterialFunctorSourceData>());
// Add asset types and extensions to AssetCatalog. Uses "AssetCatalogService".
auto assetCatalog = AZ::Data::AssetCatalogRequestBus::FindFirstHandler();
if (assetCatalog)
{
assetCatalog->EnableCatalogForAsset(AZ::AzTypeInfo<AZ::ScriptAsset>::Uuid());
}
}
void EditorCommonSystemComponent::Deactivate()
@@ -29,6 +29,8 @@
#include <AzCore/Script/ScriptContextAttributes.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Task/TaskGraph.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <AzFramework/StringFunc/StringFunc.h>
@@ -50,6 +52,15 @@ namespace AZ
"Sets the compression level for saving png screenshots. Valid values are from 0 to 8"
);
AZ_CVAR(int,
r_pngCompressionNumThreads,
8, // Number of threads to use for the png r<->b channel data swap
nullptr,
ConsoleFunctorFlags::Null,
"Sets the number of threads for saving png screenshots. Valid values are from 1 to 128, although less than or equal the number of hw threads is recommended"
);
FrameCaptureOutputResult PngFrameCaptureOutput(
const AZStd::string& outputFilePath, const AZ::RPI::AttachmentReadback::ReadbackResult& readbackResult)
{
@@ -65,33 +76,67 @@ namespace AZ
buffer = AZStd::make_shared<AZStd::vector<uint8_t>>(readbackResult.m_dataBuffer->size());
AZStd::copy(readbackResult.m_dataBuffer->begin(), readbackResult.m_dataBuffer->end(), buffer->begin());
AZ::JobCompletion jobCompletion;
const int numThreads = 8;
const int numThreads = r_pngCompressionNumThreads;
const int numPixelsPerThread = static_cast<int>(buffer->size() / numChannels / numThreads);
for (int i = 0; i < numThreads; ++i)
AZ::TaskGraphActiveInterface* taskGraphActiveInterface = AZ::Interface<AZ::TaskGraphActiveInterface>::Get();
bool taskGraphActive = taskGraphActiveInterface && taskGraphActiveInterface->IsTaskGraphActive();
if (taskGraphActive)
{
int startPixel = i * numPixelsPerThread;
static const AZ::TaskDescriptor pngTaskDescriptor{"PngWriteOutChannelSwap", "Graphics"};
AZ::TaskGraph taskGraph;
for (int i = 0; i < numThreads; ++i)
{
int startPixel = i * numPixelsPerThread;
AZ::Job* job = AZ::CreateJobFunction(
[&, startPixel, numPixelsPerThread]()
{
for (int pixelOffset = 0; pixelOffset < numPixelsPerThread; ++pixelOffset)
taskGraph.AddTask(
pngTaskDescriptor,
[&, startPixel]()
{
if (startPixel * numChannels + numChannels < buffer->size())
for (int pixelOffset = 0; pixelOffset < numPixelsPerThread; ++pixelOffset)
{
AZStd::swap(
buffer->data()[(startPixel + pixelOffset) * numChannels],
buffer->data()[(startPixel + pixelOffset) * numChannels + 2]
);
if (startPixel * numChannels + numChannels < buffer->size())
{
AZStd::swap(
buffer->data()[(startPixel + pixelOffset) * numChannels],
buffer->data()[(startPixel + pixelOffset) * numChannels + 2]
);
}
}
}
}, true, nullptr);
job->SetDependent(&jobCompletion);
job->Start();
});
}
AZ::TaskGraphEvent taskGraphFinishedEvent;
taskGraph.Submit(&taskGraphFinishedEvent);
taskGraphFinishedEvent.Wait();
}
else
{
AZ::JobCompletion jobCompletion;
for (int i = 0; i < numThreads; ++i)
{
int startPixel = i * numPixelsPerThread;
AZ::Job* job = AZ::CreateJobFunction(
[&, startPixel]()
{
for (int pixelOffset = 0; pixelOffset < numPixelsPerThread; ++pixelOffset)
{
if (startPixel * numChannels + numChannels < buffer->size())
{
AZStd::swap(
buffer->data()[(startPixel + pixelOffset) * numChannels],
buffer->data()[(startPixel + pixelOffset) * numChannels + 2]
);
}
}
}, true, nullptr);
job->SetDependent(&jobCompletion);
job->Start();
}
jobCompletion.StartAndWaitForCompletion();
}
jobCompletion.StartAndWaitForCompletion();
}
Utils::PngFile image = Utils::PngFile::Create(readbackResult.m_imageDescriptor.m_size, format, *buffer);
@@ -32,7 +32,7 @@ namespace AZ
{
if (m_rtPipeline)
{
AZ::RPI::RPISystemInterface::Get()->GetDefaultScene()->RemoveRenderPipeline(m_rtPipeline->GetId());
m_rtPipeline->RemoveFromScene();
m_rtPipeline = nullptr;
}
@@ -111,8 +111,12 @@ namespace AZ
parentPass->SetSourceTexture(m_texture, RHI::Format::R8G8B8A8_UNORM);
break;
}
AZ::RPI::RPISystemInterface::Get()->GetDefaultScene()->AddRenderPipeline(m_rtPipeline);
const auto mainScene = AZ::RPI::RPISystemInterface::Get()->GetSceneByName(AZ::Name("RPI"));
if (mainScene)
{
mainScene->AddRenderPipeline(m_rtPipeline);
}
}
bool LuxCoreTexture::IsIBLTexture()
@@ -60,6 +60,11 @@ def activate_look_modification_lut(look_modification_component, asset_relative_p
LOOK_MODIFICATION_ENABLE_PROPERTY_PATH,
True
)
azlmbr.editor.EditorComponentAPIBus(
azlmbr.bus.Broadcast,
"EnableComponents",
[look_modification_component]
)
def activate_lut_asset(entity_id, asset_relative_path):
disable_hdr_color_grading_component(entity_id)
@@ -38,6 +38,8 @@
namespace AZ
{
class Job;
class TaskGraphActiveInterface;
class TaskGraph;
namespace RHI
{
@@ -256,7 +258,13 @@ namespace AZ
//! Must be called between BeginCulling() and EndCulling(), once for each active scene/view pair.
//! Will create child jobs under the parentJob to do the processing in parallel.
//! Can be called in parallel (i.e. to perform culling on multiple views at the same time).
void ProcessCullables(const Scene& scene, View& view, AZ::Job& parentJob);
void ProcessCullablesJobs(const Scene& scene, View& view, AZ::Job& parentJob);
//! Performs render culling and lod selection for a View, then adds the visible renderpackets to that View.
//! Must be called between BeginCulling() and EndCulling(), once for each active scene/view pair.
//! Will create child task graphs that signal the TaskGraphEvent to do the processing in parallel.
//! Can be called in parallel (i.e. to perform culling on multiple views at the same time).
void ProcessCullablesTG(const Scene& scene, View& view, AZ::TaskGraph& taskGraph);
//! Adds a Cullable to the underlying visibility system(s).
//! Must be called at least once on initialization and whenever a Cullable's position or bounds is changed.
@@ -276,17 +284,20 @@ namespace AZ
return m_debugCtx;
}
static const size_t WorkListCapacity = 5;
using WorkListType = AZStd::fixed_vector<AzFramework::IVisibilityScene::NodeData, WorkListCapacity>;
protected:
size_t CountObjectsInScene();
private:
void BeginCullingTaskGraph(const AZStd::vector<ViewPtr>& views);
void BeginCullingJobs(const AZStd::vector<ViewPtr>& views);
void ProcessCullablesCommon(const Scene& scene, View& view, AZ::Frustum& frustum, void*& maskedOcclusionCulling);
const Scene* m_parentScene = nullptr;
AzFramework::IVisibilityScene* m_visScene = nullptr;
CullingDebugContext m_debugCtx;
AZStd::concurrency_checker m_cullDataConcurrencyCheck;
OcclusionPlaneVector m_occlusionPlanes;
AZ::TaskGraphActiveInterface* m_taskGraphActive = nullptr;
};
@@ -70,7 +70,8 @@ namespace AZ
void InitializeSystemAssets() override;
void RegisterScene(ScenePtr scene) override;
void UnregisterScene(ScenePtr scene) override;
ScenePtr GetScene(const SceneId& sceneId) const override;
Scene* GetScene(const SceneId& sceneId) const override;
Scene* GetSceneByName(const AZ::Name& name) const override;
ScenePtr GetDefaultScene() const override;
RenderPipelinePtr GetRenderPipelineForWindow(AzFramework::NativeWindowHandle windowHandle) override;
Data::Asset<ShaderAsset> GetCommonShaderAssetForSrgs() const override;
@@ -13,6 +13,7 @@
#include <Atom/RPI.Public/Base.h>
#include <AzCore/Name/Name.h>
#include <AzFramework/Windowing/WindowBus.h>
namespace AZ
@@ -46,11 +47,14 @@ namespace AZ
//! Unregister a scene from RPISystem. The scene won't be simulated or rendered.
virtual void UnregisterScene(ScenePtr scene) = 0;
// [GFX TODO] to be removed when we have scene setup in AZ Core
virtual ScenePtr GetDefaultScene() const = 0;
//! Deprecated. Use GetSceneByName(name), GetSceneForEntityContextId(entityContextId) or Scene::GetSceneForEntityId(AZ::EntityId entityId) instead
AZ_DEPRECATED(virtual ScenePtr GetDefaultScene() const = 0;, "This method has been deprecated. Please use GetSceneByName(name), GetSceneForEntityContextId(entityContextId) or Scene::GetSceneForEntityId(AZ::EntityId entityId) instead.");
//! Get scene by using scene id.
virtual ScenePtr GetScene(const SceneId& sceneId) const = 0;
virtual Scene* GetScene(const SceneId& sceneId) const = 0;
//! Get scene by using scene name.
virtual Scene* GetSceneByName(const AZ::Name& name) const = 0;
//! Get the render pipeline created for a window
virtual RenderPipelinePtr GetRenderPipelineForWindow(AzFramework::NativeWindowHandle windowHandle) = 0;
@@ -80,6 +80,9 @@ namespace AZ
//! Gets the RPI::Scene for a given entityContextId.
//! May return nullptr if there is no RPI::Scene created for that entityContext.
static Scene* GetSceneForEntityContextId(AzFramework::EntityContextId entityContextId);
//! Gets the RPI::Scene for a given entityId.
static Scene* GetSceneForEntityId(AZ::EntityId entityId);
~Scene();
@@ -135,6 +138,8 @@ namespace AZ
const SceneId& GetId() const;
AZ::Name GetName() const;
//! Set default pipeline by render pipeline ID.
//! It returns true if the default render pipeline was set from the input ID.
//! If the specified render pipeline doesn't exist in this scene then it won't do anything and returns false.
@@ -195,8 +200,8 @@ 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 to wait for end of TaskGraph and then delete the TaskGraphEvent
void WaitAndCleanTGEvent(AZStd::unique_ptr<AZ::TaskGraphEvent>&& completionTGEvent);
// Helper function for wait and clean up a completion job
void WaitAndCleanCompletionJob(AZ::JobCompletion*& completionJob);
@@ -225,8 +230,7 @@ namespace AZ
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;
AZStd::unique_ptr<AZ::TaskGraphEvent> m_simulationFinishedTGEvent;
// CPU simulation job completion for track all feature processors' simulation jobs
AZ::JobCompletion* m_simulationCompletion = nullptr;
@@ -245,6 +249,9 @@ namespace AZ
// The uuid to identify this scene.
SceneId m_id;
// Scene's name which is set at initialization. Can be empty
AZ::Name m_name;
bool m_activated = false;
bool m_taskGraphActive = false; // update during tick, to ensure it only changes on frame boundaries
@@ -286,13 +293,10 @@ namespace AZ
template<typename FeatureProcessorType>
FeatureProcessorType* Scene::GetFeatureProcessorForEntity(AZ::EntityId entityId)
{
// Find the entity context for the entity ID.
AzFramework::EntityContextId entityContextId = AzFramework::EntityContextId::CreateNull();
AzFramework::EntityIdContextQueryBus::EventResult(entityContextId, entityId, &AzFramework::EntityIdContextQueryBus::Events::GetOwningContextId);
if (!entityContextId.IsNull())
RPI::Scene* renderScene = GetSceneForEntityId(entityId);
if (renderScene)
{
return GetFeatureProcessorForEntityContextId<FeatureProcessorType>(entityContextId);
return renderScene->GetFeatureProcessor<FeatureProcessorType>();
}
return nullptr;
};
@@ -154,6 +154,8 @@ namespace AZ
ConstPtr<RHI::PipelineLibraryData> LoadPipelineLibrary() const;
void SavePipelineLibrary() const;
const ShaderVariant& GetVariantInternal(ShaderVariantStableId shaderVariantStableId);
///////////////////////////////////////////////////////////////////
/// AssetBus overrides
@@ -24,6 +24,9 @@ namespace AZ
class ShaderReloadDebugTracker final
{
public:
static void Init();
static void Shutdown();
static bool IsEnabled();
//! Begin a code section. Will print a "[BEGIN] <sectionName>" header, and all subsequent calls will be indented.
@@ -34,8 +37,8 @@ namespace AZ
if (IsEnabled())
{
const AZStd::string sectionName = AZStd::string::format(sectionNameFormat, args...);
AZ_TracePrintf("ShaderReloadDebug", "%*s [BEGIN] %s \n", s_indent, "", sectionName.c_str());
s_indent += IndentSpaces;
AZ_TracePrintf("ShaderReloadDebug", "%*s [BEGIN] %s \n", GetIndent(), "", sectionName.c_str());
AddIndent();
}
#endif
}
@@ -48,8 +51,8 @@ namespace AZ
if (IsEnabled())
{
const AZStd::string sectionName = AZStd::string::format(sectionNameFormat, args...);
s_indent -= IndentSpaces;
AZ_TracePrintf("ShaderReloadDebug", "%*s [_END_] %s \n", s_indent, "", sectionName.c_str());
RemoveIndent();
AZ_TracePrintf("ShaderReloadDebug", "%*s [_END_] %s \n", GetIndent(), "", sectionName.c_str());
}
#endif
}
@@ -63,7 +66,7 @@ namespace AZ
{
const AZStd::string message = AZStd::string::format(format, args...);
AZ_TracePrintf("ShaderReloadDebug", "%*s %s \n", s_indent, "", message.c_str());
AZ_TracePrintf("ShaderReloadDebug", "%*s %s \n", GetIndent(), "", message.c_str());
}
#endif
}
@@ -86,9 +89,12 @@ namespace AZ
};
private:
static bool s_enabled;
static int s_indent;
static constexpr int IndentSpaces = 4;
static void MakeReady();
static void AddIndent();
static void RemoveIndent();
static int GetIndent();
};
} // namespace RPI
@@ -96,7 +96,7 @@ namespace AZ
//! Return the timestamp when the shader asset was built.
//! This is used to synchronize versions of the ShaderAsset and ShaderVariantTreeAsset, especially during hot-reload.
AZStd::sys_time_t GetShaderAssetBuildTimestamp() const;
AZStd::sys_time_t GetBuildTimestamp() const;
//! Returns the shader option group layout.
const ShaderOptionGroupLayout* GetShaderOptionGroupLayout() const;
@@ -297,7 +297,7 @@ namespace AZ
Name m_drawListName;
//! Use to synchronize versions of the ShaderAsset and ShaderVariantTreeAsset, especially during hot-reload.
AZStd::sys_time_t m_shaderAssetBuildTimestamp = 0;
AZStd::sys_time_t m_buildTimestamp = 0;
///////////////////////////////////////////////////////////////////
@@ -9,6 +9,7 @@
#pragma once
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Name/Name.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/string/string.h>
@@ -25,6 +26,9 @@ namespace AZ
//! List of feature processors which the scene will initially enable.
AZStd::vector<AZStd::string> m_featureProcessorNames;
//! A name used as scene id. It can be used to search a registered scene via RPISystemInterface::GetScene()
AZ::Name m_nameId;
};
} // namespace RPI
} // namespace AZ
+322 -212
View File
@@ -23,6 +23,8 @@
#include <AzCore/Debug/Timer.h>
#include <AzCore/Jobs/JobFunction.h>
#include <AzCore/Jobs/Job.h>
#include <AzCore/Task/TaskGraph.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <Atom_RPI_Traits_Platform.h>
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
@@ -265,89 +267,71 @@ namespace AZ
return m_visScene->GetEntryCount();
}
class AddObjectsToViewJob final
: public Job
struct WorklistData
{
public:
AZ_CLASS_ALLOCATOR(AddObjectsToViewJob, ThreadPoolAllocator, 0);
struct JobData
{
CullingDebugContext* m_debugCtx = nullptr;
const Scene* m_scene = nullptr;
View* m_view = nullptr;
Frustum m_frustum;
CullingDebugContext* m_debugCtx = nullptr;
const Scene* m_scene = nullptr;
View* m_view = nullptr;
Frustum m_frustum;
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
MaskedOcclusionCulling* m_maskedOcclusionCulling = nullptr;
MaskedOcclusionCulling* m_maskedOcclusionCulling = nullptr;
#endif
};
};
private:
const AZStd::shared_ptr<JobData> m_jobData;
CullingScene::WorkListType m_worklist;
static AZStd::shared_ptr<WorklistData> MakeWorklistData(
CullingDebugContext& debugCtx,
const Scene& scene,
View& view,
Frustum& frustum,
void* maskedOcclusionCulling)
{
AZStd::shared_ptr<WorklistData> worklistData = AZStd::make_shared<WorklistData>();
worklistData->m_debugCtx = &debugCtx;
worklistData->m_scene = &scene;
worklistData->m_view = &view;
worklistData->m_frustum = frustum;
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
worklistData->m_maskedOcclusionCulling = static_cast<MaskedOcclusionCulling*>(maskedOcclusionCulling);
#endif
return worklistData;
}
constexpr size_t WorkListCapacity = 5;
using WorkListType = AZStd::fixed_vector<AzFramework::IVisibilityScene::NodeData, WorkListCapacity>;
public:
AddObjectsToViewJob(const AZStd::shared_ptr<AddObjectsToViewJob::JobData>& jobData, CullingScene::WorkListType& worklist)
: Job(true, nullptr) //auto-deletes, no JobContext
, m_jobData(jobData)
, m_worklist(worklist)
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
static MaskedOcclusionCulling::CullingResult TestOcclusionCulling(
const AZStd::shared_ptr<WorklistData>& worklistData,
AzFramework::VisibilityEntry* visibleEntry);
#endif
static void ProcessWorklist(const AZStd::shared_ptr<WorklistData>& worklistData, const WorkListType& worklist)
{
AZ_PROFILE_SCOPE(RPI, "AddObjectsToViewJob: Process");
const View::UsageFlags viewFlags = worklistData->m_view->GetUsageFlags();
const RHI::DrawListMask drawListMask = worklistData->m_view->GetDrawListMask();
uint32_t numDrawPackets = 0;
uint32_t numVisibleCullables = 0;
AZ_Assert(worklist.size() > 0, "Received empty worklist in ProcessWorklist");
for (const AzFramework::IVisibilityScene::NodeData& nodeData : worklist)
{
}
//work function
void Process() override
{
AZ_PROFILE_SCOPE(RPI, "AddObjectsToViewJob: Process");
const View::UsageFlags viewFlags = m_jobData->m_view->GetUsageFlags();
const RHI::DrawListMask drawListMask = m_jobData->m_view->GetDrawListMask();
uint32_t numDrawPackets = 0;
uint32_t numVisibleCullables = 0;
for (const AzFramework::IVisibilityScene::NodeData& nodeData : m_worklist)
{
//If a node is entirely contained within the frustum, then we can skip the fine grained culling.
bool nodeIsContainedInFrustum = ShapeIntersection::Contains(m_jobData->m_frustum, nodeData.m_bounds);
//If a node is entirely contained within the frustum, then we can skip the fine grained culling.
bool nodeIsContainedInFrustum = ShapeIntersection::Contains(worklistData->m_frustum, nodeData.m_bounds);
#ifdef AZ_CULL_PROFILE_VERBOSE
AZ_PROFILE_SCOPE(RPI, "process node (view: %s, skip fine cull: %d",
m_view->GetName().GetCStr(), nodeIsContainedInFrustum ? 1 : 0);
AZ_PROFILE_SCOPE(RPI, "process node (view: %s, skip fine cull: %d",
m_view->GetName().GetCStr(), nodeIsContainedInFrustum ? 1 : 0);
#endif
if (nodeIsContainedInFrustum || !m_jobData->m_debugCtx->m_enableFrustumCulling)
if (nodeIsContainedInFrustum || !worklistData->m_debugCtx->m_enableFrustumCulling)
{
//Add all objects within this node to the view, without any extra culling
for (AzFramework::VisibilityEntry* visibleEntry : nodeData.m_entries)
{
//Add all objects within this node to the view, without any extra culling
for (AzFramework::VisibilityEntry* visibleEntry : nodeData.m_entries)
{
{
if (visibleEntry->m_typeFlags & AzFramework::VisibilityEntry::TYPE_RPI_Cullable)
{
Cullable* c = static_cast<Cullable*>(visibleEntry->m_userData);
if ((c->m_cullData.m_drawListMask & drawListMask).none() ||
c->m_cullData.m_hideFlags & viewFlags ||
c->m_cullData.m_scene != m_jobData->m_scene || //[GFX_TODO][ATOM-13796] once the IVisibilitySystem supports multiple octree scenes, remove this
c->m_isHidden)
{
continue;
}
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
if (TestOcclusionCulling(visibleEntry) == MaskedOcclusionCulling::CullingResult::VISIBLE)
#endif
{
numDrawPackets += AddLodDataToView(c->m_cullData.m_boundingSphere.GetCenter(), c->m_lodData, *m_jobData->m_view);
++numVisibleCullables;
c->m_isVisible = true;
}
}
}
}
}
else
{
//Do fine-grained culling before adding objects to the view
for (AzFramework::VisibilityEntry* visibleEntry : nodeData.m_entries)
{
if (visibleEntry->m_typeFlags & AzFramework::VisibilityEntry::TYPE_RPI_Cullable)
{
@@ -355,160 +339,188 @@ namespace AZ
if ((c->m_cullData.m_drawListMask & drawListMask).none() ||
c->m_cullData.m_hideFlags & viewFlags ||
c->m_cullData.m_scene != m_jobData->m_scene || //[GFX_TODO][ATOM-13796] once the IVisibilitySystem supports multiple octree scenes, remove this
c->m_cullData.m_scene != worklistData->m_scene || //[GFX_TODO][ATOM-13796] once the IVisibilitySystem supports multiple octree scenes, remove this
c->m_isHidden)
{
continue;
}
IntersectResult res = ShapeIntersection::Classify(m_jobData->m_frustum, c->m_cullData.m_boundingSphere);
if (res == IntersectResult::Exterior)
{
continue;
}
else if (res == IntersectResult::Interior || ShapeIntersection::Overlaps(m_jobData->m_frustum, c->m_cullData.m_boundingObb))
{
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
if (TestOcclusionCulling(visibleEntry) == MaskedOcclusionCulling::CullingResult::VISIBLE)
if (TestOcclusionCulling(worklistData, visibleEntry) == MaskedOcclusionCulling::CullingResult::VISIBLE)
#endif
{
numDrawPackets += AddLodDataToView(c->m_cullData.m_boundingSphere.GetCenter(), c->m_lodData, *m_jobData->m_view);
++numVisibleCullables;
c->m_isVisible = true;
}
{
numDrawPackets += AddLodDataToView(c->m_cullData.m_boundingSphere.GetCenter(), c->m_lodData, *worklistData->m_view);
++numVisibleCullables;
c->m_isVisible = true;
}
}
}
}
if (m_jobData->m_debugCtx->m_debugDraw && (m_jobData->m_view->GetName() == m_jobData->m_debugCtx->m_currentViewSelectionName))
}
else
{
//Do fine-grained culling before adding objects to the view
for (AzFramework::VisibilityEntry* visibleEntry : nodeData.m_entries)
{
AZ_PROFILE_SCOPE(RPI, "debug draw culling");
AuxGeomDrawPtr auxGeomPtr = AuxGeomFeatureProcessorInterface::GetDrawQueueForScene(m_jobData->m_scene);
if (auxGeomPtr)
if (visibleEntry->m_typeFlags & AzFramework::VisibilityEntry::TYPE_RPI_Cullable)
{
//Draw the node bounds
// "Fully visible" nodes are nodes that are fully inside the frustum. "Partially visible" nodes intersect the edges of the frustum.
// Since the nodes of an octree have lots of overlapping boxes with coplanar edges, it's easier to view these separately, so
// we have a few debug booleans to toggle which ones to draw.
if (nodeIsContainedInFrustum && m_jobData->m_debugCtx->m_drawFullyVisibleNodes)
Cullable* c = static_cast<Cullable*>(visibleEntry->m_userData);
if ((c->m_cullData.m_drawListMask & drawListMask).none() ||
c->m_cullData.m_hideFlags & viewFlags ||
c->m_cullData.m_scene != worklistData->m_scene || //[GFX_TODO][ATOM-13796] once the IVisibilitySystem supports multiple octree scenes, remove this
c->m_isHidden)
{
auxGeomPtr->DrawAabb(nodeData.m_bounds, Colors::Lime, RPI::AuxGeomDraw::DrawStyle::Line, RPI::AuxGeomDraw::DepthTest::Off);
}
else if (!nodeIsContainedInFrustum && m_jobData->m_debugCtx->m_drawPartiallyVisibleNodes)
{
auxGeomPtr->DrawAabb(nodeData.m_bounds, Colors::Yellow, RPI::AuxGeomDraw::DrawStyle::Line, RPI::AuxGeomDraw::DepthTest::Off);
continue;
}
//Draw bounds on individual objects
if (m_jobData->m_debugCtx->m_drawBoundingBoxes || m_jobData->m_debugCtx->m_drawBoundingSpheres || m_jobData->m_debugCtx->m_drawLodRadii)
IntersectResult res = ShapeIntersection::Classify(worklistData->m_frustum, c->m_cullData.m_boundingSphere);
if (res == IntersectResult::Exterior)
{
for (AzFramework::VisibilityEntry* visibleEntry : nodeData.m_entries)
continue;
}
else if (res == IntersectResult::Interior || ShapeIntersection::Overlaps(worklistData->m_frustum, c->m_cullData.m_boundingObb))
{
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
if (TestOcclusionCulling(worklistData, visibleEntry) == MaskedOcclusionCulling::CullingResult::VISIBLE)
#endif
{
if (visibleEntry->m_typeFlags & AzFramework::VisibilityEntry::TYPE_RPI_Cullable)
{
Cullable* c = static_cast<Cullable*>(visibleEntry->m_userData);
if (m_jobData->m_debugCtx->m_drawBoundingBoxes)
{
auxGeomPtr->DrawObb(c->m_cullData.m_boundingObb, Matrix3x4::Identity(),
nodeIsContainedInFrustum ? Colors::Lime : Colors::Yellow, AuxGeomDraw::DrawStyle::Line);
}
if (m_jobData->m_debugCtx->m_drawBoundingSpheres)
{
auxGeomPtr->DrawSphere(c->m_cullData.m_boundingSphere.GetCenter(), c->m_cullData.m_boundingSphere.GetRadius(),
Color(0.5f, 0.5f, 0.5f, 0.3f), AuxGeomDraw::DrawStyle::Shaded);
}
if (m_jobData->m_debugCtx->m_drawLodRadii)
{
auxGeomPtr->DrawSphere(c->m_cullData.m_boundingSphere.GetCenter(),
c->m_lodData.m_lodSelectionRadius,
Color(1.0f, 0.5f, 0.0f, 0.3f), RPI::AuxGeomDraw::DrawStyle::Shaded);
}
}
numDrawPackets += AddLodDataToView(c->m_cullData.m_boundingSphere.GetCenter(), c->m_lodData, *worklistData->m_view);
++numVisibleCullables;
c->m_isVisible = true;
}
}
}
}
}
if (m_jobData->m_debugCtx->m_enableStats)
if (worklistData->m_debugCtx->m_debugDraw && (worklistData->m_view->GetName() == worklistData->m_debugCtx->m_currentViewSelectionName))
{
CullingDebugContext::CullStats& cullStats = m_jobData->m_debugCtx->GetCullStatsForView(m_jobData->m_view);
AZ_PROFILE_SCOPE(RPI, "debug draw culling");
//no need for mutex here since these are all atomics
cullStats.m_numVisibleDrawPackets += numDrawPackets;
cullStats.m_numVisibleCullables += numVisibleCullables;
++cullStats.m_numJobs;
AuxGeomDrawPtr auxGeomPtr = AuxGeomFeatureProcessorInterface::GetDrawQueueForScene(worklistData->m_scene);
if (auxGeomPtr)
{
//Draw the node bounds
// "Fully visible" nodes are nodes that are fully inside the frustum. "Partially visible" nodes intersect the edges of the frustum.
// Since the nodes of an octree have lots of overlapping boxes with coplanar edges, it's easier to view these separately, so
// we have a few debug booleans to toggle which ones to draw.
if (nodeIsContainedInFrustum && worklistData->m_debugCtx->m_drawFullyVisibleNodes)
{
auxGeomPtr->DrawAabb(nodeData.m_bounds, Colors::Lime, RPI::AuxGeomDraw::DrawStyle::Line, RPI::AuxGeomDraw::DepthTest::Off);
}
else if (!nodeIsContainedInFrustum && worklistData->m_debugCtx->m_drawPartiallyVisibleNodes)
{
auxGeomPtr->DrawAabb(nodeData.m_bounds, Colors::Yellow, RPI::AuxGeomDraw::DrawStyle::Line, RPI::AuxGeomDraw::DepthTest::Off);
}
//Draw bounds on individual objects
if (worklistData->m_debugCtx->m_drawBoundingBoxes || worklistData->m_debugCtx->m_drawBoundingSpheres || worklistData->m_debugCtx->m_drawLodRadii)
{
for (AzFramework::VisibilityEntry* visibleEntry : nodeData.m_entries)
{
if (visibleEntry->m_typeFlags & AzFramework::VisibilityEntry::TYPE_RPI_Cullable)
{
Cullable* c = static_cast<Cullable*>(visibleEntry->m_userData);
if (worklistData->m_debugCtx->m_drawBoundingBoxes)
{
auxGeomPtr->DrawObb(c->m_cullData.m_boundingObb, Matrix3x4::Identity(),
nodeIsContainedInFrustum ? Colors::Lime : Colors::Yellow, AuxGeomDraw::DrawStyle::Line);
}
if (worklistData->m_debugCtx->m_drawBoundingSpheres)
{
auxGeomPtr->DrawSphere(c->m_cullData.m_boundingSphere.GetCenter(), c->m_cullData.m_boundingSphere.GetRadius(),
Color(0.5f, 0.5f, 0.5f, 0.3f), AuxGeomDraw::DrawStyle::Shaded);
}
if (worklistData->m_debugCtx->m_drawLodRadii)
{
auxGeomPtr->DrawSphere(c->m_cullData.m_boundingSphere.GetCenter(),
c->m_lodData.m_lodSelectionRadius,
Color(1.0f, 0.5f, 0.0f, 0.3f), RPI::AuxGeomDraw::DrawStyle::Shaded);
}
}
}
}
}
}
}
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
MaskedOcclusionCulling::CullingResult TestOcclusionCulling(AzFramework::VisibilityEntry* visibleEntry)
if (worklistData->m_debugCtx->m_enableStats)
{
if (!m_jobData->m_maskedOcclusionCulling)
{
return MaskedOcclusionCulling::CullingResult::VISIBLE;
}
CullingDebugContext::CullStats& cullStats = worklistData->m_debugCtx->GetCullStatsForView(worklistData->m_view);
if (visibleEntry->m_boundingVolume.Contains(m_jobData->m_view->GetCameraTransform().GetTranslation()))
{
// camera is inside bounding volume
return MaskedOcclusionCulling::CullingResult::VISIBLE;
}
//no need for mutex here since these are all atomics
cullStats.m_numVisibleDrawPackets += numDrawPackets;
cullStats.m_numVisibleCullables += numVisibleCullables;
++cullStats.m_numJobs;
}
}
const Vector3& minBound = visibleEntry->m_boundingVolume.GetMin();
const Vector3& maxBound = visibleEntry->m_boundingVolume.GetMax();
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
static MaskedOcclusionCulling::CullingResult TestOcclusionCulling(
const AZStd::shared_ptr<WorklistData>& worklistData,
AzFramework::VisibilityEntry* visibleEntry)
{
if (!worklistData->m_maskedOcclusionCulling)
{
return MaskedOcclusionCulling::CullingResult::VISIBLE;
}
// compute bounding volume corners
Vector4 corners[8];
corners[0] = m_jobData->m_view->GetWorldToClipMatrix() * Vector4(minBound.GetX(), minBound.GetY(), minBound.GetZ(), 1.0f);
corners[1] = m_jobData->m_view->GetWorldToClipMatrix() * Vector4(minBound.GetX(), minBound.GetY(), maxBound.GetZ(), 1.0f);
corners[2] = m_jobData->m_view->GetWorldToClipMatrix() * Vector4(maxBound.GetX(), minBound.GetY(), maxBound.GetZ(), 1.0f);
corners[3] = m_jobData->m_view->GetWorldToClipMatrix() * Vector4(maxBound.GetX(), minBound.GetY(), minBound.GetZ(), 1.0f);
corners[4] = m_jobData->m_view->GetWorldToClipMatrix() * Vector4(minBound.GetX(), maxBound.GetY(), minBound.GetZ(), 1.0f);
corners[5] = m_jobData->m_view->GetWorldToClipMatrix() * Vector4(minBound.GetX(), maxBound.GetY(), maxBound.GetZ(), 1.0f);
corners[6] = m_jobData->m_view->GetWorldToClipMatrix() * Vector4(maxBound.GetX(), maxBound.GetY(), maxBound.GetZ(), 1.0f);
corners[7] = m_jobData->m_view->GetWorldToClipMatrix() * Vector4(maxBound.GetX(), maxBound.GetY(), minBound.GetZ(), 1.0f);
if (visibleEntry->m_boundingVolume.Contains(worklistData->m_view->GetCameraTransform().GetTranslation()))
{
// camera is inside bounding volume
return MaskedOcclusionCulling::CullingResult::VISIBLE;
}
// find min clip-space depth and NDC min/max
float minDepth = FLT_MAX;
float ndcMinX = FLT_MAX;
float ndcMinY = FLT_MAX;
float ndcMaxX = -FLT_MAX;
float ndcMaxY = -FLT_MAX;
for (uint32_t index = 0; index < 8; ++index)
{
minDepth = AZStd::min(minDepth, corners[index].GetW());
const Vector3& minBound = visibleEntry->m_boundingVolume.GetMin();
const Vector3& maxBound = visibleEntry->m_boundingVolume.GetMax();
// convert to NDC
corners[index] /= corners[index].GetW();
ndcMinX = AZStd::min(ndcMinX, corners[index].GetX());
ndcMinY = AZStd::min(ndcMinY, corners[index].GetY());
ndcMaxX = AZStd::max(ndcMaxX, corners[index].GetX());
ndcMaxY = AZStd::max(ndcMaxY, corners[index].GetY());
}
// compute bounding volume corners
Vector4 corners[8];
corners[0] = worklistData->m_view->GetWorldToClipMatrix() * Vector4(minBound.GetX(), minBound.GetY(), minBound.GetZ(), 1.0f);
corners[1] = worklistData->m_view->GetWorldToClipMatrix() * Vector4(minBound.GetX(), minBound.GetY(), maxBound.GetZ(), 1.0f);
corners[2] = worklistData->m_view->GetWorldToClipMatrix() * Vector4(maxBound.GetX(), minBound.GetY(), maxBound.GetZ(), 1.0f);
corners[3] = worklistData->m_view->GetWorldToClipMatrix() * Vector4(maxBound.GetX(), minBound.GetY(), minBound.GetZ(), 1.0f);
corners[4] = worklistData->m_view->GetWorldToClipMatrix() * Vector4(minBound.GetX(), maxBound.GetY(), minBound.GetZ(), 1.0f);
corners[5] = worklistData->m_view->GetWorldToClipMatrix() * Vector4(minBound.GetX(), maxBound.GetY(), maxBound.GetZ(), 1.0f);
corners[6] = worklistData->m_view->GetWorldToClipMatrix() * Vector4(maxBound.GetX(), maxBound.GetY(), maxBound.GetZ(), 1.0f);
corners[7] = worklistData->m_view->GetWorldToClipMatrix() * Vector4(maxBound.GetX(), maxBound.GetY(), minBound.GetZ(), 1.0f);
// find min clip-space depth and NDC min/max
float minDepth = FLT_MAX;
float ndcMinX = FLT_MAX;
float ndcMinY = FLT_MAX;
float ndcMaxX = -FLT_MAX;
float ndcMaxY = -FLT_MAX;
for (uint32_t index = 0; index < 8; ++index)
{
minDepth = AZStd::min(minDepth, corners[index].GetW());
if (minDepth < 0.00000001f)
{
return MaskedOcclusionCulling::VISIBLE;
return MaskedOcclusionCulling::CullingResult::VISIBLE;
}
// test against the occlusion buffer, which contains only the manually placed occlusion planes
return m_jobData->m_maskedOcclusionCulling->TestRect(ndcMinX, ndcMinY, ndcMaxX, ndcMaxY, minDepth);
// convert to NDC
corners[index] /= corners[index].GetW();
ndcMinX = AZStd::min(ndcMinX, corners[index].GetX());
ndcMinY = AZStd::min(ndcMinY, corners[index].GetY());
ndcMaxX = AZStd::max(ndcMaxX, corners[index].GetX());
ndcMaxY = AZStd::max(ndcMaxY, corners[index].GetY());
}
// test against the occlusion buffer, which contains only the manually placed occlusion planes
return worklistData->m_maskedOcclusionCulling->TestRect(ndcMinX, ndcMinY, ndcMaxX, ndcMaxY, minDepth);
}
#endif
};
void CullingScene::ProcessCullables(const Scene& scene, View& view, AZ::Job& parentJob)
void CullingScene::ProcessCullablesCommon(const Scene& scene, View& view, AZ::Frustum& frustum, void*& maskedOcclusionCulling)
{
AZ_PROFILE_SCOPE(RPI, "CullingScene::ProcessCullables() - %s", view.GetName().GetCStr());
AZ_PROFILE_SCOPE(RPI, "CullingScene::ProcessCullablesCommon() - %s", view.GetName().GetCStr());
const Matrix4x4& worldToClip = view.GetWorldToClipMatrix();
Frustum frustum = Frustum::CreateFromMatrixColumnMajor(worldToClip);
if (m_debugCtx.m_freezeFrustums)
{
AZStd::lock_guard<AZStd::mutex> lock(m_debugCtx.m_frozenFrustumsMutex);
@@ -536,7 +548,7 @@ namespace AZ
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
// setup occlusion culling, if necessary
MaskedOcclusionCulling* maskedOcclusionCulling = m_occlusionPlanes.empty() ? nullptr : view.GetMaskedOcclusionCulling();
maskedOcclusionCulling = m_occlusionPlanes.empty() ? nullptr : view.GetMaskedOcclusionCulling();
if (maskedOcclusionCulling)
{
// frustum cull occlusion planes
@@ -578,23 +590,27 @@ namespace AZ
static uint32_t indices[6] = { 0, 1, 2, 2, 3, 0 };
// render into the occlusion buffer, specifying BACKFACE_NONE so it functions as a double-sided occluder
maskedOcclusionCulling->RenderTriangles((float*)verts, indices, 2, nullptr, MaskedOcclusionCulling::BACKFACE_NONE);
static_cast<MaskedOcclusionCulling*>(maskedOcclusionCulling)->RenderTriangles(verts, indices, 2, nullptr, MaskedOcclusionCulling::BACKFACE_NONE);
}
}
#endif
}
void CullingScene::ProcessCullablesJobs(const Scene& scene, View& view, AZ::Job& parentJob)
{
AZ_PROFILE_SCOPE(RPI, "CullingScene::ProcessCullablesJobs() - %s", view.GetName().GetCStr());
const Matrix4x4& worldToClip = view.GetWorldToClipMatrix();
AZ::Frustum frustum = Frustum::CreateFromMatrixColumnMajor(worldToClip);
void* maskedOcclusionCulling = nullptr;
ProcessCullablesCommon(scene, view, frustum, maskedOcclusionCulling);
WorkListType worklist;
AZStd::shared_ptr<AddObjectsToViewJob::JobData> jobData = AZStd::make_shared<AddObjectsToViewJob::JobData>();
jobData->m_debugCtx = &m_debugCtx;
jobData->m_scene = &scene;
jobData->m_view = &view;
jobData->m_frustum = frustum;
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
jobData->m_maskedOcclusionCulling = maskedOcclusionCulling;
#endif
AZStd::shared_ptr<WorklistData> worklistData = MakeWorklistData(m_debugCtx, scene, view, frustum, maskedOcclusionCulling);
auto nodeVisitorLambda = [jobData, &parentJob, &worklist](const AzFramework::IVisibilityScene::NodeData& nodeData) -> void
auto nodeVisitorLambda = [worklistData, &parentJob, &worklist](const AzFramework::IVisibilityScene::NodeData& nodeData) -> void
{
AZ_PROFILE_SCOPE(RPI, "nodeVisitorLambda()");
AZ_Assert(nodeData.m_entries.size() > 0, "should not get called with 0 entries");
@@ -606,8 +622,13 @@ namespace AZ
if (worklist.size() == worklist.capacity())
{
// capture worklistData & worklist by value
auto processWorklist = [worklistData, worklist]()
{
ProcessWorklist(worklistData, worklist);
};
//Kick off a job to process the (full) worklist
AddObjectsToViewJob* job = aznew AddObjectsToViewJob(jobData, worklist); //pool allocated (cheap), auto-deletes when job finishes
AZ::Job* job = AZ::CreateJobFunction(processWorklist, true);
worklist.clear();
parentJob.SetContinuation(job);
job->Start();
@@ -616,7 +637,7 @@ namespace AZ
if (m_debugCtx.m_enableFrustumCulling)
{
m_visScene->Enumerate(frustum, nodeVisitorLambda);
m_visScene->Enumerate(frustum, nodeVisitorLambda);
}
else
{
@@ -625,21 +646,76 @@ namespace AZ
if (worklist.size() > 0)
{
AZStd::shared_ptr<AddObjectsToViewJob::JobData> remainingJobData = AZStd::make_shared<AddObjectsToViewJob::JobData>();
remainingJobData->m_debugCtx = &m_debugCtx;
remainingJobData->m_scene = &scene;
remainingJobData->m_view = &view;
remainingJobData->m_frustum = frustum;
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
remainingJobData->m_maskedOcclusionCulling = maskedOcclusionCulling;
#endif
//Kick off a job to process any remaining workitems
AddObjectsToViewJob* job = aznew AddObjectsToViewJob(remainingJobData, worklist); //pool allocated (cheap), auto-deletes when job finishes
// capture worklistData & worklist by value
auto processWorklist = [worklistData, worklist]()
{
ProcessWorklist(worklistData, worklist);
};
//Kick off a job to process the (full) worklist
AZ::Job* job = AZ::CreateJobFunction(processWorklist, true);
parentJob.SetContinuation(job);
job->Start();
}
}
void CullingScene::ProcessCullablesTG(const Scene& scene, View& view, AZ::TaskGraph& taskGraph)
{
AZ_PROFILE_SCOPE(RPI, "CullingScene::ProcessCullablesTG() - %s", view.GetName().GetCStr());
const Matrix4x4& worldToClip = view.GetWorldToClipMatrix();
AZ::Frustum frustum = Frustum::CreateFromMatrixColumnMajor(worldToClip);
void* maskedOcclusionCulling = nullptr;
ProcessCullablesCommon(scene, view, frustum, maskedOcclusionCulling);
AZStd::unique_ptr<WorkListType> worklist = AZStd::make_unique<WorkListType>();
AZStd::shared_ptr<WorklistData> worklistData = MakeWorklistData(m_debugCtx, scene, view, frustum, maskedOcclusionCulling);
static const AZ::TaskDescriptor descriptor{ "AZ::RPI::ProcessWorklist", "Graphics" };
auto nodeVisitorLambda = [worklistData, &taskGraph, &worklist](const AzFramework::IVisibilityScene::NodeData& nodeData) -> void
{
AZ_PROFILE_SCOPE(RPI, "nodeVisitorLambda()");
AZ_Assert(nodeData.m_entries.size() > 0, "should not get called with 0 entries");
AZ_Assert(worklist->size() < worklist->capacity(), "we should always have room to push a node on the queue");
//Queue up a small list of work items (NodeData*) which will be pushed to a worker task once the queue is full.
//This reduces the number of tasks in flight, reducing task-system overhead.
worklist->emplace_back(AZStd::move(nodeData));
if (worklist->size() == worklist->capacity())
{
//Task takes ownership of the worklist unique ptr
taskGraph.AddTask( descriptor, [worklistData, worklist = AZStd::move(worklist)]()
{
ProcessWorklist(worklistData, *worklist.get());
// allow worklist to go out of scope and be deleted
});
worklist = AZStd::make_unique<WorkListType>();
}
};
if (m_debugCtx.m_enableFrustumCulling)
{
m_visScene->Enumerate(frustum, nodeVisitorLambda);
}
else
{
m_visScene->EnumerateNoCull(nodeVisitorLambda);
}
if (worklist->size() > 0)
{
//Task takes ownership of the worklist unique ptr
taskGraph.AddTask( descriptor, [worklistData, worklist = AZStd::move(worklist)]()
{
ProcessWorklist(worklistData, *worklist.get());
// allow worklist to go out of scope and be deleted
});
}
}
uint32_t AddLodDataToView(const Vector3& pos, const Cullable::LodData& lodData, RPI::View& view)
{
#ifdef AZ_CULL_PROFILE_DETAILED
@@ -699,11 +775,11 @@ namespace AZ
m_parentScene = parentScene;
AZ_Assert(m_visScene == nullptr, "IVisibilityScene already created for this RPI::Scene");
char sceneIdBuf[40] = "";
m_parentScene->GetId().ToString(sceneIdBuf);
AZ::Name visSceneName(AZStd::string::format("RenderCullScene[%s]", sceneIdBuf));
AZ::Name visSceneName(AZStd::string::format("RenderCullScene[%s]", m_parentScene->GetName().GetCStr()));
m_visScene = AZ::Interface<AzFramework::IVisibilitySystem>::Get()->CreateVisibilityScene(visSceneName);
m_taskGraphActive = AZ::Interface<AZ::TaskGraphActiveInterface>::Get();
#ifdef AZ_CULL_DEBUG_ENABLED
AZ_Assert(CountObjectsInScene() == 0, "The culling system should start with 0 entries in this scene.");
#endif
@@ -721,13 +797,27 @@ namespace AZ
}
}
void CullingScene::BeginCulling(const AZStd::vector<ViewPtr>& views)
void CullingScene::BeginCullingTaskGraph(const AZStd::vector<ViewPtr>& views)
{
AZ_PROFILE_SCOPE(RPI, "CullingScene: BeginCulling");
m_cullDataConcurrencyCheck.soft_lock();
AZ::TaskGraph taskGraph;
AZ::TaskDescriptor beginCullingDescriptor{"RPI_CullingScene_BeginCullingView", "Graphics"};
for (auto& view : views)
{
taskGraph.AddTask(
beginCullingDescriptor,
[&view]()
{
view->BeginCulling();
});
}
m_debugCtx.ResetCullStats();
m_debugCtx.m_numCullablesInScene = GetNumCullables();
AZ::TaskGraphEvent waitForCompletion;
taskGraph.Submit(&waitForCompletion);
waitForCompletion.Wait();
}
void CullingScene::BeginCullingJobs(const AZStd::vector<ViewPtr>& views)
{
AZ::JobCompletion beginCullingCompletion;
for (auto& view : views)
@@ -743,6 +833,26 @@ namespace AZ
}
beginCullingCompletion.StartAndWaitForCompletion();
}
void CullingScene::BeginCulling(const AZStd::vector<ViewPtr>& views)
{
AZ_PROFILE_SCOPE(RPI, "CullingScene: BeginCulling");
m_cullDataConcurrencyCheck.soft_lock();
m_debugCtx.ResetCullStats();
m_debugCtx.m_numCullablesInScene = GetNumCullables();
m_taskGraphActive = AZ::Interface<AZ::TaskGraphActiveInterface>::Get();
if (m_taskGraphActive && m_taskGraphActive->IsTaskGraphActive())
{
BeginCullingTaskGraph(views);
}
else
{
BeginCullingJobs(views);
}
AuxGeomDrawPtr auxGeom;
if (m_debugCtx.m_debugDraw)
@@ -9,6 +9,7 @@
#include <Atom/RPI.Public/Pass/FullscreenTrianglePass.h>
#include <Atom/RPI.Public/Pass/PassUtils.h>
#include <Atom/RPI.Public/RPIUtils.h>
#include <Atom/RPI.Public/Shader/ShaderReloadDebugTracker.h>
#include <Atom/RPI.Reflect/Pass/FullscreenTrianglePassData.h>
#include <Atom/RPI.Reflect/Pass/PassTemplate.h>
@@ -46,16 +47,19 @@ namespace AZ
void FullscreenTrianglePass::OnShaderReinitialized(const Shader&)
{
ShaderReloadDebugTracker::ScopedSection reloadSection("{%p}->FullscreenTrianglePass::OnShaderReinitialized", this);
LoadShader();
}
void FullscreenTrianglePass::OnShaderAssetReinitialized(const Data::Asset<ShaderAsset>&)
{
ShaderReloadDebugTracker::ScopedSection reloadSection("{%p}->FullscreenTrianglePass::OnShaderAssetReinitialized", this);
LoadShader();
}
void FullscreenTrianglePass::OnShaderVariantReinitialized(const ShaderVariant&)
{
ShaderReloadDebugTracker::ScopedSection reloadSection("{%p}->FullscreenTrianglePass::OnShaderVariantReinitialized", this);
LoadShader();
}
@@ -129,6 +133,8 @@ namespace AZ
void FullscreenTrianglePass::InitializeInternal()
{
RenderPass::InitializeInternal();
ShaderReloadDebugTracker::ScopedSection reloadSection("{%p}->FullscreenTrianglePass::InitializeInternal", this);
// This draw item purposefully does not reference any geometry buffers.
// Instead it's expected that the extended class uses a vertex shader
@@ -159,6 +159,11 @@ namespace AZ
AZ_Assert(false, "Scene was already registered");
return;
}
else if (!scene->GetName().IsEmpty() && scene->GetName() == sceneItem->GetName())
{
// only report a warning if there is a scene with duplicated name
AZ_Warning("RPISystem", false, "There is a registered scene with same name [%s]", scene->GetName().GetCStr());
}
}
m_scenes.push_back(scene);
@@ -177,11 +182,35 @@ namespace AZ
AZ_Assert(false, "Can't unregister scene which wasn't registered");
}
ScenePtr RPISystem::GetScene(const SceneId& sceneId) const
Scene* RPISystem::GetScene(const SceneId& sceneId) const
{
for (const auto& scene : m_scenes)
{
if (scene->GetId() == sceneId)
{
return scene.get();
}
}
return nullptr;
}
Scene* RPISystem::GetSceneByName(const AZ::Name& name) const
{
for (const auto& scene : m_scenes)
{
if (scene->GetName() == name)
{
return scene.get();
}
}
return nullptr;
}
ScenePtr RPISystem::GetDefaultScene() const
{
for (const auto& scene : m_scenes)
{
if (scene->GetName() == AZ::Name("Main"))
{
return scene;
}
@@ -189,16 +218,6 @@ namespace AZ
return nullptr;
}
ScenePtr RPISystem::GetDefaultScene() const
{
if (m_scenes.size() > 0)
{
return m_scenes[0];
}
return nullptr;
}
RenderPipelinePtr RPISystem::GetRenderPipelineForWindow(AzFramework::NativeWindowHandle windowHandle)
{
RenderPipelinePtr renderPipeline;
+70 -38
View File
@@ -45,7 +45,9 @@ namespace AZ
auto shaderAsset = RPISystemInterface::Get()->GetCommonShaderAssetForSrgs();
scene->m_srg = ShaderResourceGroup::Create(shaderAsset, sceneSrgLayout->GetName());
}
scene->m_name = sceneDescriptor.m_nameId;
return ScenePtr(scene);
}
@@ -83,10 +85,23 @@ namespace AZ
return nullptr;
}
Scene* Scene::GetSceneForEntityId(AZ::EntityId entityId)
{
// Find the entity context for the entity ID.
AzFramework::EntityContextId entityContextId = AzFramework::EntityContextId::CreateNull();
AzFramework::EntityIdContextQueryBus::EventResult(entityContextId, entityId, &AzFramework::EntityIdContextQueryBus::Events::GetOwningContextId);
if (!entityContextId.IsNull())
{
return GetSceneForEntityContextId(entityContextId);
}
return nullptr;
}
Scene::Scene()
{
m_id = Uuid::CreateRandom();
m_id = AZ::Uuid::CreateRandom();
m_cullingScene = aznew CullingScene();
SceneRequestBus::Handler::BusConnect(m_id);
m_drawFilterTagRegistry = RHI::DrawFilterTagRegistry::Create();
@@ -96,7 +111,7 @@ namespace AZ
{
if (m_taskGraphActive)
{
WaitTGEvent(m_simulationFinishedTGEvent, &m_simulationFinishedWorkActive);
WaitAndCleanTGEvent(AZStd::move(m_simulationFinishedTGEvent));
}
else
{
@@ -299,7 +314,6 @@ namespace AZ
// Force to update the lookup table since adding render pipeline would effect any pipeline states created before pass system tick
RebuildPipelineStatesLookup();
AZ_Assert(!m_id.IsNull(), "RPI::Scene needs to have a valid uuid.");
SceneNotificationBus::Event(m_id, &SceneNotification::OnRenderPipelineAdded, pipeline);
}
@@ -371,8 +385,8 @@ namespace AZ
});
}
simulationTG.Detach();
m_simulationFinishedWorkActive = true;
simulationTG.Submit(&m_simulationFinishedTGEvent);
m_simulationFinishedTGEvent = AZStd::make_unique<TaskGraphEvent>();
simulationTG.Submit(m_simulationFinishedTGEvent.get());
}
void Scene::SimulateJobs()
@@ -405,7 +419,7 @@ namespace AZ
// If previous simulation job wasn't done, wait for it to finish.
if (m_taskGraphActive)
{
WaitTGEvent(m_simulationFinishedTGEvent, &m_simulationFinishedWorkActive);
WaitAndCleanTGEvent(AZStd::move(m_simulationFinishedTGEvent));
}
else
{
@@ -435,17 +449,14 @@ namespace AZ
}
}
void Scene::WaitTGEvent(AZ::TaskGraphEvent& completionTGEvent, AZStd::atomic_bool* workToWaitOn )
void Scene::WaitAndCleanTGEvent(AZStd::unique_ptr<AZ::TaskGraphEvent>&& completionTGEvent)
{
AZ_PROFILE_SCOPE(RPI, "Scene: WaitAndCleanCompletionJob");
if (!workToWaitOn || workToWaitOn->load())
AZ_PROFILE_SCOPE(RPI, "Scene: WaitAndCleanTGEvent");
if (completionTGEvent)
{
completionTGEvent.Wait();
}
if (workToWaitOn)
{
workToWaitOn->store(false);
completionTGEvent->Wait();
}
// allow completionTGEvent to go out of scope and be deleted
}
void Scene::WaitAndCleanCompletionJob(AZ::JobCompletion*& completionJob)
@@ -485,12 +496,12 @@ namespace AZ
void Scene::CollectDrawPacketsTaskGraph()
{
AZ_PROFILE_SCOPE(RPI, "CollectDrawPackets");
AZ_PROFILE_SCOPE(RPI, "CollectDrawPacketsTaskGraph");
AZ::TaskGraphEvent collectDrawPacketsTGEvent;
static const AZ::TaskDescriptor collectDrawPacketsTGDesc{"RPI_Scene_PrepareRender_CollectDrawPackets", "Graphics"};
AZ::TaskGraph collectDrawPacketsTG;
// Launch FeatureProcessor::Render() jobs
// Launch FeatureProcessor::Render() taskgraphs
for (auto& fp : m_featureProcessors)
{
collectDrawPacketsTG.AddTask(
@@ -506,32 +517,48 @@ namespace AZ
// 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)
static const AZ::TaskDescriptor processCullablesDescriptor{"AZ::RPI::Scene::ProcessCullables", "Graphics"};
AZ::TaskGraphEvent processCullablesTGEvent;
AZ::TaskGraph processCullablesTG;
if (parallelOctreeTraversal)
{
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)
for (ViewPtr& viewPtr : m_renderPacket.m_views)
{
processCullablesJob->SetDependent(&processCullablesCompletion);
processCullablesJob->Start();
}
else
{
processCullablesJob->StartAndWaitForCompletion();
processCullablesTG.AddTask(processCullablesDescriptor, [this, &viewPtr, &processCullablesTGEvent]()
{
AZ::TaskGraph subTaskGraph;
m_cullingScene->ProcessCullablesTG(*this, *viewPtr, subTaskGraph);
if (!subTaskGraph.IsEmpty())
{
subTaskGraph.Detach();
subTaskGraph.Submit(&processCullablesTGEvent);
}
});
}
}
else
{
for (ViewPtr& viewPtr : m_renderPacket.m_views)
{
m_cullingScene->ProcessCullablesTG(*this, *viewPtr, processCullablesTG);
}
}
bool processCullablesHasWork = !processCullablesTG.IsEmpty();
if (processCullablesHasWork)
{
processCullablesTG.Submit(&processCullablesTGEvent);
}
WaitTGEvent(collectDrawPacketsTGEvent);
processCullablesCompletion.StartAndWaitForCompletion();
collectDrawPacketsTGEvent.Wait();
if (processCullablesHasWork) // skip the wait if there is no work to do
{
processCullablesTGEvent.Wait();
}
}
void Scene::CollectDrawPacketsJobs()
{
AZ_PROFILE_SCOPE(RPI, "CollectDrawPackets");
AZ_PROFILE_SCOPE(RPI, "CollectDrawPacketsJobs");
AZ::JobCompletion* collectDrawPacketsCompletion = aznew AZ::JobCompletion();
// Launch FeatureProcessor::Render() jobs
@@ -553,7 +580,7 @@ namespace AZ
{
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
m_cullingScene->ProcessCullablesJobs(*this, *viewPtr, thisJob); // can't call directly because ProcessCullables needs a parent job
},
true, nullptr); //auto-deletes
if (m_cullingScene->GetDebugContext().m_parallelOctreeTraversal)
@@ -586,7 +613,7 @@ namespace AZ
});
}
finalizeDrawListsTG.Submit(&finalizeDrawListsTGEvent);
WaitTGEvent(finalizeDrawListsTGEvent);
finalizeDrawListsTGEvent.Wait();
}
void Scene::FinalizeDrawListsJobs()
@@ -612,7 +639,7 @@ namespace AZ
if (m_taskGraphActive)
{
WaitTGEvent(m_simulationFinishedTGEvent, &m_simulationFinishedWorkActive);
WaitAndCleanTGEvent(AZStd::move(m_simulationFinishedTGEvent));
}
else
{
@@ -785,6 +812,11 @@ namespace AZ
{
return m_id;
}
AZ::Name Scene::GetName() const
{
return m_name;
}
bool Scene::SetDefaultRenderPipeline(const RenderPipelineId& pipelineId)
{
@@ -320,6 +320,30 @@ namespace AZ
}
const ShaderVariant& Shader::GetVariant(ShaderVariantStableId shaderVariantStableId)
{
const ShaderVariant& variant = GetVariantInternal(shaderVariantStableId);
if (ShaderReloadDebugTracker::IsEnabled())
{
auto makeTimeString = [](AZStd::sys_time_t timestamp, AZStd::sys_time_t now)
{
AZStd::sys_time_t elapsedMicroseconds = now - timestamp;
double elapsedSeconds = aznumeric_cast<double>(elapsedMicroseconds / 1'000'000);
AZStd::string timeString = AZStd::string::format("%lld (%f seconds ago)", timestamp, elapsedSeconds);
return timeString;
};
AZStd::sys_time_t now = AZStd::GetTimeNowMicroSecond();
ShaderReloadDebugTracker::Printf("{%p}->Shader::GetVariant for shader '%s' [build time %s] found variant '%s' [build time %s]", this,
m_asset.GetHint().c_str(), makeTimeString(m_asset->GetBuildTimestamp(), now).c_str(),
variant.GetShaderVariantAsset().GetHint().c_str(), makeTimeString(variant.GetShaderVariantAsset()->GetBuildTimestamp(), now).c_str());
}
return variant;
}
const ShaderVariant& Shader::GetVariantInternal(ShaderVariantStableId shaderVariantStableId)
{
if (!shaderVariantStableId.IsValid() || shaderVariantStableId == ShaderAsset::RootShaderVariantStableId)
{
@@ -336,7 +360,7 @@ namespace AZ
// reloaded, but some (or all) shader variants haven't been built yet. Since we want to use the latest version of the
// shader code, ignore the old variants and fall back to the newer root variant instead. There's no need to report a
// warning here because m_asset->GetVariant below will report one.
if (findIt->second.GetBuildTimestamp() >= m_asset->GetShaderAssetBuildTimestamp())
if (findIt->second.GetBuildTimestamp() >= m_asset->GetBuildTimestamp())
{
return findIt->second;
}
@@ -359,7 +383,7 @@ namespace AZ
auto findIt = m_shaderVariants.find(shaderVariantStableId);
if (findIt != m_shaderVariants.end())
{
if (findIt->second.GetBuildTimestamp() >= m_asset->GetShaderAssetBuildTimestamp())
if (findIt->second.GetBuildTimestamp() >= m_asset->GetBuildTimestamp())
{
return findIt->second;
}
@@ -7,24 +7,75 @@
*/
#include <Atom/RPI.Public/Shader/ShaderReloadDebugTracker.h>
#include <AzCore/Module/Environment.h>
namespace AZ
{
namespace RPI
{
bool ShaderReloadDebugTracker::s_enabled = false;
int ShaderReloadDebugTracker::s_indent = 0;
namespace ShaderReloadDebugTrackerInternal
{
static const char EnabledVariableName[] = "ShaderReloadDebugTracker enabled";
static const char IndentVariableName[] = "ShaderReloadDebugTracker indent";
static EnvironmentVariable<bool> s_enabled;
static EnvironmentVariable<int> s_indent;
}
void ShaderReloadDebugTracker::Init()
{
ShaderReloadDebugTrackerInternal::s_enabled = AZ::Environment::CreateVariable<bool>(ShaderReloadDebugTrackerInternal::EnabledVariableName);
ShaderReloadDebugTrackerInternal::s_indent = AZ::Environment::CreateVariable<int>(ShaderReloadDebugTrackerInternal::IndentVariableName);
ShaderReloadDebugTrackerInternal::s_enabled.Get() = false;
ShaderReloadDebugTrackerInternal::s_indent.Get() = 0;
}
void ShaderReloadDebugTracker::Shutdown()
{
ShaderReloadDebugTrackerInternal::s_enabled.Reset();
ShaderReloadDebugTrackerInternal::s_indent.Reset();
}
void ShaderReloadDebugTracker::MakeReady()
{
if (!ShaderReloadDebugTrackerInternal::s_enabled.IsValid())
{
ShaderReloadDebugTrackerInternal::s_enabled = AZ::Environment::FindVariable<bool>(ShaderReloadDebugTrackerInternal::EnabledVariableName);
ShaderReloadDebugTrackerInternal::s_indent = AZ::Environment::FindVariable<int>(ShaderReloadDebugTrackerInternal::IndentVariableName);
}
}
bool ShaderReloadDebugTracker::IsEnabled()
{
#ifdef AZ_ENABLE_SHADER_RELOAD_DEBUG_TRACKER
MakeReady();
// Set this to true in the debugger to turn on hot reload tracing.
// If needed, we could hook this up to a CVar.
return s_enabled;
return ShaderReloadDebugTrackerInternal::s_enabled.Get();
#else
return false;
#endif
}
void ShaderReloadDebugTracker::AddIndent()
{
MakeReady();
ShaderReloadDebugTrackerInternal::s_indent.Get() += IndentSpaces;
}
void ShaderReloadDebugTracker::RemoveIndent()
{
MakeReady();
ShaderReloadDebugTrackerInternal::s_indent.Get() -= IndentSpaces;
}
int ShaderReloadDebugTracker::GetIndent()
{
MakeReady();
return ShaderReloadDebugTrackerInternal::s_indent.Get();
}
ShaderReloadDebugTracker::ScopedSection::~ScopedSection()
{
@@ -10,6 +10,7 @@
#include <Atom/RPI.Public/Shader/Shader.h>
#include <Atom/RPI.Public/Shader/ShaderResourceGroup.h>
#include <Atom/RPI.Public/Shader/ShaderResourceGroupPool.h>
#include <Atom/RPI.Public/Shader/ShaderReloadDebugTracker.h>
#include <Atom/RPI.Reflect/Asset/AssetHandler.h>
#include <Atom/RPI.Reflect/Asset/AssetUtils.h>
@@ -86,10 +87,13 @@ namespace AZ
};
Data::InstanceDatabase<ShaderResourceGroupPool>::Create(azrtti_typeid<ShaderResourceGroupPool>(), handler, false);
}
ShaderReloadDebugTracker::Init();
}
void ShaderSystem::Shutdown()
{
ShaderReloadDebugTracker::Shutdown();
Data::InstanceDatabase<Shader>::Destroy();
Data::InstanceDatabase<ShaderResourceGroup>::Destroy();
Data::InstanceDatabase<ShaderResourceGroupPool>::Destroy();
@@ -100,7 +100,7 @@ namespace AZ
->Field("pipelineStateType", &ShaderAsset::m_pipelineStateType)
->Field("shaderOptionGroupLayout", &ShaderAsset::m_shaderOptionGroupLayout)
->Field("drawListName", &ShaderAsset::m_drawListName)
->Field("shaderAssetBuildTimestamp", &ShaderAsset::m_shaderAssetBuildTimestamp)
->Field("shaderAssetBuildTimestamp", &ShaderAsset::m_buildTimestamp)
->Field("perAPIShaderData", &ShaderAsset::m_perAPIShaderData)
;
}
@@ -134,11 +134,11 @@ namespace AZ
return m_drawListName;
}
AZStd::sys_time_t ShaderAsset::GetShaderAssetBuildTimestamp() const
AZStd::sys_time_t ShaderAsset::GetBuildTimestamp() const
{
return m_shaderAssetBuildTimestamp;
return m_buildTimestamp;
}
void ShaderAsset::SetReady()
{
m_status = AssetStatus::Ready;
@@ -256,7 +256,7 @@ namespace AZ
}
return GetRootVariant(supervariantIndex);
}
else if (variant->GetBuildTimestamp() >= m_shaderAssetBuildTimestamp)
else if (variant->GetBuildTimestamp() >= m_buildTimestamp)
{
return variant;
}
@@ -21,7 +21,7 @@ namespace AZ
{
if (ValidateIsReady())
{
m_asset->m_shaderAssetBuildTimestamp = shaderAssetBuildTimestamp;
m_asset->m_buildTimestamp = shaderAssetBuildTimestamp;
}
}
@@ -390,7 +390,7 @@ namespace AZ
m_asset->m_pipelineStateType = sourceShaderAsset.m_pipelineStateType;
m_asset->m_drawListName = sourceShaderAsset.m_drawListName;
m_asset->m_shaderOptionGroupLayout = sourceShaderAsset.m_shaderOptionGroupLayout;
m_asset->m_shaderAssetBuildTimestamp = sourceShaderAsset.m_shaderAssetBuildTimestamp;
m_asset->m_buildTimestamp = sourceShaderAsset.m_buildTimestamp;
// copy root variant assets
for (auto& perAPIShaderData : sourceShaderAsset.m_perAPIShaderData)
@@ -43,6 +43,7 @@ namespace AtomToolsFramework
&PreviewerFeatureProcessorProviderBus::Handler::GetRequiredFeatureProcessors, featureProcessors);
AZ::RPI::SceneDescriptor sceneDesc;
sceneDesc.m_nameId = AZ::Name("PreviewRenderer");
sceneDesc.m_featureProcessorNames.assign(featureProcessors.begin(), featureProcessors.end());
m_scene = AZ::RPI::Scene::CreateScene(sceneDesc);
@@ -47,30 +47,27 @@ namespace AtomToolsFramework
void PreviewRendererSystemComponent::Activate()
{
AzFramework::AssetCatalogEventBus::Handler::BusConnect();
AzFramework::ApplicationLifecycleEvents::Bus::Handler::BusConnect();
PreviewRendererSystemRequestBus::Handler::BusConnect();
AZ::TickBus::QueueFunction(
[this]()
{
if (!m_previewRenderer)
{
m_previewRenderer.reset(aznew AtomToolsFramework::PreviewRenderer(
"PreviewRendererSystemComponent Preview Scene", "PreviewRendererSystemComponent Preview Pipeline"));
}
});
}
void PreviewRendererSystemComponent::Deactivate()
{
PreviewRendererSystemRequestBus::Handler::BusDisconnect();
AzFramework::ApplicationLifecycleEvents::Bus::Handler::BusDisconnect();
AzFramework::AssetCatalogEventBus::Handler::BusDisconnect();
m_previewRenderer.reset();
}
void PreviewRendererSystemComponent::OnCatalogLoaded([[maybe_unused]] const char* catalogFile)
{
AZ::TickBus::QueueFunction([this](){
if (!m_previewRenderer)
{
m_previewRenderer.reset(aznew AtomToolsFramework::PreviewRenderer(
"PreviewRendererSystemComponent Preview Scene", "PreviewRendererSystemComponent Preview Pipeline"));
}
});
}
void PreviewRendererSystemComponent::OnApplicationAboutToStop()
{
m_previewRenderer.reset();
@@ -9,7 +9,6 @@
#pragma once
#include <AtomToolsFramework/PreviewRenderer/PreviewRendererSystemRequestBus.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Component/Component.h>
#include <AzFramework/Application/Application.h>
#include <PreviewRenderer/PreviewRenderer.h>
@@ -19,7 +18,6 @@ namespace AtomToolsFramework
//! System component that manages a global PreviewRenderer.
class PreviewRendererSystemComponent final
: public AZ::Component
, public AzFramework::AssetCatalogEventBus::Handler
, public AzFramework::ApplicationLifecycleEvents::Bus::Handler
, public PreviewRendererSystemRequestBus::Handler
{
@@ -38,9 +36,6 @@ namespace AtomToolsFramework
void Deactivate() override;
private:
// AzFramework::AssetCatalogEventBus::Handler overrides ...
void OnCatalogLoaded(const char* catalogFile) override;
// AzFramework::ApplicationLifecycleEvents overrides...
void OnApplicationAboutToStop() override;
@@ -279,9 +279,9 @@ namespace MaterialEditor
// reset environment
AZ::Transform iblTransform = AZ::Transform::CreateIdentity();
AZ::TransformBus::Event(m_iblEntityId, &AZ::TransformBus::Events::SetLocalTM, iblTransform);
const AZ::Matrix4x4 rotationMatrix = AZ::Matrix4x4::CreateIdentity();
AZ::RPI::ScenePtr scene = AZ::RPI::RPISystemInterface::Get()->GetDefaultScene();
auto skyBoxFeatureProcessorInterface = scene->GetFeatureProcessor<AZ::Render::SkyBoxFeatureProcessorInterface>();
auto skyBoxFeatureProcessorInterface = AZ::RPI::Scene::GetFeatureProcessorForEntity<AZ::Render::SkyBoxFeatureProcessorInterface>(m_iblEntityId);
skyBoxFeatureProcessorInterface->SetCubemapRotationMatrix(rotationMatrix);
if (m_behavior)
@@ -25,8 +25,7 @@ namespace MaterialEditor
m_iblEntityId,
&MaterialEditorViewportInputControllerRequestBus::Handler::GetIblEntityId);
AZ_Assert(m_iblEntityId.IsValid(), "Failed to find m_iblEntityId");
AZ::RPI::ScenePtr scene = AZ::RPI::RPISystemInterface::Get()->GetDefaultScene();
m_skyBoxFeatureProcessorInterface = scene->GetFeatureProcessor<AZ::Render::SkyBoxFeatureProcessorInterface>();
m_skyBoxFeatureProcessorInterface = AZ::RPI::Scene::GetFeatureProcessorForEntity<AZ::Render::SkyBoxFeatureProcessorInterface>(m_iblEntityId);
}
void RotateEnvironmentBehavior::TickInternal(float x, float y, float z)
@@ -67,6 +67,7 @@ namespace MaterialEditor
// Create and register a scene with all available feature processors
AZ::RPI::SceneDescriptor sceneDesc;
sceneDesc.m_nameId = AZ::Name("MaterialViewport");
m_scene = AZ::RPI::Scene::CreateScene(sceneDesc);
m_scene->EnableAllFeatureProcessors();
@@ -108,7 +108,7 @@ namespace AZ
{
m_dynamicDrawManager.reset();
AZ::RPI::ViewportContextManagerNotificationsBus::Handler::BusDisconnect();
RPI::Scene* scene = RPI::RPISystemInterface::Get()->GetDefaultScene().get();
RPI::Scene* scene = AZ::RPI::Scene::GetSceneForEntityContextId(m_entityContextId);
// Check if scene is emptry since scene might be released already when running AtomSampleViewer
if (scene)
{
@@ -157,9 +157,9 @@ namespace AZ
void AtomBridgeSystemComponent::OnBootstrapSceneReady(AZ::RPI::Scene* bootstrapScene)
{
AZ_UNUSED(bootstrapScene);
// Make default AtomDebugDisplayViewportInterface
AZStd::shared_ptr<AtomDebugDisplayViewportInterface> mainEntityDebugDisplay = AZStd::make_shared<AtomDebugDisplayViewportInterface>(AzFramework::g_defaultSceneEntityDebugDisplayId);
AZStd::shared_ptr<AtomDebugDisplayViewportInterface> mainEntityDebugDisplay =
AZStd::make_shared<AtomDebugDisplayViewportInterface>(AzFramework::g_defaultSceneEntityDebugDisplayId, bootstrapScene);
m_activeViewportsList[AzFramework::g_defaultSceneEntityDebugDisplayId] = mainEntityDebugDisplay;
}
@@ -256,12 +256,11 @@ namespace AZ::AtomBridge
viewportContextPtr->ConnectSceneChangedHandler(m_sceneChangeHandler);
}
AtomDebugDisplayViewportInterface::AtomDebugDisplayViewportInterface(uint32_t defaultInstanceAddress)
AtomDebugDisplayViewportInterface::AtomDebugDisplayViewportInterface(uint32_t defaultInstanceAddress, RPI::Scene* scene)
{
ResetRenderState();
m_viewportId = defaultInstanceAddress;
m_defaultInstance = true;
RPI::Scene* scene = RPI::RPISystemInterface::Get()->GetDefaultScene().get();
InitInternal(scene, nullptr);
}
@@ -124,7 +124,7 @@ namespace AZ::AtomBridge
AZ_RTTI(AtomDebugDisplayViewportInterface, "{09AF6A46-0100-4FBF-8F94-E6B221322D14}", AzFramework::DebugDisplayRequestBus::Handler);
explicit AtomDebugDisplayViewportInterface(AZ::RPI::ViewportContextPtr viewportContextPtr);
explicit AtomDebugDisplayViewportInterface(uint32_t defaultInstanceAddress);
explicit AtomDebugDisplayViewportInterface(uint32_t defaultInstanceAddress, RPI::Scene* scene);
~AtomDebugDisplayViewportInterface();
void ResetRenderState();
@@ -133,18 +133,6 @@ namespace AZ
typedef std::vector<FontEffect> FontEffects;
typedef FontEffects::iterator FontEffectsIterator;
struct FontPipelineStateMapKey
{
AZ::RPI::SceneId m_sceneId; // which scene pipeline state is attached to (via Render Pipeline)
AZ::RHI::DrawListTag m_drawListTag; // which render pass this pipeline draws in by default
bool operator<(const FontPipelineStateMapKey& other) const
{
return m_sceneId < other.m_sceneId
|| (m_sceneId == other.m_sceneId && m_drawListTag < other.m_drawListTag);
}
};
struct FontShaderData
{
AZ::RHI::ShaderInputNameIndex m_imageInputIndex = "m_texture";
@@ -48,11 +48,7 @@ namespace AZ
void DiffuseGlobalIlluminationComponentController::Activate(EntityId entityId)
{
AZ_UNUSED(entityId);
const RPI::Scene* scene = AZ::RPI::RPISystemInterface::Get()->GetDefaultScene().get();
m_featureProcessor = scene->GetFeatureProcessor<DiffuseGlobalIlluminationFeatureProcessorInterface>();
m_featureProcessor = AZ::RPI::Scene::GetFeatureProcessorForEntity<DiffuseGlobalIlluminationFeatureProcessorInterface>(entityId);
OnConfigChanged();
}
@@ -79,7 +79,7 @@ namespace AZ
m_entityId = entityId;
m_dirty = true;
RPI::ScenePtr scene = RPI::RPISystemInterface::Get()->GetDefaultScene();
RPI::Scene* scene = RPI::Scene::GetSceneForEntityId(m_entityId);
if (scene)
{
AZ::RPI::SceneNotificationBus::Handler::BusConnect(scene->GetId());
@@ -6,23 +6,24 @@
*
*/
#include <Material/EditorMaterialComponent.h>
#include <Material/EditorMaterialComponentExporter.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <Atom/RPI.Edit/Common/AssetUtils.h>
#include <Atom/RPI.Edit/Material/MaterialPropertyId.h>
#include <Atom/RPI.Public/Image/StreamingImage.h>
#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
#include <Atom/RPI.Reflect/Material/MaterialTypeAsset.h>
#include <AtomLyIntegration/CommonFeatures/Mesh/MeshComponentBus.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Serialization/Json/RegistrationContext.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <Material/EditorMaterialComponent.h>
#include <Material/EditorMaterialComponentExporter.h>
#include <Material/EditorMaterialComponentSerializer.h>
AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT
#include <QMenu>
#include <QAction>
#include <QCursor>
#include <QMenu>
AZ_POP_DISABLE_WARNING
namespace AZ
@@ -59,7 +60,12 @@ namespace AZ
BaseClass::Reflect(context);
EditorMaterialComponentSlot::Reflect(context);
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
if (auto jsonContext = azrtti_cast<JsonRegistrationContext*>(context))
{
jsonContext->Serializer<JsonEditorMaterialComponentSerializer>()->HandlesType<EditorMaterialComponent>();
}
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->RegisterGenericType<EditorMaterialComponentSlotContainer>();
serializeContext->RegisterGenericType<EditorMaterialComponentSlotsByLodContainer>();
@@ -76,7 +82,7 @@ namespace AZ
serializeContext->RegisterGenericType<AZStd::unordered_map<MaterialAssignmentId, Data::AssetId, AZStd::hash<MaterialAssignmentId>, AZStd::equal_to<MaterialAssignmentId>, AZStd::allocator>>();
serializeContext->RegisterGenericType<AZStd::unordered_map<MaterialAssignmentId, MaterialPropertyOverrideMap, AZStd::hash<MaterialAssignmentId>, AZStd::equal_to<MaterialAssignmentId>, AZStd::allocator>>();
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
if (auto editContext = serializeContext->GetEditContext())
{
editContext->Class<EditorMaterialComponent>(
"Material", "The material component specifies the material to use for this entity")
@@ -129,7 +135,7 @@ namespace AZ
}
}
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->ConstantProperty("EditorMaterialComponentTypeId", BehaviorConstant(Uuid(EditorMaterialComponentTypeId)))
->Attribute(AZ::Script::Attributes::Module, "render")
@@ -27,6 +27,8 @@ namespace AZ
, public EditorMaterialSystemComponentNotificationBus::Handler
{
public:
friend class JsonEditorMaterialComponentSerializer;
using BaseClass = EditorRenderComponentAdapter<MaterialComponentController, MaterialComponent, MaterialComponentConfig>;
AZ_EDITOR_COMPONENT(EditorMaterialComponent, EditorMaterialComponentTypeId, BaseClass);
@@ -0,0 +1,109 @@
/*
* 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/Serialization/Json/JsonSerializationResult.h>
#include <Material/EditorMaterialComponent.h>
#include <Material/EditorMaterialComponentSerializer.h>
namespace AZ
{
namespace Render
{
AZ_CLASS_ALLOCATOR_IMPL(JsonEditorMaterialComponentSerializer, AZ::SystemAllocator, 0);
AZ::JsonSerializationResult::Result JsonEditorMaterialComponentSerializer::Load(
void* outputValue,
[[maybe_unused]] const AZ::Uuid& outputValueTypeId,
const rapidjson::Value& inputValue,
AZ::JsonDeserializerContext& context)
{
namespace JSR = AZ::JsonSerializationResult;
AZ_Assert(
azrtti_typeid<EditorMaterialComponent>() == outputValueTypeId,
"Unable to deserialize EditorMaterialComponent from json because the provided type is %s.",
outputValueTypeId.ToString<AZStd::string>().c_str());
auto componentInstance = reinterpret_cast<EditorMaterialComponent*>(outputValue);
AZ_Assert(componentInstance, "Output value for JsonEditorMaterialComponentSerializer can't be null.");
JSR::ResultCode result(JSR::Tasks::ReadField);
result.Combine(ContinueLoadingFromJsonObjectField(
&componentInstance->m_id, azrtti_typeid<decltype(componentInstance->m_id)>(), inputValue, "Id", context));
result.Combine(ContinueLoadingFromJsonObjectField(
&componentInstance->m_controller, azrtti_typeid<decltype(componentInstance->m_controller)>(), inputValue, "Controller",
context));
result.Combine(ContinueLoadingFromJsonObjectField(
&componentInstance->m_materialSlotsByLodEnabled, azrtti_typeid<decltype(componentInstance->m_materialSlotsByLodEnabled)>(),
inputValue, "materialSlotsByLodEnabled", context));
return context.Report(
result,
result.GetProcessing() != JSR::Processing::Halted ? "Successfully loaded EditorMaterialComponent information."
: "Failed to load EditorMaterialComponent information.");
}
AZ::JsonSerializationResult::Result JsonEditorMaterialComponentSerializer::Store(
rapidjson::Value& outputValue,
const void* inputValue,
const void* defaultValue,
[[maybe_unused]] const AZ::Uuid& valueTypeId,
AZ::JsonSerializerContext& context)
{
namespace JSR = AZ::JsonSerializationResult;
AZ_Assert(
azrtti_typeid<EditorMaterialComponent>() == valueTypeId,
"Unable to Serialize EditorMaterialComponent because the provided type is %s.",
valueTypeId.ToString<AZStd::string>().c_str());
auto componentInstance = reinterpret_cast<const EditorMaterialComponent*>(inputValue);
AZ_Assert(componentInstance, "Input value for JsonEditorMaterialComponentSerializer can't be null.");
auto defaultComponentInstance = reinterpret_cast<const EditorMaterialComponent*>(defaultValue);
JSR::ResultCode result(JSR::Tasks::WriteValue);
{
AZ::ScopedContextPath subPathName(context, "m_id");
const auto componentId = &componentInstance->m_id;
const auto defaultComponentId = defaultComponentInstance ? &defaultComponentInstance->m_id : nullptr;
result.Combine(ContinueStoringToJsonObjectField(
outputValue, "Id", componentId, defaultComponentId, azrtti_typeid<decltype(componentInstance->m_id)>(), context));
}
{
AZ::ScopedContextPath subPathName(context, "Controller");
const auto controller = &componentInstance->m_controller;
const auto defaultController = defaultComponentInstance ? &defaultComponentInstance->m_controller : nullptr;
result.Combine(ContinueStoringToJsonObjectField(
outputValue, "Controller", controller, defaultController, azrtti_typeid<decltype(componentInstance->m_controller)>(),
context));
}
{
AZ::ScopedContextPath subPathName(context, "materialSlotsByLodEnabled");
const auto enabled = &componentInstance->m_materialSlotsByLodEnabled;
const auto defaultEnabled = defaultComponentInstance ? &defaultComponentInstance->m_materialSlotsByLodEnabled : nullptr;
result.Combine(ContinueStoringToJsonObjectField(
outputValue, "materialSlotsByLodEnabled", enabled, defaultEnabled,
azrtti_typeid<decltype(componentInstance->m_materialSlotsByLodEnabled)>(), context));
}
return context.Report(
result,
result.GetProcessing() != JSR::Processing::Halted ? "Successfully stored EditorMaterialComponent information."
: "Failed to store EditorMaterialComponent information.");
}
} // namespace Render
} // namespace AZ
@@ -0,0 +1,41 @@
/*
* 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/Memory/Memory.h>
#include <AzCore/Serialization/Json/BaseJsonSerializer.h>
namespace AZ
{
namespace Render
{
// JsonEditorMaterialComponentSerializer skips serialization of EditorMaterialComponentSlot(s) which are only needed at runtime in
// the editor
class JsonEditorMaterialComponentSerializer : public AZ::BaseJsonSerializer
{
public:
AZ_RTTI(JsonEditorMaterialComponentSerializer, "{D354FE3C-34D2-4E80-B3F9-49450D252336}", BaseJsonSerializer);
AZ_CLASS_ALLOCATOR_DECL;
AZ::JsonSerializationResult::Result Load(
void* outputValue,
const AZ::Uuid& outputValueTypeId,
const rapidjson::Value& inputValue,
AZ::JsonDeserializerContext& context) override;
AZ::JsonSerializationResult::Result Store(
rapidjson::Value& outputValue,
const void* inputValue,
const void* defaultValue,
const AZ::Uuid& valueTypeId,
AZ::JsonSerializerContext& context) override;
};
} // namespace Render
} // namespace AZ
@@ -357,8 +357,7 @@ namespace AZ
void DisplayMapperComponentController::OnConfigChanged()
{
// Register the configuration with the AcesDisplayMapperFeatureProcessor for this scene.
const AZ::RPI::Scene* scene = AZ::RPI::RPISystemInterface::Get()->GetDefaultScene().get();
DisplayMapperFeatureProcessorInterface* fp = scene->GetFeatureProcessor<DisplayMapperFeatureProcessorInterface>();
DisplayMapperFeatureProcessorInterface* fp = AZ::RPI::Scene::GetFeatureProcessorForEntity<DisplayMapperFeatureProcessorInterface>(m_entityId);
DisplayMapperConfigurationDescriptor desc;
desc.m_operationType = m_configuration.m_displayMapperOperation;
desc.m_ldrGradingLutEnabled = m_configuration.m_ldrColorGradingLutEnabled;
@@ -48,7 +48,7 @@ namespace AZ
// CVar for toggling the display of the scene stats
int r_skinnedMeshDisplaySceneStats = 0;
// SceneId to query for the stats
RPI::SceneId m_sceneId = RPI::SceneId::CreateNull();
RPI::SceneId m_sceneId;
};
}// namespace Render
}// namespace AZ
@@ -31,6 +31,8 @@ set(FILES
Source/ImageBasedLights/EditorImageBasedLightComponent.cpp
Source/Material/EditorMaterialComponent.cpp
Source/Material/EditorMaterialComponent.h
Source/Material/EditorMaterialComponentSerializer.cpp
Source/Material/EditorMaterialComponentSerializer.h
Source/Material/EditorMaterialComponentUtil.cpp
Source/Material/EditorMaterialComponentUtil.h
Source/Material/EditorMaterialComponentSlot.cpp
@@ -60,6 +60,7 @@ namespace EMStudio
// Create and register a scene with all available feature processors
AZ::RPI::SceneDescriptor sceneDesc;
sceneDesc.m_nameId = AZ::Name("AnimViewport");
m_scene = AZ::RPI::Scene::CreateScene(sceneDesc);
m_scene->EnableAllFeatureProcessors();
@@ -227,8 +228,7 @@ namespace EMStudio
AZ::TransformBus::Event(m_iblEntity->GetId(), &AZ::TransformBus::Events::SetLocalTM, iblTransform);
const AZ::Matrix4x4 rotationMatrix = AZ::Matrix4x4::CreateIdentity();
AZ::RPI::ScenePtr scene = AZ::RPI::RPISystemInterface::Get()->GetDefaultScene();
auto skyBoxFeatureProcessorInterface = scene->GetFeatureProcessor<AZ::Render::SkyBoxFeatureProcessorInterface>();
auto skyBoxFeatureProcessorInterface = m_scene->GetFeatureProcessor<AZ::Render::SkyBoxFeatureProcessorInterface>();
skyBoxFeatureProcessorInterface->SetCubemapRotationMatrix(rotationMatrix);
}
@@ -217,7 +217,7 @@ namespace AZ
bool m_forceClearRenderData = false;
bool m_initialized = false;
bool m_isEnabled = true;
bool m_usePPLLRenderTechnique = true;
bool m_usePPLLRenderTechnique = false;
static uint32_t s_instanceCount;
HairGlobalSettings m_hairGlobalSettings;
@@ -255,19 +255,22 @@ namespace Blast
BlastFamilyComponentRequestBus::Broadcast(
&BlastFamilyComponentRequests::FillDebugRenderBuffer, buffer, m_debugRenderMode);
// This is a system component, and thus is not associated with a specific scene, so use the default scene
// This is a system component, and thus is not associated with a specific scene, so use the bootstrap scene
// for the debug drawing
const auto defaultScene = AZ::RPI::RPISystemInterface::Get()->GetDefaultScene();
auto drawQueue = AZ::RPI::AuxGeomFeatureProcessorInterface::GetDrawQueueForScene(defaultScene);
for (DebugLine& line : buffer.m_lines)
const auto mainScene = AZ::RPI::RPISystemInterface::Get()->GetSceneByName(AZ::Name("Main"));
if (mainScene)
{
AZ::RPI::AuxGeomDraw::AuxGeomDynamicDrawArguments drawArguments;
drawArguments.m_verts = &line.m_p0;
drawArguments.m_vertCount = 2;
drawArguments.m_colors = &line.m_color;
drawArguments.m_colorCount = 1;
drawQueue->DrawLines(drawArguments);
auto drawQueue = AZ::RPI::AuxGeomFeatureProcessorInterface::GetDrawQueueForScene(mainScene);
for (DebugLine& line : buffer.m_lines)
{
AZ::RPI::AuxGeomDraw::AuxGeomDynamicDrawArguments drawArguments;
drawArguments.m_verts = &line.m_p0;
drawArguments.m_vertCount = 2;
drawArguments.m_colors = &line.m_color;
drawArguments.m_colorCount = 1;
drawQueue->DrawLines(drawArguments);
}
}
}
}
@@ -61,7 +61,6 @@
// Asset types
#include <AzCore/Slice/SliceAsset.h>
#include <AzCore/Script/ScriptAsset.h>
#include <LmbrCentral/Rendering/MaterialAsset.h>
#include <LmbrCentral/Rendering/MeshAsset.h>
#include <LmbrCentral/Rendering/MaterialHandle.h>
@@ -354,7 +353,6 @@ namespace LmbrCentral
// Add asset types and extensions to AssetCatalog. Uses "AssetCatalogService".
if (auto assetCatalog = AZ::Data::AssetCatalogRequestBus::FindFirstHandler(); assetCatalog)
{
assetCatalog->EnableCatalogForAsset(AZ::AzTypeInfo<AZ::ScriptAsset>::Uuid());
assetCatalog->EnableCatalogForAsset(AZ::AzTypeInfo<MaterialAsset>::Uuid());
assetCatalog->EnableCatalogForAsset(AZ::AzTypeInfo<DccMaterialAsset>::Uuid());
assetCatalog->EnableCatalogForAsset(AZ::AzTypeInfo<MeshAsset>::Uuid());
@@ -368,7 +366,6 @@ namespace LmbrCentral
assetCatalog->AddExtension("xml");
assetCatalog->AddExtension("mtl");
assetCatalog->AddExtension("dccmtl");
assetCatalog->AddExtension("lua");
assetCatalog->AddExtension("sprite");
assetCatalog->AddExtension("cax");
}
+2 -1
View File
@@ -52,8 +52,9 @@ ly_add_target(
Gem::TextureAtlas
)
# by default, load the above "Gem::LyShine" module in Client applications:
# by default, load the above "Gem::LyShine" module in Client and Server applications:
ly_create_alias(NAME LyShine.Clients NAMESPACE Gem TARGETS Gem::LyShine)
ly_create_alias(NAME LyShine.Servers NAMESPACE Gem TARGETS Gem::LyShine)
if (PAL_TRAIT_BUILD_HOST_TOOLS)
ly_add_target(
+6 -6
View File
@@ -65,7 +65,7 @@ CDraw2d::~CDraw2d()
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void CDraw2d::OnBootstrapSceneReady([[maybe_unused]] AZ::RPI::Scene* bootstrapScene)
void CDraw2d::OnBootstrapSceneReady(AZ::RPI::Scene* bootstrapScene)
{
// At this point the RPI is ready for use
@@ -74,16 +74,16 @@ void CDraw2d::OnBootstrapSceneReady([[maybe_unused]] AZ::RPI::Scene* bootstrapSc
AZ::Data::Instance<AZ::RPI::Shader> shader = AZ::RPI::LoadCriticalShader(shaderFilepath);
// Set scene to be associated with the dynamic draw context
AZ::RPI::ScenePtr scene;
AZ::RPI::Scene* scene = nullptr;
if (m_viewportContext)
{
// Use scene associated with the specified viewport context
scene = m_viewportContext->GetRenderScene();
scene = m_viewportContext->GetRenderScene().get();
}
else
{
// No viewport context specified, use default scene
scene = AZ::RPI::RPISystemInterface::Get()->GetDefaultScene();
// No viewport context specified, use main scene
scene = bootstrapScene;
}
AZ_Assert(scene != nullptr, "Attempting to create a DynamicDrawContext for a viewport context that has not been associated with a scene yet.");
@@ -113,7 +113,7 @@ void CDraw2d::OnBootstrapSceneReady([[maybe_unused]] AZ::RPI::Scene* bootstrapSc
else
{
// Render target support is disabled
m_dynamicDraw->SetOutputScope(scene.get());
m_dynamicDraw->SetOutputScope(scene);
}
m_dynamicDraw->EndInit();
+2 -2
View File
@@ -653,12 +653,12 @@ void CLyShine::OnRenderTick()
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void CLyShine::OnBootstrapSceneReady([[maybe_unused]] AZ::RPI::Scene* bootstrapScene)
void CLyShine::OnBootstrapSceneReady(AZ::RPI::Scene* bootstrapScene)
{
// Load cursor if its path was set before RPI was initialized
LoadUiCursor();
LyShinePassDataRequestBus::Handler::BusConnect(AZ::RPI::RPISystemInterface::Get()->GetDefaultScene()->GetId());
LyShinePassDataRequestBus::Handler::BusConnect(bootstrapScene->GetId());
}
////////////////////////////////////////////////////////////////////////////////////////////////////
+7 -6
View File
@@ -52,7 +52,7 @@ bool UiRenderer::IsReady()
return m_isRPIReady;
}
void UiRenderer::OnBootstrapSceneReady([[maybe_unused]] AZ::RPI::Scene* bootstrapScene)
void UiRenderer::OnBootstrapSceneReady(AZ::RPI::Scene* bootstrapScene)
{
// At this point the RPI is ready for use
@@ -64,16 +64,17 @@ void UiRenderer::OnBootstrapSceneReady([[maybe_unused]] AZ::RPI::Scene* bootstra
if (m_viewportContext)
{
// Create a new scene based on the user specified viewport context
m_scene = CreateScene(m_viewportContext);
m_ownedScene = CreateScene(m_viewportContext);
m_scene = m_ownedScene.get();
}
else
{
// No viewport context specified, use default scene
m_scene = AZ::RPI::RPISystemInterface::Get()->GetDefaultScene();
m_scene = bootstrapScene;
}
// Create a dynamic draw context for UI Canvas drawing for the scene
m_dynamicDraw = CreateDynamicDrawContext(m_scene, uiShader);
m_dynamicDraw = CreateDynamicDrawContext(uiShader);
if (m_dynamicDraw)
{
@@ -93,6 +94,7 @@ AZ::RPI::ScenePtr UiRenderer::CreateScene(AZStd::shared_ptr<AZ::RPI::ViewportCon
{
// Create a scene with the necessary feature processors
AZ::RPI::SceneDescriptor sceneDesc;
sceneDesc.m_nameId = AZ::Name("UiRenderer");
AZ::RPI::ScenePtr atomScene = AZ::RPI::Scene::CreateScene(sceneDesc);
atomScene->EnableAllFeatureProcessors(); // LYSHINE_ATOM_TODO - have a UI pipeline and enable only needed fps
@@ -116,7 +118,6 @@ AZ::RPI::ScenePtr UiRenderer::CreateScene(AZStd::shared_ptr<AZ::RPI::ViewportCon
}
AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> UiRenderer::CreateDynamicDrawContext(
AZ::RPI::ScenePtr scene,
AZ::Data::Instance<AZ::RPI::Shader> uiShader)
{
// Find the pass that renders the UI canvases after the rtt passes
@@ -144,7 +145,7 @@ AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> UiRenderer::CreateDynamicDrawContext(
else
{
// Render target support is disabled
dynamicDraw->SetOutputScope(m_scene.get());
dynamicDraw->SetOutputScope(m_scene);
}
dynamicDraw->EndInit();
+2 -2
View File
@@ -152,7 +152,6 @@ private: // member functions
//! Create a dynamic draw context for this renderer
AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> CreateDynamicDrawContext(
AZ::RPI::ScenePtr scene,
AZ::Data::Instance<AZ::RPI::Shader> uiShader);
//! Bind the global white texture for all the texture units we use
@@ -175,7 +174,8 @@ protected: // attributes
// Set by user when viewport context is not the main/default viewport
AZStd::shared_ptr<AZ::RPI::ViewportContext> m_viewportContext;
AZ::RPI::ScenePtr m_scene;
AZ::RPI::ScenePtr m_ownedScene;
AZ::RPI::Scene* m_scene = nullptr;
#ifndef _RELEASE
int m_debugTextureDataRecordLevel = 0;
+2
View File
@@ -149,6 +149,8 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS)
AZ::AzFramework
AZ::AzNetworking
AZ::AzToolsFramework
Gem::Atom_RPI.Public
Gem::Atom_RHI.Reflect
Gem::Multiplayer.Static
Gem::Multiplayer.Builders
)
@@ -0,0 +1,32 @@
/*
* 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/ComponentBus.h>
namespace Multiplayer
{
/**
* This bus can be used to send commands to the editor.
*/
class MultiplayerEditorLayerPythonRequests
: public AZ::ComponentBus
{
public:
/*
* Enters the editor game mode and launches/connects to the server launcher.
*/
virtual void EnterGameMode() = 0;
/*
* Queries if the Editor is in game mode, the editor-server has finished connecting, and the default network player has spawned.
*/
virtual bool IsInGameMode() = 0;
};
using MultiplayerEditorLayerPythonRequestBus = AZ::EBus<MultiplayerEditorLayerPythonRequests>;
}
@@ -26,6 +26,7 @@ namespace Multiplayer
using namespace AzNetworking;
AZ_CVAR(bool, editorsv_isDedicated, false, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Whether to init as a server expecting data from an Editor. Do not modify unless you're sure of what you're doing.");
AZ_CVAR(uint16_t, editorsv_port, DefaultServerEditorPort, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port that the multiplayer editor gem will bind to for traffic.");
MultiplayerEditorConnection::MultiplayerEditorConnection()
: m_byteStream(&m_buffer)
@@ -33,33 +34,37 @@ namespace Multiplayer
m_networkEditorInterface = AZ::Interface<INetworking>::Get()->CreateNetworkInterface(
AZ::Name(MpEditorInterfaceName), ProtocolType::Tcp, TrustZone::ExternalClientToServer, *this);
m_networkEditorInterface->SetTimeoutMs(AZ::TimeMs{ 0 }); // Disable timeouts on this network interface
if (editorsv_isDedicated)
ActivateDedicatedEditorServer();
}
void MultiplayerEditorConnection::ActivateDedicatedEditorServer() const
{
if (m_isActivated || !editorsv_isDedicated)
{
uint16_t editorsv_port = DefaultServerEditorPort;
const auto console = AZ::Interface<AZ::IConsole>::Get();
if (console->GetCvarValue("editorsv_port", editorsv_port) != AZ::GetValueResult::Success)
{
AZ_Assert( false,
"MultiplayerEditorConnection failed! Could not find the editorsv_port cvar; we may not be able to connect to the editor's port! Please update this code to use a valid cvar!")
}
return;
}
m_isActivated = true;
AZ_Assert(m_networkEditorInterface, "MP Editor Network Interface was unregistered before Editor Server could start listening.")
AZ_Assert(m_networkEditorInterface, "MP Editor Network Interface was unregistered before Editor Server could start listening.")
// Check if there's already an Editor out there waiting to connect
const ConnectionId editorServerToEditorConnectionId =
m_networkEditorInterface->Connect(IpAddress(LocalHost.data(), editorsv_port, ProtocolType::Tcp));
// Check if there's already an Editor out there waiting to connect
const ConnectionId editorServerToEditorConnectionId = m_networkEditorInterface->Connect(IpAddress(LocalHost.data(), editorsv_port, ProtocolType::Tcp));
// If there wasn't an Editor waiting for this server to start, then assume this is an editor-server launched by hand... listen and wait for the editor to request a connection
if (editorServerToEditorConnectionId == InvalidConnectionId)
{
m_networkEditorInterface->Listen(editorsv_port);
}
else
{
m_networkEditorInterface->SendReliablePacket(editorServerToEditorConnectionId, MultiplayerEditorPackets::EditorServerReadyForLevelData());
}
// If there wasn't an Editor waiting for this server to start, then assume this is an editor-server launched by hand... listen
// and wait for the editor to request a connection
if (editorServerToEditorConnectionId == InvalidConnectionId)
{
m_networkEditorInterface->Listen(editorsv_port);
AZ_Printf("MultiplayerEditorConnection", "Editor-server activation did not find an editor in game-mode willing to connect; we'll instead wait and listen for an editor trying to connect to us.")
}
else
{
m_networkEditorInterface->SendReliablePacket(editorServerToEditorConnectionId, MultiplayerEditorPackets::EditorServerReadyForLevelData());
AZ_Printf("MultiplayerEditorConnection", "Editor-server activation has found and connected to the editor.")
}
}
bool MultiplayerEditorConnection::HandleRequest
(
[[maybe_unused]] AzNetworking::IConnection* connection,
@@ -136,7 +141,7 @@ namespace Multiplayer
networkInterface->Listen(sv_port);
AZLOG_INFO("Editor Server completed asset receive, responding to Editor...");
AZLOG_INFO("Editor Server completed receiving the editor's level assets, responding to Editor...");
return connection->SendReliablePacket(MultiplayerEditorPackets::EditorServerReady());
}
@@ -180,8 +185,14 @@ namespace Multiplayer
}
// Connect the Editor to the editor server for Multiplayer simulation
AZ::Interface<IMultiplayer>::Get()->Connect(editorsv_serveraddr.c_str(), sv_port);
if (AZ::Interface<IMultiplayer>::Get()->Connect(editorsv_serveraddr.c_str(), sv_port))
{
AZ_Printf("MultiplayerEditorConnection", "Editor-server ready. Editor has successfully connected to the editor-server's network simulation.")
}
else
{
AZ_Warning("MultiplayerEditorConnection", false, "MultiplayerEditorConnection::HandleRequest for EditorServerReady failed! Connecting to the editor-server's network simulation failed.")
}
return true;
}
@@ -45,9 +45,11 @@ namespace Multiplayer
//! @}
private:
void ActivateDedicatedEditorServer() const;
AzNetworking::INetworkInterface* m_networkEditorInterface = nullptr;
AZStd::vector<uint8_t> m_buffer;
AZ::IO::ByteContainerStream<AZStd::vector<uint8_t>> m_byteStream;
mutable bool m_isActivated = false;
};
}
@@ -25,6 +25,7 @@ namespace Multiplayer
m_descriptors.end(),
{
MultiplayerEditorSystemComponent::CreateDescriptor(),
PythonEditorFuncs::CreateDescriptor()
});
}
@@ -12,6 +12,7 @@
#include <Multiplayer/MultiplayerConstants.h>
#include <MultiplayerSystemComponent.h>
#include <PythonEditorEventsBus.h>
#include <Editor/MultiplayerEditorSystemComponent.h>
#include <Source/AutoGen/Multiplayer.AutoPackets.h>
@@ -23,6 +24,7 @@
#include <AzCore/Utils/Utils.h>
#include <AzNetworking/Framework/INetworking.h>
#include <AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h>
#include <Atom/RPI.Public/RPISystemInterface.h>
namespace Multiplayer
{
@@ -35,8 +37,47 @@ namespace Multiplayer
AZ_CVAR(AZ::CVarFixedString, editorsv_process, "", nullptr, AZ::ConsoleFunctorFlags::DontReplicate,
"The server executable that should be run. Empty to use the current project's ServerLauncher");
AZ_CVAR(AZ::CVarFixedString, editorsv_serveraddr, AZ::CVarFixedString(LocalHost), nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The address of the server to connect to");
AZ_CVAR(uint16_t, editorsv_port, DefaultServerEditorPort, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port that the multiplayer editor gem will bind to for traffic");
AZ_CVAR(AZ::CVarFixedString, editorsv_rhi_override, "", nullptr, AZ::ConsoleFunctorFlags::DontReplicate,
"Override the default rendering hardware interface (rhi) when launching the Editor server. For example, you may be running an Editor using 'dx12', but want to launch a headless server using 'null'. If empty the server will launch using the same rhi as the Editor.");
AZ_CVAR_EXTERNED(uint16_t, editorsv_port);
//////////////////////////////////////////////////////////////////////////
void PyEnterGameMode()
{
editorsv_enabled = true;
editorsv_launch = true;
AzToolsFramework::EditorLayerPythonRequestBus::Broadcast(&AzToolsFramework::EditorLayerPythonRequestBus::Events::EnterGameMode);
}
bool PyIsInGameMode()
{
// If the network entity manager is tracking at least 1 entity then the editor has connected and the autonomous player exists and is being replicated.
if (const INetworkEntityManager* networkEntityManager = AZ::Interface<INetworkEntityManager>::Get())
{
return networkEntityManager->GetEntityCount() > 0;
}
AZ_Warning("MultiplayerEditorSystemComponent", false, "PyIsInGameMode returning false; NetworkEntityManager has not been created yet.")
return false;
}
void PythonEditorFuncs::Reflect(AZ::ReflectContext* context)
{
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
// This will create static python methods in the 'azlmbr.multiplayer' module
// Note: The methods will be prefixed with the class name, PythonEditorFuncs
// Example Hydra Python: azlmbr.multiplayer.PythonEditorFuncs_enter_game_mode()
behaviorContext->Class<PythonEditorFuncs>()
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Attribute(AZ::Script::Attributes::Module, "multiplayer")
->Method("enter_game_mode", PyEnterGameMode, nullptr, "Enters the editor game mode and launches/connects to the server launcher.")
->Method("is_in_game_mode", PyIsInGameMode, nullptr, "Queries if it's in the game mode and the server has finished connecting and the default network player has spawned.")
;
}
}
void MultiplayerEditorSystemComponent::Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
@@ -44,6 +85,18 @@ namespace Multiplayer
serializeContext->Class<MultiplayerEditorSystemComponent, AZ::Component>()
->Version(1);
}
// Reflect Python Editor Functions
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
// This will add the MultiplayerPythonEditorBus into the 'azlmbr.multiplayer' module
behaviorContext->EBus<MultiplayerEditorLayerPythonRequestBus>("MultiplayerPythonEditorBus")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Attribute(AZ::Script::Attributes::Module, "multiplayer")
->Event("EnterGameMode", &MultiplayerEditorLayerPythonRequestBus::Events::EnterGameMode)
->Event("IsInGameMode", &MultiplayerEditorLayerPythonRequestBus::Events::IsInGameMode)
;
}
}
void MultiplayerEditorSystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
@@ -147,11 +200,21 @@ namespace Multiplayer
// Start the configured server if it's available
AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
// Open the server launcher using the same rhi as the editor (or launch with the override rhi)
AZ::Name server_rhi = AZ::RPI::RPISystemInterface::Get()->GetRenderApiName();
if (!static_cast<AZ::CVarFixedString>(editorsv_rhi_override).empty())
{
server_rhi = static_cast<AZ::CVarFixedString>(editorsv_rhi_override);
}
processLaunchInfo.m_commandlineParameters = AZStd::string::format(
R"("%s" --project-path "%s" --editorsv_isDedicated true --sv_defaultPlayerSpawnAsset "%s")",
R"("%s" --project-path "%s" --editorsv_isDedicated true --sv_defaultPlayerSpawnAsset "%s" --rhi "%s")",
serverPath.c_str(),
AZ::Utils::GetProjectPath().c_str(),
static_cast<AZ::CVarFixedString>(sv_defaultPlayerSpawnAsset).c_str());
static_cast<AZ::CVarFixedString>(sv_defaultPlayerSpawnAsset).c_str(),
server_rhi.GetCStr()
);
processLaunchInfo.m_showWindow = true;
processLaunchInfo.m_processPriority = AzFramework::ProcessPriority::PROCESSPRIORITY_NORMAL;
@@ -159,6 +222,10 @@ namespace Multiplayer
AzFramework::ProcessWatcher* outProcess = AzFramework::ProcessWatcher::LaunchProcess(
processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_NONE);
AZ_Error(
"MultiplayerEditor", processLaunchInfo.m_launchResult != AzFramework::ProcessLauncher::ProcessLaunchResult::PLR_MissingFile,
"LaunchEditorServer failed! The ServerLauncher binary is missing! (%s) Please build server launcher.", serverPath.c_str())
return outProcess;
}
@@ -231,7 +298,7 @@ namespace Multiplayer
"Editor multiplayer game-mode failed! Could not connect to an editor-server. editorsv_launch is false so we're assuming you're running your own editor-server at editorsv_serveraddr(%s) on editorsv_port(%i). "
"Either set editorsv_launch=true so the editor launches an editor-server for you, or launch your own editor-server by hand before entering game-mode. Remember editor-servers must use editorsv_isDedicated=true.",
remoteAddress.c_str(),
static_cast < uint16_t>(editorsv_port))
static_cast<uint16_t>(editorsv_port))
return;
}
@@ -261,6 +328,8 @@ namespace Multiplayer
return;
}
AZ_Printf("MultiplayerEditor", "Editor is sending the editor-server the level data packet.")
const AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& assetData = prefabEditorEntityOwnershipInterface->GetPlayInEditorAssetData();
AZStd::vector<uint8_t> buffer;
@@ -311,4 +380,13 @@ namespace Multiplayer
}
}
void MultiplayerEditorSystemComponent::EnterGameMode()
{
PyEnterGameMode();
}
bool MultiplayerEditorSystemComponent::IsInGameMode()
{
return PyIsInGameMode();
}
}
@@ -10,6 +10,7 @@
#include <Multiplayer/IMultiplayer.h>
#include <Multiplayer/MultiplayerEditorServerBus.h>
#include <Multiplayer/Editor/MultiplayerPythonEditorEventsBus.h>
#include <IEditor.h>
#include <Editor/MultiplayerEditorConnection.h>
@@ -29,9 +30,24 @@ namespace AzNetworking
namespace Multiplayer
{
//! A component to reflect scriptable commands for the Editor
class PythonEditorFuncs : public AZ::Component
{
public:
AZ_COMPONENT(PythonEditorFuncs, "{22AEEA59-94E6-4033-B67D-7C8FBB84DF0D}")
SANDBOX_API static void Reflect(AZ::ReflectContext* context);
// AZ::Component ...
void Activate() override {}
void Deactivate() override {}
};
//! Multiplayer system component wraps the bridging logic between the game and transport layer.
class MultiplayerEditorSystemComponent final
: public AZ::Component
, public MultiplayerEditorLayerPythonRequestBus::Handler
, private AzFramework::GameEntityContextEventBus::Handler
, private AzToolsFramework::EditorEvents::Bus::Handler
, private IEditorNotifyListener
@@ -62,6 +78,12 @@ namespace Multiplayer
void NotifyRegisterViews() override;
//! @}
//! MultiplayerEditorLayerPythonRequestBus::Handler overrides.
//! @{
void EnterGameMode() override;
bool IsInGameMode() override;
//! @}
private:
//! EditorEvents::Handler overrides
//! @{
@@ -719,11 +719,6 @@ namespace Multiplayer
void MultiplayerSystemComponent::OnConnect(AzNetworking::IConnection* connection)
{
MultiplayerAgentDatum datum;
datum.m_id = connection->GetConnectionId();
datum.m_isInvited = false;
datum.m_agentType = MultiplayerAgentType::Client;
AZStd::string providerTicket;
if (connection->GetConnectionRole() == ConnectionRole::Connector)
{
@@ -737,7 +732,12 @@ namespace Multiplayer
}
else
{
AZLOG_INFO("New incoming connection from remote address: %s", connection->GetRemoteAddress().GetString().c_str());
AZLOG_INFO("New incoming connection from remote address: %s", connection->GetRemoteAddress().GetString().c_str())
MultiplayerAgentDatum datum;
datum.m_id = connection->GetConnectionId();
datum.m_isInvited = false;
datum.m_agentType = MultiplayerAgentType::Client;
m_connectionAcquiredEvent.Signal(datum);
}
@@ -771,15 +771,14 @@ namespace Multiplayer
AZLOG_INFO("%s from remote address %s due to %s", endpointString, connection->GetRemoteAddress().GetString().c_str(), reasonString.c_str());
// The client is disconnecting
if (GetAgentType() == MultiplayerAgentType::Client)
if (m_agentType == MultiplayerAgentType::Client)
{
AZ_Assert(connection->GetConnectionRole() == ConnectionRole::Connector, "Client connection role should only ever be Connector");
m_clientDisconnectedEvent.Signal();
}
// Signal to session management that a user has left the server
if (m_agentType == MultiplayerAgentType::DedicatedServer || m_agentType == MultiplayerAgentType::ClientServer)
else if (m_agentType == MultiplayerAgentType::DedicatedServer || m_agentType == MultiplayerAgentType::ClientServer)
{
// Signal to session management that a user has left the server
if (AZ::Interface<AzFramework::ISessionHandlingProviderRequests>::Get() != nullptr &&
connection->GetConnectionRole() == ConnectionRole::Acceptor)
{
@@ -1130,7 +1129,10 @@ namespace Multiplayer
return m_networkEntityManager.GetNetworkEntityTracker()->Get(node->second);
}
PrefabEntityId playerPrefabEntityId(AZ::Name(static_cast<AZ::CVarFixedString>(sv_defaultPlayerSpawnAsset).c_str()));
// make sure the player prefab path is lowercase (how it's stored in the cache folder)
auto sv_defaultPlayerSpawnAssetLowerCase = static_cast<AZ::CVarFixedString>(sv_defaultPlayerSpawnAsset);
AZStd::to_lower(sv_defaultPlayerSpawnAssetLowerCase.begin(), sv_defaultPlayerSpawnAssetLowerCase.end());
PrefabEntityId playerPrefabEntityId(AZ::Name(static_cast<AZ::CVarFixedString>(sv_defaultPlayerSpawnAssetLowerCase).c_str()));
INetworkEntityManager::EntityList entityList = m_networkEntityManager.CreateEntitiesImmediate(playerPrefabEntityId, NetEntityRole::Authority, AZ::Transform::CreateIdentity(), Multiplayer::AutoActivate::DoNotActivate);
for (NetworkEntityHandle subEntity : entityList)
@@ -31,8 +31,10 @@ namespace Multiplayer
mpTools->SetDidProcessNetworkPrefabs(false);
}
context.ListPrefabs([&context](AZStd::string_view prefabName, PrefabDom& prefab) {
ProcessPrefab(context, prefabName, prefab);
AZ::DataStream::StreamType serializationFormat = GetAzSerializationFormat();
context.ListPrefabs([&context, serializationFormat](AZStd::string_view prefabName, PrefabDom& prefab) {
ProcessPrefab(context, prefabName, prefab, serializationFormat);
});
if (mpTools && !context.GetProcessedObjects().empty())
@@ -45,7 +47,15 @@ namespace Multiplayer
{
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context); serializeContext != nullptr)
{
serializeContext->Class<NetworkPrefabProcessor, PrefabProcessor>()->Version(2);
serializeContext->Enum<SerializationFormats>()
->Value("Binary", SerializationFormats::Binary)
->Value("Text", SerializationFormats::Text)
;
serializeContext->Class<NetworkPrefabProcessor, PrefabProcessor>()
->Version(3)
->Field("SerializationFormat", &NetworkPrefabProcessor::m_serializationFormat)
;
}
}
@@ -93,7 +103,7 @@ namespace Multiplayer
});
}
void NetworkPrefabProcessor::ProcessPrefab(PrefabProcessorContext& context, AZStd::string_view prefabName, PrefabDom& prefab)
void NetworkPrefabProcessor::ProcessPrefab(PrefabProcessorContext& context, AZStd::string_view prefabName, PrefabDom& prefab, AZ::DataStream::StreamType serializationFormat)
{
using namespace AzToolsFramework::Prefab;
@@ -107,10 +117,10 @@ namespace Multiplayer
AZStd::string uniqueName = prefabName;
uniqueName += ".network.spawnable";
auto serializer = [](AZStd::vector<uint8_t>& output, const ProcessedObjectStore& object) -> bool {
auto serializer = [serializationFormat](AZStd::vector<uint8_t>& output, const ProcessedObjectStore& object) -> bool {
AZ::IO::ByteContainerStream stream(&output);
auto& asset = object.GetAsset();
return AZ::Utils::SaveObjectToStream(stream, AZ::DataStream::ST_BINARY, &asset, asset.GetType());
return AZ::Utils::SaveObjectToStream(stream, serializationFormat, &asset, asset.GetType());
};
auto&& [object, networkSpawnable] =
@@ -178,4 +188,14 @@ namespace Multiplayer
context.GetProcessedObjects().push_back(AZStd::move(object));
}
AZ::DataStream::StreamType NetworkPrefabProcessor::GetAzSerializationFormat() const
{
if (m_serializationFormat == SerializationFormats::Text)
{
return AZ::DataStream::StreamType::ST_JSON;
}
return AZ::DataStream::StreamType::ST_BINARY;
}
}
@@ -9,6 +9,7 @@
#pragma once
#include <AzToolsFramework/Prefab/Spawnable/PrefabProcessor.h>
#include <AzCore/Serialization/ObjectStream.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
@@ -33,7 +34,23 @@ namespace Multiplayer
static void Reflect(AZ::ReflectContext* context);
//! The format the network spawnables are going to be stored in.
enum class SerializationFormats
{
Binary, //!< Binary is generally preferable for performance.
Text //!< Store in text format which is usually slower but helps with debugging.
};
AZ::DataStream::StreamType GetAzSerializationFormat() const;
protected:
static void ProcessPrefab(PrefabProcessorContext& context, AZStd::string_view prefabName, PrefabDom& prefab);
static void ProcessPrefab(PrefabProcessorContext& context, AZStd::string_view prefabName, PrefabDom& prefab, AZ::DataStream::StreamType serializationFormat);
SerializationFormats m_serializationFormat = SerializationFormats::Binary;
};
}
namespace AZ
{
AZ_TYPE_INFO_SPECIALIZE(Multiplayer::NetworkPrefabProcessor::SerializationFormats, "{F69B49EB-9D67-4D9C-99E7-DFA35D4ACCD2}");
}
@@ -13,4 +13,5 @@ set(FILES
Source/Editor/MultiplayerEditorGem.h
Source/Editor/MultiplayerEditorSystemComponent.cpp
Source/Editor/MultiplayerEditorSystemComponent.h
Include/Multiplayer/Editor/MultiplayerPythonEditorEventsBus.h
)
@@ -18,8 +18,14 @@
"GameObjectCreation":
[
{ "$type": "AzToolsFramework::Prefab::PrefabConversionUtils::EditorInfoRemover" },
{ "$type": "Multiplayer::NetworkPrefabProcessor" },
{ "$type": "AzToolsFramework::Prefab::PrefabConversionUtils::PrefabCatchmentProcessor" }
{
"$type": "Multiplayer::NetworkPrefabProcessor",
"SerializationFormat": "Binary" // Options are "Binary" (default) or "Text". Prefer "Binary" for performance.
},
{
"$type": "AzToolsFramework::Prefab::PrefabConversionUtils::PrefabCatchmentProcessor",
"SerializationFormat": "Binary" // Options are "Binary" (default) or "Text". Prefer "Binary" for performance.
}
]
}
}
+4 -1
View File
@@ -1175,7 +1175,10 @@ namespace PhysX
using physx::PxGeometryType;
bool isProfilingActive = false;
AZ::Debug::ProfilerRequestBus::BroadcastResult(isProfilingActive, &AZ::Debug::ProfilerRequests::IsActive);
if (auto profilerSystem = AZ::Debug::ProfilerSystemInterface::Get(); profilerSystem)
{
isProfilingActive = profilerSystem->IsActive();
}
if (!isProfilingActive)
{
@@ -1,59 +0,0 @@
/*
* 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 <AzCore/Interface/Interface.h>
#include <AzCore/std/string/string.h>
namespace Profiler
{
class ProfilerRequests
{
public:
AZ_RTTI(ProfilerRequests, "{3757c4e5-1941-457c-85ae-16305e17a4c6}");
virtual ~ProfilerRequests() = default;
//! Enable/Disable the CpuProfiler
virtual void SetProfilerEnabled(bool enabled) = 0;
//! Dump a single frame of Cpu profiling data
virtual bool CaptureCpuProfilingStatistics(const AZStd::string& outputFilePath) = 0;
//! Start a multiframe capture of CPU profiling data.
virtual bool BeginContinuousCpuProfilingCapture() = 0;
//! End and dump an in-progress continuous capture.
virtual bool EndContinuousCpuProfilingCapture(const AZStd::string& outputFilePath) = 0;
};
class ProfilerBusTraits
: public AZ::EBusTraits
{
public:
// EBusTraits overrides
static constexpr AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static constexpr AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
};
class ProfilerNotifications
: public AZ::EBusTraits
{
public:
virtual ~ProfilerNotifications() = default;
//! Notify when the current CpuProfilingStatistics capture is finished
//! @param result Set to true if it's finished successfully
//! @param info The output file path or error information which depends on the return.
virtual void OnCaptureCpuProfilingStatisticsFinished(bool result, const AZStd::string& info) = 0;
};
using ProfilerInterface = AZ::Interface<ProfilerRequests>;
using ProfilerRequestBus = AZ::EBus<ProfilerRequests, ProfilerBusTraits>;
using ProfilerNotificationBus = AZ::EBus<ProfilerNotifications>;
} // namespace Profiler
+20 -26
View File
@@ -10,9 +10,9 @@
#include <ImGuiCpuProfiler.h>
#include <Profiler/ProfilerBus.h>
#include <CpuProfilerImpl.h>
#include <AzCore/Debug/ProfilerBus.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/JSON/filereadstream.h>
#include <AzCore/Outcome/Outcome.h>
@@ -26,8 +26,6 @@
namespace Profiler
{
static constexpr const char* defaultSaveLocation = "@user@/Profiler";
namespace CpuProfilerImGuiHelper
{
float TicksToMs(double ticks)
@@ -156,16 +154,7 @@ namespace Profiler
if (m_captureToFile)
{
AZStd::string timeString;
AZStd::to_string(timeString, AZStd::GetTimeNowSecond());
const AZStd::string frameDataFilePath = AZStd::string::format("%s/cpu_single_%s.json", defaultSaveLocation, timeString.c_str());
char resolvedPath[AZ::IO::MaxPathLength];
AZ::IO::FileIOBase::GetInstance()->ResolvePath(frameDataFilePath.c_str(), resolvedPath, AZ::IO::MaxPathLength);
m_lastCapturedFilePath = resolvedPath;
ProfilerRequestBus::Broadcast(&ProfilerRequestBus::Events::CaptureCpuProfilingStatistics, frameDataFilePath);
AZ::Debug::ProfilerSystemInterface::Get()->CaptureFrame(GenerateOutputFile("single"));
}
m_captureToFile = false;
@@ -206,24 +195,15 @@ namespace Profiler
bool isInProgress = CpuProfiler::Get()->IsContinuousCaptureInProgress();
if (ImGui::Button(isInProgress ? "End" : "Begin"))
{
auto profilerSystem = AZ::Debug::ProfilerSystemInterface::Get();
if (isInProgress)
{
AZStd::string timeString;
AZStd::to_string(timeString, AZStd::GetTimeNowSecond());
const AZStd::string frameDataFilePath = AZStd::string::format("%s/cpu_multi_%s.json", defaultSaveLocation, timeString.c_str());
char resolvedPath[AZ::IO::MaxPathLength];
AZ::IO::FileIOBase::GetInstance()->ResolvePath(frameDataFilePath.c_str(), resolvedPath, AZ::IO::MaxPathLength);
m_lastCapturedFilePath = resolvedPath;
ProfilerRequestBus::Broadcast(&ProfilerRequestBus::Events::EndContinuousCpuProfilingCapture, frameDataFilePath);
profilerSystem->EndCapture();
m_paused = true;
}
else
{
ProfilerRequestBus::Broadcast(&ProfilerRequestBus::Events::BeginContinuousCpuProfilingCapture);
profilerSystem->StartCapture(GenerateOutputFile("multi"));
}
}
@@ -235,8 +215,10 @@ namespace Profiler
// Only update the cached file list when opened so that we aren't making IO calls on every frame.
m_cachedCapturePaths.clear();
AZ::IO::FixedMaxPathString captureOutput = AZ::Debug::GetProfilerCaptureLocation();
auto* base = AZ::IO::FileIOBase::GetInstance();
base->FindFiles(defaultSaveLocation, "*.json",
base->FindFiles(captureOutput.c_str(), "*.json",
[&paths = m_cachedCapturePaths](const char* path) -> bool
{
auto foundPath = AZ::IO::Path(path);
@@ -418,6 +400,18 @@ namespace Profiler
ImGui::End();
}
AZStd::string ImGuiCpuProfiler::GenerateOutputFile(const char* nameHint)
{
AZ::IO::FixedMaxPathString captureOutput = AZ::Debug::GetProfilerCaptureLocation();
const AZ::IO::FixedMaxPathString frameDataFilePath =
AZ::IO::FixedMaxPathString::format("%s/cpu_%s_%lld.json", captureOutput.c_str(), nameHint, AZStd::GetTimeNowSecond());
AZ::IO::FileIOBase::GetInstance()->ResolvePath(m_lastCapturedFilePath, frameDataFilePath.c_str());
return m_lastCapturedFilePath.String();
}
void ImGuiCpuProfiler::LoadFile()
{
const AZ::IO::Path& pathToLoad = m_cachedCapturePaths[m_currentFileIndex];
+4 -1
View File
@@ -107,6 +107,9 @@ namespace Profiler
//! Draws the statistical view of the CPU profiling data.
void DrawStatisticsView();
//! Generates the full output timestamped file path based on nameHint
AZStd::string GenerateOutputFile(const char* nameHint);
//! Callback invoked when the "Load File" button is pressed in the file picker.
void LoadFile();
@@ -214,7 +217,7 @@ namespace Profiler
AZStd::vector<CpuTimingEntry> m_cpuTimingStatisticsWhenPause;
AZStd::sys_time_t m_frameToFrameTime{};
AZStd::string m_lastCapturedFilePath;
AZ::IO::FixedMaxPath m_lastCapturedFilePath;
bool m_showFilePicker = false;
@@ -51,32 +51,6 @@ namespace Profiler
int m_framesLeft{ 0 };
};
class ProfilerNotificationBusHandler final
: public ProfilerNotificationBus::Handler
, public AZ::BehaviorEBusHandler
{
public:
AZ_EBUS_BEHAVIOR_BINDER(ProfilerNotificationBusHandler, "{44161459-B816-4876-95A4-BA16DEC767D6}", AZ::SystemAllocator,
OnCaptureCpuProfilingStatisticsFinished
);
void OnCaptureCpuProfilingStatisticsFinished(bool result, const AZStd::string& info) override
{
Call(FN_OnCaptureCpuProfilingStatisticsFinished, result, info);
}
static void Reflect(AZ::ReflectContext* context)
{
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->EBus<ProfilerNotificationBus>("ProfilerNotificationBus")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Attribute(AZ::Script::Attributes::Module, "profiler")
->Handler<ProfilerNotificationBusHandler>();
}
}
};
bool SerializeCpuProfilingData(const AZStd::ring_buffer<CpuProfiler::TimeRegionMap>& data, AZStd::string outputFilePath, bool wasEnabled)
{
AZ_TracePrintf("ProfilerSystemComponent", "Beginning serialization of %zu frames of profiling data\n", data.size());
@@ -107,8 +81,8 @@ namespace Profiler
CpuProfiler::Get()->SetProfilerEnabled(false);
}
// Notify listeners that the pass' PipelineStatistics queries capture has finished.
ProfilerNotificationBus::Broadcast(&ProfilerNotificationBus::Events::OnCaptureCpuProfilingStatisticsFinished,
// Notify listeners that the profiler capture has finished.
AZ::Debug::ProfilerNotificationBus::Broadcast(&AZ::Debug::ProfilerNotificationBus::Events::OnCaptureFinished,
saveResult.IsSuccess(),
captureInfo);
@@ -128,21 +102,9 @@ namespace Profiler
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("System"))
->Attribute(AZ::Edit::Attributes::AutoExpand, true);
ProfilerNotificationBusHandler::Reflect(context);
}
}
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->EBus<ProfilerRequestBus>("ProfilerRequestBus")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Attribute(AZ::Script::Attributes::Module, "profiler")
->Event("CaptureCpuProfilingStatistics", &ProfilerRequestBus::Events::CaptureCpuProfilingStatistics);
ProfilerNotificationBusHandler::Reflect(context);
}
CpuProfilingStatisticsSerializer::Reflect(context);
}
@@ -166,24 +128,22 @@ namespace Profiler
ProfilerSystemComponent::ProfilerSystemComponent()
{
if (ProfilerInterface::Get() == nullptr)
if (AZ::Debug::ProfilerSystemInterface::Get() == nullptr)
{
ProfilerInterface::Register(this);
AZ::Debug::ProfilerSystemInterface::Register(this);
}
}
ProfilerSystemComponent::~ProfilerSystemComponent()
{
if (ProfilerInterface::Get() == this)
if (AZ::Debug::ProfilerSystemInterface::Get() == this)
{
ProfilerInterface::Unregister(this);
AZ::Debug::ProfilerSystemInterface::Unregister(this);
}
}
void ProfilerSystemComponent::Activate()
{
ProfilerRequestBus::Handler::BusConnect();
m_cpuProfiler.Init();
}
@@ -191,8 +151,6 @@ namespace Profiler
{
m_cpuProfiler.Shutdown();
ProfilerRequestBus::Handler::BusDisconnect();
// Block deactivation until the IO thread has finished serializing the CPU data
if (m_cpuDataSerializationThread.joinable())
{
@@ -200,12 +158,17 @@ namespace Profiler
}
}
void ProfilerSystemComponent::SetProfilerEnabled(bool enabled)
bool ProfilerSystemComponent::IsActive() const
{
return m_cpuProfiler.IsProfilerEnabled();
}
void ProfilerSystemComponent::SetActive(bool enabled)
{
m_cpuProfiler.SetProfilerEnabled(enabled);
}
bool ProfilerSystemComponent::CaptureCpuProfilingStatistics(const AZStd::string& outputFilePath)
bool ProfilerSystemComponent::CaptureFrame(const AZStd::string& outputFilePath)
{
bool expected = false;
if (!m_cpuCaptureInProgress.compare_exchange_strong(expected, true))
@@ -236,12 +199,13 @@ namespace Profiler
return true;
}
bool ProfilerSystemComponent::BeginContinuousCpuProfilingCapture()
bool ProfilerSystemComponent::StartCapture(AZStd::string outputFilePath)
{
m_captureFile = AZStd::move(outputFilePath);
return m_cpuProfiler.BeginContinuousCapture();
}
bool ProfilerSystemComponent::EndContinuousCpuProfilingCapture(const AZStd::string& outputFilePath)
bool ProfilerSystemComponent::EndCapture()
{
bool expected = false;
if (!m_cpuDataSerializationInProgress.compare_exchange_strong(expected, true))
@@ -263,7 +227,7 @@ namespace Profiler
// cpuProfilingData could be 1GB+ once saved, so use an IO thread to write it to disk.
auto threadIoFunction =
[data = AZStd::move(captureResult), filePath = AZStd::string(outputFilePath), &flag = m_cpuDataSerializationInProgress]()
[data = AZStd::move(captureResult), filePath = m_captureFile, &flag = m_cpuDataSerializationInProgress]()
{
SerializeCpuProfilingData(data, filePath, true);
flag.store(false);
@@ -8,17 +8,17 @@
#pragma once
#include <Profiler/ProfilerBus.h>
#include <CpuProfilerImpl.h>
#include <AzCore/Component/Component.h>
#include <AzCore/Debug/ProfilerBus.h>
#include <AzCore/std/parallel/thread.h>
namespace Profiler
{
class ProfilerSystemComponent
: public AZ::Component
, protected ProfilerRequestBus::Handler
, protected AZ::Debug::ProfilerRequests
{
public:
AZ_COMPONENT(ProfilerSystemComponent, "{3f52c1d7-d920-4781-8ed7-88077ec4f305}");
@@ -38,11 +38,12 @@ namespace Profiler
void Activate() override;
void Deactivate() override;
// ProfilerRequestBus interface implementation
void SetProfilerEnabled(bool enabled) override;
bool CaptureCpuProfilingStatistics(const AZStd::string& outputFilePath) override;
bool BeginContinuousCpuProfilingCapture() override;
bool EndContinuousCpuProfilingCapture(const AZStd::string& outputFilePath) override;
// ProfilerRequests interface implementation
bool IsActive() const override;
void SetActive(bool active) override;
bool CaptureFrame(const AZStd::string& outputFilePath) override;
bool StartCapture(AZStd::string outputFilePath) override;
bool EndCapture() override;
AZStd::thread m_cpuDataSerializationThread;
@@ -51,6 +52,7 @@ namespace Profiler
AZStd::atomic_bool m_cpuCaptureInProgress{ false };
CpuProfilerImpl m_cpuProfiler;
AZStd::string m_captureFile;
};
} // namespace Profiler
-1
View File
@@ -7,7 +7,6 @@
#
set(FILES
Include/Profiler/ProfilerBus.h
Include/Profiler/ProfilerImGuiBus.h
Source/CpuProfiler.h
Source/CpuProfilerImpl.cpp
@@ -27,7 +27,7 @@ def get_editor_main_window():
return editor_main_window
# Helper method for registering a Python widget as a tool/view pane with the Editor
def register_view_pane(name, widget_type, options=editor.ViewPaneOptions()):
def register_view_pane(name, widget_type, category="Tools", options=editor.ViewPaneOptions()):
global view_pane_handlers
# The view pane names are unique in the Editor, so make sure one with the same name doesn't exist already
@@ -45,10 +45,10 @@ def register_view_pane(name, widget_type, options=editor.ViewPaneOptions()):
return new_widget.winId()
def on_notify_register_views(parameters, my_name=name, my_options=options):
def on_notify_register_views(parameters, my_name=name, my_category=category, my_options=options):
# Register our widget as an Editor view pane
print('Calling on_notify_register_views RegisterCustomViewPane')
editor.EditorRequestBus(azlmbr.bus.Broadcast, 'RegisterCustomViewPane', my_name, 'Tools', my_options)
editor.EditorRequestBus(azlmbr.bus.Broadcast, 'RegisterCustomViewPane', my_name, my_category, my_options)
# We keep a handler around in case a request for registering custom view panes comes later
print('Initializing callback for RegisterCustomViewPane')
@@ -57,7 +57,7 @@ def register_view_pane(name, widget_type, options=editor.ViewPaneOptions()):
registration_handler.add_callback("NotifyRegisterViews", on_notify_register_views)
global registration_handlers
registration_handlers[name] = registration_handler
editor.EditorRequestBus(azlmbr.bus.Broadcast, 'RegisterCustomViewPane', name, 'Tools', options)
editor.EditorRequestBus(azlmbr.bus.Broadcast, 'RegisterCustomViewPane', name, category, options)
# Connect to the ViewPaneCallbackBus in order to respond to requests to create our widget
# We also need to store our handler so it will exist for the life of the Editor
@@ -23,11 +23,9 @@ struct VSOutput
{
float4 m_position : SV_Position;
float3 m_normal: NORMAL;
float3 m_tangent : TANGENT;
float3 m_bitangent : BITANGENT;
float3 m_worldPosition : UV0;
float3 m_shadowCoords[ViewSrg::MaxCascadeCount] : UV2;
float2 m_uv : UV1;
float3 m_shadowCoords[ViewSrg::MaxCascadeCount] : UV2;
};
option bool o_debugDetailMaterialIds = false;
@@ -50,9 +48,9 @@ VSOutput TerrainPBR_MainPassVS(VertexInput IN)
float down = GetHeight(origUv + terrainData.m_uvStep * float2( 0.0f, 1.0f));
float left = GetHeight(origUv + terrainData.m_uvStep * float2(-1.0f, 0.0f));
OUT.m_bitangent = normalize(float3(0.0, terrainData.m_sampleSpacing * 2.0f, down - up));
OUT.m_tangent = normalize(float3(terrainData.m_sampleSpacing * 2.0f, 0.0, right - left));
OUT.m_normal = cross(OUT.m_tangent, OUT.m_bitangent);
float3 bitangent = normalize(float3(0.0, terrainData.m_sampleSpacing * 2.0f, down - up));
float3 tangent = normalize(float3(terrainData.m_sampleSpacing * 2.0f, 0.0, right - left));
OUT.m_normal = normalize(cross(tangent, bitangent));
OUT.m_uv = uv;
// directional light shadow
@@ -78,18 +76,14 @@ ForwardPassOutput TerrainPBR_MainPassPS(VSOutput IN)
surface.position = IN.m_worldPosition.xyz;
float viewDistance = length(ViewSrg::m_worldPosition - surface.position);
float detailFactor = saturate((viewDistance - TerrainMaterialSrg::m_detailFadeDistance) / max(TerrainMaterialSrg::m_detailFadeLength, EPSILON));
ObjectSrg::TerrainData terrainData = ObjectSrg::m_terrainData;
float2 origUv = lerp(terrainData.m_uvMin, terrainData.m_uvMax, IN.m_uv);
origUv.y = 1.0 - origUv.y;
float2 detailUv = IN.m_uv * TerrainMaterialSrg::m_detailTextureMultiplier;
// ------- Normal -------
float3 macroNormal = IN.m_normal;
float3 macroNormal = normalize(IN.m_normal);
// ------- Macro Color / Normal -------
float3 macroColor = TerrainMaterialSrg::m_baseColor.rgb;
[unroll] for (uint i = 0; i < 4; ++i)
[unroll] for (uint i = 0; i < 4 && (i < ObjectSrg::m_macroMaterialCount); ++i)
{
float2 macroUvMin = ObjectSrg::m_macroMaterialData[i].m_uvMin;
float2 macroUvMax = ObjectSrg::m_macroMaterialData[i].m_uvMax;
@@ -248,6 +248,7 @@ namespace Terrain
{
m_configuration.m_macroColorAsset = asset;
m_colorImage = AZ::RPI::StreamingImage::FindOrCreate(m_configuration.m_macroColorAsset);
m_colorImage->GetRHIImage()->SetName(AZ::Name(m_configuration.m_macroColorAsset.GetHint()));
// Clear the texture asset reference to make sure we don't prevent hot-reloading.
m_configuration.m_macroColorAsset.Release();
@@ -256,6 +257,7 @@ namespace Terrain
{
m_configuration.m_macroNormalAsset = asset;
m_normalImage = AZ::RPI::StreamingImage::FindOrCreate(m_configuration.m_macroNormalAsset);
m_normalImage->GetRHIImage()->SetName(AZ::Name(m_configuration.m_macroNormalAsset.GetHint()));
// Clear the texture asset reference to make sure we don't prevent hot-reloading.
m_configuration.m_macroColorAsset.Release();
@@ -1149,7 +1149,9 @@ namespace Terrain
sectorData.m_srg->SetConstant(m_terrainDataIndex, terrainDataForSrg);
AZStd::array<ShaderMacroMaterialData, MaxMaterialsPerSector> macroMaterialData;
for (uint32_t i = 0; i < sectorData.m_macroMaterials.size(); ++i)
uint32_t i = 0;
for (; i < sectorData.m_macroMaterials.size(); ++i)
{
const MacroMaterialData& materialData = m_macroMaterials.GetData(sectorData.m_macroMaterials.at(i));
ShaderMacroMaterialData& shaderData = macroMaterialData.at(i);
@@ -1178,6 +1180,11 @@ namespace Terrain
// set flags for which images are used.
shaderData.m_mapsInUse = (colorImageView ? ColorImageUsed : 0) | (normalImageView ? NormalImageUsed : 0);
}
for (; i < sectorData.m_macroMaterials.capacity(); ++i)
{
sectorData.m_srg->SetImageView(m_macroColorMapIndex, nullptr, i);
sectorData.m_srg->SetImageView(m_macroNormalMapIndex, nullptr, i);
}
sectorData.m_srg->SetConstantArray(m_macroMaterialDataIndex, macroMaterialData);
sectorData.m_srg->SetConstant(m_macroMaterialCountIndex, aznumeric_cast<uint32_t>(sectorData.m_macroMaterials.size()));
@@ -71,18 +71,18 @@ namespace Terrain
struct ShaderTerrainData // Must align with struct in Object Srg
{
AZStd::array<float, 2> m_uvMin;
AZStd::array<float, 2> m_uvMax;
AZStd::array<float, 2> m_uvStep;
float m_sampleSpacing;
float m_heightScale;
AZStd::array<float, 2> m_uvMin{ 0.0f, 0.0f };
AZStd::array<float, 2> m_uvMax{ 1.0f, 1.0f };
AZStd::array<float, 2> m_uvStep{ 1.0f, 1.0f };
float m_sampleSpacing{ 1.0f };
float m_heightScale{ 1.0f };
};
struct ShaderMacroMaterialData
struct ShaderMacroMaterialData // Must align with struct in Object Srg
{
AZStd::array<float, 2> m_uvMin;
AZStd::array<float, 2> m_uvMax;
float m_normalFactor;
AZStd::array<float, 2> m_uvMin{ 0.0f, 0.0f };
AZStd::array<float, 2> m_uvMax{ 1.0f, 1.0f };
float m_normalFactor{ 0.0f };
uint32_t m_flipNormalX{ 0 }; // bool in shader
uint32_t m_flipNormalY{ 0 }; // bool in shader
uint32_t m_mapsInUse{ 0b00 }; // 0b01 = color, 0b10 = normal