diff --git a/zephyr/renderer/include/zephyr/renderer/engine/geometry_cache.hpp b/zephyr/renderer/include/zephyr/renderer/engine/geometry_cache.hpp index da10e2a..b729271 100644 --- a/zephyr/renderer/include/zephyr/renderer/engine/geometry_cache.hpp +++ b/zephyr/renderer/include/zephyr/renderer/engine/geometry_cache.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -16,11 +17,12 @@ namespace zephyr { ~GeometryCache(); // Game Thread API: - void CommitPendingDeleteTaskList(); - void UpdateGeometry(const Geometry* geometry); + void QueueTasksForRenderThread(); + void IncrementGeometryRefCount(const Geometry* geometry); + void DecrementGeometryRefCount(const Geometry* geometry); // Render Thread API: - void ProcessPendingUpdates(); + void ProcessQueuedTasks(); RenderGeometry* GetCachedRenderGeometry(const Geometry* geometry) const { const auto match = m_render_geometry_table.find(geometry); @@ -34,6 +36,7 @@ namespace zephyr { struct GeometryState { bool uploaded{false}; u64 current_version{}; + size_t ref_count{}; VoidEvent::SubID destruct_event_subscription; }; @@ -51,10 +54,18 @@ namespace zephyr { const Geometry* geometry; }; - void ProcessPendingDeletes(); - void ProcessPendingUploads(); + // Game Thread: + void QueueUploadTasksForUsedGeometries(); + void QueueDeleteTaskFromPreviousFrame(); + void QueueGeometryUploadTaskIfNeeded(const Geometry* geometry); + void QueueGeometryDeleteTaskForNextFrame(const Geometry* geometry); + + // Render Thread: + void ProcessQueuedDeleteTasks(); + void ProcessQueuedUploadTasks(); std::shared_ptr m_render_backend; + eastl::hash_set m_used_geometry_set{}; eastl::hash_map m_geometry_state_table{}; mutable eastl::hash_map m_render_geometry_table{}; std::vector m_upload_tasks{}; diff --git a/zephyr/renderer/include/zephyr/renderer/render_scene.hpp b/zephyr/renderer/include/zephyr/renderer/render_scene.hpp index 38959b5..a9c28c0 100644 --- a/zephyr/renderer/include/zephyr/renderer/render_scene.hpp +++ b/zephyr/renderer/include/zephyr/renderer/render_scene.hpp @@ -84,7 +84,6 @@ namespace zephyr { 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{}; diff --git a/zephyr/renderer/src/backend/opengl/render_backend.cpp b/zephyr/renderer/src/backend/opengl/render_backend.cpp index 2eaf8fe..42c2108 100644 --- a/zephyr/renderer/src/backend/opengl/render_backend.cpp +++ b/zephyr/renderer/src/backend/opengl/render_backend.cpp @@ -48,7 +48,7 @@ namespace zephyr { m_render_geometry_manager = std::make_unique(); - SDL_GL_SetSwapInterval(0); +// SDL_GL_SetSwapInterval(0); } void OpenGLRenderBackend::DestroyContext() { diff --git a/zephyr/renderer/src/engine/geometry_cache.cpp b/zephyr/renderer/src/engine/geometry_cache.cpp index c3fe0ef..1d2b3c2 100644 --- a/zephyr/renderer/src/engine/geometry_cache.cpp +++ b/zephyr/renderer/src/engine/geometry_cache.cpp @@ -1,6 +1,5 @@ #include -#include #include #include @@ -9,15 +8,43 @@ namespace zephyr { GeometryCache::~GeometryCache() { // Ensure that we do not receive any destruction callbacks from geometries that outlive this geometry cache. for(const auto& [geometry, state] : m_geometry_state_table) { - geometry->OnBeforeDestruct().Unsubscribe(state.destruct_event_subscription); + if(state.uploaded) { + geometry->OnBeforeDestruct().Unsubscribe(state.destruct_event_subscription); + } + } + } + + void GeometryCache::QueueTasksForRenderThread() { + // Queue (re-)uploads for all geometries used in the submitted frame which are either new or have changed since the last frame. + QueueUploadTasksForUsedGeometries(); + + // Queue eviction of geometries which had been deleted in the previously submitted frame. + QueueDeleteTaskFromPreviousFrame(); + } + + void GeometryCache::IncrementGeometryRefCount(const Geometry* geometry) { + if(++m_geometry_state_table[geometry].ref_count == 1u) { + m_used_geometry_set.insert(geometry); } } - void GeometryCache::CommitPendingDeleteTaskList() { + void GeometryCache::DecrementGeometryRefCount(const Geometry* geometry) { + if(--m_geometry_state_table[geometry].ref_count == 0u) { + m_used_geometry_set.erase(geometry); + } + } + + void GeometryCache::QueueUploadTasksForUsedGeometries() { + for(const Geometry* geometry : m_used_geometry_set) { + QueueGeometryUploadTaskIfNeeded(geometry); + } + } + + void GeometryCache::QueueDeleteTaskFromPreviousFrame() { std::swap(m_delete_tasks[0], m_delete_tasks[1]); } - void GeometryCache::UpdateGeometry(const Geometry* geometry) { + void GeometryCache::QueueGeometryUploadTaskIfNeeded(const Geometry* geometry) { GeometryState& state = m_geometry_state_table[geometry]; if(!state.uploaded || state.current_version != geometry->CurrentVersion()) { @@ -39,15 +66,8 @@ namespace zephyr { }); if(!state.uploaded) { - state.destruct_event_subscription = geometry->OnBeforeDestruct().Subscribe([this, geometry]() { - /** - * This callback is called from the game thread and may be called outside the frame submission phase. - * To avoid deleting geometries too early, we have to push the delete task to an intermediate list, - * which is then committed for execution for the next frame submission. - */ - m_delete_tasks[1].push_back({.geometry = (const Geometry*)geometry}); - m_geometry_state_table.erase((const Geometry*)geometry); - }); + state.destruct_event_subscription = geometry->OnBeforeDestruct().Subscribe( + std::bind(&GeometryCache::QueueGeometryDeleteTaskForNextFrame, this, geometry)); } state.uploaded = true; @@ -55,12 +75,22 @@ namespace zephyr { } } - void GeometryCache::ProcessPendingUpdates() { - ProcessPendingDeletes(); - ProcessPendingUploads(); + void GeometryCache::QueueGeometryDeleteTaskForNextFrame(const Geometry* geometry) { + /** + * Queue the geometry for eviction from the cache. + * To avoid deleting the geometry before the current frame-in-flight has been rendered, + * these tasks will only be processed at the start of the *next* frame on the render thread. + */ + m_delete_tasks[1].push_back({.geometry = (const Geometry*)geometry}); + m_geometry_state_table.erase((const Geometry*)geometry); + } + + void GeometryCache::ProcessQueuedTasks() { + ProcessQueuedDeleteTasks(); + ProcessQueuedUploadTasks(); } - void GeometryCache::ProcessPendingDeletes() { + void GeometryCache::ProcessQueuedDeleteTasks() { for(const auto& delete_task : m_delete_tasks[0]) { RenderGeometry* render_geometry = m_render_geometry_table[delete_task.geometry]; if(render_geometry) { @@ -72,7 +102,7 @@ namespace zephyr { m_delete_tasks[0].clear(); } - void GeometryCache::ProcessPendingUploads() { + void GeometryCache::ProcessQueuedUploadTasks() { for(const auto& upload_task : m_upload_tasks) { const Geometry* geometry = upload_task.geometry; RenderGeometry* render_geometry = m_render_geometry_table[geometry]; diff --git a/zephyr/renderer/src/render_scene.cpp b/zephyr/renderer/src/render_scene.cpp index 8698e1a..b8771d5 100644 --- a/zephyr/renderer/src/render_scene.cpp +++ b/zephyr/renderer/src/render_scene.cpp @@ -26,13 +26,8 @@ namespace zephyr { PatchScene(); } - // Instruct the geometry cache to evict geometries which had been deleted in the submitted frame. - m_geometry_cache.CommitPendingDeleteTaskList(); - - // Update all geometries which might be rendered in this frame. - for(const Geometry* geometry : m_active_geometry_set) { - m_geometry_cache.UpdateGeometry(geometry); - } + // Queue geometry cache updates and evictions to be processed on the render thread. + m_geometry_cache.QueueTasksForRenderThread(); } void RenderScene::GetRenderCamera(RenderCamera& out_render_camera) { @@ -54,7 +49,7 @@ namespace zephyr { } void RenderScene::UpdateStage2() { - m_geometry_cache.ProcessPendingUpdates(); + m_geometry_cache.ProcessQueuedTasks(); for(const RenderScenePatch& render_scene_patch : m_render_scene_patches) { switch(render_scene_patch.type) { @@ -152,7 +147,7 @@ namespace zephyr { 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); + m_geometry_cache.IncrementGeometryRefCount(entity_mesh.geometry); m_render_scene_patches.push_back({.type = RenderScenePatch::Type::MeshMounted, .entity_id = entity_id}); } @@ -175,7 +170,7 @@ namespace zephyr { 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); + m_geometry_cache.DecrementGeometryRefCount(m_components_mesh[entity_id].geometry); m_render_scene_patches.push_back({.type = RenderScenePatch::Type::MeshRemoved, .entity_id = entity_id}); did_remove_component = true; }