/* * 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 #include #include #include #include #include #include #include namespace AZ { namespace RPI { Data::Instance Shader::FindOrCreate(const Data::Asset& shaderAsset) { return Data::InstanceDatabase::Instance().FindOrCreate( Data::InstanceId::CreateFromAssetId(shaderAsset.GetId()), shaderAsset); } Data::Instance Shader::CreateInternal(ShaderAsset& shaderAsset) { Data::Instance shader = aznew Shader(); const RHI::ResultCode resultCode = shader->Init(shaderAsset); if (resultCode == RHI::ResultCode::Success) { return shader; } return nullptr; } Shader::~Shader() { Shutdown(); } RHI::ResultCode Shader::Init(ShaderAsset& shaderAsset) { Data::AssetBus::Handler::BusDisconnect(); ShaderReloadNotificationBus::Handler::BusDisconnect(); ShaderVariantFinderNotificationBus::Handler::BusDisconnect(); RHI::RHISystemInterface* rhiSystem = RHI::RHISystemInterface::Get(); RHI::DrawListTagRegistry* drawListTagRegistry = rhiSystem->GetDrawListTagRegistry(); m_asset = { &shaderAsset, AZ::Data::AssetLoadBehavior::PreLoad }; m_pipelineStateType = shaderAsset.GetPipelineStateType(); { AZStd::unique_lock lock(m_variantCacheMutex); m_shaderVariants.clear(); } m_rootVariant.Init(Data::Asset{&shaderAsset, AZ::Data::AssetLoadBehavior::PreLoad}, shaderAsset.GetRootVariant()); if (m_pipelineLibraryHandle.IsNull()) { // We set up a pipeline library only once for the lifetime of the Shader instance. // This should allow the Shader to be reloaded at runtime many times, and cache and reuse PipelineState objects rather than rebuild them. // It also fixes a particular TDR crash that occurred on some hardware when hot-reloading shaders and building pipeline states // in a new pipeline library every time. RHI::PipelineStateCache* pipelineStateCache = rhiSystem->GetPipelineStateCache(); ConstPtr serializedData = LoadPipelineLibrary(); RHI::PipelineLibraryHandle pipelineLibraryHandle = pipelineStateCache->CreateLibrary(serializedData.get()); if (pipelineLibraryHandle.IsNull()) { AZ_Error("Shader", false, "Failed to create pipeline library from pipeline state cache."); return RHI::ResultCode::Fail; } m_pipelineLibraryHandle = pipelineLibraryHandle; m_pipelineStateCache = pipelineStateCache; } const Name& drawListName = shaderAsset.GetDrawListName(); if (!drawListName.IsEmpty()) { m_drawListTag = drawListTagRegistry->AcquireTag(drawListName); if (!m_drawListTag.IsValid()) { AZ_Error("Shader", false, "Failed to acquire a DrawListTag. Entries are full."); } } ShaderVariantFinderNotificationBus::Handler::BusConnect(m_asset.GetId()); Data::AssetBus::Handler::BusConnect(m_asset.GetId()); ShaderReloadNotificationBus::Handler::BusConnect(m_asset.GetId()); return RHI::ResultCode::Success; } void Shader::Shutdown() { ShaderVariantFinderNotificationBus::Handler::BusDisconnect(); Data::AssetBus::Handler::BusDisconnect(); ShaderReloadNotificationBus::Handler::BusDisconnect(); if (m_pipelineLibraryHandle.IsValid()) { SavePipelineLibrary(); m_pipelineStateCache->ReleaseLibrary(m_pipelineLibraryHandle); m_pipelineStateCache = nullptr; m_pipelineLibraryHandle = {}; } if (m_drawListTag.IsValid()) { RHI::DrawListTagRegistry* drawListTagRegistry = RHI::RHISystemInterface::Get()->GetDrawListTagRegistry(); drawListTagRegistry->ReleaseTag(m_drawListTag); m_drawListTag.Reset(); } } /////////////////////////////////////////////////////////////////////// // AssetBus overrides void Shader::OnAssetReloaded(Data::Asset asset) { ShaderReloadDebugTracker::ScopedSection reloadSection("{%p}->Shader::OnAssetReloaded %s", this, asset.GetHint().c_str()); if (asset->GetId() == m_asset->GetId()) { Data::Asset newAsset = { asset.GetAs(), AZ::Data::AssetLoadBehavior::PreLoad }; AZ_Assert(newAsset, "Reloaded ShaderAsset is null"); Init(*newAsset.Get()); ShaderReloadNotificationBus::Event(asset.GetId(), &ShaderReloadNotificationBus::Events::OnShaderReinitialized, *this); } } /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////// /// ShaderVariantFinderNotificationBus overrides void Shader::OnShaderVariantAssetReady(Data::Asset shaderVariantAsset, bool isError) { AZ_Assert(shaderVariantAsset, "Reloaded ShaderVariantAsset is null"); const ShaderVariantStableId stableId = shaderVariantAsset->GetStableId(); // We make a copy of the updated variant because OnShaderVariantReinitialized must not be called inside // m_variantCacheMutex or deadlocks may occur. // Or if there is an error, we leave this object in its default state to indicate there was an error. // [GFX TODO] We really should have a dedicated message/event for this, but that will be covered by a future task where // we will merge ShaderReloadNotificationBus messages into one. For now, we just indicate the error by passing an empty ShaderVariant, // all our call sites don't use this data anyway. ShaderVariant updatedVariant; if (isError) { //Remark: We do not assert if the stableId == RootShaderVariantStableId, because we can not trust in the asset data //on error. so it is possible that on error the stbleId == RootShaderVariantStableId; if (stableId == RootShaderVariantStableId) { return; } AZStd::unique_lock lock(m_variantCacheMutex); m_shaderVariants.erase(stableId); } else { AZ_Assert(stableId != RootShaderVariantStableId, "The root variant is expected to be updated by the ShaderAsset."); AZStd::unique_lock lock(m_variantCacheMutex); auto iter = m_shaderVariants.find(stableId); if (iter != m_shaderVariants.end()) { ShaderVariant& shaderVariant = iter->second; if (!shaderVariant.Init(m_asset, shaderVariantAsset)) { AZ_Error("Shader", false, "Failed to init shaderVariant with StableId=%u", shaderVariantAsset->GetStableId()); m_shaderVariants.erase(stableId); } else { updatedVariant = shaderVariant; } } else { //This is the first time the shader variant asset comes to life. updatedVariant.Init(m_asset, shaderVariantAsset); m_shaderVariants.emplace(stableId, updatedVariant); } } // [GFX TODO] It might make more sense to call OnShaderReinitialized here ShaderReloadNotificationBus::Event(m_asset.GetId(), &ShaderReloadNotificationBus::Events::OnShaderVariantReinitialized, updatedVariant); } /////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////// // ShaderReloadNotificationBus overrides... void Shader::OnShaderAssetReinitialized(const Data::Asset& shaderAsset) { // When reloads occur, it's possible for old Asset objects to hang around and report reinitialization, // so we can reduce unnecessary reinitialization in that case. if (shaderAsset.Get() == m_asset.Get()) { ShaderReloadDebugTracker::ScopedSection reloadSection("{%p}->Shader::OnShaderAssetReinitialized %s", this, shaderAsset.GetHint().c_str()); Init(*m_asset.Get()); ShaderReloadNotificationBus::Event(shaderAsset.GetId(), &ShaderReloadNotificationBus::Events::OnShaderReinitialized, *this); } } /////////////////////////////////////////////////////////////////// ConstPtr Shader::LoadPipelineLibrary() const { if (IO::FileIOBase::GetInstance()) { return Utils::LoadObjectFromFile(GetPipelineLibraryPath()); } return nullptr; } void Shader::SavePipelineLibrary() const { if (auto* fileIOBase = IO::FileIOBase::GetInstance()) { RHI::ConstPtr serializedData = m_pipelineStateCache->GetLibrarySerializedData(m_pipelineLibraryHandle); if (serializedData) { const AZStd::string pipelineLibraryPath = GetPipelineLibraryPath(); char pipelineLibraryPathResolved[AZ_MAX_PATH_LEN] = { 0 }; fileIOBase->ResolvePath(pipelineLibraryPath.c_str(), pipelineLibraryPathResolved, AZ_MAX_PATH_LEN); Utils::SaveObjectToFile(pipelineLibraryPathResolved, DataStream::ST_BINARY, serializedData.get()); } } else { AZ_Error("Shader", false, "FileIOBase is not initialized"); } } AZStd::string Shader::GetPipelineLibraryPath() const { const Data::InstanceId& instanceId = GetId(); Name platformName = RHI::Factory::Get().GetName(); Name shaderName = m_asset->GetName(); AZStd::string uuidString; instanceId.m_guid.ToString(uuidString, false, false); return AZStd::string::format("@user@/Atom/PipelineStateCache/%s/%s_%s_%d.bin", platformName.GetCStr(), shaderName.GetCStr(), uuidString.data(), instanceId.m_subId); } ShaderOptionGroup Shader::CreateShaderOptionGroup() const { return ShaderOptionGroup(m_asset->GetShaderOptionGroupLayout()); } const ShaderVariant& Shader::GetVariant(const ShaderVariantId& shaderVariantId) { AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender); Data::Asset shaderVariantAsset = m_asset->GetVariant(shaderVariantId); if (!shaderVariantAsset || shaderVariantAsset->IsRootVariant()) { return m_rootVariant; } return GetVariant(shaderVariantAsset->GetStableId()); } const ShaderVariant& Shader::GetRootVariant() { return m_rootVariant; } ShaderVariantSearchResult Shader::FindVariantStableId(const ShaderVariantId& shaderVariantId) const { AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender); ShaderVariantSearchResult variantSearchResult = m_asset->FindVariantStableId(shaderVariantId); return variantSearchResult; } const ShaderVariant& Shader::GetVariant(ShaderVariantStableId shaderVariantStableId) { AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender); if (!shaderVariantStableId.IsValid() || shaderVariantStableId == ShaderAsset::RootShaderVariantStableId) { return m_rootVariant; } { AZStd::shared_lock lock(m_variantCacheMutex); auto findIt = m_shaderVariants.find(shaderVariantStableId); if (findIt != m_shaderVariants.end()) { // When rebuilding shaders we may be in a state where the ShaderAsset and root ShaderVariantAsset have been rebuilt and // 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.GetShaderAssetBuildTimestamp() == m_asset->GetShaderAssetBuildTimestamp()) { return findIt->second; } } } // By calling GetVariant, an asynchronous asset load request is enqueued if the variant // is not fully ready. Data::Asset shaderVariantAsset = m_asset->GetVariant(shaderVariantStableId); if (!shaderVariantAsset || shaderVariantAsset == m_asset->GetRootVariant()) { // Return the root variant when the requested variant is not ready. return m_rootVariant; } AZStd::unique_lock lock(m_variantCacheMutex); // For performance reasons We are breaking this function into two locking steps. // which means We must check again if the variant is already in the cache. auto findIt = m_shaderVariants.find(shaderVariantStableId); if (findIt != m_shaderVariants.end()) { if (findIt->second.GetShaderAssetBuildTimestamp() == m_asset->GetShaderAssetBuildTimestamp()) { return findIt->second; } else { // This is probably very rare, but if the variant was loaded on another thread and it's out of date // we just return the root variant. Otherwise we could end up replacing the variant in the map below while // it's being used for rendering. AZ_Warning( "Shader", false, "Detected an uncommon state during shader reload. Returning the root variant instead of replacing the old one."); return m_rootVariant; } } ShaderVariant newVariant; newVariant.Init(m_asset, shaderVariantAsset); m_shaderVariants.emplace(shaderVariantStableId, newVariant); return m_shaderVariants.at(shaderVariantStableId); } RHI::PipelineStateType Shader::GetPipelineStateType() const { return m_pipelineStateType; } const RHI::PipelineState* Shader::AcquirePipelineState(const RHI::PipelineStateDescriptor& descriptor) const { return m_pipelineStateCache->AcquirePipelineState(m_pipelineLibraryHandle, descriptor); } const Data::Asset& Shader::FindShaderResourceGroupAsset(const Name& shaderResourceGroupName) const { return m_asset->FindShaderResourceGroupAsset(shaderResourceGroupName); } const Data::Asset& Shader::FindShaderResourceGroupAsset(uint32_t bindingSlot) const { return m_asset->FindShaderResourceGroupAsset(bindingSlot); } const Data::Asset& Shader::FindFallbackShaderResourceGroupAsset() const { return m_asset->FindFallbackShaderResourceGroupAsset(); } AZStd::array_view> Shader::GetShaderResourceGroupAssets() const { return m_asset->GetShaderResourceGroupAssets(); } const Data::Asset& Shader::GetAsset() const { return m_asset; } RHI::DrawListTag Shader::GetDrawListTag() const { return m_drawListTag; } } // namespace RPI } // namespace AZ