/* * 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 namespace AZ { namespace RPI { void ShaderVariantAsyncLoader::Init() { m_isServiceShutdown.store(false); AZStd::thread_desc threadDesc; threadDesc.m_name = "ShaderVariantAsyncLoader"; m_serviceThread = AZStd::thread( [this]() { this->ThreadServiceLoop(); }, &threadDesc ); } void ShaderVariantAsyncLoader::ThreadServiceLoop() { AZStd::unordered_set newShaderVariantPendingRequests; AZStd::unordered_set shaderVariantTreePendingRequests; AZStd::unordered_set shaderVariantPendingRequests; while (true) { //We'll wait here until there's work to do or this service has been shutdown. { AZStd::unique_lock lock(m_mutex); m_workCondition.wait(lock, [&] { return m_isServiceShutdown.load() || !m_newShaderVariantPendingRequests.empty() || !m_shaderVariantTreePendingRequests.empty() || !m_shaderVariantPendingRequests.empty() || !newShaderVariantPendingRequests.empty() || !shaderVariantTreePendingRequests.empty() || !shaderVariantPendingRequests.empty(); } ); } if (m_isServiceShutdown.load()) { break; } { //Move pending requests to the local lists. AZStd::unique_lock lock(m_mutex); AZStd::for_each( m_newShaderVariantPendingRequests.begin(), m_newShaderVariantPendingRequests.end(), [&](const ShaderVariantAsyncLoader::PairOfShaderAssetAndShaderVariantId& pair) { newShaderVariantPendingRequests.insert(pair); }); m_newShaderVariantPendingRequests.clear(); AZStd::for_each(m_shaderVariantTreePendingRequests.begin(), m_shaderVariantTreePendingRequests.end(), [&](const Data::AssetId& assetId) { shaderVariantTreePendingRequests.insert(assetId); }); m_shaderVariantTreePendingRequests.clear(); AZStd::for_each(m_shaderVariantPendingRequests.begin(), m_shaderVariantPendingRequests.end(), [&](const Data::AssetId& assetId) { shaderVariantPendingRequests.insert(assetId); }); m_shaderVariantPendingRequests.clear(); } // Time to work hard. auto pairItor = newShaderVariantPendingRequests.begin(); while (pairItor != newShaderVariantPendingRequests.end()) { auto shaderVariantTreeAsset = GetShaderVariantTreeAsset(pairItor->m_shaderAsset.GetId()); if (shaderVariantTreeAsset) { AZ_Assert(shaderVariantTreeAsset.IsReady(), "shaderVariantTreeAsset is not ready!"); // Get the stableId from the variant tree. auto searchResult = shaderVariantTreeAsset->FindVariantStableId( pairItor->m_shaderAsset->GetShaderOptionGroupLayout(), pairItor->m_shaderVariantId); if (searchResult.IsRoot()) { pairItor = newShaderVariantPendingRequests.erase(pairItor); continue; } // Record the request for metrics. ShaderMetricsSystem::Get()->RequestShaderVariant(pairItor->m_shaderAsset.Get(), pairItor->m_shaderVariantId, searchResult); uint32_t shaderVariantProductSubId = ShaderVariantAsset::MakeAssetProductSubId(RHI::Factory::Get().GetAPIUniqueIndex(), searchResult.GetStableId()); Data::AssetId shaderVariantAssetId(shaderVariantTreeAsset.GetId().m_guid, shaderVariantProductSubId); shaderVariantPendingRequests.insert(shaderVariantAssetId); pairItor = newShaderVariantPendingRequests.erase(pairItor); continue; } // If we are here the shaderVariantTreeAsset is not ready, but maybe it is already queued for loading, // but we try to queue it anyways. QueueShaderVariantTreeForLoading(*pairItor, shaderVariantTreePendingRequests); pairItor++; } auto variantTreeItor = shaderVariantTreePendingRequests.begin(); while (variantTreeItor != shaderVariantTreePendingRequests.end()) { if (TryToLoadShaderVariantTreeAsset(*variantTreeItor)) { variantTreeItor = shaderVariantTreePendingRequests.erase(variantTreeItor); } else { variantTreeItor++; } } auto variantItor = shaderVariantPendingRequests.begin(); while (variantItor != shaderVariantPendingRequests.end()) { if (TryToLoadShaderVariantAsset(*variantItor)) { variantItor = shaderVariantPendingRequests.erase(variantItor); } else { variantItor++; } } AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(1000)); } } void ShaderVariantAsyncLoader::Shutdown() { if (m_isServiceShutdown.load()) { return; } { AZStd::unique_lock lock(m_mutex); m_isServiceShutdown.store(true); } m_workCondition.notify_one(); m_serviceThread.join(); Data::AssetBus::MultiHandler::BusDisconnect(); m_newShaderVariantPendingRequests.clear(); m_shaderVariantTreePendingRequests.clear(); m_shaderVariantPendingRequests.clear(); m_shaderVariantData.clear(); m_shaderAssetIdToShaderVariantTreeAssetId.clear(); } /////////////////////////////////////////////////////////////////// // IShaderVariantFinder overrides bool ShaderVariantAsyncLoader::QueueLoadShaderVariantAssetByVariantId( Data::Asset shaderAsset, const ShaderVariantId& shaderVariantId) { if (m_isServiceShutdown.load()) { return false; } { AZStd::unique_lock lock(m_mutex); PairOfShaderAssetAndShaderVariantId pair = {shaderAsset, shaderVariantId}; m_newShaderVariantPendingRequests.push_back(pair); } m_workCondition.notify_one(); return true; } bool ShaderVariantAsyncLoader::QueueLoadShaderVariantAsset( const Data::AssetId& shaderVariantTreeAssetId, ShaderVariantStableId variantStableId) { if (m_isServiceShutdown.load()) { return false; } AZ_Assert(variantStableId != RootShaderVariantStableId, "Root Variants Are Found inside ShaderAssets"); uint32_t shaderVariantProductSubId = ShaderVariantAsset::MakeAssetProductSubId(RHI::Factory::Get().GetAPIUniqueIndex(), variantStableId); Data::AssetId shaderVariantAssetId(shaderVariantTreeAssetId.m_guid, shaderVariantProductSubId); { AZStd::unique_lock lock(m_mutex); m_shaderVariantPendingRequests.push_back(shaderVariantAssetId); } m_workCondition.notify_one(); return true; } bool ShaderVariantAsyncLoader::QueueLoadShaderVariantTreeAsset(const Data::AssetId& shaderAssetId) { if (m_isServiceShutdown.load()) { return false; } { AZStd::unique_lock lock(m_mutex); m_shaderVariantTreePendingRequests.push_back(shaderAssetId); } m_workCondition.notify_one(); return true; } Data::Asset ShaderVariantAsyncLoader::GetShaderVariantAssetByVariantId( Data::Asset shaderAsset, const ShaderVariantId& shaderVariantId) { Data::Asset shaderVariantTreeAsset = GetShaderVariantTreeAsset(shaderAsset.GetId()); if (!shaderVariantTreeAsset) { return {}; } // Find the stable id. ShaderVariantSearchResult searchResult = shaderVariantTreeAsset->FindVariantStableId(shaderAsset->GetShaderOptionGroupLayout(), shaderVariantId); if (searchResult.IsRoot()) { return shaderAsset->GetRootVariant(); } // Record the request for metrics. ShaderMetricsSystem::Get()->RequestShaderVariant(shaderAsset.Get(), shaderVariantId, searchResult); return GetShaderVariantAsset(shaderVariantTreeAsset.GetId(), searchResult.GetStableId()); } Data::Asset ShaderVariantAsyncLoader::GetShaderVariantAssetByStableId( Data::Asset shaderAsset, ShaderVariantStableId shaderVariantStableId) { AZ_Assert(shaderVariantStableId != RootShaderVariantStableId, "Root Variants Are Found inside ShaderAssets"); Data::Asset shaderVariantTreeAsset = GetShaderVariantTreeAsset(shaderAsset.GetId()); if (!shaderVariantTreeAsset) { return {}; } return GetShaderVariantAsset(shaderVariantTreeAsset.GetId(), shaderVariantStableId); } Data::Asset ShaderVariantAsyncLoader::GetShaderVariantTreeAsset(const Data::AssetId& shaderAssetId) { AZStd::unique_lock lock(m_mutex); auto assetIdFindIt = m_shaderAssetIdToShaderVariantTreeAssetId.find(shaderAssetId); if (assetIdFindIt == m_shaderAssetIdToShaderVariantTreeAssetId.end()) { return {}; } const Data::AssetId& shaderVariantTreeAssetId = assetIdFindIt->second; auto findIt = m_shaderVariantData.find(shaderVariantTreeAssetId); if (findIt == m_shaderVariantData.end()) { return {}; } ShaderVariantCollection& shaderVariantCollection = findIt->second; if (shaderVariantCollection.m_shaderVariantTree.IsReady()) { return shaderVariantCollection.m_shaderVariantTree; } return {}; } Data::Asset ShaderVariantAsyncLoader::GetShaderVariantAsset(const Data::AssetId& shaderVariantTreeAssetId, ShaderVariantStableId variantStableId) { AZ_Assert(variantStableId != RootShaderVariantStableId, "Root Variants Are Found inside ShaderAssets"); uint32_t shaderVariantProductSubId = ShaderVariantAsset::MakeAssetProductSubId(RHI::Factory::Get().GetAPIUniqueIndex(), variantStableId); Data::AssetId shaderVariantAssetId(shaderVariantTreeAssetId.m_guid, shaderVariantProductSubId); AZStd::unique_lock lock(m_mutex); auto findIt = m_shaderVariantData.find(shaderVariantTreeAssetId); if (findIt == m_shaderVariantData.end()) { return {}; } ShaderVariantCollection& shaderVariantCollection = findIt->second; auto& shaderVariantsMap = shaderVariantCollection.m_shaderVariantsMap; auto variantFindIt = shaderVariantsMap.find(shaderVariantAssetId); if (variantFindIt == shaderVariantsMap.end()) { return {}; } Data::Asset shaderVariantAsset = variantFindIt->second; if (shaderVariantAsset.IsReady()) { return shaderVariantAsset; } return {}; } void ShaderVariantAsyncLoader::Reset() { Shutdown(); Init(); } /////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// // AZ::Data::AssetBus::Handler overrides void ShaderVariantAsyncLoader::OnAssetReady(Data::Asset asset) { //Cast it to our known asset types. if (asset.GetType() == AZ::AzTypeInfo::Uuid()) { OnShaderVariantTreeAssetReady(Data::static_pointer_cast(asset)); return; } if (asset.GetType() == AZ::AzTypeInfo::Uuid()) { OnShaderVariantAssetReady(Data::static_pointer_cast(asset)); return; } AZ_Error(LogName, false, "Got OnAssetReady for unknown asset type with id=%s and hint=%s\n", asset.GetId().ToString().c_str(), asset.GetHint().c_str()); } void ShaderVariantAsyncLoader::OnAssetReloaded(Data::Asset asset) { return OnAssetReady(asset); } void ShaderVariantAsyncLoader::OnAssetError(Data::Asset asset) { Data::AssetBus::MultiHandler::BusDisconnect(asset.GetId()); //Cast it to our known asset types. if (asset.GetType() == AZ::AzTypeInfo::Uuid()) { OnShaderVariantTreeAssetError(Data::static_pointer_cast(asset)); return; } if (asset.GetType() == AZ::AzTypeInfo::Uuid()) { OnShaderVariantAssetError(Data::static_pointer_cast(asset)); return; } AZ_Error(LogName, false, "Got OnAssetError for unknown asset type with id=%s and hint=%s\n", asset.GetId().ToString().c_str(), asset.GetHint().c_str()); } /////////////////////////////////////////////////////////////////////// void ShaderVariantAsyncLoader::OnShaderVariantTreeAssetReady(Data::Asset shaderVariantTreeAsset) { // Will be used to address the notification bus. Data::AssetId shaderAssetId; { AZStd::unique_lock lock(m_mutex); auto findIt = m_shaderVariantData.find(shaderVariantTreeAsset.GetId()); if (findIt != m_shaderVariantData.end()) { ShaderVariantCollection& shaderVariantCollection = findIt->second; shaderAssetId = shaderVariantCollection.m_shaderAssetId; shaderVariantCollection.m_shaderVariantTree = shaderVariantTreeAsset; } else { AZ_Assert(false, "Was expecting a ShaderVariantCollection for shader variant tree asset with id=%s and hint=%s", shaderVariantTreeAsset.GetId().ToString().c_str(), shaderVariantTreeAsset.GetHint().c_str()); return; } } AZ::TickBus::QueueFunction([shaderAssetId, shaderVariantTreeAsset]() { ShaderVariantFinderNotificationBus::Event( shaderAssetId, &ShaderVariantFinderNotification::OnShaderVariantTreeAssetReady, shaderVariantTreeAsset, false /*isError*/); }); } void ShaderVariantAsyncLoader::OnShaderVariantAssetReady(Data::Asset shaderVariantAsset) { // Will be used to address the notification bus. Data::AssetId shaderAssetId; { AZStd::unique_lock lock(m_mutex); Data::AssetId shaderVariantTreeAssetId(shaderVariantAsset.GetId().m_guid, 0); auto findIt = m_shaderVariantData.find(shaderVariantTreeAssetId); if (findIt != m_shaderVariantData.end()) { ShaderVariantCollection& shaderVariantCollection = findIt->second; shaderAssetId = shaderVariantCollection.m_shaderAssetId; auto& shaderVariantMap = shaderVariantCollection.m_shaderVariantsMap; shaderVariantMap.emplace(shaderVariantAsset.GetId(), shaderVariantAsset); } else { AZ_Assert(false, "Was expecting a ShaderVariantCollection for shader variant asset with id=%s and hint=%s", shaderVariantAsset.GetId().ToString().c_str(), shaderVariantAsset.GetHint().c_str()); return; } } AZ::TickBus::QueueFunction([shaderAssetId, shaderVariantAsset]() { ShaderVariantFinderNotificationBus::Event( shaderAssetId, &ShaderVariantFinderNotification::OnShaderVariantAssetReady, shaderVariantAsset, false /*isError*/); }); } void ShaderVariantAsyncLoader::OnShaderVariantTreeAssetError(Data::Asset shaderVariantTreeAsset) { // Will be used to address the notification bus. Data::AssetId shaderAssetId; { AZStd::unique_lock lock(m_mutex); auto findIt = m_shaderVariantData.find(shaderVariantTreeAsset.GetId()); if (findIt != m_shaderVariantData.end()) { ShaderVariantCollection& shaderVariantCollection = findIt->second; shaderAssetId = shaderVariantCollection.m_shaderAssetId; m_shaderVariantData.erase(findIt); } else { AZ_Warning(LogName, false, "Was expecting a ShaderVariantCollection for shader variant tree asset with id=%s and hint=%s", shaderVariantTreeAsset.GetId().ToString().c_str(), shaderVariantTreeAsset.GetHint().c_str()); return; } } AZ::TickBus::QueueFunction([shaderAssetId, shaderVariantTreeAsset]() { ShaderVariantFinderNotificationBus::Event( shaderAssetId, &ShaderVariantFinderNotification::OnShaderVariantTreeAssetReady, shaderVariantTreeAsset, true /*isError*/); }); } void ShaderVariantAsyncLoader::OnShaderVariantAssetError(Data::Asset shaderVariantAsset) { // Will be used to address the notification bus. Data::AssetId shaderAssetId; { AZStd::unique_lock lock(m_mutex); Data::AssetId shaderVariantTreeAssetId(shaderVariantAsset.GetId().m_guid, 0); auto findIt = m_shaderVariantData.find(shaderVariantTreeAssetId); if (findIt != m_shaderVariantData.end()) { ShaderVariantCollection& shaderVariantCollection = findIt->second; shaderAssetId = shaderVariantCollection.m_shaderAssetId; auto& shaderVariantMap = shaderVariantCollection.m_shaderVariantsMap; auto shaderVariantFindIt = shaderVariantMap.find(shaderVariantAsset.GetId()); if (shaderVariantFindIt != shaderVariantMap.end()) { shaderVariantMap.erase(shaderVariantFindIt); } } } AZ::TickBus::QueueFunction([shaderAssetId, shaderVariantAsset]() { ShaderVariantFinderNotificationBus::Event( shaderAssetId, &ShaderVariantFinderNotification::OnShaderVariantAssetReady, shaderVariantAsset, true /*isError*/); }); } void ShaderVariantAsyncLoader::QueueShaderVariantTreeForLoading( const PairOfShaderAssetAndShaderVariantId& shaderAndVariantPair, AZStd::unordered_set& shaderVariantTreePendingRequests) { auto shaderAssetId = shaderAndVariantPair.m_shaderAsset.GetId(); if (shaderVariantTreePendingRequests.count(shaderAndVariantPair.m_shaderAsset.GetId())) { // Already queued. return; } Data::AssetId shaderVariantTreeAssetId = ShaderVariantTreeAsset::GetShaderVariantTreeAssetIdFromShaderAssetId(shaderAssetId); if (!shaderVariantTreeAssetId.IsValid()) { shaderVariantTreePendingRequests.insert(shaderAssetId); return; } { AZStd::unique_lock lock(m_mutex); auto findIt = m_shaderVariantData.find(shaderVariantTreeAssetId); if (findIt != m_shaderVariantData.end()) { // Already queued. return; } } shaderVariantTreePendingRequests.insert(shaderAssetId); } bool ShaderVariantAsyncLoader::TryToLoadShaderVariantTreeAsset(const Data::AssetId& shaderAssetId) { Data::AssetId shaderVariantTreeAssetId = ShaderVariantTreeAsset::GetShaderVariantTreeAssetIdFromShaderAssetId(shaderAssetId); if (!shaderVariantTreeAssetId.IsValid()) { // Return false in hope to retry later. return false; } //If we already loaded such asset let's simply dispatch the notification on the next tick. Data::Asset shaderVariantTreeAsset; { AZStd::unique_lock lock(m_mutex); m_shaderAssetIdToShaderVariantTreeAssetId.emplace(shaderAssetId, shaderVariantTreeAssetId); auto findIt = m_shaderVariantData.find(shaderVariantTreeAssetId); if (findIt != m_shaderVariantData.end()) { shaderVariantTreeAsset = findIt->second.m_shaderVariantTree; } } if (shaderVariantTreeAsset.IsReady()) { AZ::TickBus::QueueFunction([shaderAssetId, shaderVariantTreeAsset]() { ShaderVariantFinderNotificationBus::Event( shaderAssetId, &ShaderVariantFinderNotification::OnShaderVariantTreeAssetReady, shaderVariantTreeAsset, false /*isError*/); }); return true; } Data::AssetBus::MultiHandler::BusDisconnect(shaderVariantTreeAssetId); //Let's queue the asset for loading. shaderVariantTreeAsset = Data::AssetManager::Instance().GetAsset(shaderVariantTreeAssetId, AZ::Data::AssetLoadBehavior::QueueLoad); if (shaderVariantTreeAsset.IsError()) { // The asset doesn't exist in the database yet. Return false in hope to retry later. return false; } // We need to preserve a reference to shaderVariantTreeAsset, otherwise the asset load will be cancelled. // This means we have to create the ShaderVariantCollection struct before OnAssetReady is called. { AZStd::unique_lock lock(m_mutex); auto findIt = m_shaderVariantData.find(shaderVariantTreeAssetId); if (findIt != m_shaderVariantData.end()) { ShaderVariantCollection& shaderVariantCollection = findIt->second; shaderVariantCollection.m_shaderVariantTree = shaderVariantTreeAsset; shaderVariantCollection.m_shaderAssetId = shaderAssetId; } else { m_shaderVariantData.emplace(shaderVariantTreeAssetId, ShaderVariantCollection()); ShaderVariantCollection& collection = m_shaderVariantData[shaderVariantTreeAssetId]; collection.m_shaderAssetId = shaderAssetId; collection.m_shaderVariantTree = shaderVariantTreeAsset; } } Data::AssetBus::MultiHandler::BusConnect(shaderVariantTreeAssetId); return true; } bool ShaderVariantAsyncLoader::TryToLoadShaderVariantAsset(const Data::AssetId& shaderVariantAssetId) { // Will be used to address the notification bus. Data::AssetId shaderAssetId; //If we already loaded such asset let's simply dispatch the notification on the next tick. Data::Asset shaderVariantAsset; Data::AssetId shaderVariantTreeAssetId(shaderVariantAssetId.m_guid, 0); { AZStd::unique_lock lock(m_mutex); auto findIt = m_shaderVariantData.find(shaderVariantTreeAssetId); if (findIt != m_shaderVariantData.end()) { ShaderVariantCollection& shaderVariantCollection = findIt->second; shaderAssetId = shaderVariantCollection.m_shaderAssetId; auto& shaderVariantMap = shaderVariantCollection.m_shaderVariantsMap; auto variantFindIt = shaderVariantMap.find(shaderVariantAssetId); if (variantFindIt != shaderVariantMap.end()) { shaderVariantAsset = variantFindIt->second; } } else { AZ_Assert(false, "Looking for a variant without a tree."); return true; //Returning true means the requested asset should be removed from the queue. } } if (shaderVariantAsset.IsReady()) { AZ::TickBus::QueueFunction([shaderAssetId, shaderVariantAsset]() { ShaderVariantFinderNotificationBus::Event( shaderAssetId, &ShaderVariantFinderNotification::OnShaderVariantAssetReady, shaderVariantAsset, false /*isError*/); }); return true; } Data::AssetBus::MultiHandler::BusDisconnect(shaderVariantAssetId); // Make sure the asset actually exists Data::AssetInfo assetInfo; Data::AssetCatalogRequestBus::BroadcastResult(assetInfo, &Data::AssetCatalogRequestBus::Events::GetAssetInfoById, shaderVariantAssetId); if (!assetInfo.m_assetId.IsValid()) { // The asset doesn't exist in the database yet. Return false to retry later. return false; } // Let's queue the asset for loading. shaderVariantAsset = Data::AssetManager::Instance().GetAsset(shaderVariantAssetId, AZ::Data::AssetLoadBehavior::QueueLoad); if (shaderVariantAsset.IsError()) { // The asset exists (we just checked GetAssetInfoById above) but some error occurred. return false; } // We need to preserve a reference to shaderVariantAsset, otherwise the asset load will be cancelled. { AZStd::unique_lock lock(m_mutex); auto findIt = m_shaderVariantData.find(shaderVariantTreeAssetId); if (findIt != m_shaderVariantData.end()) { ShaderVariantCollection& shaderVariantCollection = findIt->second; auto& shaderVariantMap = shaderVariantCollection.m_shaderVariantsMap; shaderVariantMap.emplace(shaderVariantAssetId, shaderVariantAsset); } else { AZ_Assert(false, "Looking for a variant without a tree."); return true; //Returning true means the requested asset should be removed from the queue. } } Data::AssetBus::MultiHandler::BusConnect(shaderVariantAssetId); return true; } } // namespace RPI } // namespace AZ