From 35d87141346f594d241e483927a5bb81d5d80371 Mon Sep 17 00:00:00 2001 From: fleroviux Date: Sat, 25 May 2024 01:20:56 +0200 Subject: [PATCH] Zephyr: Scene: optimize transform updates --- app/next/src/main_window.cpp | 31 +++++------ app/next/src/main_window.hpp | 4 +- zephyr/math/include/zephyr/math/rotation.hpp | 17 +++--- zephyr/scene/CMakeLists.txt | 2 + zephyr/scene/include/zephyr/scene/node.hpp | 34 ++++++++++-- zephyr/scene/include/zephyr/scene/scene.hpp | 53 +++++++++++++++++++ .../scene/include/zephyr/scene/transform.hpp | 14 ++++- zephyr/scene/src/node.cpp | 13 +++++ 8 files changed, 135 insertions(+), 33 deletions(-) create mode 100644 zephyr/scene/include/zephyr/scene/scene.hpp create mode 100644 zephyr/scene/src/node.cpp diff --git a/app/next/src/main_window.cpp b/app/next/src/main_window.cpp index d6c752b..3bb2db3 100644 --- a/app/next/src/main_window.cpp +++ b/app/next/src/main_window.cpp @@ -7,7 +7,7 @@ #include "main_window.hpp" static const bool enable_validation_layers = true; -static const bool benchmark_scene_size = false; +static const bool benchmark_scene_size = true; namespace zephyr { @@ -58,7 +58,7 @@ namespace zephyr { switch(key_event->keysym.sym) { case SDLK_z: { if(m_behemoth_scene) { - m_scene_root->Remove(m_behemoth_scene.get()); + m_scene->GetRoot()->Remove(m_behemoth_scene.get()); m_behemoth_scene.reset(); } break; @@ -94,14 +94,8 @@ namespace zephyr { } void MainWindow::RenderFrame() { - m_scene_root->Traverse([&](SceneNode* node) { - node->GetTransform().UpdateLocal(); - node->GetTransform().UpdateWorld(); - return true; - }); - - m_render_engine->RenderScene(m_scene_root.get()); - + m_scene->UpdateTransforms(); + m_render_engine->RenderScene(m_scene->GetRoot()); m_frame++; UpdateFramesPerSecondCounter(); @@ -167,9 +161,9 @@ namespace zephyr { } void MainWindow::CreateScene() { - m_scene_root = SceneNode::New(); + m_scene = std::make_unique(); - m_camera_node = m_scene_root->CreateChild("RenderCamera"); + m_camera_node = m_scene->GetRoot()->CreateChild("RenderCamera"); m_camera_node->CreateComponent(45.0f, 16.f / 9.f, 0.01f, 100.f); m_camera_node->GetTransform().SetPosition({0.f, 0.f, 5.f}); @@ -177,23 +171,22 @@ namespace zephyr { std::shared_ptr gltf_scene_1 = gltf_loader.Parse("models/DamagedHelmet/DamagedHelmet.gltf"); gltf_scene_1->GetTransform().SetPosition({1.0f, 0.0f, -5.0f}); gltf_scene_1->GetTransform().GetRotation().SetFromEuler(1.5f, 0.0f, 0.0f); - m_scene_root->Add(std::move(gltf_scene_1)); + m_scene->GetRoot()->Add(std::move(gltf_scene_1)); - m_scene_root->Add(gltf_loader.Parse("models/triangleWithoutIndices/TriangleWithoutIndices.gltf")); - //m_scene_root->Add(gltf_loader.Parse("models/triangle/Triangle.gltf")); + m_scene->GetRoot()->Add(gltf_loader.Parse("models/triangleWithoutIndices/TriangleWithoutIndices.gltf")); m_behemoth_scene = gltf_loader.Parse("models/Behemoth/scene.gltf"); m_behemoth_scene->GetTransform().SetPosition({-1.0f, 0.0f, -5.0f}); m_behemoth_scene->GetTransform().GetRotation().SetFromEuler(-M_PI * 0.5, M_PI, 0.0f); m_behemoth_scene->GetTransform().SetScale({0.5f, 0.5f, 0.5f}); - m_scene_root->Add(m_behemoth_scene); + m_scene->GetRoot()->Add(m_behemoth_scene); } void MainWindow::CreateBenchmarkScene() { - m_scene_root = SceneNode::New(); + m_scene = std::make_unique(); // TODO(fleroviux): engine crashes when there is no camera in the scene! VERY BAD!!! - m_camera_node = m_scene_root->CreateChild("RenderCamera"); + m_camera_node = m_scene->GetRoot()->CreateChild("RenderCamera"); m_camera_node->CreateComponent(45.0f, 16.f / 9.f, 0.01f, 100.f); m_camera_node->GetTransform().SetPosition({0.f, 0.f, 5.f}); @@ -268,7 +261,7 @@ namespace zephyr { for(int x = -grid_size / 2; x < grid_size / 2; x++) { for(int y = -grid_size / 2; y < grid_size / 2; y++) { for(int z = -grid_size / 2; z < grid_size / 2; z++) { - std::shared_ptr cube = m_scene_root->CreateChild("Cube"); + std::shared_ptr cube = m_scene->GetRoot()->CreateChild("Cube"); cube->CreateComponent(cube_geometry, std::shared_ptr{}); cube->GetTransform().SetPosition({(f32)x, (f32)y, (f32)-z}); cube->GetTransform().SetScale({0.1, 0.1, 0.1}); diff --git a/app/next/src/main_window.hpp b/app/next/src/main_window.hpp index 9134aa1..53d90b9 100644 --- a/app/next/src/main_window.hpp +++ b/app/next/src/main_window.hpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include #include #include @@ -41,7 +41,7 @@ namespace zephyr { void CleanupOpenGL(); std::unique_ptr m_render_engine{}; - std::shared_ptr m_scene_root{}; + std::shared_ptr m_scene{}; std::shared_ptr m_camera_node{}; std::shared_ptr m_behemoth_scene{}; diff --git a/zephyr/math/include/zephyr/math/rotation.hpp b/zephyr/math/include/zephyr/math/rotation.hpp index 58b0942..5ba88d4 100644 --- a/zephyr/math/include/zephyr/math/rotation.hpp +++ b/zephyr/math/include/zephyr/math/rotation.hpp @@ -4,13 +4,9 @@ #include #include #include +#include #include -/** - * @todo: make this class thread-safe ideally, so that at the least calling Get*() operations - * from multiple threads is safe. - */ - namespace zephyr { /** @@ -36,6 +32,10 @@ namespace zephyr { SetFromEuler(x, y, z); } + [[nodiscard]] VoidEvent& OnChange() const { + return m_event_on_change; + } + /** * Construct a Rotation from a quaternion. * @param quaternion the Quaternion @@ -84,6 +84,7 @@ namespace zephyr { // The 4x4 matrix is likely to be read and copying it now is faster than reconstructing it later. m_matrix = matrix; m_needs_euler_refresh = true; + m_event_on_change.Emit(); } /** @@ -142,6 +143,8 @@ namespace zephyr { /// Signal internally that the quaternion has changed void MarkQuaternionAsChanged() { m_needs_matrix_refresh = true; + m_needs_euler_refresh = true; + m_event_on_change.Emit(); } /// Update the 4x4 matrix from the quaternion. @@ -184,7 +187,9 @@ namespace zephyr { mutable Matrix4 m_matrix{}; ///< a 4x4 rotation matrix which is updated from the quaternion on demand. mutable Vector3 m_euler{}; ///< a vector of euler angles which is updated from the quaternion on demand. mutable bool m_needs_matrix_refresh{true}; ///< true when the 4x4 matrix (#{@link m_matrix}) is outdated and false otherwise. - mutable bool m_needs_euler_refresh{true}; ///< true whe euler angles (#{@link m_euler}) are outdated and false otherwise. + mutable bool m_needs_euler_refresh{true}; ///< true when euler angles (#{@link m_euler}) are outdated and false otherwise. + + mutable VoidEvent m_event_on_change{}; }; } // namespace zephyr diff --git a/zephyr/scene/CMakeLists.txt b/zephyr/scene/CMakeLists.txt index 3b5e24a..f56b62b 100644 --- a/zephyr/scene/CMakeLists.txt +++ b/zephyr/scene/CMakeLists.txt @@ -1,5 +1,6 @@ set(SOURCES + src/node.cpp src/transform.cpp ) @@ -8,6 +9,7 @@ set(HEADERS set(HEADERS_PUBLIC include/zephyr/scene/node.hpp + include/zephyr/scene/scene.hpp include/zephyr/scene/transform.hpp ) diff --git a/zephyr/scene/include/zephyr/scene/node.hpp b/zephyr/scene/include/zephyr/scene/node.hpp index 624f231..6e2858b 100644 --- a/zephyr/scene/include/zephyr/scene/node.hpp +++ b/zephyr/scene/include/zephyr/scene/node.hpp @@ -16,12 +16,19 @@ namespace zephyr { + class Scene; + class SceneNode : public std::enable_shared_from_this, NonCopyable, NonMoveable { struct Private {}; public: - explicit SceneNode(Private) {}; - SceneNode(Private, std::string name) : m_name{std::move(name)} {} + explicit SceneNode(Private) { + ListenForTransformChange(); + } + + SceneNode(Private, std::string name) : m_name{std::move(name)} { + ListenForTransformChange(); + } ~SceneNode() { for(const auto& child : m_children) { @@ -50,15 +57,18 @@ namespace zephyr { node->RemoveFromParent(); node->m_parent = this; node->m_parent_weak = shared_from_this(); + node->Traverse([&](SceneNode* child_node){ + child_node->m_scene = m_scene; + child_node->MarkTransformAsDirty(); + return true; + }); m_children.push_back(std::move(node)); } template std::shared_ptr CreateChild(Args&&... args) { std::shared_ptr node = New(std::forward(args)...); - node->m_parent = this; - node->m_parent_weak = shared_from_this(); - m_children.push_back(node); + Add(node); return std::move(node); } @@ -74,7 +84,12 @@ namespace zephyr { std::shared_ptr node_ptr = std::move(*it); node_ptr->m_parent = nullptr; node_ptr->m_parent_weak.reset(); + node->Traverse([](SceneNode* child_node){ + child_node->m_scene = nullptr; + return true; + }); m_children.erase(it); + return std::move(node_ptr); } @@ -159,6 +174,14 @@ namespace zephyr { } private: + friend Scene; + + void ListenForTransformChange() { + m_transform.OnChange().Subscribe([this] {MarkTransformAsDirty();}); + } + + void MarkTransformAsDirty(); + SceneNode* m_parent{}; std::weak_ptr m_parent_weak{}; std::vector> m_children{}; @@ -166,6 +189,7 @@ namespace zephyr { bool m_is_visible{true}; Transform3D m_transform{this}; std::unordered_map> m_components{}; + Scene* m_scene{}; }; } // namespace zephyr diff --git a/zephyr/scene/include/zephyr/scene/scene.hpp b/zephyr/scene/include/zephyr/scene/scene.hpp new file mode 100644 index 0000000..85482b6 --- /dev/null +++ b/zephyr/scene/include/zephyr/scene/scene.hpp @@ -0,0 +1,53 @@ + +#pragma once + +#include +#include + +namespace zephyr { + + class Scene { + public: + Scene() { + m_root_node = SceneNode::New("SceneRoot"); + + // TODO(fleroviux): make passing the scene into the node less dirty. + m_root_node->m_scene = this; + + // TODO(fleroviux): kinda sucks that we have to do this. + m_root_node->GetTransform().UpdateLocal(); + m_root_node->GetTransform().UpdateWorld(); + } + + [[nodiscard]] const SceneNode* GetRoot() const { + return m_root_node.get(); + } + + SceneNode* GetRoot() { + return m_root_node.get(); + } + + void UpdateTransforms() { + for(auto node : m_nodes_with_dirty_transforms) { + node->Traverse([](SceneNode* child_node) { + child_node->GetTransform().UpdateLocal(); + child_node->GetTransform().UpdateWorld(); + return true; + }); + } + + m_nodes_with_dirty_transforms.clear(); + } + + private: + friend SceneNode; + + void MarkSceneNodeTransformAsDirty(SceneNode* node) { + m_nodes_with_dirty_transforms.insert(node); + } + + std::shared_ptr m_root_node{}; + std::unordered_set m_nodes_with_dirty_transforms{}; + }; + +} // namespace zephyr diff --git a/zephyr/scene/include/zephyr/scene/transform.hpp b/zephyr/scene/include/zephyr/scene/transform.hpp index 48d9610..c308a01 100644 --- a/zephyr/scene/include/zephyr/scene/transform.hpp +++ b/zephyr/scene/include/zephyr/scene/transform.hpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace zephyr { @@ -11,7 +12,15 @@ namespace zephyr { class Transform3D { public: - explicit Transform3D(SceneNode* node) : m_node{node} {} + explicit Transform3D(SceneNode* node) : m_node{node} { + m_rotation.OnChange().Subscribe([this] { + OnChange().Emit(); + }); + } + + [[nodiscard]] VoidEvent& OnChange() const { + return m_event_on_change; + } [[nodiscard]] const Vector3& GetPosition() const { return m_position; @@ -19,6 +28,7 @@ namespace zephyr { void SetPosition(const Vector3& position) { m_position = position; + m_event_on_change.Emit(); } [[nodiscard]] const Vector3& GetScale() const { @@ -27,6 +37,7 @@ namespace zephyr { void SetScale(const Vector3& scale) { m_scale = scale; + m_event_on_change.Emit(); } [[nodiscard]] const Rotation& GetRotation() const { @@ -55,6 +66,7 @@ namespace zephyr { Rotation m_rotation; Matrix4 m_local_matrix; Matrix4 m_world_matrix; + mutable VoidEvent m_event_on_change{}; }; } // namespace zephyr diff --git a/zephyr/scene/src/node.cpp b/zephyr/scene/src/node.cpp new file mode 100644 index 0000000..e46f2e2 --- /dev/null +++ b/zephyr/scene/src/node.cpp @@ -0,0 +1,13 @@ + +#include +#include + +namespace zephyr { + + void SceneNode::MarkTransformAsDirty() { + if(m_scene) { + m_scene->MarkSceneNodeTransformAsDirty(this); + } + } + +} // namespace zephyr