/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace AZ { namespace RPI { ScenePtr Scene::CreateScene(const SceneDescriptor& sceneDescriptor) { Scene* scene = aznew Scene(); for (const auto& fpId : sceneDescriptor.m_featureProcessorNames) { scene->EnableFeatureProcessor(FeatureProcessorId{ fpId }); } auto sceneSrgLayout = RPISystemInterface::Get()->GetSceneSrgLayout(); if (sceneSrgLayout) { auto shaderAsset = RPISystemInterface::Get()->GetCommonShaderAssetForSrgs(); scene->m_srg = ShaderResourceGroup::Create(shaderAsset, sceneSrgLayout->GetName()); // Set value for constants defined in SceneTimeSrg.azsli scene->m_timeInputIndex = scene->m_srg->FindShaderInputConstantIndex(Name{ "m_time" }); } scene->m_name = sceneDescriptor.m_nameId; return ScenePtr(scene); } ScenePtr Scene::CreateSceneFromAsset(Data::Asset sceneAsset) { const SceneDescriptor* sceneDescriptor = GetDataFromAnyAsset(sceneAsset); if (sceneDescriptor == nullptr) { return nullptr; } ScenePtr scene = Scene::CreateScene(*sceneDescriptor); if (scene == nullptr) { AZ_Error("RPISystem", false, "Failed to create scene from asset %s", sceneAsset.GetHint().c_str()); return nullptr; } return scene; } Scene* Scene::GetSceneForEntityContextId(AzFramework::EntityContextId entityContextId) { // Find the scene for this entity context. AZStd::shared_ptr scene = AzFramework::EntityContext::FindContainingScene(entityContextId); if (scene) { // Get the RPI::Scene subsystem from the AZFramework Scene. RPI::ScenePtr* scenePtr = scene->FindSubsystem(); if (scenePtr) { return scenePtr->get(); } } 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 = AZ::Uuid::CreateRandom(); m_cullingScene = aznew CullingScene(); SceneRequestBus::Handler::BusConnect(m_id); m_drawFilterTagRegistry = RHI::DrawFilterTagRegistry::Create(); } Scene::~Scene() { if (m_taskGraphActive) { WaitAndCleanTGEvent(AZStd::move(m_simulationFinishedTGEvent)); m_simulationFinishedTGEvent.reset(); } else { WaitAndCleanCompletionJob(m_simulationCompletion); } SceneRequestBus::Handler::BusDisconnect(); // Remove all the render pipelines. Need to process queued changes with pass system before and after remove render pipelines AZ::RPI::PassSystemInterface::Get()->ProcessQueuedChanges(); for (auto it = m_pipelines.begin(); it != m_pipelines.end(); ++it) { RenderPipelinePtr pipelineToRemove = (*it); pipelineToRemove->OnRemovedFromScene(this); } m_pipelines.clear(); AZ::RPI::PassSystemInterface::Get()->ProcessQueuedChanges(); Deactivate(); delete m_cullingScene; } void Scene::Activate() { AZ_Assert(!m_activated, "Already activated"); m_activated = true; m_cullingScene->Activate(this); // We have to tick the PassSystem in order for all the pass attachments to get created. // This has to be done before FeatureProcessors are activated, because they may try to // create PipelineState objects (PSOs) which would require data from attachments in the // the pass tree. AZ::RPI::PassSystemInterface::Get()->ProcessQueuedChanges(); for (auto& fp : m_featureProcessors) { fp->Activate(); } m_dynamicDrawSystem = static_cast(RPI::DynamicDrawInterface::Get()); } void Scene::Deactivate() { if (!m_activated) { return; } for (auto& fp : m_featureProcessors) { fp->Deactivate(); } m_cullingScene->Deactivate(); m_activated = false; m_pipelineStatesLookup.clear(); m_dynamicDrawSystem = nullptr; } FeatureProcessor* Scene::EnableFeatureProcessor(const FeatureProcessorId& featureProcessorId) { FeatureProcessor* foundFeatureProcessor = GetFeatureProcessor(featureProcessorId); if (foundFeatureProcessor) { AZ_Warning("Scene", false, "FeatureProcessor '%s' is already enabled for this scene. Will not re-enable.", featureProcessorId.GetCStr()); return foundFeatureProcessor; } // Check to make sure there aren't multiple different implementations of the same interface enabled for the scene // otherwise it would be ambiguous which feature processor is returned when GetFeatureProcessor is called with the interface. AZ::TypeId interfaceTypeId = FeatureProcessorFactory::Get()->GetFeatureProcessorInterfaceTypeId(featureProcessorId); if (!interfaceTypeId.IsNull()) { foundFeatureProcessor = GetFeatureProcessor(interfaceTypeId); if (foundFeatureProcessor) { AZ_Error("Scene", false, "FeatureProcessor '%s' is already enabled for this scene, which implements the same interface as %s. You cannot have more than one implementation of the same feature processor interface in a scene.", foundFeatureProcessor->RTTI_GetTypeName(), featureProcessorId.GetCStr()); return foundFeatureProcessor; } } // The feature processor was not found, so create it FeatureProcessorPtr createdFeatureProcessor = FeatureProcessorFactory::Get()->CreateFeatureProcessor(featureProcessorId); if (!createdFeatureProcessor) { AZ_Error("Scene", false, "FeatureProcessor '%s' could not be created. Check to make sure it has been registered with the FeatureProcessorFactory.", featureProcessorId.GetCStr()); return nullptr; } foundFeatureProcessor = createdFeatureProcessor.get(); AddFeatureProcessor(AZStd::move(createdFeatureProcessor)); return foundFeatureProcessor; } void Scene::AddFeatureProcessor(FeatureProcessorPtr fp) { fp->m_parentScene = this; // If the Scene is not active then we should not activate the new FeatureProcessor either. // In this case, the FeatureProcessor will be activated when Scene::Activate() is called. if (m_activated) { fp->Activate(); } m_featureProcessors.emplace_back(AZStd::move(fp)); } void Scene::EnableAllFeatureProcessors() { FeatureProcessorFactory::Get()->EnableAllForScene(this); } void Scene::DisableFeatureProcessor(const FeatureProcessorId& featureProcessorId) { auto foundFeatureProcessor = AZStd::find_if( AZStd::begin(m_featureProcessors), AZStd::end(m_featureProcessors), [featureProcessorId](const FeatureProcessorPtr& fp) { return FeatureProcessorId{ fp->RTTI_GetTypeName() } == featureProcessorId; }); if (foundFeatureProcessor != AZStd::end(m_featureProcessors)) { // If the Scene is not active then the removed FeatureProcessor is not active either, so no need to deactivate it. if (m_activated) { (*foundFeatureProcessor)->Deactivate(); } m_featureProcessors.erase(foundFeatureProcessor); } else { AZ_Warning("Scene", false, "FeatureProcessor '%s' is already disabled for this scene. Will not re-disable.", featureProcessorId.GetCStr()); } } void Scene::DisableAllFeatureProcessors() { for (auto& fp : m_featureProcessors) { fp->Deactivate(); } m_featureProcessors.clear(); } FeatureProcessor* Scene::GetFeatureProcessor(const FeatureProcessorId& featureProcessorId) const { AZ::TypeId featureProcessorTypeId = FeatureProcessorFactory::Get()->GetFeatureProcessorTypeId(featureProcessorId); return GetFeatureProcessor(featureProcessorTypeId); } FeatureProcessor* Scene::GetFeatureProcessor(const TypeId& featureProcessorTypeId) const { auto foundFP = AZStd::find_if( AZStd::begin(m_featureProcessors), AZStd::end(m_featureProcessors), [featureProcessorTypeId](const FeatureProcessorPtr& fp) { return fp->RTTI_IsTypeOf(featureProcessorTypeId); }); return foundFP == AZStd::end(m_featureProcessors) ? nullptr : (*foundFP).get(); } void Scene::TryApplyRenderPipelineChanges(RenderPipeline* pipeline) { // return directly if the pipeline doesn't allow modification or it was already modifed by scene if (!pipeline->m_descriptor.m_allowModification || pipeline->m_wasModifiedByScene) { return; } pipeline->m_wasModifiedByScene = true; for (auto& fp : m_featureProcessors) { fp->ApplyRenderPipelineChange(pipeline); } AZ::RPI::PassSystemInterface::Get()->ProcessQueuedChanges(); } void Scene::AddRenderPipeline(RenderPipelinePtr pipeline) { if (pipeline->m_scene != nullptr) { AZ_Assert(false, "Pipeline was added to another scene"); return; } auto pipelineId = pipeline->GetId(); if (GetRenderPipeline(pipelineId)) { AZ_Assert(false, "Pipeline with same name id is already added to this scene. Please set the pipeline with a different id"); return; } pipeline->SetDrawFilterTag(m_drawFilterTagRegistry->AcquireTag(pipelineId)); m_pipelines.push_back(pipeline); // Set this pipeline as default if the default pipeline was empty. This pipeline should be the first pipeline be added to the scene if (m_defaultPipeline == nullptr) { m_defaultPipeline = pipeline; } pipeline->OnAddedToScene(this); TryApplyRenderPipelineChanges(pipeline.get()); PassSystemInterface::Get()->ProcessQueuedChanges(); pipeline->BuildPipelineViews(); // Force to update the lookup table since adding render pipeline would effect any pipeline states created before pass system tick RebuildPipelineStatesLookup(); SceneNotificationBus::Event(m_id, &SceneNotification::OnRenderPipelineAdded, pipeline); } void Scene::RemoveRenderPipeline(const RenderPipelineId& pipelineId) { [[maybe_unused]] bool removed = false; for (auto it = m_pipelines.begin(); it != m_pipelines.end(); ++it) { if (pipelineId == (*it)->GetId()) { // process queued changes first before remove pipeline passes AZ::RPI::PassSystemInterface::Get()->ProcessQueuedChanges(); RenderPipelinePtr pipelineToRemove = (*it); if (m_defaultPipeline == pipelineToRemove) { m_defaultPipeline = nullptr; } m_drawFilterTagRegistry->ReleaseTag(pipelineToRemove->GetDrawFilterTag()); pipelineToRemove->OnRemovedFromScene(this); m_pipelines.erase(it); SceneNotificationBus::Event(m_id, &SceneNotification::OnRenderPipelineRemoved, pipelineToRemove.get()); // If the default pipeline was removed, set to the first one in the list if (m_defaultPipeline == nullptr && m_pipelines.size() > 0) { m_defaultPipeline = m_pipelines[0]; } AZ::RPI::PassSystemInterface::Get()->ProcessQueuedChanges(); RebuildPipelineStatesLookup(); removed = true; break; } } AZ_Assert(removed, "Pipeline %s is not added to this Scene", pipelineId.GetCStr()); } RenderPipelinePtr Scene::GetRenderPipeline(const RenderPipelineId& pipelineId) const { for (auto& pipeline : m_pipelines) { if (pipeline->GetId() == pipelineId) { return pipeline; } } return nullptr; } void Scene::SimulateTaskGraph() { static const AZ::TaskDescriptor simulationTGDesc{"RPI::Scene::Simulate", "Graphics"}; AZ::TaskGraph simulationTG; for (FeatureProcessorPtr& fp : m_featureProcessors) { FeatureProcessor* featureProcessor = fp.get(); simulationTG.AddTask( simulationTGDesc, [this, featureProcessor]() { FeatureProcessor::SimulatePacket jobPacket = m_simulatePacket; jobPacket.m_parentJob = nullptr; featureProcessor->Simulate(jobPacket); }); } simulationTG.Detach(); m_simulationFinishedTGEvent = AZStd::make_unique(); simulationTG.Submit(m_simulationFinishedTGEvent.get()); } void Scene::SimulateJobs() { // Create a new job to track completion. m_simulationCompletion = aznew AZ::JobCompletion(); for (FeatureProcessorPtr& fp : m_featureProcessors) { FeatureProcessor* featureProcessor = fp.get(); const auto jobLambda = [this, featureProcessor](AZ::Job& owner) { FeatureProcessor::SimulatePacket jobPacket = m_simulatePacket; jobPacket.m_parentJob = &owner; featureProcessor->Simulate(jobPacket); }; AZ::Job* simulationJob = AZ::CreateJobFunction(AZStd::move(jobLambda), true, nullptr); //auto-deletes simulationJob->SetDependent(m_simulationCompletion); simulationJob->Start(); } //[GFX TODO]: the completion job should start here } void Scene::Simulate(RHI::JobPolicy jobPolicy, float simulationTime) { AZ_PROFILE_SCOPE(RPI, "Scene: Simulate"); m_simulationTime = simulationTime; // If previous simulation job wasn't done, wait for it to finish. if (m_taskGraphActive) { WaitAndCleanTGEvent(AZStd::move(m_simulationFinishedTGEvent)); m_simulationFinishedTGEvent.reset(); } else { WaitAndCleanCompletionJob(m_simulationCompletion); } auto taskGraphActiveInterface = AZ::Interface::Get(); m_taskGraphActive = taskGraphActiveInterface && taskGraphActiveInterface->IsTaskGraphActive(); if (jobPolicy == RHI::JobPolicy::Serial) { for (auto& fp : m_featureProcessors) { fp->Simulate(m_simulatePacket); } } else { if (m_taskGraphActive) { SimulateTaskGraph(); } else { SimulateJobs(); } } } void Scene::WaitAndCleanTGEvent(AZStd::unique_ptr&& completionTGEvent) { AZ_PROFILE_SCOPE(RPI, "Scene: WaitAndCleanTGEvent"); if (completionTGEvent) { completionTGEvent->Wait(); } // allow completionTGEvent to go out of scope and be deleted } void Scene::WaitAndCleanCompletionJob(AZ::JobCompletion*& completionJob) { if (completionJob) { AZ_PROFILE_SCOPE(RPI, "Scene: WaitAndCleanCompletionJob"); //[GFX TODO]: the completion job should start earlier and wait for completion here completionJob->StartAndWaitForCompletion(); delete completionJob; completionJob = nullptr; } } void Scene::ConnectEvent(PrepareSceneSrgEvent::Handler& handler) { handler.Connect(m_prepareSrgEvent); } void Scene::PrepareSceneSrg() { if (m_srg) { if (m_timeInputIndex.IsValid()) { m_srg->SetConstant(m_timeInputIndex, m_simulationTime); } // signal any handlers to update values for their partial scene srg m_prepareSrgEvent.Signal(m_srg.get()); m_srg->Compile(); } } void Scene::CollectDrawPacketsTaskGraph() { AZ_PROFILE_SCOPE(RPI, "CollectDrawPacketsTaskGraph"); AZ::TaskGraphEvent collectDrawPacketsTGEvent; static const AZ::TaskDescriptor collectDrawPacketsTGDesc{"RPI_Scene_PrepareRender_CollectDrawPackets", "Graphics"}; AZ::TaskGraph collectDrawPacketsTG; // Launch FeatureProcessor::Render() taskgraphs for (auto& fp : m_featureProcessors) { collectDrawPacketsTG.AddTask( collectDrawPacketsTGDesc, [this, &fp]() { fp->Render(m_renderPacket); }); } collectDrawPacketsTG.Submit(&collectDrawPacketsTGEvent); // Launch CullingSystem::ProcessCullables() jobs (will run concurrently with FeatureProcessor::Render() jobs if m_parallelOctreeTraversal) const bool parallelOctreeTraversal = m_cullingScene->GetDebugContext().m_parallelOctreeTraversal; m_cullingScene->BeginCulling(m_renderPacket.m_views); static const AZ::TaskDescriptor processCullablesDescriptor{"AZ::RPI::Scene::ProcessCullables", "Graphics"}; AZ::TaskGraphEvent processCullablesTGEvent; AZ::TaskGraph processCullablesTG; if (parallelOctreeTraversal) { for (ViewPtr& viewPtr : m_renderPacket.m_views) { 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); } collectDrawPacketsTGEvent.Wait(); if (processCullablesHasWork) // skip the wait if there is no work to do { processCullablesTGEvent.Wait(); } } void Scene::CollectDrawPacketsJobs() { AZ_PROFILE_SCOPE(RPI, "CollectDrawPacketsJobs"); AZ::JobCompletion* collectDrawPacketsCompletion = aznew AZ::JobCompletion(); // Launch FeatureProcessor::Render() jobs for (auto& fp : m_featureProcessors) { const auto renderLambda = [this, &fp]() { fp->Render(m_renderPacket); }; AZ::Job* renderJob = AZ::CreateJobFunction(AZStd::move(renderLambda), true, nullptr); //auto-deletes renderJob->SetDependent(collectDrawPacketsCompletion); renderJob->Start(); } // Launch CullingSystem::ProcessCullables() jobs (will run concurrently with FeatureProcessor::Render() jobs) const bool parallelOctreeTraversal = m_cullingScene->GetDebugContext().m_parallelOctreeTraversal; m_cullingScene->BeginCulling(m_renderPacket.m_views); for (ViewPtr& viewPtr : m_renderPacket.m_views) { AZ::Job* processCullablesJob = AZ::CreateJobFunction([this, &viewPtr](AZ::Job& thisJob) { m_cullingScene->ProcessCullablesJobs(*this, *viewPtr, thisJob); // can't call directly because ProcessCullables needs a parent job }, true, nullptr); //auto-deletes if (parallelOctreeTraversal) { processCullablesJob->SetDependent(collectDrawPacketsCompletion); processCullablesJob->Start(); } else { processCullablesJob->StartAndWaitForCompletion(); } } WaitAndCleanCompletionJob(collectDrawPacketsCompletion); } void Scene::FinalizeDrawListsTaskGraph() { AZ::TaskGraphEvent finalizeDrawListsTGEvent; static const AZ::TaskDescriptor finalizeDrawListsTGDesc{"RPI_Scene_PrepareRender_FinalizeDrawLists", "Graphics"}; AZ::TaskGraph finalizeDrawListsTG; for (auto& view : m_renderPacket.m_views) { finalizeDrawListsTG.AddTask( finalizeDrawListsTGDesc, [view, &finalizeDrawListsTGEvent]() { view->FinalizeDrawListsTG(finalizeDrawListsTGEvent); }); } finalizeDrawListsTG.Submit(&finalizeDrawListsTGEvent); finalizeDrawListsTGEvent.Wait(); } void Scene::FinalizeDrawListsJobs() { AZ::JobCompletion* finalizeDrawListsCompletion = aznew AZ::JobCompletion(); for (auto& view : m_renderPacket.m_views) { const auto finalizeDrawListsLambda = [view](AZ::Job& job) { view->FinalizeDrawListsJob(&job); }; AZ::Job* finalizeDrawListsJob = AZ::CreateJobFunction(AZStd::move(finalizeDrawListsLambda), true, nullptr); //auto-deletes finalizeDrawListsJob->SetDependent(finalizeDrawListsCompletion); finalizeDrawListsJob->Start(); } WaitAndCleanCompletionJob(finalizeDrawListsCompletion); } void Scene::PrepareRender(RHI::JobPolicy jobPolicy, float simulationTime) { AZ_PROFILE_SCOPE(RPI, "Scene: PrepareRender"); if (m_taskGraphActive) { WaitAndCleanTGEvent(AZStd::move(m_simulationFinishedTGEvent)); m_simulationFinishedTGEvent.reset(); } else { WaitAndCleanCompletionJob(m_simulationCompletion); } SceneNotificationBus::Event(GetId(), &SceneNotification::OnBeginPrepareRender); // Get active pipelines which need to be rendered and notify them frame started AZStd::vector activePipelines; { AZ_PROFILE_SCOPE(RPI, "Scene: OnStartFrame"); for (auto& pipeline : m_pipelines) { if (pipeline->NeedsRender()) { activePipelines.push_back(pipeline); pipeline->OnStartFrame(simulationTime); } } } // Return if there is no active render pipeline if (activePipelines.empty()) { SceneNotificationBus::Event(GetId(), &SceneNotification::OnEndPrepareRender); return; } // Init render packet m_renderPacket.m_views.clear(); AZ_Assert(m_cullingScene, "m_cullingScene is not initialized"); m_renderPacket.m_cullingScene = m_cullingScene; m_renderPacket.m_jobPolicy = jobPolicy; { AZ_PROFILE_SCOPE(RPI, "Setup Views"); // Collect persistent views from all pipelines to be rendered AZStd::map persistentViews; for (const auto& pipeline : activePipelines) { pipeline->CollectPersistentViews(persistentViews); } // Set combined draw list mask for each persistent view and get a global draw list mask for late use for (auto& viewInfo : persistentViews) { viewInfo.first->SetDrawListMask(viewInfo.second); m_renderPacket.m_views.push_back(viewInfo.first); m_renderPacket.m_drawListMask |= viewInfo.second; } // Collect transient views from each feature processor FeatureProcessor::PrepareViewsPacket prepareViewPacket; AZStd::vector> transientViews; for (auto& fp : m_featureProcessors) { fp->PrepareViews(prepareViewPacket, transientViews); } // Save transient views to RenderPacket and add them to each pipeline. for (auto view : transientViews) { m_renderPacket.m_views.push_back(view.second); m_renderPacket.m_drawListMask |= view.second->GetDrawListMask(); for (auto& itr : activePipelines) { itr->AddTransientView(view.first, view.second); } } } { if (m_taskGraphActive) { CollectDrawPacketsTaskGraph(); } else { CollectDrawPacketsJobs(); } m_cullingScene->EndCulling(); // Add dynamic draw data for all the views if (m_dynamicDrawSystem) { m_dynamicDrawSystem->SubmitDrawData(this, m_renderPacket.m_views); } } { AZ_PROFILE_SCOPE(RPI, "FinalizeDrawLists"); if (jobPolicy == RHI::JobPolicy::Serial || m_renderPacket.m_views.size() <= 1) // FinalizeDrawListsX both immediately wait for the job to complete, skip job if only 1 job would be generated { for (auto& view : m_renderPacket.m_views) { view->FinalizeDrawListsJob(nullptr); } } else { if (m_taskGraphActive) { FinalizeDrawListsTaskGraph(); } else { FinalizeDrawListsJobs(); } } } { AZ_PROFILE_SCOPE(RPI, "Scene OnEndPrepareRender"); SceneNotificationBus::Event(GetId(), &SceneNotification::OnEndPrepareRender); } } void Scene::OnFrameEnd() { AZ_PROFILE_SCOPE(RPI, "Scene: OnFrameEnd"); for (auto& pipeline : m_pipelines) { if (pipeline->NeedsRender()) { pipeline->OnFrameEnd(); } } for (auto& fp : m_featureProcessors) { fp->OnRenderEnd(); } } void Scene::UpdateSrgs() { PrepareSceneSrg(); for (auto& view : m_renderPacket.m_views) { view->UpdateSrg(); } } const RHI::ShaderResourceGroup* Scene::GetRHIShaderResourceGroup() const { if (m_srg.get()) { return m_srg.get()->GetRHIShaderResourceGroup(); } else { return nullptr; } } Data::Instance Scene::GetShaderResourceGroup() const { return m_srg; } const SceneId& Scene::GetId() const { return m_id; } AZ::Name Scene::GetName() const { return m_name; } bool Scene::SetDefaultRenderPipeline(const RenderPipelineId& pipelineId) { RenderPipelinePtr newPipeline = GetRenderPipeline(pipelineId); if (newPipeline) { m_defaultPipeline = newPipeline; return true; } return false; } RenderPipelinePtr Scene::GetDefaultRenderPipeline() const { return m_defaultPipeline; } const AZStd::vector& Scene::GetRenderPipelines() const { return m_pipelines; } void Scene::OnSceneNotifictaionHandlerConnected(SceneNotification* handler) { for (auto renderPipeline : m_pipelines) { handler->OnRenderPipelineAdded(renderPipeline); const RenderPipeline::PipelineViewMap& viewsInfo = renderPipeline->GetPipelineViews(); for (RenderPipeline::PipelineViewMap::const_iterator itr = viewsInfo.begin(); itr != viewsInfo.end(); itr++) { if (itr->second.m_type == PipelineViewType::Persistent && itr->second.m_views.size() == 1) { handler->OnRenderPipelinePersistentViewChanged(renderPipeline.get(), itr->first, itr->second.m_views[0], nullptr); } } } } bool Scene::ConfigurePipelineState(RHI::DrawListTag drawListTag, RHI::PipelineStateDescriptorForDraw& outPipelineState) const { auto pipelineStatesItr = m_pipelineStatesLookup.find(drawListTag); if (pipelineStatesItr != m_pipelineStatesLookup.end()) { const PipelineStateList& pipelineStateList = pipelineStatesItr->second; AZ_Error("RPI", pipelineStateList.size() == 1, "Scene::ConfigurePipelineState called for drawListTag [%s] which has [%zu] different pipeline states." "Using first pipeline state by default.", RHI::RHISystemInterface::Get()->GetDrawListTagRegistry()->GetName(drawListTag).GetCStr(), pipelineStateList.size() ); AZ_Assert(pipelineStateList.size() > 0, "Scene::ConfigurePipelineState called for drawListTag [%s] which has [%zu] pipeline states.", RHI::RHISystemInterface::Get()->GetDrawListTagRegistry()->GetName(drawListTag).GetCStr(), pipelineStateList.size() ); outPipelineState.m_renderAttachmentConfiguration = pipelineStateList[0].m_renderAttachmentConfiguration; outPipelineState.m_renderStates.m_multisampleState = pipelineStateList[0].m_multisampleState; return true; } return false; } const Scene::PipelineStateList& Scene::GetPipelineStates(RHI::DrawListTag drawListTag) const { static const PipelineStateList emptyList; auto pipelineStatesItr = m_pipelineStatesLookup.find(drawListTag); if (pipelineStatesItr != m_pipelineStatesLookup.end()) { return pipelineStatesItr->second; } return emptyList; } bool Scene::HasOutputForPipelineState(RHI::DrawListTag drawListTag) const { return m_pipelineStatesLookup.find(drawListTag) != m_pipelineStatesLookup.end(); } void Scene::RebuildPipelineStatesLookup() { AZ_PROFILE_SCOPE(RPI, "Scene: RebuildPipelineStatesLookup"); m_pipelineStatesLookup.clear(); AZStd::queue parents; for (auto renderPipeline : m_pipelines) { parents.push(renderPipeline->GetRootPass().get()); // Visit all the passes under this root pass while (!parents.empty()) { // Visit the first parent pass in the parents queue then remove it from the queue ParentPass* parent = parents.front(); parents.pop(); for (const Ptr& child : parent->GetChildren()) { ParentPass* asParent = child->AsParent(); if (asParent) { // Add to parent queue for late visiting parents.push(asParent); } else if (child->HasDrawListTag()) { // only need to process RasterPass since it and its derived classes need to use draw list tag to // acquire OutputAttachmentLayout and MultisampleState RasterPass* rasterPass = azrtti_cast(child.get()); if (rasterPass == nullptr) { continue; } RHI::DrawListTag drawListTag = child->GetDrawListTag(); auto pipelineStatesItr = m_pipelineStatesLookup.find(drawListTag); if (pipelineStatesItr == m_pipelineStatesLookup.end()) { m_pipelineStatesLookup[drawListTag].push_back(); m_pipelineStatesLookup[drawListTag][0].m_multisampleState = rasterPass->GetMultisampleState(); m_pipelineStatesLookup[drawListTag][0].m_renderAttachmentConfiguration = rasterPass->GetRenderAttachmentConfiguration(); rasterPass->SetPipelineStateDataIndex(0); } else { PipelineStateList& pipelineStateList = pipelineStatesItr->second; size_t size = pipelineStateList.size(); u32 index = 0; auto multisampleState = rasterPass->GetMultisampleState(); auto renderAttachmentConfg = rasterPass->GetRenderAttachmentConfiguration(); for (; index < size; ++index) { PipelineStateData stateData = pipelineStateList[index]; if (stateData.m_multisampleState == multisampleState && stateData.m_renderAttachmentConfiguration == renderAttachmentConfg) { break; } } if (index < size) { // Found matching pipeline state data, set index rasterPass->SetPipelineStateDataIndex(index); } else { // No match found, add new pipeline state data pipelineStateList.push_back(); pipelineStateList[size].m_multisampleState = rasterPass->GetMultisampleState(); pipelineStateList[size].m_renderAttachmentConfiguration = rasterPass->GetRenderAttachmentConfiguration(); rasterPass->SetPipelineStateDataIndex(static_cast(size)); } } } } } } } RenderPipelinePtr Scene::FindRenderPipelineForWindow(AzFramework::NativeWindowHandle windowHandle) { for (auto renderPipeline : m_pipelines) { if (renderPipeline->GetWindowHandle() == windowHandle) { return renderPipeline; } } return nullptr; } } }