diff --git a/zephyr/renderer/CMakeLists.txt b/zephyr/renderer/CMakeLists.txt index 0de4214..aeb2f2b 100644 --- a/zephyr/renderer/CMakeLists.txt +++ b/zephyr/renderer/CMakeLists.txt @@ -11,6 +11,7 @@ set(SOURCES src/engine/geometry_cache.cpp src/vulkan/vulkan_instance.cpp src/render_engine.cpp + src/render_scene.cpp ) set(HEADERS @@ -37,6 +38,7 @@ set(HEADERS_PUBLIC include/zephyr/renderer/vulkan/vulkan_instance.hpp include/zephyr/renderer/vulkan/vulkan_physical_device.hpp include/zephyr/renderer/render_engine.hpp + include/zephyr/renderer/render_scene.hpp ) find_package(SDL2 REQUIRED) diff --git a/zephyr/renderer/include/zephyr/renderer/engine/geometry_cache.hpp b/zephyr/renderer/include/zephyr/renderer/engine/geometry_cache.hpp index f482017..715ddeb 100644 --- a/zephyr/renderer/include/zephyr/renderer/engine/geometry_cache.hpp +++ b/zephyr/renderer/include/zephyr/renderer/engine/geometry_cache.hpp @@ -21,7 +21,7 @@ namespace zephyr { // Render Thread API: void ProcessPendingUpdates(); - RenderGeometry* GetCachedRenderGeometry(const Geometry* geometry); + RenderGeometry* GetCachedRenderGeometry(const Geometry* geometry) const; private: struct GeometryState { @@ -49,7 +49,7 @@ namespace zephyr { std::shared_ptr m_render_backend; std::unordered_map m_geometry_state_table{}; - std::unordered_map m_render_geometry_table{}; + mutable std::unordered_map m_render_geometry_table{}; std::vector m_upload_tasks{}; std::vector m_delete_tasks[2]{}; }; diff --git a/zephyr/renderer/include/zephyr/renderer/render_engine.hpp b/zephyr/renderer/include/zephyr/renderer/render_engine.hpp index 1a258d5..90c76e0 100644 --- a/zephyr/renderer/include/zephyr/renderer/render_engine.hpp +++ b/zephyr/renderer/include/zephyr/renderer/render_engine.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -26,31 +27,6 @@ namespace zephyr { void RenderScene(); private: - struct SceneNodeMeshData { - SceneNodeMeshData(const Matrix4& local_to_world, Geometry* geometry) : local_to_world{local_to_world}, geometry{geometry} {} - Matrix4 local_to_world; - Geometry* geometry; - }; - - struct SceneNodeData { - std::optional mesh_data_id{}; - std::optional camera_data_id{}; - - [[nodiscard]] bool Empty() const { - return !mesh_data_id.has_value() && - !camera_data_id.has_value(); - } - }; - - // Methods used for translating the scene graph into our internal representation. - void RebuildScene(); - void PatchScene(); - void PatchNodeMounted(SceneNode* node); - void PatchNodeRemoved(SceneNode* node); - void PatchNodeComponentMounted(SceneNode* node, std::type_index component_type); - void PatchNodeComponentRemoved(SceneNode* node, std::type_index component_type); - void PatchNodeTransformChanged(SceneNode* node); - void CreateRenderThread(); void JoinRenderThread(); void RenderThreadMain(); @@ -61,18 +37,12 @@ namespace zephyr { std::thread m_render_thread; std::atomic_bool m_render_thread_running; std::atomic_bool m_render_thread_is_waiting; - std::binary_semaphore m_caller_thread_semaphore{0}; //> Semaphore signalled by the calling thread - std::binary_semaphore m_render_thread_semaphore{1}; //> Semaphore signalled by the rendering thread + std::binary_semaphore m_caller_thread_semaphore{0}; //< Semaphore signalled by the calling thread + std::binary_semaphore m_render_thread_semaphore{1}; //< Semaphore signalled by the rendering thread GeometryCache m_geometry_cache; - std::shared_ptr m_current_scene_graph{}; - bool m_need_scene_rebuild{}; - - // Representation of the scene graph that is internal to the render engine. - std::vector m_scene_node_mesh_data{}; - std::vector m_scene_node_camera_data{}; - eastl::hash_map m_scene_node_data{}; + class RenderScene m_render_scene{}; //< Representation of the scene graph that is internal to the render engine. std::vector m_render_objects{}; RenderCamera m_render_camera{}; diff --git a/zephyr/renderer/include/zephyr/renderer/render_scene.hpp b/zephyr/renderer/include/zephyr/renderer/render_scene.hpp new file mode 100644 index 0000000..6e4c76f --- /dev/null +++ b/zephyr/renderer/include/zephyr/renderer/render_scene.hpp @@ -0,0 +1,76 @@ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace zephyr { + + class RenderScene { + public: + void SetSceneGraph(std::shared_ptr scene_graph); + void Update(); + void GetRenderObjects(std::vector& out_render_objects, const GeometryCache& geometry_cache); + void GetRenderCamera(RenderCamera& out_render_camera); + void UpdateGeometries(GeometryCache& geometry_cache); + + private: + using Entity = u32; + using EntityID = size_t; + + enum ComponentFlag : Entity { + COMPONENT_FLAG_MESH = 1ul << 0, + COMPONENT_FLAG_CAMERA = 1ul << 1 + }; + + struct Transform { + Matrix4 local_to_world; + }; + + struct Mesh { + const Geometry* geometry; + }; + + struct Camera { + Matrix4 projection; + Frustum frustum; + }; + + void RebuildScene(); + void PatchScene(); + void PatchNodeMounted(SceneNode* node); + void PatchNodeRemoved(SceneNode* node); + void PatchNodeComponentMounted(SceneNode* node, std::type_index component_type); + void PatchNodeComponentRemoved(SceneNode* node, std::type_index component_type); + void PatchNodeTransformChanged(SceneNode* node); + + EntityID GetOrCreateEntityForNode(const SceneNode* node); + + EntityID CreateEntity(); + void DestroyEntity(EntityID entity_id); + void ResizeComponentStorage(size_t capacity); + + std::shared_ptr m_current_scene_graph{}; + eastl::hash_map m_node_entity_map{}; + eastl::hash_set m_active_geometry_set{}; + bool m_require_full_rebuild{}; + + std::vector m_entities{}; + std::vector m_free_entity_list{}; + std::vector m_components_transform{}; + std::vector m_components_mesh{}; + std::vector m_components_camera{}; + std::vector m_view_mesh{}; + std::vector m_view_camera{}; + }; + +} // namespace zephyr \ No newline at end of file diff --git a/zephyr/renderer/src/engine/geometry_cache.cpp b/zephyr/renderer/src/engine/geometry_cache.cpp index 05f27e2..60586ff 100644 --- a/zephyr/renderer/src/engine/geometry_cache.cpp +++ b/zephyr/renderer/src/engine/geometry_cache.cpp @@ -60,7 +60,7 @@ namespace zephyr { ProcessPendingUploads(); } - RenderGeometry* GeometryCache::GetCachedRenderGeometry(const Geometry* geometry) { + RenderGeometry* GeometryCache::GetCachedRenderGeometry(const Geometry* geometry) const { if(!m_render_geometry_table.contains(geometry)) { ZEPHYR_PANIC("Bad attempt to retrieve cached render geometry of a geometry which isn't cached.") } diff --git a/zephyr/renderer/src/render_engine.cpp b/zephyr/renderer/src/render_engine.cpp index 131e8ce..96d9894 100644 --- a/zephyr/renderer/src/render_engine.cpp +++ b/zephyr/renderer/src/render_engine.cpp @@ -17,10 +17,7 @@ namespace zephyr { } void RenderEngine::SetSceneGraph(std::shared_ptr scene_graph) { - if(m_current_scene_graph != scene_graph) { - m_current_scene_graph = std::move(scene_graph); - m_need_scene_rebuild = true; - } + m_render_scene.SetSceneGraph(std::move(scene_graph)); } void RenderEngine::RenderScene() { @@ -31,132 +28,15 @@ namespace zephyr { m_geometry_cache.CommitPendingDeleteTaskList(); // Update the internal scene graph representation of the render engine based on changes in the scene graph. - if(m_need_scene_rebuild) { - RebuildScene(); - m_need_scene_rebuild = false; - } else { - PatchScene(); - } + m_render_scene.Update(); // Update all geometries which might be rendered in this frame. - for(const auto& game_thread_render_object : m_scene_node_mesh_data) { - m_geometry_cache.UpdateGeometry(game_thread_render_object.geometry); - } + m_render_scene.UpdateGeometries(m_geometry_cache); // Signal to the render thread that the next frame is ready m_caller_thread_semaphore.release(); } - void RenderEngine::RebuildScene() { - m_scene_node_data.clear(); - m_scene_node_mesh_data.clear(); - m_scene_node_camera_data.clear(); - - m_current_scene_graph->GetRoot()->Traverse([this](SceneNode* child_node) { - if(child_node->IsVisible()) { - PatchNodeMounted(child_node); - return true; - } - return false; - }); - } - - void RenderEngine::PatchScene() { - for(const ScenePatch& patch : m_current_scene_graph->GetScenePatches()) { - switch(patch.type) { - case ScenePatch::Type::NodeMounted: PatchNodeMounted(patch.node.get()); break; - case ScenePatch::Type::NodeRemoved: PatchNodeRemoved(patch.node.get()); break; - case ScenePatch::Type::ComponentMounted: PatchNodeComponentMounted(patch.node.get(), patch.component_type); break; - case ScenePatch::Type::ComponentRemoved: PatchNodeComponentRemoved(patch.node.get(), patch.component_type); break; - case ScenePatch::Type::NodeTransformChanged: PatchNodeTransformChanged(patch.node.get()); break; - default: ZEPHYR_PANIC("Unhandled scene patch type: {}", (int)patch.type); - } - } - } - - void RenderEngine::PatchNodeMounted(SceneNode* node) { - for(auto& [component_type, _] : node->GetComponents()) { - PatchNodeComponentMounted(node, component_type); - } - } - - void RenderEngine::PatchNodeRemoved(SceneNode* node) { - // TODO(fleroviux): this could be optimized, just unload everything. - for(auto& [component_type, _] : node->GetComponents()) { - PatchNodeComponentRemoved(node, component_type); - } - } - - void RenderEngine::PatchNodeComponentMounted(SceneNode* node, std::type_index component_type) { - if(component_type == typeid(MeshComponent)) { - SceneNodeData& node_data = m_scene_node_data[node]; - auto& mesh_component = node->GetComponent(); - - node_data.mesh_data_id = m_scene_node_mesh_data.size(); - m_scene_node_mesh_data.emplace_back(node->GetTransform().GetWorld(), mesh_component.geometry.get()); - } - - if(component_type == typeid(PerspectiveCameraComponent)) { - SceneNodeData& node_data = m_scene_node_data[node]; - auto& camera_component = node->GetComponent(); - - node_data.camera_data_id = m_scene_node_camera_data.size(); - m_scene_node_camera_data.push_back({ - .projection = camera_component.GetProjectionMatrix(), - .view = node->GetTransform().GetWorld().Inverse(), - .frustum = camera_component.GetFrustum() - }); - } - } - - void RenderEngine::PatchNodeComponentRemoved(SceneNode* node, std::type_index component_type) { - const auto node_data_match = m_scene_node_data.find(node); - - if(node_data_match == m_scene_node_data.end()) { - return; - } - - SceneNodeData& node_data = node_data_match->second; - - if(component_type == typeid(MeshComponent)) { - if(node_data.mesh_data_id.has_value()) { - // TODO(fleroviux): this breaks indices in other SceneNodeData - m_scene_node_mesh_data.erase(m_scene_node_mesh_data.begin() + node_data.mesh_data_id.value()); - node_data.mesh_data_id.reset(); - } - } - - if(component_type == typeid(PerspectiveCameraComponent)) { - if(node_data.camera_data_id.has_value()) { - // TODO(fleroviux): this breaks indices in other SceneNodeData - m_scene_node_camera_data.erase(m_scene_node_camera_data.begin() + node_data.camera_data_id.value()); - node_data.camera_data_id.reset(); - } - } - - if(m_scene_node_data[node].Empty()) { - m_scene_node_data.erase(node); - } - } - - void RenderEngine::PatchNodeTransformChanged(SceneNode* node) { - const auto node_data_match = m_scene_node_data.find(node); - - if(node_data_match == m_scene_node_data.end()) { - return; - } - - const SceneNodeData& node_data = node_data_match->second; - - if(node_data.mesh_data_id.has_value()) { - m_scene_node_mesh_data[node_data.mesh_data_id.value()].local_to_world = node->GetTransform().GetWorld(); - } - - if(node_data.camera_data_id.has_value()) { - m_scene_node_camera_data[node_data.camera_data_id.value()].view = node->GetTransform().GetWorld().Inverse(); - } - } - void RenderEngine::CreateRenderThread() { m_render_thread_running = true; m_render_thread_is_waiting = false; @@ -191,20 +71,8 @@ namespace zephyr { m_render_thread_is_waiting = false; m_geometry_cache.ProcessPendingUpdates(); - - m_render_objects.clear(); - - for(const auto& scene_node_mesh_data : m_scene_node_mesh_data) { - m_render_objects.push_back({ - .render_geometry = m_geometry_cache.GetCachedRenderGeometry(scene_node_mesh_data.geometry), - .local_to_world = scene_node_mesh_data.local_to_world - }); - } - - if(m_scene_node_camera_data.empty()) { - ZEPHYR_PANIC("Scene graph does not contain a camera to render with."); - } - m_render_camera = m_scene_node_camera_data[0]; + m_render_scene.GetRenderObjects(m_render_objects, m_geometry_cache); + m_render_scene.GetRenderCamera(m_render_camera); // Signal to the caller thread that we are done reading the internal render structures. m_render_thread_semaphore.release(); diff --git a/zephyr/renderer/src/render_scene.cpp b/zephyr/renderer/src/render_scene.cpp new file mode 100644 index 0000000..39fb4aa --- /dev/null +++ b/zephyr/renderer/src/render_scene.cpp @@ -0,0 +1,195 @@ + +#include +#include +#include +#include +#include + +namespace zephyr { + + void RenderScene::SetSceneGraph(std::shared_ptr scene_graph) { + if(m_current_scene_graph != scene_graph) { + m_current_scene_graph = std::move(scene_graph); + m_require_full_rebuild = true; + } + } + + void RenderScene::Update() { + if(m_require_full_rebuild) { + RebuildScene(); + m_require_full_rebuild = false; + } else { + PatchScene(); + } + } + + void RenderScene::GetRenderObjects(std::vector& out_render_objects, const GeometryCache& geometry_cache) { + out_render_objects.clear(); + + for(const EntityID entity_id : m_view_mesh) { + const Transform& entity_transform = m_components_transform[entity_id]; + const Mesh& entity_mesh = m_components_mesh[entity_id]; + + out_render_objects.push_back({ + .render_geometry = geometry_cache.GetCachedRenderGeometry(entity_mesh.geometry), + .local_to_world = entity_transform.local_to_world + }); + } + } + + void RenderScene::GetRenderCamera(RenderCamera& out_render_camera) { + // TODO(fleroviux): implement a better way to pick the camera to use. + if(m_view_camera.empty()) { + ZEPHYR_PANIC("Scene graph does not contain a camera to render with."); + } + + const EntityID entity_id = m_view_camera[0]; + const Transform& entity_transform = m_components_transform[entity_id]; + const Camera& entity_camera = m_components_camera[entity_id]; + out_render_camera.projection = entity_camera.projection; + out_render_camera.frustum = entity_camera.frustum; + out_render_camera.view = entity_transform.local_to_world.Inverse(); + } + + void RenderScene::UpdateGeometries(GeometryCache& geometry_cache) { + for(const Geometry* geometry : m_active_geometry_set) { + geometry_cache.UpdateGeometry(geometry); + } + } + + void RenderScene::RebuildScene() { + m_node_entity_map.clear(); + m_entities.clear(); + ResizeComponentStorage(0); + + m_current_scene_graph->GetRoot()->Traverse([this](SceneNode* child_node) { + if(child_node->IsVisible()) { + PatchNodeMounted(child_node); + return true; + } + return false; + }); + } + + void RenderScene::PatchScene() { + for(const ScenePatch& patch : m_current_scene_graph->GetScenePatches()) { + switch(patch.type) { + case ScenePatch::Type::NodeMounted: PatchNodeMounted(patch.node.get()); break; + case ScenePatch::Type::NodeRemoved: PatchNodeRemoved(patch.node.get()); break; + case ScenePatch::Type::ComponentMounted: PatchNodeComponentMounted(patch.node.get(), patch.component_type); break; + case ScenePatch::Type::ComponentRemoved: PatchNodeComponentRemoved(patch.node.get(), patch.component_type); break; + case ScenePatch::Type::NodeTransformChanged: PatchNodeTransformChanged(patch.node.get()); break; + default: ZEPHYR_PANIC("Unhandled scene patch type: {}", (int)patch.type); + } + } + } + + void RenderScene::PatchNodeMounted(SceneNode* node) { + for(auto& [component_type, _] : node->GetComponents()) { + PatchNodeComponentMounted(node, component_type); + } + + PatchNodeTransformChanged(node); + } + + void RenderScene::PatchNodeRemoved(SceneNode* node) { + for(auto& [component_type, _] : node->GetComponents()) { + PatchNodeComponentRemoved(node, component_type); + } + } + + void RenderScene::PatchNodeComponentMounted(SceneNode* node, std::type_index component_type) { + if(component_type == typeid(MeshComponent)) { + const EntityID entity_id = GetOrCreateEntityForNode(node); + Mesh& entity_mesh = m_components_mesh[entity_id]; + entity_mesh.geometry = node->GetComponent().geometry.get(); + m_entities[entity_id] |= COMPONENT_FLAG_MESH; + m_view_mesh.push_back(entity_id); + m_active_geometry_set.insert(entity_mesh.geometry); + } + + if(component_type == typeid(PerspectiveCameraComponent)) { + const PerspectiveCameraComponent& node_camera_component = node->GetComponent(); + + const EntityID entity_id = GetOrCreateEntityForNode(node); + Camera& entity_camera = m_components_camera[entity_id]; + entity_camera.projection = node_camera_component.GetProjectionMatrix(); + entity_camera.frustum = node_camera_component.GetFrustum(); + m_entities[entity_id] |= COMPONENT_FLAG_CAMERA; + m_view_camera.push_back(entity_id); + } + } + + void RenderScene::PatchNodeComponentRemoved(SceneNode* node, std::type_index component_type) { + bool did_remove_component = false; + + if(component_type == typeid(MeshComponent)) { + const EntityID entity_id = GetOrCreateEntityForNode(node); + m_entities[entity_id] &= ~COMPONENT_FLAG_MESH; + m_view_mesh.erase(std::ranges::find(m_view_mesh, entity_id)); + m_active_geometry_set.erase(m_components_mesh[entity_id].geometry); + did_remove_component = true; + } + + if(component_type == typeid(PerspectiveCameraComponent)) { + const EntityID entity_id = GetOrCreateEntityForNode(node); + m_entities[entity_id] &= ~COMPONENT_FLAG_CAMERA; + m_view_camera.erase(std::ranges::find(m_view_camera, entity_id)); + did_remove_component = true; + } + + if(did_remove_component) { + const EntityID entity_id = m_node_entity_map[node]; + + if(m_entities[entity_id] == 0u) { + DestroyEntity(entity_id); + } + } + } + + void RenderScene::PatchNodeTransformChanged(SceneNode* node) { + const auto node_and_entity_id = m_node_entity_map.find(node); + if(node_and_entity_id == m_node_entity_map.end()) { + return; + } + + Transform& entity_transform = m_components_transform[node_and_entity_id->second]; + entity_transform.local_to_world = node->GetTransform().GetWorld(); + } + + RenderScene::EntityID RenderScene::GetOrCreateEntityForNode(const SceneNode* node) { + const auto node_and_entity_id = m_node_entity_map.find(node); + + if(node_and_entity_id == m_node_entity_map.end()) { + const EntityID entity_id = CreateEntity(); + m_node_entity_map[node] = entity_id; + return entity_id; + } + + return node_and_entity_id->second; + } + + RenderScene::EntityID RenderScene::CreateEntity() { + if(m_free_entity_list.empty()) { + m_entities.push_back(0u); + ResizeComponentStorage(m_entities.size()); + return m_entities.size() - 1; + } + + const EntityID entity_id = m_free_entity_list.back(); + m_entities[entity_id] = 0u; + m_free_entity_list.pop_back(); + return entity_id; + } + + void RenderScene::DestroyEntity(EntityID entity_id) { + m_free_entity_list.push_back(entity_id); + } + + void RenderScene::ResizeComponentStorage(size_t capacity) { + m_components_transform.resize(capacity); + m_components_mesh.resize(capacity); + m_components_camera.resize(capacity); + } + +} // namespace zephyr