/* * 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 #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()); } 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() { m_id = Uuid::CreateRandom(); m_cullingScene = aznew CullingScene(); SceneRequestBus::Handler::BusConnect(m_id); m_drawFilterTagRegistry = RHI::DrawFilterTagRegistry::Create(); } Scene::~Scene() { 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::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); 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(); AZ_Assert(!m_id.IsNull(), "RPI::Scene needs to have a valid uuid."); SceneNotificationBus::Event(m_id, &SceneNotification::OnRenderPipelineAdded, pipeline); } void Scene::RemoveRenderPipeline(const RenderPipelineId& pipelineId) { 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::Simulate([[maybe_unused]] const TickTimeInfo& tickInfo, RHI::JobPolicy jobPolicy) { AZ_ATOM_PROFILE_FUNCTION("RPI", "Scene: Simulate"); m_simulationTime = tickInfo.m_currentGameTime; // If previous simulation job wasn't done, wait for it to finish. WaitAndCleanCompletionJob(m_simulationCompletion); if (jobPolicy == RHI::JobPolicy::Serial) { for (auto& fp : m_featureProcessors) { fp->Simulate(m_simulatePacket); } } else { // 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]() { featureProcessor->Simulate(m_simulatePacket); }; 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::WaitAndCleanCompletionJob(AZ::JobCompletion*& completionJob) { if (completionJob) { AZ_ATOM_PROFILE_FUNCTION("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) { // Set value for constants defined in SceneTimeSrg.azsli RHI::ShaderInputConstantIndex timeIndex = m_srg->FindShaderInputConstantIndex(Name{ "m_time" }); if (timeIndex.IsValid()) { m_srg->SetConstant(timeIndex, m_simulationTime); } // signal any handlers to update values for their partial scene srg m_prepareSrgEvent.Signal(m_srg.get()); m_srg->Compile(); } } void Scene::PrepareRender(const TickTimeInfo& tickInfo, RHI::JobPolicy jobPolicy) { AZ_ATOM_PROFILE_FUNCTION("RPI", "Scene: PrepareRender"); { AZ_PROFILE_SCOPE(RPI, "WaitForSimulationCompletion"); AZ_ATOM_PROFILE_TIME_GROUP_REGION("RPI", "WaitForSimulationCompletion"); 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_ATOM_PROFILE_TIME_GROUP_REGION("RPI", "Scene: OnStartFrame"); for (auto& pipeline : m_pipelines) { if (pipeline->NeedsRender()) { activePipelines.push_back(pipeline); pipeline->OnStartFrame(tickInfo); } } } // 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_ATOM_PROFILE_TIME_GROUP_REGION("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); } } } { AZ_PROFILE_SCOPE(RPI, "CollectDrawPackets"); AZ_ATOM_PROFILE_TIME_GROUP_REGION("RPI", "CollectDrawPackets"); 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) 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->ProcessCullables(*this, *viewPtr, thisJob); }, true, nullptr); //auto-deletes if (m_cullingScene->GetDebugContext().m_parallelOctreeTraversal) { processCullablesJob->SetDependent(collectDrawPacketsCompletion); processCullablesJob->Start(); } else { processCullablesJob->StartAndWaitForCompletion(); } } WaitAndCleanCompletionJob(collectDrawPacketsCompletion); m_cullingScene->EndCulling(); // Add dynamic draw data for all the views if (m_dynamicDrawSystem) { AZ_ATOM_PROFILE_TIME_GROUP_REGION("RPI", "DynamicDraw SubmitDrawData"); m_dynamicDrawSystem->SubmitDrawData(this, m_renderPacket.m_views); } } { AZ_PROFILE_BEGIN(RPI, "FinalizeDrawLists"); AZ_ATOM_PROFILE_TIME_GROUP_REGION("RPI", "FinalizeDrawLists"); if (jobPolicy == RHI::JobPolicy::Serial) { for (auto& view : m_renderPacket.m_views) { view->FinalizeDrawLists(); } AZ_PROFILE_END(RPI); } else { AZ::JobCompletion* finalizeDrawListsCompletion = aznew AZ::JobCompletion(); for (auto& view : m_renderPacket.m_views) { const auto finalizeDrawListsLambda = [view]() { view->FinalizeDrawLists(); }; AZ::Job* finalizeDrawListsJob = AZ::CreateJobFunction(AZStd::move(finalizeDrawListsLambda), true, nullptr); //auto-deletes finalizeDrawListsJob->SetDependent(finalizeDrawListsCompletion); finalizeDrawListsJob->Start(); } AZ_PROFILE_END(RPI); WaitAndCleanCompletionJob(finalizeDrawListsCompletion); } } { AZ_ATOM_PROFILE_TIME_GROUP_REGION("RPI", "Scene OnEndPrepareRender"); SceneNotificationBus::Event(GetId(), &SceneNotification::OnEndPrepareRender); } } void Scene::OnFrameEnd() { AZ_ATOM_PROFILE_FUNCTION("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; } 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_ATOM_PROFILE_FUNCTION("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; } } }