// Copyright (C) 2009-present, Panagiotis Christopoulos Charitos and contributors. // All rights reserved. // Code licensed under the BSD License. // http://www.anki3d.org/LICENSE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace anki { ANKI_SVAR(SceneUpdateTime, StatCategory::kTime, "All scene update", StatFlag::kMilisecond | StatFlag::kShowAverage | StatFlag::kMainThreadUpdates) ANKI_SVAR(SceneComponentsUpdated, StatCategory::kScene, "Scene components updated per frame", StatFlag::kZeroEveryFrame) ANKI_SVAR(SceneNodesUpdated, StatCategory::kScene, "Scene nodes updated per frame", StatFlag::kZeroEveryFrame) constexpr U32 kUpdateNodeBatchSize = 10; class SceneGraph::UpdateSceneNodesCtx { public: class PerThread { public: DynamicArray> m_nodesForDeletion; LightComponent* m_dirLightComponent = nullptr; SkyboxComponent* m_skyboxComponent = nullptr; Vec3 m_sceneMin = Vec3(kMaxF32); Vec3 m_sceneMax = Vec3(kMinF32); Bool m_multipleDirLights = false; Bool m_multipleSkyboxes = false; PerThread() : m_nodesForDeletion(&SceneGraph::getSingleton().m_framePool) { } }; IntrusiveList::Iterator m_crntNode; SpinLock m_crntNodeLock; Second m_prevUpdateTime = 0.0; Second m_crntTime = 0.0; DynamicArray> m_perThread; Bool m_forceUpdateSceneBounds = false; UpdateSceneNodesCtx(U32 threadCount) : m_perThread(&SceneGraph::getSingleton().m_framePool) { m_perThread.resize(threadCount); } }; SceneGraph::SceneGraph() { } SceneGraph::~SceneGraph() { while(!m_nodesForRegistration.isEmpty()) { deleteInstance(SceneMemoryPool::getSingleton(), m_nodesForRegistration.popBack()); } while(!m_nodes.isEmpty()) { deleteInstance(SceneMemoryPool::getSingleton(), m_nodes.popBack()); } #define ANKI_CAT_TYPE(arrayName, gpuSceneType, id, cvarName) GpuSceneArrays::arrayName::freeSingleton(); #include RenderStateBucketContainer::freeSingleton(); } Error SceneGraph::init(AllocAlignedCallback allocCallback, void* allocCallbackData) { SceneMemoryPool::allocateSingleton(allocCallback, allocCallbackData); m_framePool.init(allocCallback, allocCallbackData, 1_MB, 2.0, 0, true, "SceneGraphFramePool"); #define ANKI_CAT_TYPE(arrayName, gpuSceneType, id, cvarName) GpuSceneArrays::arrayName::allocateSingleton(U32(cvarName)); #include // Init the default main camera m_defaultMainCam = newSceneNode("_MainCamera"); CameraComponent* camc = m_defaultMainCam->newComponent(); camc->setPerspective(0.1f, 1000.0f, toRad(60.0f), (1080.0f / 1920.0f) * toRad(60.0f)); m_mainCam = m_defaultMainCam; RenderStateBucketContainer::allocateSingleton(); // Construct a few common nodes if(g_cvarCoreDisplayStats > 0) { StatsUiNode* statsNode = newSceneNode("_StatsUi"); statsNode->setFpsOnly(g_cvarCoreDisplayStats == 1); } newSceneNode("_DevConsole"); EditorUiNode* editorNode = newSceneNode("_Editor"); editorNode->getFirstComponentOfType().setEnabled(false); m_editorUi = editorNode; return Error::kNone; } SceneNode& SceneGraph::findSceneNode(const CString& name) { SceneNode* node = tryFindSceneNode(name); ANKI_ASSERT(node); return *node; } SceneNode* SceneGraph::tryFindSceneNode(const CString& name) { // Search registered nodes auto it = m_nodesDict.find(name); if(it != m_nodesDict.getEnd()) { return *it; } // Didn't found it, search those up for registration LockGuard lock(m_nodesForRegistrationMtx); for(SceneNode& node : m_nodesForRegistration) { if(node.getName() == name) { return &node; } } return nullptr; } void SceneGraph::update(Second prevUpdateTime, Second crntTime) { ANKI_ASSERT(m_mainCam); ANKI_TRACE_SCOPED_EVENT(SceneUpdate); const Second startUpdateTime = HighRezTimer::getCurrentTime(); // Reset the framepool m_framePool.reset(); // Register new nodes while(!m_nodesForRegistration.isEmpty()) { SceneNode* node = m_nodesForRegistration.popFront(); // Add to dict if it has a name if(node->getName() != "Unnamed") { if(m_nodesDict.find(node->getName()) != m_nodesDict.getEnd()) { ANKI_SCENE_LOGE("Node with the same name already exists. New node will not be searchable: %s", node->getName().cstr()); } else { m_nodesDict.emplace(node->getName(), node); } } // Add to list m_nodes.pushBack(node); ++m_nodesCount; } // Re-index renamed nodes for(auto& pair : m_nodesRenamed) { SceneNode& node = *pair.first; CString oldName = pair.second; auto it = m_nodesDict.find(oldName); if(it != m_nodesDict.getEnd()) { m_nodesDict.erase(it); } else { ANKI_ASSERT(oldName == "Unnamed" && "Didn't found the SceneNode and its name wasn't unnamed"); } if(node.getName() != "Unnamed") { if(m_nodesDict.find(node.getName()) != m_nodesDict.getEnd()) { ANKI_SCENE_LOGE("Node with the same name already exists. New node will not be searchable: %s", node.getName().cstr()); } else { m_nodesDict.emplace(node.getName(), &node); } } } m_nodesRenamed.destroy(); // Update physics PhysicsWorld::getSingleton().update(crntTime - prevUpdateTime); // Before the update wake some threads with dummy work for(U i = 0; i < 2; i++) { CoreThreadJobManager::getSingleton().dispatchTask([]([[maybe_unused]] U32 tid) {}); } // Update events { ANKI_TRACE_SCOPED_EVENT(EventsUpdate); m_events.updateAllEvents(prevUpdateTime, crntTime); } // Update events and scene nodes UpdateSceneNodesCtx updateCtx(CoreThreadJobManager::getSingleton().getThreadCount()); { ANKI_TRACE_SCOPED_EVENT(SceneNodesUpdate); updateCtx.m_crntNode = m_nodes.getBegin(); updateCtx.m_prevUpdateTime = prevUpdateTime; updateCtx.m_crntTime = crntTime; updateCtx.m_forceUpdateSceneBounds = (m_frame % kForceSetSceneBoundsFrameCount) == 0; for(U i = 0; i < CoreThreadJobManager::getSingleton().getThreadCount(); i++) { CoreThreadJobManager::getSingleton().dispatchTask([this, &updateCtx](U32 tid) { updateNodes(tid, updateCtx); }); } CoreThreadJobManager::getSingleton().waitForAllTasksToFinish(); } // Update scene bounds { Vec3 sceneMin = Vec3(kMaxF32); Vec3 sceneMax = Vec3(kMinF32); for(U32 tid = 0; tid < updateCtx.m_perThread.getSize(); ++tid) { const UpdateSceneNodesCtx::PerThread& thread = updateCtx.m_perThread[tid]; sceneMin = sceneMin.min(thread.m_sceneMin); sceneMax = sceneMax.max(thread.m_sceneMax); } if(sceneMin.x() != kMaxF32) { if(updateCtx.m_forceUpdateSceneBounds) { m_sceneMin = sceneMin; m_sceneMax = sceneMax; } else { m_sceneMin = m_sceneMin.min(sceneMin); m_sceneMax = m_sceneMax.max(sceneMax); } } } // Set some unique components { m_activeDirLight = nullptr; m_activeSkybox = nullptr; Bool bMultipleDirLights = false; Bool bMultipleSkyboxes = false; for(U32 tid = 0; tid < updateCtx.m_perThread.getSize(); ++tid) { const UpdateSceneNodesCtx::PerThread& thread = updateCtx.m_perThread[tid]; bMultipleDirLights = bMultipleDirLights || thread.m_multipleDirLights; bMultipleSkyboxes = bMultipleSkyboxes || thread.m_multipleSkyboxes; if(thread.m_dirLightComponent) { if(m_activeDirLight) { bMultipleDirLights = true; if(thread.m_dirLightComponent->getUuid() < m_activeDirLight->getUuid()) { m_activeDirLight = thread.m_dirLightComponent; } } else { m_activeDirLight = thread.m_dirLightComponent; } } if(thread.m_skyboxComponent) { if(m_activeSkybox) { bMultipleSkyboxes = true; if(thread.m_skyboxComponent->getUuid() < m_activeSkybox->getUuid()) { m_activeSkybox = thread.m_skyboxComponent; } } else { m_activeSkybox = thread.m_skyboxComponent; } } } if(bMultipleDirLights) { ANKI_SCENE_LOGW("There are multiple dir lights in the scene. Choosing one to be active"); } if(bMultipleSkyboxes) { ANKI_SCENE_LOGW("There are multiple skyboxes in the scene. Choosing one to be active"); } } // Cleanup for(U32 tid = 0; tid < updateCtx.m_perThread.getSize(); ++tid) { const auto& thread = updateCtx.m_perThread[tid]; for(SceneNode* node : thread.m_nodesForDeletion) { // Remove from the graph m_nodes.erase(node); ANKI_ASSERT(m_nodesCount > 0); --m_nodesCount; if(m_mainCam != m_defaultMainCam && m_mainCam == node) { m_mainCam = m_defaultMainCam; } // Remove from dict if(node->getName() != "Unnamed") { auto it = m_nodesDict.find(node->getName()); ANKI_ASSERT(it != m_nodesDict.getEnd()); if(*it == node) { m_nodesDict.erase(it); } } deleteInstance(SceneMemoryPool::getSingleton(), node); } } // Misc #define ANKI_CAT_TYPE(arrayName, gpuSceneType, id, cvarName) GpuSceneArrays::arrayName::getSingleton().flush(); #include g_svarSceneUpdateTime.set((HighRezTimer::getCurrentTime() - startUpdateTime) * 1000.0); ++m_frame; } void SceneGraph::updateNode(U32 tid, SceneNode& node, UpdateSceneNodesCtx& ctx) { ANKI_TRACE_INC_COUNTER(SceneNodeUpdated, 1); UpdateSceneNodesCtx::PerThread& thread = ctx.m_perThread[tid]; // Components update SceneComponentUpdateInfo componentUpdateInfo(ctx.m_prevUpdateTime, ctx.m_crntTime, ctx.m_forceUpdateSceneBounds, m_checkForResourceUpdates); componentUpdateInfo.m_framePool = &m_framePool; U32 sceneComponentUpdatedCount = 0; node.iterateComponents([&](SceneComponent& comp) { componentUpdateInfo.m_node = &node; Bool updated = false; comp.update(componentUpdateInfo, updated); if(updated) { ANKI_TRACE_INC_COUNTER(SceneComponentUpdated, 1); comp.setTimestamp(GlobalFrameIndex::getSingleton().m_value); ++sceneComponentUpdatedCount; } if(comp.getType() == SceneComponentType::kLight) { LightComponent& lc = static_cast(comp); if(lc.getLightComponentType() == LightComponentType::kDirectional) { if(thread.m_dirLightComponent) { thread.m_multipleDirLights = true; // Try to choose the same dir light in a deterministic way if(lc.getUuid() < thread.m_dirLightComponent->getUuid()) { ctx.m_perThread[tid].m_dirLightComponent = &lc; } } else { ctx.m_perThread[tid].m_dirLightComponent = &lc; } } } else if(comp.getType() == SceneComponentType::kSkybox) { SkyboxComponent& skyc = static_cast(comp); if(thread.m_skyboxComponent) { thread.m_multipleSkyboxes = true; // Try to choose the same skybox in a deterministic way if(skyc.getUuid() < thread.m_skyboxComponent->getUuid()) { ctx.m_perThread[tid].m_skyboxComponent = &skyc; } } else { ctx.m_perThread[tid].m_skyboxComponent = &skyc; } } }); // Frame update { if(sceneComponentUpdatedCount) { node.setComponentMaxTimestamp(GlobalFrameIndex::getSingleton().m_value); g_svarSceneComponentsUpdated.increment(sceneComponentUpdatedCount); g_svarSceneNodesUpdated.increment(1); } else { // No components or nothing updated, don't change the timestamp } node.frameUpdate(ctx.m_prevUpdateTime, ctx.m_crntTime); } // Update children node.visitChildrenMaxDepth(0, [&](SceneNode& child) { updateNode(tid, child, ctx); return FunctorContinue::kContinue; }); ctx.m_perThread[tid].m_sceneMin = ctx.m_perThread[tid].m_sceneMin.min(componentUpdateInfo.m_sceneMin); ctx.m_perThread[tid].m_sceneMax = ctx.m_perThread[tid].m_sceneMax.max(componentUpdateInfo.m_sceneMax); } void SceneGraph::updateNodes(U32 tid, UpdateSceneNodesCtx& ctx) { ANKI_TRACE_SCOPED_EVENT(SceneNodeUpdate); IntrusiveList::ConstIterator end = m_nodes.getEnd(); Bool quit = false; while(!quit) { // Fetch a batch of scene nodes that don't have parent Array batch; U batchSize = 0; { LockGuard lock(ctx.m_crntNodeLock); while(1) { if(batchSize == batch.getSize()) { break; } if(ctx.m_crntNode == end) { quit = true; break; } SceneNode& node = *ctx.m_crntNode; if(node.isMarkedForDeletion()) { ctx.m_perThread[tid].m_nodesForDeletion.emplaceBack(&node); } else if(node.getParent() == nullptr) { batch[batchSize++] = &node; } else { // Ignore } ++ctx.m_crntNode; } } // Process nodes for(U i = 0; i < batchSize; ++i) { updateNode(tid, *batch[i], ctx); } } } const SceneNode& SceneGraph::getActiveCameraNode() const { ANKI_ASSERT(m_mainCam); if(ANKI_EXPECT(m_mainCam->hasComponent())) { return *m_mainCam; } else { return *m_defaultMainCam; } } void SceneGraph::setActiveCameraNode(SceneNode* cam) { if(cam) { m_mainCam = cam; } else { m_mainCam = m_defaultMainCam; } } void SceneGraph::sceneNodeChangedName(SceneNode& node, CString oldName) { LockGuard lock(m_nodesForRegistrationMtx); m_nodesRenamed.emplaceBack(std::pair(&node, oldName)); } Error SceneGraph::saveToTextFile(CString filename) { File file; ANKI_CHECK(file.open(filename, FileOpenFlag::kWrite)); TextSceneSerializer serializer(&file, true); U32 version = kSceneBinaryVersion; ANKI_SERIALIZE(version, 1); #define ANKI_DEFINE_SCENE_COMPONENT(name, weight, sceneNodeCanHaveMany, icon, serializable) \ if(serializable) \ { \ U32 name##Count = m_componentArrays.get##name##s().getSize(); \ ANKI_SERIALIZE(name##Count, 1); \ for(SceneComponent & comp : m_componentArrays.get##name##s()) \ { \ U32 uuid = comp.getUuid(); \ ANKI_SERIALIZE(uuid, 1); \ ANKI_CHECK(comp.serialize(serializer)); \ } \ } #include // Scene nodes Error err = Error::kNone; visitNodes([&](SceneNode& node) { if(node.getParent() != nullptr) { // Skip non-root nodes return FunctorContinue::kContinue; } node.visitThisAndChildren([&](SceneNode& node) { err = node.serializeCommon(serializer); if(err) { return FunctorContinue::kStop; } return FunctorContinue::kContinue; }); if(err) { return FunctorContinue::kStop; } return FunctorContinue::kContinue; }); if(err) { return err; } return Error::kNone; } } // end namespace anki