Files
o3de/Gems/Atom/RPI/Code/Source/RPI.Public/RPISystem.cpp
T
Chris Santora 982406d4d5 Fixed race conditions that could prevent successful hot-reload of shaders.
ATOM-15728 Shader Hot Reload Fails in Debug Build

The main change was to add OnAssetReady handlers to each of the asset classes. See comments in ShaderAsset::OnAssetReady for a detailed explanation. In short, OnAssetReloaded gets missed while assets are being reloaded at the same time on multiple threads, but OnAssetReady is always called whenever connecting to the AssetBus because of its AssetConnectionPolicy.

The above change required the addition of a new AssetInitBus to call the PostLoadInit() functions. Because OnAssetReady connects to buses that are not mutex-protected, they have to be connected on the main thread. AssetInitBus::PostLoadInit is called every frame in RPISystem::SimulationTick. All Atom's asset handlers that need to do post-load initialization must connect to the AssetInitBus, and the asset will disconnect itself after initialization is complete.

We also need the Shader class to handle OnShaderAssetReinitialized to properly handle the shader reload.

With these changes I can click back and forth between "Blending On" and "Blending Off" many times (like 20 times) without issue.
2021-06-14 20:36:12 -07:00

419 lines
15 KiB
C++

/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <Atom/RPI.Public/RPISystem.h>
#include <Atom/RPI.Reflect/Asset/AssetReference.h>
#include <Atom/RPI.Reflect/Asset/AssetHandler.h>
#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
#include <Atom/RPI.Reflect/ResourcePoolAsset.h>
#include <Atom/RPI.Reflect/Shader/ShaderAsset.h>
#include <Atom/RPI.Reflect/System/AnyAsset.h>
#include <Atom/RPI.Reflect/System/AssetAliases.h>
#include <Atom/RPI.Reflect/System/PipelineRenderSettings.h>
#include <Atom/RPI.Reflect/System/RenderPipelineDescriptor.h>
#include <Atom/RPI.Reflect/Asset/AssetUtils.h>
#include <Atom/RPI.Public/AssetInitBus.h>
#include <Atom/RPI.Public/FeatureProcessor.h>
#include <Atom/RPI.Public/GpuQuery/GpuQueryTypes.h>
#include <Atom/RPI.Public/Scene.h>
#include <Atom/RPI.Public/RenderPipeline.h>
#include <Atom/RPI.Public/View.h>
#include <Atom/RPI.Public/Pass/PassFactory.h>
#include <Atom/RHI/Factory.h>
#include <Atom/RHI/Device.h>
#include <Atom/RHI.Reflect/PlatformLimitsDescriptor.h>
#include <AzCore/Debug/EventTrace.h>
#include <AzCore/Interface/Interface.h>
#include <AzFramework/Asset/AssetSystemBus.h>
// This will cause the RPI System to print out global state (like the current pass hierarchy) when an assert is hit
// This is useful for rendering engineers debugging a crash in the RPI/RHI layers
#define AZ_RPI_PRINT_GLOBAL_STATE_ON_ASSERT 0
namespace AZ
{
namespace RPI
{
RPISystemInterface* RPISystemInterface::Get()
{
return Interface<RPISystemInterface>::Get();
}
void RPISystem::Reflect(ReflectContext* context)
{
AssetReference::Reflect(context);
BufferSystem::Reflect(context);
ImageSystem::Reflect(context);
MaterialSystem::Reflect(context);
ModelSystem::Reflect(context);
ShaderSystem::Reflect(context);
PassSystem::Reflect(context);
ResourcePoolAsset::Reflect(context);
SceneDescriptor::Reflect(context);
PipelineRenderSettings::Reflect(context);
RenderPipelineDescriptor::Reflect(context);
AssetAliases::Reflect(context);
RPISystemDescriptor::Reflect(context);
GpuQuerySystemDescriptor::Reflect(context);
ShaderMetricsSystem::Reflect(context);
PipelineStatisticsResult::Reflect(context);
}
void RPISystem::Initialize(const RPISystemDescriptor& rpiSystemDescriptor)
{
m_rhiSystem.InitDevice();
// Gather asset handlers from sub-systems.
ImageSystem::GetAssetHandlers(m_assetHandlers);
BufferSystem::GetAssetHandlers(m_assetHandlers);
MaterialSystem::GetAssetHandlers(m_assetHandlers);
ModelSystem::GetAssetHandlers(m_assetHandlers);
PassSystem::GetAssetHandlers(m_assetHandlers);
ShaderSystem::GetAssetHandlers(m_assetHandlers);
m_assetHandlers.emplace_back(MakeAssetHandler<ResourcePoolAssetHandler>());
m_assetHandlers.emplace_back(MakeAssetHandler<AnyAssetHandler>());
m_materialSystem.Init();
m_modelSystem.Init();
m_shaderSystem.Init();
m_shaderMetricsSystem.Init();
m_passSystem.Init();
m_featureProcessorFactory.Init();
m_querySystem.Init(m_descriptor.m_gpuQuerySystemDescriptor);
Interface<RPISystemInterface>::Register(this);
SystemTickBus::Handler::BusConnect();
#if AZ_RPI_PRINT_GLOBAL_STATE_ON_ASSERT
Debug::TraceMessageBus::Handler::BusConnect();
#endif
m_descriptor = rpiSystemDescriptor;
}
void RPISystem::Shutdown()
{
m_viewportContextManager.Shutdown();
m_viewSrgAsset.Reset();
m_sceneSrgAsset.Reset();
#if AZ_RPI_PRINT_GLOBAL_STATE_ON_ASSERT
Debug::TraceMessageBus::Handler::BusDisconnect();
#endif
SystemTickBus::Handler::BusDisconnect();
Interface<RPISystemInterface>::Unregister(this);
m_featureProcessorFactory.Shutdown();
m_passSystem.Shutdown();
m_dynamicDraw.Shutdown();
m_bufferSystem.Shutdown();
m_materialSystem.Shutdown();
m_modelSystem.Shutdown();
m_shaderSystem.Shutdown();
m_shaderMetricsSystem.Shutdown();
m_imageSystem.Shutdown();
m_querySystem.Shutdown();
m_rhiSystem.Shutdown();
/**
* [LY-86745] We need to pump the asset manager queue here, because
* it uses AZStd::function<> with vtable pointers embedded from this DLL.
* If we allow the DLL to shutdown with queued events, they will be pumped
* later by the asset manager component, which will then reference garbage
* vtable pointers.
*
* Note that it's necessary to pump before *and* after we clear the handlers,
* since the handler clear could result in more events dispatched.
*/
Data::AssetManager::Instance().DispatchEvents();
m_assetHandlers.clear();
Data::AssetManager::Instance().DispatchEvents();
}
void RPISystem::RegisterScene(ScenePtr scene)
{
for (auto& sceneItem : m_scenes)
{
if (sceneItem == scene)
{
AZ_Assert(false, "Scene was already registered");
return;
}
}
m_scenes.push_back(scene);
}
void RPISystem::UnregisterScene(ScenePtr scene)
{
for (auto itr = m_scenes.begin(); itr != m_scenes.end(); itr++)
{
if (*itr == scene)
{
m_scenes.erase(itr);
return;
}
}
AZ_Assert(false, "Can't unregister scene which wasn't registered");
}
ScenePtr RPISystem::GetScene(const SceneId& sceneId) const
{
for (const auto& scene : m_scenes)
{
if (scene->GetId() == sceneId)
{
return scene;
}
}
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;
for (auto& scene : m_scenes)
{
renderPipeline = scene->FindRenderPipelineForWindow(windowHandle);
if (renderPipeline)
{
return renderPipeline;
}
}
return nullptr;
}
Data::Asset<ShaderResourceGroupAsset> RPISystem::GetSceneSrgAsset() const
{
AZ_Assert(m_systemAssetsInitialized, "InitializeSystemAssets() should be called once when asset catalog loaded'");
return m_sceneSrgAsset;
}
Data::Asset<ShaderResourceGroupAsset> RPISystem::GetViewSrgAsset() const
{
AZ_Assert(m_systemAssetsInitialized, "InitializeSystemAssets() should be called once when asset catalog loaded'");
return m_viewSrgAsset;
}
void RPISystem::OnSystemTick()
{
AZ_ATOM_PROFILE_FUNCTION("RPI", "RPISystem: OnSystemTick");
// Image system update is using system tick but not game tick so it can stream images in background even game is pausing
m_imageSystem.Update();
}
void RPISystem::SimulationTick()
{
if (!m_systemAssetsInitialized)
{
return;
}
AZ_ATOM_PROFILE_FUNCTION("RPI", "RPISystem: SimulationTick");
AssetInitBus::Broadcast(&AssetInitBus::Events::PostLoadInit);
// Update tick time info
FillTickTimeInfo();
for (auto& scene : m_scenes)
{
scene->Simulate(m_tickTime, m_simulationJobPolicy);
}
}
void RPISystem::FillTickTimeInfo()
{
AZ::TickRequestBus::BroadcastResult(m_tickTime.m_gameDeltaTime, &AZ::TickRequestBus::Events::GetTickDeltaTime);
ScriptTimePoint currentTime;
AZ::TickRequestBus::BroadcastResult(currentTime, &AZ::TickRequestBus::Events::GetTimeAtCurrentTick);
m_tickTime.m_currentGameTime = static_cast<float>(currentTime.GetMilliseconds());
}
void RPISystem::RenderTick()
{
if (!m_systemAssetsInitialized)
{
return;
}
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzRender);
AZ_ATOM_PROFILE_FUNCTION("RPI", "RPISystem: RenderTick");
// Query system update is to increment the frame count
m_querySystem.Update();
// Collect draw packets for each scene and prepare RPI system SRGs
// [GFX TODO] We may parallel scenes' prepare render.
for (auto& scenePtr : m_scenes)
{
scenePtr->PrepareRender(m_tickTime, m_prepareRenderJobPolicy);
}
m_rhiSystem.FrameUpdate(
[this](RHI::FrameGraphBuilder& frameGraphBuilder)
{
// Pass system's frame update, which includes the logic of adding scope producers, has to be added here since the scope producers only can be added to the frame
// when frame started which cleans up previous scope producers.
m_passSystem.FrameUpdate(frameGraphBuilder);
// Update View Srgs
for (auto& scenePtr : m_scenes)
{
scenePtr->UpdateSrgs();
}
});
m_dynamicDraw.FrameEnd();
m_passSystem.FrameEnd();
for (auto& scenePtr : m_scenes)
{
scenePtr->OnFrameEnd();
}
m_renderTick++;
}
void RPISystem::SetSimulationJobPolicy(RHI::JobPolicy jobPolicy)
{
m_simulationJobPolicy = jobPolicy;
}
RHI::JobPolicy RPISystem::GetSimulationJobPolicy() const
{
return m_simulationJobPolicy;
}
void RPISystem::SetRenderPrepareJobPolicy(RHI::JobPolicy jobPolicy)
{
m_prepareRenderJobPolicy = jobPolicy;
}
RHI::JobPolicy RPISystem::GetRenderPrepareJobPolicy() const
{
return m_prepareRenderJobPolicy;
}
const RPISystemDescriptor& RPISystem::GetDescriptor() const
{
return m_descriptor;
}
Name RPISystem::GetRenderApiName() const
{
return RHI::Factory::Get().GetName();
}
void RPISystem::InitializeSystemAssets()
{
if (m_systemAssetsInitialized)
{
AZ_Warning("RPISystem", false , "InitializeSystemAssets should only be called once'");
return;
}
//[GFX TODO][ATOM-5867] - Move file loading code within RHI to reduce coupling with RPI
AZStd::string platformLimitsFilePath = AZStd::string::format("config/platform/%s/%s/platformlimits.azasset", AZ_TRAIT_OS_PLATFORM_NAME, GetRenderApiName().GetCStr());
AZStd::to_lower(platformLimitsFilePath.begin(), platformLimitsFilePath.end());
Data::Asset<AnyAsset> platformLimitsAsset;
platformLimitsAsset = RPI::AssetUtils::LoadCriticalAsset<AnyAsset>(platformLimitsFilePath.c_str(), RPI::AssetUtils::TraceLevel::None);
// Only read the m_platformLimits if the platformLimitsAsset is ready.
// The platformLimitsAsset may not exist for null renderer which is allowed
if (platformLimitsAsset.IsReady())
{
m_descriptor.m_rhiSystemDescriptor.m_platformLimits = RPI::GetDataFromAnyAsset<RHI::PlatformLimits>(platformLimitsAsset);
}
m_viewSrgAsset = AssetUtils::LoadCriticalAsset<ShaderResourceGroupAsset>( m_descriptor.m_viewSrgAssetPath.c_str());
if (!m_viewSrgAsset.IsReady())
{
return;
}
m_sceneSrgAsset = AssetUtils::LoadCriticalAsset<ShaderResourceGroupAsset>(m_descriptor.m_sceneSrgAssetPath.c_str());
if (!m_sceneSrgAsset.IsReady())
{
return;
}
m_rhiSystem.Init(m_descriptor.m_rhiSystemDescriptor);
m_imageSystem.Init(m_descriptor.m_imageSystemDescriptor);
m_bufferSystem.Init();
m_dynamicDraw.Init(m_descriptor.m_dynamicDrawSystemDescriptor);
m_passSystem.InitPassTemplates();
m_systemAssetsInitialized = true;
}
bool RPISystem::IsInitialized() const
{
return m_systemAssetsInitialized;
}
void RPISystem::InitializeSystemAssetsForTests()
{
if (m_systemAssetsInitialized)
{
AZ_Warning("RPISystem", false, "InitializeSystemAssets should only be called once'");
return;
}
//Init rhi/image/buffer systems to match InitializeSystemAssets
m_rhiSystem.Init(m_descriptor.m_rhiSystemDescriptor);
m_imageSystem.Init(m_descriptor.m_imageSystemDescriptor);
m_bufferSystem.Init();
// Assets aren't actually available or needed for tests, but the m_systemAssetsInitialized flag still needs to be flipped.
m_systemAssetsInitialized = true;
return;
}
bool RPISystem::OnPreAssert([[maybe_unused]] const char* fileName, [[maybe_unused]] int line, [[maybe_unused]] const char* func, [[maybe_unused]] const char* message)
{
#if AZ_RPI_PRINT_GLOBAL_STATE_ON_ASSERT
AZ_Printf("RPI System", "\n--- Assert hit! Dumping RPI state ---\n\n");
m_passSystem.DebugPrintPassHierarchy();
#endif
return false;
}
uint64_t RPISystem::GetCurrentTick() const
{
return m_renderTick;
}
} //namespace RPI
} //namespace AZ