diff --git a/_Design/glossary.md b/_Design/glossary.md index 01efd25..370cea5 100644 --- a/_Design/glossary.md +++ b/_Design/glossary.md @@ -12,16 +12,15 @@ pool in a thin class instead: ``` ResourceRegistry - ├── meshes Pool - ├── renderTargets RenderTargetPool → RenderTargetHandle - │ (wraps Pool; waits for the GPU on release, applies resizes) - ├── scenes Pool - ├── textures (planned) - └── materials (planned) + ├── meshPool Pool + ├── renderTargetPool RenderTargetPool → RenderTargetHandle + │ (wraps Pool; waits for the GPU on release, applies resizes) + ├── texturePool (planned) + └── materialPool (planned) ``` Engine code (`Application`, `ImGuiRenderer`, `SceneRenderer`) uses the pools -directly, e.g. `resources.renderTargets.get(handle)`. +directly, e.g. `resources.renderTargetPool.get(handle)`. A Handle is a small (index, generation) pair. The index locates the slot in the pool; the generation is bumped when that slot is released, so a Handle @@ -277,12 +276,14 @@ This is primarily a UI concept, rather than a GPU concept. A PanelRegistry owns Panels on the caller's behalf and hands out lightweight PanelHandles instead of pointers or ownership. -Internally it's a generation-checked slot pool, the same shape ResourceRegistry's -`Pool` uses: +Internally it wraps a `Pool` (the same generation-checked +slot pool ResourceRegistry uses, in `sophia/util/pool.hpp`) and adds the +Panel-specific parts: lookup by type or name, and the default dock layout: ``` PanelRegistry - └── Slot { Panel, generation } → PanelHandle + └── Pool + └── Slot { Panel, generation } → PanelHandle ``` Typical lifecycle: @@ -300,6 +301,34 @@ Each frame, update(frameInfo) calls onUpdate on every visible Panel it holds. Think: "Where do I register or look up a UI panel by handle, without owning its lifetime directly?" +## SceneRegistry + +A SceneRegistry owns Scenes on the caller's behalf and hands out lightweight +SceneHandles instead of pointers or ownership. Like PanelRegistry, it is owned +by Application and reached from frontend code directly (`scenes()`), rather than +through ResourceRegistryRef: scenes are CPU-side entity data, not GPU resources. + +Internally it wraps a `Pool`: + +``` +SceneRegistry + └── Pool + └── Slot { Scene, generation } → SceneHandle +``` + +Typical lifecycle: + +``` +create() → SceneHandle + │ + get(handle) → Scene& + │ + release(handle) +``` + +Think: "Where do I create or look up a Scene by handle, without owning its +lifetime directly?" + ## Sprite A Sprite is a 2D renderable representation of an image, usually a rectangular region of a texture. diff --git a/engine/include/sophia/application.hpp b/engine/include/sophia/application.hpp index 88b333e..f7675b0 100644 --- a/engine/include/sophia/application.hpp +++ b/engine/include/sophia/application.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -47,6 +48,7 @@ class Application { ResourceRegistryRef resources(); PanelRegistry& panels() { return this->panelRegistry; } + SceneRegistry& scenes() { return this->sceneRegistry; } UI& ui() { return this->_ui; } DebugUI& debugUI() { return this->_debugUI; } @@ -60,6 +62,7 @@ class Application { void drawMesh(MeshHandle handle, const glm::mat4& transform); /** + * !TEMP !TODO remove this func eventually * Chooses where the scene is rendered. An invalid handle (the default) means * the swapchain; a target created with * ResourceRegistryRef::create() redirects it offscreen, and the @@ -78,6 +81,7 @@ class Application { std::unique_ptr impl; PanelRegistry panelRegistry; + SceneRegistry sceneRegistry; UI _ui; DebugUI _debugUI; diff --git a/engine/include/sophia/resource_registry_ref.hpp b/engine/include/sophia/resource_registry_ref.hpp index 5823b63..34d4785 100644 --- a/engine/include/sophia/resource_registry_ref.hpp +++ b/engine/include/sophia/resource_registry_ref.hpp @@ -16,7 +16,6 @@ namespace sophia { class Mesh; class RenderTarget; -class Scene; class ResourceRegistry; template @@ -32,11 +31,6 @@ struct ResourceTraits { using Handle = RenderTargetHandle; }; -template <> -struct ResourceTraits { - using Handle = SceneHandle; -}; - class ResourceRegistryRef { public: template @@ -52,7 +46,6 @@ class ResourceRegistryRef { glm::vec2 getExtent(RenderTargetHandle handle) const; bool isAlive(RenderTargetHandle handle) const; - bool isAlive(SceneHandle handle) const; template void release(typename ResourceTraits::Handle handle); @@ -70,8 +63,6 @@ class ResourceRegistryRef { RenderTargetHandle createResource(std::type_identity, u32 width = 1, u32 height = 1); - SceneHandle createResource(std::type_identity); - ResourceRegistry* registry; }; @@ -84,7 +75,4 @@ void ResourceRegistryRef::release(MeshHandle handle); template <> void ResourceRegistryRef::release(RenderTargetHandle handle); -template <> -void ResourceRegistryRef::release(SceneHandle handle); - } // namespace sophia diff --git a/engine/include/sophia/scene.hpp b/engine/include/sophia/scene.hpp new file mode 100644 index 0000000..bbc1c70 --- /dev/null +++ b/engine/include/sophia/scene.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include + +namespace sophia { + +class Scene { + public: + Scene(); + ~Scene(); + + // The camera a render request uses when it doesn't name one. Null until set. + entt::entity getActiveCamera() const { return activeCamera; } + void setActiveCamera(entt::entity camera) { activeCamera = camera; } + + private: + entt::registry registry; + entt::entity activeCamera = entt::null; +}; + +} // namespace sophia diff --git a/engine/include/sophia/scene_registry.hpp b/engine/include/sophia/scene_registry.hpp new file mode 100644 index 0000000..70e2739 --- /dev/null +++ b/engine/include/sophia/scene_registry.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include + +namespace sophia { + +class SceneRegistry { + public: + SceneRegistry(const SceneRegistry&) = delete; + SceneRegistry& operator=(const SceneRegistry&) = delete; + + SceneRegistry() = default; + ~SceneRegistry() = default; + + SceneHandle create() { return pool.create(); } + + const Scene& get(SceneHandle handle) const { return pool.get(handle); } + Scene& get(SceneHandle handle) { return pool.get(handle); } + + void release(SceneHandle handle) { pool.release(handle); } + bool isAlive(SceneHandle handle) const { return pool.isAlive(handle); } + + u32 size() const { return pool.size(); } + + private: + Pool pool{"Scene"}; +}; + +} // namespace sophia diff --git a/engine/include/sophia/ui/panel_registry.hpp b/engine/include/sophia/ui/panel_registry.hpp index 09b3b87..b53b11c 100644 --- a/engine/include/sophia/ui/panel_registry.hpp +++ b/engine/include/sophia/ui/panel_registry.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -21,17 +22,17 @@ class PanelRegistry { PanelRegistry() = default; ~PanelRegistry() = default; - PanelHandle add(std::unique_ptr panel); + PanelHandle add(std::unique_ptr panel) { + return pool.add(std::move(panel)); + } template PanelHandle emplace(Args&&... args) { return add(std::make_unique(std::forward(args)...)); } - const Panel& get(PanelHandle handle) const; - Panel& get(PanelHandle handle) { - return const_cast(std::as_const(*this).get(handle)); - } + const Panel& get(PanelHandle handle) const { return pool.get(handle); } + Panel& get(PanelHandle handle) { return pool.get(handle); } template const T& get(PanelHandle handle) const { @@ -46,19 +47,17 @@ class PanelRegistry { return const_cast(std::as_const(*this).template get(handle)); } - void remove(PanelHandle handle); - bool isValid(PanelHandle handle) const; + void remove(PanelHandle handle) { pool.release(handle); } + bool isValid(PanelHandle handle) const { return pool.isAlive(handle); } template const T* find() const { - for (const Slot& slot : slots) { - if (slot.panel) { - if (const T* panel = dynamic_cast(slot.panel.get())) { - return panel; - } - } - } - return nullptr; + const T* result = nullptr; + pool.findIf([&result](const Panel& panel) { + result = dynamic_cast(&panel); + return result != nullptr; + }); + return result; } template @@ -67,29 +66,22 @@ class PanelRegistry { } const Panel* find(std::string_view name) const { - for (const Slot& slot : slots) { - if (slot.panel && slot.panel->getName() == name) { - return slot.panel.get(); - } - } - return nullptr; + return pool.findIf( + [name](const Panel& panel) { return panel.getName() == name; }); } Panel* find(std::string_view name) { return const_cast(std::as_const(*this).find(name)); } - u32 size() const; + u32 size() const { return pool.size(); } u32 visibleCount() const; std::vector names() const { std::vector result; - result.reserve(slots.size()); - for (const Slot& slot : slots) { - if (slot.panel) { - result.push_back(slot.panel->getName()); - } - } + result.reserve(pool.size()); + pool.forEach( + [&result](const Panel& panel) { result.push_back(panel.getName()); }); return result; } @@ -101,13 +93,7 @@ class PanelRegistry { const DockLayout& getDefaultLayout() const { return defaultLayout; } private: - struct Slot { - std::unique_ptr panel; - u32 generation = 0; - }; - - std::vector slots; - std::vector freeList; + Pool pool{"Panel"}; DockLayout defaultLayout; }; diff --git a/engine/src/resources/pools/pool.hpp b/engine/include/sophia/util/pool.hpp similarity index 60% rename from engine/src/resources/pools/pool.hpp rename to engine/include/sophia/util/pool.hpp index 99669bd..f5b8a53 100644 --- a/engine/src/resources/pools/pool.hpp +++ b/engine/include/sophia/util/pool.hpp @@ -8,7 +8,7 @@ namespace sophia { -template +template class Pool { public: explicit Pool(const char* name) : name{name} {} @@ -17,27 +17,29 @@ class Pool { Pool& operator=(const Pool&) = delete; template - HandleT create(Args&&... args) { - auto item = std::make_unique(std::forward(args)...); + HandleType create(Args&&... args) { + return add(std::make_unique(std::forward(args)...)); + } + HandleType add(std::unique_ptr item) { if (!freeList.empty()) { u32 index = freeList.back(); freeList.pop_back(); slots[index].item = std::move(item); - return HandleT{index, slots[index].generation}; + return HandleType{index, slots[index].generation}; } slots.push_back(Slot{std::move(item), 0}); - return HandleT{static_cast(slots.size() - 1), 0}; + return HandleType{static_cast(slots.size() - 1), 0}; } - T& get(HandleT handle) { + T& get(HandleType handle) { return const_cast(std::as_const(*this).get(handle)); } - const T& get(HandleT handle) const { return *validate(handle).item; } + const T& get(HandleType handle) const { return *validate(handle).item; } - void release(HandleT handle) { + void release(HandleType handle) { validate(handle); Slot& slot = slots[handle.index]; @@ -46,12 +48,14 @@ class Pool { freeList.push_back(handle.index); } - bool isAlive(HandleT handle) const { + bool isAlive(HandleType handle) const { return handle.isValid() && handle.index < slots.size() && slots[handle.index].generation == handle.generation && slots[handle.index].item != nullptr; } + u32 size() const { return static_cast(slots.size() - freeList.size()); } + template void forEach(Fn&& fn) { for (Slot& slot : slots) { @@ -61,6 +65,31 @@ class Pool { } } + template + void forEach(Fn&& fn) const { + for (const Slot& slot : slots) { + if (slot.item) { + fn(*slot.item); + } + } + } + + template + T* findIf(Predicate&& predicate) { + return const_cast( + std::as_const(*this).findIf(std::forward(predicate))); + } + + template + const T* findIf(Predicate&& predicate) const { + for (const Slot& slot : slots) { + if (slot.item && predicate(*slot.item)) { + return slot.item.get(); + } + } + return nullptr; + } + private: static constexpr u32 GENERATION_MASK = (1u << 12) - 1; @@ -69,7 +98,7 @@ class Pool { u32 generation = 0; }; - const Slot& validate(HandleT handle) const { + const Slot& validate(HandleType handle) const { SOPHIA_ASSERT(handle.isValid(), "invalid handle:", name); SOPHIA_ASSERT(handle.index < slots.size(), name, "handle index out of range:", handle.index); diff --git a/engine/src/render_context.cpp b/engine/src/render_context.cpp index 76296c6..c07dd0e 100644 --- a/engine/src/render_context.cpp +++ b/engine/src/render_context.cpp @@ -5,6 +5,8 @@ #include #include +#include "rendering/access.hpp" + namespace sophia { RenderContext::RenderContext(Window& window, Device& device) @@ -80,8 +82,7 @@ void RenderContext::endFrame() { vk::Semaphore waitSemaphore = frame.imageAvailable.get(); vk::Semaphore signalSemaphore = this->renderFinishedSemaphores.at(this->currentImageIndex).get(); - vk::PipelineStageFlags waitStage = - vk::PipelineStageFlagBits::eColorAttachmentOutput; + vk::PipelineStageFlags waitStage = SWAPCHAIN_ACQUIRE_STAGE; vk::CommandBuffer commandBuffer = frame.commandBuffer; vk::SubmitInfo submitInfo{}; diff --git a/engine/src/render_context.hpp b/engine/src/render_context.hpp index 34e7ac1..afe4728 100644 --- a/engine/src/render_context.hpp +++ b/engine/src/render_context.hpp @@ -31,14 +31,8 @@ class RenderContext { vk::CommandBuffer beginFrame(); void endFrame(); - void transitionCurrentImage(vk::CommandBuffer commandBuffer, - vk::ImageLayout newLayout) const { - this->swapchain->getImage(static_cast(this->currentImageIndex)) - .transitionLayout(commandBuffer, newLayout); - } - vk::ImageView getCurrentImageView() const { - return this->swapchain->getImageView( - static_cast(this->currentImageIndex)); + Image& getCurrentImage() const { + return this->swapchain->getImage(static_cast(this->currentImageIndex)); } vk::Extent2D getExtent() const { return this->swapchain->getExtent(); } diff --git a/engine/src/resources/pools/render_target_pool.hpp b/engine/src/resources/pools/render_target_pool.hpp index ff7ca0f..63a6afb 100644 --- a/engine/src/resources/pools/render_target_pool.hpp +++ b/engine/src/resources/pools/render_target_pool.hpp @@ -1,10 +1,10 @@ #pragma once #include +#include #include "device.hpp" #include "render_target.hpp" -#include "resources/pools/pool.hpp" namespace sophia { diff --git a/engine/src/resources/resource_registry.hpp b/engine/src/resources/resource_registry.hpp index 6dc5629..1acb28d 100644 --- a/engine/src/resources/resource_registry.hpp +++ b/engine/src/resources/resource_registry.hpp @@ -1,12 +1,11 @@ #pragma once #include +#include #include "device.hpp" #include "mesh.hpp" -#include "resources/pools/pool.hpp" #include "resources/pools/render_target_pool.hpp" -#include "scene.hpp" namespace sophia { @@ -21,7 +20,6 @@ class ResourceRegistry { Device& device; Pool meshPool{"Mesh"}; RenderTargetPool renderTargetPool; - Pool scenePool{"Scene"}; // TexturePool textures; // MaterialPool materials; }; diff --git a/engine/src/resources/resource_registry_ref.cpp b/engine/src/resources/resource_registry_ref.cpp index 5e24d7b..6d963a8 100644 --- a/engine/src/resources/resource_registry_ref.cpp +++ b/engine/src/resources/resource_registry_ref.cpp @@ -24,11 +24,6 @@ void ResourceRegistryRef::release(RenderTargetHandle handle) { registry->renderTargetPool.release(handle); } -template <> -void ResourceRegistryRef::release(SceneHandle handle) { - registry->scenePool.release(handle); -} - MeshHandle ResourceRegistryRef::createResource( std::type_identity, std::vector vertices, std::vector indices) { @@ -47,10 +42,6 @@ RenderTargetHandle ResourceRegistryRef::createResource( return registry->renderTargetPool.create(width, height); } -SceneHandle ResourceRegistryRef::createResource(std::type_identity) { - return registry->scenePool.create(); -} - void ResourceRegistryRef::requestResize(RenderTargetHandle handle, u32 width, u32 height) { registry->renderTargetPool.get(handle).requestResize(width, height); @@ -65,8 +56,4 @@ bool ResourceRegistryRef::isAlive(RenderTargetHandle handle) const { return registry->renderTargetPool.isAlive(handle); } -bool ResourceRegistryRef::isAlive(SceneHandle handle) const { - return registry->scenePool.isAlive(handle); -} - } // namespace sophia diff --git a/engine/src/scene.cpp b/engine/src/scene.cpp index 865868f..375424b 100644 --- a/engine/src/scene.cpp +++ b/engine/src/scene.cpp @@ -1,6 +1,5 @@ -#include "scene.hpp" - #include +#include #include namespace sophia { diff --git a/engine/src/scene.hpp b/engine/src/scene.hpp deleted file mode 100644 index ed40054..0000000 --- a/engine/src/scene.hpp +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include - -namespace sophia { - -class Scene { - public: - Scene(); - ~Scene(); - - private: - entt::registry registry; -}; - -} // namespace sophia diff --git a/engine/src/swapchain.hpp b/engine/src/swapchain.hpp index 8168700..0422aec 100644 --- a/engine/src/swapchain.hpp +++ b/engine/src/swapchain.hpp @@ -23,9 +23,6 @@ class Swapchain { Image& getImage(int index) const { return *this->images.at(index); } Image& getDepthImage(int index) const { return *this->depthImages.at(index); } - vk::ImageView getImageView(int index) const { - return this->images.at(index)->getView(); - } vk::ImageView getDepthImageView(int index) const { return this->depthImages.at(index)->getView(); } diff --git a/engine/src/ui/panel_registry.cpp b/engine/src/ui/panel_registry.cpp index 5b34ea9..f372ffd 100644 --- a/engine/src/ui/panel_registry.cpp +++ b/engine/src/ui/panel_registry.cpp @@ -1,88 +1,23 @@ #include -#include namespace sophia { -namespace { -constexpr u32 GENERATION_MASK = (1u << 12) - 1; -} // namespace - -PanelHandle PanelRegistry::add(std::unique_ptr panel) { - if (!freeList.empty()) { - u32 index = freeList.back(); - freeList.pop_back(); - slots[index].panel = std::move(panel); - return PanelHandle{index, slots[index].generation}; - } - - slots.push_back(Slot{std::move(panel), 0}); - return PanelHandle{static_cast(slots.size() - 1), 0}; -} - -const Panel& PanelRegistry::get(PanelHandle handle) const { - SOPHIA_ASSERT(handle.isValid(), "invalid PanelHandle"); - SOPHIA_ASSERT(handle.index < slots.size(), - "PanelHandle index out of range:", handle.index); - - const Slot& slot = slots[handle.index]; - SOPHIA_ASSERT(slot.generation == handle.generation, - "stale PanelHandle (generation mismatch):", handle.index); - SOPHIA_ASSERT(slot.panel != nullptr, - "PanelHandle refers to a removed panel:", handle.index); - return *slot.panel; -} - -void PanelRegistry::remove(PanelHandle handle) { - SOPHIA_ASSERT(handle.isValid(), "invalid PanelHandle"); - SOPHIA_ASSERT(handle.index < slots.size(), - "PanelHandle index out of range:", handle.index); - - Slot& slot = slots[handle.index]; - SOPHIA_ASSERT(slot.generation == handle.generation, - "stale PanelHandle (generation mismatch):", handle.index); - SOPHIA_ASSERT(slot.panel != nullptr, - "PanelHandle already removed:", handle.index); - - slot.panel.reset(); - slot.generation = (slot.generation + 1) & GENERATION_MASK; - freeList.push_back(handle.index); -} - -bool PanelRegistry::isValid(PanelHandle handle) const { - if (!handle.isValid() || handle.index >= slots.size()) { - return false; - } - - const Slot& slot = slots[handle.index]; - return slot.generation == handle.generation && slot.panel != nullptr; -} - -u32 PanelRegistry::size() const { - u32 count = 0; - for (const Slot& slot : slots) { - if (slot.panel) { - count++; - } - } - return count; -} - u32 PanelRegistry::visibleCount() const { u32 count = 0; - for (const Slot& slot : slots) { - if (slot.panel && slot.panel->isVisible()) { + pool.forEach([&count](const Panel& panel) { + if (panel.isVisible()) { count++; } - } + }); return count; } void PanelRegistry::update(const FrameInfo& frameInfo) { - for (auto& slot : slots) { - if (slot.panel && slot.panel->isVisible()) { - slot.panel->draw(frameInfo); + pool.forEach([&frameInfo](Panel& panel) { + if (panel.isVisible()) { + panel.draw(frameInfo); } - } + }); } } // namespace sophia