diff --git a/hitagi/asset/transform.cpp b/hitagi/asset/transform.cpp index 70ba54c6..56350a4d 100644 --- a/hitagi/asset/transform.cpp +++ b/hitagi/asset/transform.cpp @@ -13,12 +13,21 @@ void RelationShipSystem::OnUpdate(ecs::Schedule& schedule) { if (relation_ship.prev_parent) { auto& prev_parent = relation_ship.prev_parent.Get(); prev_parent.children.erase(entity); + prev_parent.subtree_dirty = true; + for (auto parent = prev_parent.parent; parent; parent = parent.Get().parent) { + parent.Get().subtree_dirty = true; + } } if (relation_ship.parent) { auto& parent = relation_ship.parent.Get(); parent.children.insert(entity); + parent.subtree_dirty = true; + for (auto ancestor = parent.parent; ancestor; ancestor = ancestor.Get().parent) { + ancestor.Get().subtree_dirty = true; + } } relation_ship.prev_parent = relation_ship.parent; + relation_ship.subtree_dirty = true; } }); } @@ -29,25 +38,47 @@ void TransformSystem::OnUpdate(ecs::Schedule& schedule) { schedule .Request( "update_local_matrix", - [](Transform& transform) { - transform.world_matrix = transform.ToMatrix(); + [](const ecs::Entity entity, Transform& transform, RelationShip& relation_ship) { + if (transform.position == transform.cached_position && + transform.rotation == transform.cached_rotation && + transform.scaling == transform.cached_scaling) { + return; + } + + transform.local_matrix = transform.ToMatrix(); + transform.cached_position = transform.position; + transform.cached_rotation = transform.rotation; + transform.cached_scaling = transform.scaling; + + relation_ship.subtree_dirty = true; + for (auto parent = relation_ship.parent; parent; parent = parent.Get().parent) { + parent.Get().subtree_dirty = true; + } }) .Request( "update_world_matrix_from_root", - [](const Transform& transform, const RelationShip& relation_ship) { - const std::function recursive_update = - [&](ecs::Entity entity, const math::mat4f parent_transform) { - auto& transform = entity.Get(); - transform.world_matrix = parent_transform * transform.world_matrix; - for (auto child : entity.Get().GetChildren()) { - recursive_update(child, transform.world_matrix); + [](Transform& transform, RelationShip& relation_ship) { + const std::function recursive_update = + [&](ecs::Entity entity, const math::mat4f parent_transform, bool parent_dirty) { + auto& child_relation = entity.Get(); + if (!parent_dirty && !child_relation.subtree_dirty) return; + + auto& child_transform = entity.Get(); + child_transform.world_matrix = parent_transform * child_transform.local_matrix; + const bool subtree_dirty = parent_dirty || child_relation.subtree_dirty; + child_relation.subtree_dirty = false; + + for (auto child : child_relation.GetChildren()) { + recursive_update(child, child_transform.world_matrix, subtree_dirty); } }; // root entity - if (!relation_ship.parent) { + if (!relation_ship.parent && relation_ship.subtree_dirty) { + transform.world_matrix = transform.local_matrix; + relation_ship.subtree_dirty = false; for (auto entity : relation_ship.GetChildren()) { - recursive_update(entity, transform.world_matrix); + recursive_update(entity, transform.world_matrix, true); } } }); diff --git a/hitagi/asset/transform.cppm b/hitagi/asset/transform.cppm index 7762a8bd..3c4b4dbe 100644 --- a/hitagi/asset/transform.cppm +++ b/hitagi/asset/transform.cppm @@ -16,8 +16,10 @@ struct RelationShip { private: friend struct RelationShipSystem; + friend struct TransformSystem; ecs::Entity prev_parent = {}; std::pmr::unordered_set children; + bool subtree_dirty = true; }; static_assert(ecs::Component); @@ -29,19 +31,31 @@ struct Transform { Transform(math::vec3f position = math::vec3f(0.0f), math::quatf rotation = math::quatf::identity(), math::vec3f scaling = math::vec3f(1.0f)) : position(position), rotation(rotation), - scaling(scaling) {} + scaling(scaling), + local_matrix(math::translate(position) * math::rotate(rotation) * math::scale(scaling)), + world_matrix(local_matrix), + cached_position(position), + cached_rotation(rotation), + cached_scaling(scaling) {} math::vec3f position; math::quatf rotation; math::vec3f scaling; - math::mat4f world_matrix = math::mat4f::identity(); + math::mat4f local_matrix; + math::mat4f world_matrix; inline void ApplyScale(float value) noexcept { scaling = value; } inline void Translate(const math::vec3f& value) noexcept { position += value; } inline void Rotate(const math::quatf& value) noexcept { rotation = value * rotation; } inline auto ToMatrix() const noexcept { return math::translate(position) * math::rotate(rotation) * math::scale(scaling); } + +private: + friend struct TransformSystem; + math::vec3f cached_position; + math::quatf cached_rotation; + math::vec3f cached_scaling; }; struct TransformSystem { diff --git a/hitagi/ecs/schedule.cpp b/hitagi/ecs/schedule.cpp index dd283bc8..fb2914a5 100644 --- a/hitagi/ecs/schedule.cpp +++ b/hitagi/ecs/schedule.cpp @@ -27,15 +27,28 @@ void Schedule::Request(std::shared_ptr&& task, const ParameterSets& pa for (auto parameter : read_after_write) { m_ReadAfterWriteSet[parameter].emplace_back(m_Tasks.size() - 1); } + + m_TaskflowDirty = true; } void Schedule::SetOrder(std::string_view first_task, std::string_view second_task) { m_CustomOrder.emplace(first_task, second_task); + m_TaskflowDirty = true; } void Schedule::Run(tf::Executor& executor) { - tf::Taskflow taskflow; - std::pmr::vector tasks; + if (m_TaskflowDirty) { + BuildTaskflow(executor); + } + + ZoneScopedN("ECSFrame"); + executor.run(m_Taskflow).wait(); +} + +void Schedule::BuildTaskflow(tf::Executor& executor) { + m_Taskflow.clear(); + m_TaskflowTasks.clear(); + m_TaskflowTasks.reserve(m_Tasks.size()); // adjacency list std::pmr::unordered_map> direct_graph; @@ -43,7 +56,7 @@ void Schedule::Run(tf::Executor& executor) { direct_graph[i] = {}; for (const auto& task : m_Tasks) { - tasks.emplace_back(taskflow.emplace([this, &executor, task]() { + m_TaskflowTasks.emplace_back(m_Taskflow.emplace([this, &executor, task]() { thread_local bool tracy_thread_named = false; if (!tracy_thread_named) { if (const auto worker_id = executor.this_worker_id(); worker_id >= 0) { @@ -62,13 +75,13 @@ void Schedule::Run(tf::Executor& executor) { for (const auto& [component, task_indices] : m_ReadBeforeWriteSet) { for (const auto task_index : task_indices) { for (const auto write_task_index : m_WriteSet[component]) { - tasks[task_index].precede(tasks[write_task_index]); + m_TaskflowTasks[task_index].precede(m_TaskflowTasks[write_task_index]); direct_graph[task_index].emplace(write_task_index); } } for (const auto& task_index : task_indices) { for (const auto read_after_write_task_index : m_ReadAfterWriteSet[component]) { - tasks[task_index].precede(tasks[read_after_write_task_index]); + m_TaskflowTasks[task_index].precede(m_TaskflowTasks[read_after_write_task_index]); direct_graph[task_index].emplace(read_after_write_task_index); } } @@ -76,13 +89,13 @@ void Schedule::Run(tf::Executor& executor) { for (const auto& [component, task_indices] : m_WriteSet) { for (const auto [task_index, next_task_index] : std::ranges::views::zip(task_indices, task_indices | std::ranges::views::drop(1))) { - tasks[task_index].precede(tasks[next_task_index]); + m_TaskflowTasks[task_index].precede(m_TaskflowTasks[next_task_index]); direct_graph[task_index].emplace(next_task_index); } for (const auto task_index : task_indices) { for (const auto read_after_write_task_index : m_ReadAfterWriteSet[component]) { - tasks[task_index].precede(tasks[read_after_write_task_index]); + m_TaskflowTasks[task_index].precede(m_TaskflowTasks[read_after_write_task_index]); direct_graph[task_index].emplace(read_after_write_task_index); } } @@ -103,7 +116,7 @@ void Schedule::Run(tf::Executor& executor) { } const auto first_task_index = m_TaskNameToIndex[first_task_name]; const auto second_task_index = m_TaskNameToIndex[second_task_name]; - tasks[first_task_index].precede(tasks[second_task_index]); + m_TaskflowTasks[first_task_index].precede(m_TaskflowTasks[second_task_index]); direct_graph[first_task_index].emplace(second_task_index); } @@ -111,8 +124,7 @@ void Schedule::Run(tf::Executor& executor) { return; } - ZoneScopedN("ECSFrame"); - executor.run(taskflow).wait(); + m_TaskflowDirty = false; } bool Schedule::CheckValid(const std::pmr::unordered_map>& graph) { diff --git a/hitagi/ecs/schedule.cppm b/hitagi/ecs/schedule.cppm index 2b444fd5..58a7cc38 100644 --- a/hitagi/ecs/schedule.cppm +++ b/hitagi/ecs/schedule.cppm @@ -60,6 +60,7 @@ private: void Request(std::shared_ptr&& task, const ParameterSets& parameter_sets); void Run(tf::Executor& executor); + void BuildTaskflow(tf::Executor& executor); bool CheckValid(const std::pmr::unordered_map>& graph); @@ -70,6 +71,10 @@ private: std::pmr::unordered_map> m_ReadAfterWriteSet; std::pmr::unordered_map m_CustomOrder; + + tf::Taskflow m_Taskflow; + std::pmr::vector m_TaskflowTasks; + bool m_TaskflowDirty = true; }; } // namespace hitagi::ecs diff --git a/hitagi/ecs/system_manager.cpp b/hitagi/ecs/system_manager.cpp index 39887d93..f14cf095 100644 --- a/hitagi/ecs/system_manager.cpp +++ b/hitagi/ecs/system_manager.cpp @@ -29,6 +29,7 @@ void SystemManager::EnableOne(utils::TypeID id) { m_DisabledSystems.erase(id); m_EnabledSystems.emplace(id); + m_World.InvalidateSchedule(); if (m_OnEnableFns.contains(id)) m_OnEnableFns.at(id)(m_World); @@ -47,6 +48,7 @@ void SystemManager::DisableOne(utils::TypeID id) { m_EnabledSystems.erase(id); m_DisabledSystems.emplace(id); + m_World.InvalidateSchedule(); if (m_OnDisableFns.contains(id)) m_OnDisableFns.at(id)(m_World); @@ -55,6 +57,7 @@ void SystemManager::DisableOne(utils::TypeID id) { void SystemManager::UnRegisterOne(utils::TypeID id) { DisableOne(id); m_DisabledSystems.erase(id); + m_World.InvalidateSchedule(); if (m_OnDestroyFns.contains(id)) m_OnDestroyFns.at(id)(m_World); diff --git a/hitagi/ecs/world.cpp b/hitagi/ecs/world.cpp index b4c4699c..64ec29c7 100644 --- a/hitagi/ecs/world.cpp +++ b/hitagi/ecs/world.cpp @@ -14,11 +14,22 @@ World::World(std::string_view name) m_SystemManager(*this) { } +World::~World() = default; + void World::Update() { ZoneScopedN("ECS::World::Update"); - Schedule schedule(*this); - m_SystemManager.Update(schedule); - schedule.Run(m_Executor); + if (m_ScheduleDirty || !m_Schedule) { + m_Schedule = std::make_unique(*this); + m_SystemManager.Update(*m_Schedule); + m_ScheduleDirty = false; + } + + m_Schedule->Run(m_Executor); +} + +void World::InvalidateSchedule() noexcept { + m_ScheduleDirty = true; + m_Schedule.reset(); } } // namespace hitagi::ecs diff --git a/hitagi/ecs/world.cppm b/hitagi/ecs/world.cppm index 47b5f31b..72485a1c 100644 --- a/hitagi/ecs/world.cppm +++ b/hitagi/ecs/world.cppm @@ -10,9 +10,12 @@ import :system_manager; export namespace hitagi::ecs { +class Schedule; + class World { public: World(std::string_view name); + ~World(); void Update(); @@ -24,12 +27,18 @@ public: inline auto GetLogger() noexcept { return m_Logger; } private: + friend SystemManager; + + void InvalidateSchedule() noexcept; + std::pmr::string m_Name; std::shared_ptr m_Logger; EntityManager m_EntityManager; SystemManager m_SystemManager; tf::Executor m_Executor; + std::unique_ptr m_Schedule; + bool m_ScheduleDirty = true; }; } // namespace hitagi::ecs diff --git a/hitagi/gfx/render_graph/pass_builder.cpp b/hitagi/gfx/render_graph/pass_builder.cpp index 403c0645..78de9710 100644 --- a/hitagi/gfx/render_graph/pass_builder.cpp +++ b/hitagi/gfx/render_graph/pass_builder.cpp @@ -19,8 +19,7 @@ PassBuilder::~PassBuilder() { } auto PassBuilder::Finish() -> std::size_t { - pass_base->m_Handle = m_RenderGraph.m_Nodes.size(); - m_RenderGraph.m_Nodes.emplace_back(pass_base); + pass_base->m_Handle = m_RenderGraph.AllocateNode(pass_base); // create edge for move resource: // resource_1 -- move --> resource_2(*) resource_1 -- new_edge --> pass_2 @@ -789,4 +788,4 @@ void PresentPassBuilder::Finish() noexcept { m_RenderGraph.m_PresentPassNode = pass; } -} // namespace hitagi::rg \ No newline at end of file +} // namespace hitagi::rg diff --git a/hitagi/gfx/render_graph/render_graph.cpp b/hitagi/gfx/render_graph/render_graph.cpp index 307e7da4..61351396 100644 --- a/hitagi/gfx/render_graph/render_graph.cpp +++ b/hitagi/gfx/render_graph/render_graph.cpp @@ -86,16 +86,14 @@ auto RenderGraph::ImportResource(std::shared_ptr resource, std::s utils::unreachable(); } } - new_node->m_Handle = m_Nodes.size(); - - m_Nodes.emplace_back(new_node); - m_ImportedResources.emplace(resource, new_node->m_Handle); + const auto handle = AllocateNode(new_node); + m_ImportedResources.emplace(resource, handle); if (!name.empty()) { - m_BlackBoard[node_type].emplace(name, new_node->m_Handle); + m_BlackBoard[node_type].emplace(name, handle); } - return new_node->m_Handle; + return handle; } auto RenderGraph::Import(std::shared_ptr buffer, std::string_view name) noexcept -> GPUBufferHandle { @@ -164,14 +162,13 @@ auto RenderGraph::CreateResource(ResourceDesc desc, std::string_view name) noexc utils::unreachable(); } - new_node->m_Handle = m_Nodes.size(); - m_Nodes.emplace_back(new_node); + const auto handle = AllocateNode(new_node); if (!name.empty()) { - m_BlackBoard[node_type].emplace(name, new_node->m_Handle); + m_BlackBoard[node_type].emplace(name, handle); } - return new_node->m_Handle; + return handle; } auto RenderGraph::Create(gfx::GPUBufferDesc desc, std::string_view name) noexcept -> GPUBufferHandle { @@ -209,15 +206,15 @@ auto RenderGraph::MoveFrom(RenderGraphNode::Type type, std::size_t resource_node return invalid_index; } - const auto new_handle = m_Nodes.size(); + const auto new_handle = m_FreeNodeSlots.empty() ? m_Nodes.size() : m_FreeNodeSlots.back(); switch (type) { case RenderGraphNode::Type::GPUBuffer: { const auto buffer_node = std::static_pointer_cast(m_Nodes[resource_node_index]); - m_Nodes.emplace_back(buffer_node->Move(new_handle, name)); + AllocateNode(buffer_node->Move(new_handle, name)); } break; case RenderGraphNode::Type::Texture: { const auto texture_node = std::static_pointer_cast(m_Nodes[resource_node_index]); - m_Nodes.emplace_back(texture_node->Move(new_handle, name)); + AllocateNode(texture_node->Move(new_handle, name)); } break; default: { m_Logger->error("Move resource failed: resource is not a GPUBuffer or Texture"); @@ -408,15 +405,72 @@ auto RenderGraph::Execute() -> std::uint64_t { return m_FrameIndex++; } +void RenderGraph::ClearImportedResources() noexcept { + for (const auto& [resource, handle] : m_ImportedResources) { + if (!IsValid(gfx_resource_type_to_node_type(resource->GetType()), handle)) continue; + + auto& node = m_Nodes[handle]; + node->m_InputNodes.clear(); + node->m_OutputNodes.clear(); + node.reset(); + m_FreeNodeSlots.emplace_back(handle); + } + + m_ImportedResources.clear(); + RebuildBlackBoard(); +} + void RenderGraph::Reset() noexcept { m_Compiled = false; m_PresentPassNode = nullptr; - m_Nodes.clear(); - m_ImportedResources.clear(); + + for (std::size_t handle = 0; handle < m_Nodes.size(); handle++) { + auto& node = m_Nodes[handle]; + if (!node) continue; + + node->m_InputNodes.clear(); + node->m_OutputNodes.clear(); + + if (node->IsResourceNode() && static_cast(node.get())->m_IsImported) continue; + + node.reset(); + m_FreeNodeSlots.emplace_back(handle); + } + + RebuildBlackBoard(); + m_ExecuteLayers.clear(); +} + +auto RenderGraph::AllocateNode(std::shared_ptr node) noexcept -> std::size_t { + if (node == nullptr) return std::numeric_limits::max(); + + std::size_t handle = std::numeric_limits::max(); + if (!m_FreeNodeSlots.empty()) { + handle = m_FreeNodeSlots.back(); + m_FreeNodeSlots.pop_back(); + m_Nodes[handle] = std::move(node); + } else { + handle = m_Nodes.size(); + m_Nodes.emplace_back(std::move(node)); + } + + m_Nodes[handle]->m_Handle = handle; + return handle; +} + +void RenderGraph::RebuildBlackBoard() noexcept { for (auto& black_board : m_BlackBoard) { black_board.clear(); } - m_ExecuteLayers.clear(); + + for (const auto& node : m_Nodes) { + if (!node || !node->IsResourceNode()) continue; + + const auto* resource_node = static_cast(node.get()); + if (!resource_node->m_IsImported || node->GetName().empty()) continue; + + m_BlackBoard[node->GetType()].insert_or_assign(std::pmr::string(node->GetName()), node->m_Handle); + } } void RenderGraph::RetireNodesFromPassNode(PassNode* pass_node, const FenceValue& fence_value) noexcept { diff --git a/hitagi/gfx/render_graph/render_graph.cppm b/hitagi/gfx/render_graph/render_graph.cppm index 2c298ad7..191ed4ca 100644 --- a/hitagi/gfx/render_graph/render_graph.cppm +++ b/hitagi/gfx/render_graph/render_graph.cppm @@ -48,6 +48,7 @@ public: bool Compile(); auto Execute() -> std::uint64_t; + void ClearImportedResources() noexcept; inline auto& GetDevice() const noexcept { return m_Device; } inline auto GetFrameIndex() const noexcept { return m_FrameIndex; } @@ -78,9 +79,11 @@ private: auto ImportResource(std::shared_ptr resource, std::string_view name) noexcept -> std::size_t; auto CreateResource(ResourceDesc desc, std::string_view name) noexcept -> std::size_t; auto MoveFrom(RenderGraphNode::Type type, std::size_t resource_node_index, std::string_view name) noexcept -> std::size_t; + auto AllocateNode(std::shared_ptr node) noexcept -> std::size_t; + void RebuildBlackBoard() noexcept; inline bool IsValid(RenderGraphNode::Type type, std::size_t resource_node_index) const noexcept { - return resource_node_index < m_Nodes.size() && m_Nodes[resource_node_index]->m_Type == type; + return resource_node_index < m_Nodes.size() && m_Nodes[resource_node_index] && m_Nodes[resource_node_index]->m_Type == type; } template @@ -104,6 +107,7 @@ private: std::pmr::vector> m_Nodes; std::pmr::unordered_map, std::size_t> m_ImportedResources; + std::pmr::vector m_FreeNodeSlots; bool m_Compiled = false; std::pmr::vector m_ExecuteLayers; diff --git a/hitagi/render/forward_renderer.cpp b/hitagi/render/forward_renderer.cpp index e92a263f..af1076ab 100644 --- a/hitagi/render/forward_renderer.cpp +++ b/hitagi/render/forward_renderer.cpp @@ -63,6 +63,11 @@ void ForwardRenderer::Tick() { void ForwardRenderer::RenderScene(std::shared_ptr scene, const asset::Camera& camera, math::mat4f camera_transform, rg::TextureHandle target) { ZoneScoped; + if (scene.get() != m_CachedScene) { + InvalidateSceneCaches(); + m_CachedScene = scene.get(); + } + m_Sampler = m_RenderGraph.Import(m_PersistentSampler, "sampler"); rg::RenderPassBuilder render_pass_builder(m_RenderGraph); @@ -154,13 +159,14 @@ void ForwardRenderer::RenderScene(std::shared_ptr scene, const ass }); // update material instance data - for (const auto& [material_instance, info] : m_MaterialInstanceInfos) { + for (auto* const material_instance : m_ActiveMaterialInstances) { auto material_info = m_MaterialInfos.at(material_instance->GetMaterial().get()); auto& material_constant_buffer = pass.Resolve(material_info.material_constant); + const auto material_instance_index = m_MaterialInstanceIndices.at(material_instance); auto material_constant_data = material_instance->GenerateMaterialBuffer(m_GfxDevice.device_type == gfx::Device::Type::DX12); - std::memcpy(material_constant_buffer.Map() + material_constant_buffer.AlignedElementSize() * info.material_instance_index, + std::memcpy(material_constant_buffer.Map() + material_constant_buffer.AlignedElementSize() * material_instance_index, material_constant_data.GetData(), material_constant_data.GetDataSize()); material_constant_buffer.UnMap(); @@ -184,13 +190,13 @@ void ForwardRenderer::RenderScene(std::shared_ptr scene, const ass cmd.SetPipeline(pipeline); const auto material_constant_handle = material_info.material_constant; - const auto material_instance_index = material_instance_info.material_instance_index; + const auto material_instance_index = m_MaterialInstanceIndices.at(sub_mesh.material_instance.get()); bindless_infos[draw_index] = { .frame_constant = pass.GetBindless(m_FrameConstantBuffer), .instance_constant = pass.GetBindless(m_InstanceConstantBuffer, instance_info.instance_index), .material_constant = pass.GetBindless(material_constant_handle, material_instance_index), - .sampler = pass.GetBindless(material_instance_info.samplers[0]), + .sampler = pass.GetBindless(m_Sampler), }; for (auto [texture_bindless, texture_handle] : ranges::views::zip(bindless_infos[draw_index].textures, material_instance_info.textures)) { @@ -237,61 +243,64 @@ void ForwardRenderer::RecordMaterialInstance(rg::RenderPassBuilder& builder, con spdlog::warn("Material instance '{}' has no material assigned, skipping", material_instance->GetName()); return; } - if (m_MaterialInstanceInfos.contains(material_instance.get())) return; - - MaterialInstanceInfo info{ - .material_instance = material_instance, - .samplers = {m_Sampler}, - }; + auto [it, inserted] = m_MaterialInstanceInfos.try_emplace( + material_instance.get(), + MaterialInstanceInfo{ + .material_instance = material_instance, + }); + auto& info = it->second; - auto associated = material_instance->GetAssociatedTextures(); - if (associated.empty() && material_instance->GetMaterial()) { - spdlog::info(" '{}' (mat='{}') has 0 associated textures", material_instance->GetName(), material_instance->GetMaterial()->GetName()); - } - for (std::size_t ti = 0; ti < associated.size(); ti++) { - const auto& texture = associated[ti]; - if (texture && !texture->Empty()) { - texture->InitGPUData(m_GfxDevice); - builder.Read( - info.textures.emplace_back(m_RenderGraph.Import(texture->GetGPUData(), texture->GetUniqueName())), - {}, - gfx::PipelineStage::PixelShader); - } else { - if (m_InstanceInfos.size() <= 1) { - spdlog::info(" '{}' texture[{}] = {}", material_instance->GetName(), ti, texture ? "empty" : "null"); + if (inserted) { + auto associated = material_instance->GetAssociatedTextures(); + if (associated.empty() && material_instance->GetMaterial()) { + spdlog::info(" '{}' (mat='{}') has 0 associated textures", material_instance->GetName(), material_instance->GetMaterial()->GetName()); + } + for (std::size_t ti = 0; ti < associated.size(); ti++) { + const auto& texture = associated[ti]; + if (texture && !texture->Empty()) { + texture->InitGPUData(m_GfxDevice); + info.textures.emplace_back(m_RenderGraph.Import(texture->GetGPUData(), texture->GetUniqueName())); + } else { + if (m_InstanceInfos.size() <= 1) { + spdlog::info(" '{}' texture[{}] = {}", material_instance->GetName(), ti, texture ? "empty" : "null"); + } + info.textures.emplace_back(rg::TextureHandle{}); } - info.textures.emplace_back(rg::TextureHandle{}); } } - m_MaterialInstanceInfos.emplace(material_instance.get(), std::move(info)); + for (const auto texture : info.textures) { + if (texture) { + builder.Read(texture, {}, gfx::PipelineStage::PixelShader); + } + } + m_ActiveMaterialInstances.emplace(material_instance.get()); } void ForwardRenderer::RecordMesh(rg::RenderPassBuilder& builder, const std::shared_ptr& mesh) { - if (m_MeshInfos.contains(mesh.get())) return; + auto [it, inserted] = m_MeshInfos.try_emplace(mesh.get(), MeshInfo{.mesh = mesh}); + auto& info = it->second; - mesh->vertices->InitGPUData(m_GfxDevice); - mesh->indices->InitGPUData(m_GfxDevice); + if (inserted) { + mesh->vertices->InitGPUData(m_GfxDevice); + mesh->indices->InitGPUData(m_GfxDevice); - utils::EnumArray vertex_handles; - magic_enum::enum_for_each([&](asset::VertexAttribute attr) { - auto attr_data = mesh->vertices->GetAttributeData(attr); - if (!attr_data.has_value()) return; - auto vertex_buffer_handle = m_RenderGraph.Import(attr_data->get().gpu_buffer); - builder.ReadAsVertices(vertex_buffer_handle); - vertex_handles[attr] = vertex_buffer_handle; - }); + magic_enum::enum_for_each([&](asset::VertexAttribute attr) { + auto attr_data = mesh->vertices->GetAttributeData(attr); + if (!attr_data.has_value()) return; + info.vertices[attr] = m_RenderGraph.Import(attr_data->get().gpu_buffer); + }); - auto index_buffer_handle = m_RenderGraph.Import(mesh->indices->GetIndexData().gpu_buffer); - builder.ReadAsIndices(index_buffer_handle); + info.indices = m_RenderGraph.Import(mesh->indices->GetIndexData().gpu_buffer); + } - m_MeshInfos.emplace( - mesh.get(), - MeshInfo{ - .mesh = mesh, - .vertices = vertex_handles, - .indices = index_buffer_handle, - }); + magic_enum::enum_for_each([&](asset::VertexAttribute attr) { + const auto handle = info.vertices[attr]; + if (handle) { + builder.ReadAsVertices(handle); + } + }); + builder.ReadAsIndices(info.indices); for (const auto& sub_mesh : mesh->sub_meshes) { RecordMaterialInstance(builder, sub_mesh.material_instance); @@ -311,13 +320,18 @@ void ForwardRenderer::RecordInstance(rg::RenderPassBuilder& builder, const std:: void ForwardRenderer::UpdateConstantBuffer(rg::RenderPassBuilder& builder) { std::pmr::unordered_map, std::size_t> material_instance_counter; - for (auto& [material_instance, info] : m_MaterialInstanceInfos) { - const auto material = material_instance->GetMaterial(); - info.material_instance_index = material_instance_counter[material]++; + m_MaterialInstanceIndices.clear(); + for (auto* const material_instance : m_ActiveMaterialInstances) { + const auto material = material_instance->GetMaterial(); + m_MaterialInstanceIndices.emplace(material_instance, material_instance_counter[material]++); } for (const auto& [material, num_instances] : material_instance_counter) { - auto pipeline_handle = m_RenderGraph.Import(material->GetPipeline(m_GfxDevice), material->GetName()); + auto pipeline_it = m_PipelineHandles.find(material.get()); + if (pipeline_it == m_PipelineHandles.end()) { + pipeline_it = m_PipelineHandles.emplace(material.get(), m_RenderGraph.Import(material->GetPipeline(m_GfxDevice), material->GetName())).first; + } + auto pipeline_handle = pipeline_it->second; builder.AddPipeline(pipeline_handle); auto constant_handle = m_RenderGraph.Create( @@ -376,9 +390,19 @@ void ForwardRenderer::ClearFrameState() { m_BindlessInfoConstantBuffer = {}; m_MaterialInfos.clear(); + m_MaterialInstanceIndices.clear(); + m_ActiveMaterialInstances.clear(); + m_InstanceInfos.clear(); +} + +void ForwardRenderer::InvalidateSceneCaches() { + m_RenderGraph.ClearImportedResources(); + m_PipelineHandles.clear(); m_MaterialInstanceInfos.clear(); + m_MaterialInstanceIndices.clear(); + m_ActiveMaterialInstances.clear(); m_MeshInfos.clear(); - m_InstanceInfos.clear(); + m_CachedScene = nullptr; } } // namespace hitagi::render diff --git a/hitagi/render/render.cppm b/hitagi/render/render.cppm index 3821e142..e5c9d679 100644 --- a/hitagi/render/render.cppm +++ b/hitagi/render/render.cppm @@ -110,8 +110,6 @@ private: struct MaterialInstanceInfo { std::shared_ptr material_instance; std::pmr::vector textures; - std::pmr::vector samplers; - std::size_t material_instance_index; }; struct MeshInfo { @@ -137,6 +135,7 @@ private: // 5. create constant buffer of bindless info void UpdateConstantBuffer(rg::RenderPassBuilder& builder); void ClearFrameState(); + void InvalidateSceneCaches(); const Application& m_App; gfx::Device& m_GfxDevice; @@ -156,9 +155,13 @@ private: rg::GPUBufferHandle m_FrameConstantBuffer; rg::GPUBufferHandle m_InstanceConstantBuffer; rg::GPUBufferHandle m_BindlessInfoConstantBuffer; + asset::Scene* m_CachedScene = nullptr; std::pmr::unordered_map m_MaterialInfos; + std::pmr::unordered_map m_PipelineHandles; std::pmr::unordered_map m_MaterialInstanceInfos; + std::pmr::unordered_map m_MaterialInstanceIndices; + std::pmr::unordered_set m_ActiveMaterialInstances; std::pmr::unordered_map m_MeshInfos; std::pmr::vector m_InstanceInfos; };