Upgrading pool pattern

This commit is contained in:
2026-09-20 20:52:09 -07:00
parent fe40d4a629
commit 16f7bb7da3
18 changed files with 268 additions and 332 deletions
+31 -10
View File
@@ -3,34 +3,55 @@
## ResourceRegistry ## ResourceRegistry
A ResourceRegistry owns GPU-backed resources on the caller's behalf and hands out A ResourceRegistry owns GPU-backed resources on the caller's behalf and hands out
lightweight Handles instead of pointers or ownership. lightweight Handles instead of pointers or ownership. It is engine-internal:
frontend code never sees it, and reaches it through a ResourceRegistryRef.
Internally it's a collection of per-type pools, each responsible for one Internally it's a collection of per-type pools, each a `Pool<T, Handle>`
resource type: (a generation-checked slot pool). A type that needs extra behaviour wraps its
pool in a thin class instead:
``` ```
ResourceRegistry ResourceRegistry
├── MeshPool → MeshHandle ├── meshes Pool<Mesh, MeshHandle>
├── TexturePool → TextureHandle (planned) ├── renderTargets RenderTargetPool → RenderTargetHandle
└── MaterialPool → MaterialHandle (planned) │ (wraps Pool; waits for the GPU on release, applies resizes)
├── scenes Pool<Scene, SceneHandle>
├── textures (planned)
└── materials (planned)
``` ```
Engine code (`Application`, `ImGuiRenderer`, `SceneRenderer`) uses the pools
directly, e.g. `resources.renderTargets.get(handle)`.
A Handle is a small (index, generation) pair. The index locates the slot in 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 the pool; the generation is bumped when that slot is released, so a Handle
kept around after release() is detected as stale instead of resolving to kept around after release() is detected as stale instead of resolving to
someone else's resource (or freed memory). someone else's resource (or freed memory).
Think: "Where does the engine keep every GPU resource, and how does it look one
up by handle?"
## ResourceRegistryRef
A ResourceRegistryRef is the frontend's non-owning, cheap-to-copy view of the
ResourceRegistry, returned by `Application::resources()`. It exposes only what
frontend code needs and hands out no pools, so no Vulkan-facing types leak
through the public API:
```
create<T>(...) load<T>(path) release<T>(handle)
requestResize(target, w, h) getExtent(target) isAlive(handle)
```
Typical lifecycle: Typical lifecycle:
``` ```
load<Mesh>(path) / create<Mesh>(...) → MeshHandle load<Mesh>(path) / create<Mesh>(...) → MeshHandle
│ │
get<Mesh>(handle) → const Mesh&
│
release<Mesh>(handle) release<Mesh>(handle)
``` ```
Think: "Where do I get or release a GPU resource by handle, without owning its Think: "How do I create or release a GPU resource by handle, without owning its
lifetime directly?" lifetime directly?"
## Buffer ## Buffer
@@ -257,7 +278,7 @@ A PanelRegistry owns Panels on the caller's behalf and hands out lightweight
PanelHandles instead of pointers or ownership. PanelHandles instead of pointers or ownership.
Internally it's a generation-checked slot pool, the same shape ResourceRegistry's Internally it's a generation-checked slot pool, the same shape ResourceRegistry's
per-type pools use: `Pool<T, Handle>` uses:
``` ```
PanelRegistry PanelRegistry
+3 -4
View File
@@ -7,7 +7,7 @@
#include <sophia/canvas.hpp> #include <sophia/canvas.hpp>
#include <sophia/console/console_command_registry.hpp> #include <sophia/console/console_command_registry.hpp>
#include <sophia/frame_info.hpp> #include <sophia/frame_info.hpp>
#include <sophia/resource_registry.hpp> #include <sophia/resource_registry_ref.hpp>
#include <sophia/types.hpp> #include <sophia/types.hpp>
#include <sophia/ui/debug_ui.hpp> #include <sophia/ui/debug_ui.hpp>
#include <sophia/ui/panel_registry.hpp> #include <sophia/ui/panel_registry.hpp>
@@ -45,7 +45,7 @@ class Application {
virtual void onRender() {} virtual void onRender() {}
virtual void onDestroy() {} virtual void onDestroy() {}
ResourceRegistry& resources() { return this->resourceRegistry; } ResourceRegistryRef resources();
PanelRegistry& panels() { return this->panelRegistry; } PanelRegistry& panels() { return this->panelRegistry; }
UI& ui() { return this->_ui; } UI& ui() { return this->_ui; }
@@ -62,7 +62,7 @@ class Application {
/** /**
* Chooses where the scene is rendered. An invalid handle (the default) means * Chooses where the scene is rendered. An invalid handle (the default) means
* the swapchain; a target created with * the swapchain; a target created with
* ResourceRegistry::create<RenderTarget>() redirects it offscreen, and the * ResourceRegistryRef::create<RenderTarget>() redirects it offscreen, and the
* swapchain then only receives the UI. FrameInfo::currentFramebufferExtent * swapchain then only receives the UI. FrameInfo::currentFramebufferExtent
* and the camera projection follow the target's size. * and the camera projection follow the target's size.
*/ */
@@ -77,7 +77,6 @@ class Application {
class Impl; class Impl;
std::unique_ptr<Impl> impl; std::unique_ptr<Impl> impl;
ResourceRegistry resourceRegistry;
PanelRegistry panelRegistry; PanelRegistry panelRegistry;
UI _ui; UI _ui;
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once #pragma once
#include <sophia/resource_registry.hpp> #include <sophia/handles.hpp>
#include <string> #include <string>
#define GLM_FORCE_RADIANS #define GLM_FORCE_RADIANS
+2
View File
@@ -27,9 +27,11 @@ struct Handle {
struct MeshTag {}; struct MeshTag {};
struct PanelTag {}; struct PanelTag {};
struct RenderTargetTag {}; struct RenderTargetTag {};
struct SceneTag {};
using MeshHandle = Handle<MeshTag>; using MeshHandle = Handle<MeshTag>;
using PanelHandle = Handle<PanelTag>; using PanelHandle = Handle<PanelTag>;
using RenderTargetHandle = Handle<RenderTargetTag>; using RenderTargetHandle = Handle<RenderTargetTag>;
using SceneHandle = Handle<SceneTag>;
} // namespace sophia } // namespace sophia
@@ -1,25 +1,23 @@
#pragma once #pragma once
#include <memory> #include <sophia/canvas.hpp>
#include <sophia/handles.hpp> #include <sophia/handles.hpp>
#include <sophia/types.hpp>
#include <string> #include <string>
#include <type_traits> #include <type_traits>
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "sophia/canvas.hpp"
#include "sophia/types.hpp"
#define GLM_FORCE_RADIANS #define GLM_FORCE_RADIANS
#define GLM_FORCE_DEPTH_ZERO_TO_ONE #define GLM_FORCE_DEPTH_ZERO_TO_ONE
#include <glm/glm.hpp> #include <glm/glm.hpp>
namespace sophia { namespace sophia {
class Device;
class Mesh; class Mesh;
class RenderTarget; class RenderTarget;
class ImGuiRenderer; class Scene;
class ResourceRegistry;
template <typename T> template <typename T>
struct ResourceTraits; struct ResourceTraits;
@@ -34,13 +32,13 @@ struct ResourceTraits<RenderTarget> {
using Handle = RenderTargetHandle; using Handle = RenderTargetHandle;
}; };
class ResourceRegistry { template <>
struct ResourceTraits<Scene> {
using Handle = SceneHandle;
};
class ResourceRegistryRef {
public: public:
~ResourceRegistry();
ResourceRegistry(const ResourceRegistry&) = delete;
ResourceRegistry& operator=(const ResourceRegistry&) = delete;
template <typename T> template <typename T>
typename ResourceTraits<T>::Handle load(const std::string& path); typename ResourceTraits<T>::Handle load(const std::string& path);
@@ -54,21 +52,16 @@ class ResourceRegistry {
glm::vec2 getExtent(RenderTargetHandle handle) const; glm::vec2 getExtent(RenderTargetHandle handle) const;
bool isAlive(RenderTargetHandle handle) const; bool isAlive(RenderTargetHandle handle) const;
bool isAlive(SceneHandle handle) const;
/**
* Waits for the GPU to go idle, so don't call this every frame. Don't release
* the target the scene is currently rendering into.
*/
template <typename T> template <typename T>
void release(typename ResourceTraits<T>::Handle handle); void release(typename ResourceTraits<T>::Handle handle);
private: private:
friend class Application; friend class Application;
friend class ImGuiRenderer;
// `renderTargetFormat` is a VkFormat value; it must match the color format explicit ResourceRegistryRef(ResourceRegistry& registry)
// the scene pipelines are built against. : registry{&registry} {}
ResourceRegistry(Device& device, u32 renderTargetFormat);
MeshHandle createResource(std::type_identity<Mesh>, MeshHandle createResource(std::type_identity<Mesh>,
std::vector<CanvasVertex> vertices, std::vector<CanvasVertex> vertices,
@@ -77,27 +70,21 @@ class ResourceRegistry {
RenderTargetHandle createResource(std::type_identity<RenderTarget>, RenderTargetHandle createResource(std::type_identity<RenderTarget>,
u32 width = 1, u32 height = 1); u32 width = 1, u32 height = 1);
RenderTarget& getRenderTarget(RenderTargetHandle handle); SceneHandle createResource(std::type_identity<Scene>);
const RenderTarget& getRenderTarget(RenderTargetHandle handle) const;
void applyPendingResizes();
template <typename T> ResourceRegistry* registry;
const T& get(typename ResourceTraits<T>::Handle handle) const;
class Impl;
std::unique_ptr<Impl> impl;
}; };
template <> template <>
MeshHandle ResourceRegistry::load<Mesh>(const std::string& path); MeshHandle ResourceRegistryRef::load<Mesh>(const std::string& path);
template <> template <>
void ResourceRegistry::release<Mesh>(MeshHandle handle); void ResourceRegistryRef::release<Mesh>(MeshHandle handle);
template <> template <>
void ResourceRegistry::release<RenderTarget>(RenderTargetHandle handle); void ResourceRegistryRef::release<RenderTarget>(RenderTargetHandle handle);
template <> template <>
const Mesh& ResourceRegistry::get<Mesh>(MeshHandle handle) const; void ResourceRegistryRef::release<Scene>(SceneHandle handle);
} // namespace sophia } // namespace sophia
+3 -3
View File
@@ -3,11 +3,11 @@
#include <imgui.h> #include <imgui.h>
#include <sophia/handles.hpp> #include <sophia/handles.hpp>
#include <sophia/resource_registry_ref.hpp>
namespace sophia { namespace sophia {
class ImGuiRenderer; class ImGuiRenderer;
class ResourceRegistry;
class UI { class UI {
public: public:
@@ -31,9 +31,9 @@ class UI {
private: private:
friend class Application; friend class Application;
explicit UI(ResourceRegistry& resources) : resources{resources} {} explicit UI(ResourceRegistryRef resources) : resources{resources} {}
ResourceRegistry& resources; ResourceRegistryRef resources;
ImGuiRenderer* renderer = nullptr; ImGuiRenderer* renderer = nullptr;
}; };
+5 -4
View File
@@ -92,7 +92,7 @@ void ImGuiRenderer::beginFrame() {
ImTextureID ImGuiRenderer::getTextureId(RenderTargetHandle handle) { ImTextureID ImGuiRenderer::getTextureId(RenderTargetHandle handle) {
vk::ImageView view = vk::ImageView view =
std::as_const(resources).getRenderTarget(handle).getImage().getView(); resources.renderTargetPool.get(handle).getImage().getView();
auto it = textures.find(handle.index); auto it = textures.find(handle.index);
if (it != textures.end()) { if (it != textures.end()) {
@@ -116,10 +116,11 @@ ImTextureID ImGuiRenderer::getTextureId(RenderTargetHandle handle) {
void ImGuiRenderer::releaseStaleTextures() { void ImGuiRenderer::releaseStaleTextures() {
for (auto it = textures.begin(); it != textures.end();) { for (auto it = textures.begin(); it != textures.end();) {
RenderTargetHandle handle{it->first, it->second.generation}; RenderTargetHandle handle{it->first, it->second.generation};
RenderTargetPool& renderTargetPool = resources.renderTargetPool;
bool stale = bool stale =
!resources.isAlive(handle) || !renderTargetPool.isAlive(handle) ||
std::as_const(resources).getRenderTarget(handle).getImage().getView() != renderTargetPool.get(handle).getImage().getView() != it->second.view;
it->second.view;
if (stale) { if (stale) {
ImGui_ImplVulkan_RemoveTexture(it->second.descriptorSet); ImGui_ImplVulkan_RemoveTexture(it->second.descriptorSet);
it = textures.erase(it); it = textures.erase(it);
+1 -1
View File
@@ -3,12 +3,12 @@
#include <imgui.h> #include <imgui.h>
#include <sophia/handles.hpp> #include <sophia/handles.hpp>
#include <sophia/resource_registry.hpp>
#include <sophia/types.hpp> #include <sophia/types.hpp>
#include <sophia/ui/dock_layout.hpp> #include <sophia/ui/dock_layout.hpp>
#include <unordered_map> #include <unordered_map>
#include "device.hpp" #include "device.hpp"
#include "resources/resource_registry.hpp"
#include "window.hpp" #include "window.hpp"
namespace sophia { namespace sophia {
+1 -1
View File
@@ -1,7 +1,7 @@
#pragma once #pragma once
#include <memory> #include <memory>
#include <sophia/resource_registry.hpp> #include <sophia/handles.hpp>
#include <sophia/types.hpp> #include <sophia/types.hpp>
#include <vector> #include <vector>
-58
View File
@@ -1,58 +0,0 @@
#include "resources/pools/mesh_pool.hpp"
#include <sophia/util/assert.hpp>
namespace sophia {
namespace {
constexpr u32 GENERATION_MASK = (1u << 12) - 1;
} // namespace
MeshPool::MeshPool(Device& device) : device{device} {}
MeshPool::~MeshPool() = default;
MeshHandle MeshPool::create(const Mesh::Builder& builder) {
auto mesh = std::make_unique<Mesh>(device, builder);
if (!freeList.empty()) {
u32 index = freeList.back();
freeList.pop_back();
slots[index].mesh = std::move(mesh);
return MeshHandle{index, slots[index].generation};
}
slots.push_back(Slot{std::move(mesh), 0});
return MeshHandle{static_cast<u32>(slots.size() - 1), 0};
}
const Mesh& MeshPool::get(MeshHandle handle) const {
SOPHIA_ASSERT(handle.isValid(), "invalid MeshHandle");
SOPHIA_ASSERT(handle.index < slots.size(),
"MeshHandle index out of range:", handle.index);
const Slot& slot = slots[handle.index];
SOPHIA_ASSERT(slot.generation == handle.generation,
"stale MeshHandle (generation mismatch):", handle.index);
SOPHIA_ASSERT(slot.mesh != nullptr,
"MeshHandle refers to a released mesh:", handle.index);
return *slot.mesh;
}
void MeshPool::release(MeshHandle handle) {
SOPHIA_ASSERT(handle.isValid(), "invalid MeshHandle");
SOPHIA_ASSERT(handle.index < slots.size(),
"MeshHandle index out of range:", handle.index);
Slot& slot = slots[handle.index];
SOPHIA_ASSERT(slot.generation == handle.generation,
"stale MeshHandle (generation mismatch):", handle.index);
SOPHIA_ASSERT(slot.mesh != nullptr,
"MeshHandle already released:", handle.index);
slot.mesh.reset();
slot.generation = (slot.generation + 1) & GENERATION_MASK;
freeList.push_back(handle.index);
}
} // namespace sophia
-35
View File
@@ -1,35 +0,0 @@
#pragma once
#include <memory>
#include <vector>
#include "device.hpp"
#include "mesh.hpp"
namespace sophia {
class MeshPool {
public:
explicit MeshPool(Device& device);
~MeshPool();
MeshPool(const MeshPool&) = delete;
MeshPool& operator=(const MeshPool&) = delete;
MeshHandle create(const Mesh::Builder& builder);
const Mesh& get(MeshHandle handle) const;
void release(MeshHandle handle);
private:
struct Slot {
std::unique_ptr<Mesh> mesh;
u32 generation = 0;
};
Device& device;
std::vector<Slot> slots;
std::vector<u32> freeList;
};
} // namespace sophia
+90
View File
@@ -0,0 +1,90 @@
#pragma once
#include <memory>
#include <sophia/types.hpp>
#include <sophia/util/assert.hpp>
#include <utility>
#include <vector>
namespace sophia {
template <typename T, typename HandleT>
class Pool {
public:
explicit Pool(const char* name) : name{name} {}
Pool(const Pool&) = delete;
Pool& operator=(const Pool&) = delete;
template <typename... Args>
HandleT create(Args&&... args) {
auto item = std::make_unique<T>(std::forward<Args>(args)...);
if (!freeList.empty()) {
u32 index = freeList.back();
freeList.pop_back();
slots[index].item = std::move(item);
return HandleT{index, slots[index].generation};
}
slots.push_back(Slot{std::move(item), 0});
return HandleT{static_cast<u32>(slots.size() - 1), 0};
}
T& get(HandleT handle) {
return const_cast<T&>(std::as_const(*this).get(handle));
}
const T& get(HandleT handle) const { return *validate(handle).item; }
void release(HandleT handle) {
validate(handle);
Slot& slot = slots[handle.index];
slot.item.reset();
slot.generation = (slot.generation + 1) & GENERATION_MASK;
freeList.push_back(handle.index);
}
bool isAlive(HandleT handle) const {
return handle.isValid() && handle.index < slots.size() &&
slots[handle.index].generation == handle.generation &&
slots[handle.index].item != nullptr;
}
template <typename Fn>
void forEach(Fn&& fn) {
for (Slot& slot : slots) {
if (slot.item) {
fn(*slot.item);
}
}
}
private:
static constexpr u32 GENERATION_MASK = (1u << 12) - 1;
struct Slot {
std::unique_ptr<T> item;
u32 generation = 0;
};
const Slot& validate(HandleT handle) const {
SOPHIA_ASSERT(handle.isValid(), "invalid handle:", name);
SOPHIA_ASSERT(handle.index < slots.size(), name,
"handle index out of range:", handle.index);
const Slot& slot = slots[handle.index];
SOPHIA_ASSERT(slot.generation == handle.generation, "stale", name,
"handle (generation mismatch):", handle.index);
SOPHIA_ASSERT(slot.item != nullptr, name,
"handle refers to a released item:", handle.index);
return slot;
}
const char* name;
std::vector<Slot> slots;
std::vector<u32> freeList;
};
} // namespace sophia
@@ -1,81 +1,22 @@
#include "resources/pools/render_target_pool.hpp" #include "resources/pools/render_target_pool.hpp"
#include <sophia/util/assert.hpp>
#include <utility>
namespace sophia { namespace sophia {
namespace {
constexpr u32 GENERATION_MASK = (1u << 12) - 1;
} // namespace
RenderTargetPool::RenderTargetPool(Device& device, vk::Format format) RenderTargetPool::RenderTargetPool(Device& device, vk::Format format)
: device{device}, format{format} {} : device{device}, format{format} {}
RenderTargetPool::~RenderTargetPool() = default;
RenderTargetHandle RenderTargetPool::create(u32 width, u32 height) { RenderTargetHandle RenderTargetPool::create(u32 width, u32 height) {
auto target = std::make_unique<RenderTarget>(device, format, width, height); return pool.create(device, format, width, height);
if (!freeList.empty()) {
u32 index = freeList.back();
freeList.pop_back();
slots[index].target = std::move(target);
return RenderTargetHandle{index, slots[index].generation};
}
slots.push_back(Slot{std::move(target), 0});
return RenderTargetHandle{static_cast<u32>(slots.size() - 1), 0};
}
const RenderTargetPool::Slot& RenderTargetPool::validate(
RenderTargetHandle handle) const {
SOPHIA_ASSERT(handle.isValid(), "invalid RenderTargetHandle");
SOPHIA_ASSERT(handle.index < slots.size(),
"RenderTargetHandle index out of range:", handle.index);
const Slot& slot = slots[handle.index];
SOPHIA_ASSERT(
slot.generation == handle.generation,
"stale RenderTargetHandle (generation mismatch):", handle.index);
SOPHIA_ASSERT(
slot.target != nullptr,
"RenderTargetHandle refers to a released target:", handle.index);
return slot;
}
RenderTarget& RenderTargetPool::get(RenderTargetHandle handle) {
return const_cast<RenderTarget&>(std::as_const(*this).get(handle));
}
const RenderTarget& RenderTargetPool::get(RenderTargetHandle handle) const {
return *validate(handle).target;
} }
void RenderTargetPool::release(RenderTargetHandle handle) { void RenderTargetPool::release(RenderTargetHandle handle) {
validate(handle); SOPHIA_ASSERT(pool.isAlive(handle), "invalid RenderTargetHandle");
// Earlier frames may still be rendering into or sampling the target.
device.waitIdle(); device.waitIdle();
pool.release(handle);
Slot& slot = slots[handle.index];
slot.target.reset();
slot.generation = (slot.generation + 1) & GENERATION_MASK;
freeList.push_back(handle.index);
}
bool RenderTargetPool::isAlive(RenderTargetHandle handle) const {
return handle.isValid() && handle.index < slots.size() &&
slots[handle.index].generation == handle.generation &&
slots[handle.index].target != nullptr;
} }
void RenderTargetPool::applyPendingResizes() { void RenderTargetPool::applyPendingResizes() {
for (Slot& slot : slots) { pool.forEach([](RenderTarget& target) { target.applyPendingResize(); });
if (slot.target) {
slot.target->applyPendingResize();
}
}
} }
} // namespace sophia } // namespace sophia
@@ -1,44 +1,36 @@
#pragma once #pragma once
#include <memory>
#include <sophia/handles.hpp> #include <sophia/handles.hpp>
#include <vector>
#include "device.hpp" #include "device.hpp"
#include "render_target.hpp" #include "render_target.hpp"
#include "resources/pools/pool.hpp"
namespace sophia { namespace sophia {
class RenderTargetPool { class RenderTargetPool {
public: public:
RenderTargetPool(Device& device, vk::Format format); RenderTargetPool(Device& device, vk::Format format);
~RenderTargetPool();
RenderTargetPool(const RenderTargetPool&) = delete; RenderTargetPool(const RenderTargetPool&) = delete;
RenderTargetPool& operator=(const RenderTargetPool&) = delete; RenderTargetPool& operator=(const RenderTargetPool&) = delete;
RenderTargetHandle create(u32 width, u32 height); RenderTargetHandle create(u32 width, u32 height);
RenderTarget& get(RenderTargetHandle handle); RenderTarget& get(RenderTargetHandle handle) { return pool.get(handle); }
const RenderTarget& get(RenderTargetHandle handle) const; const RenderTarget& get(RenderTargetHandle handle) const {
return pool.get(handle);
}
void release(RenderTargetHandle handle); void release(RenderTargetHandle handle);
bool isAlive(RenderTargetHandle handle) const; bool isAlive(RenderTargetHandle handle) const { return pool.isAlive(handle); }
void applyPendingResizes(); void applyPendingResizes();
private: private:
struct Slot {
std::unique_ptr<RenderTarget> target;
u32 generation = 0;
};
const Slot& validate(RenderTargetHandle handle) const;
Device& device; Device& device;
vk::Format format; vk::Format format;
std::vector<Slot> slots; Pool<RenderTarget, RenderTargetHandle> pool{"RenderTarget"};
std::vector<u32> freeList;
}; };
} // namespace sophia } // namespace sophia
-104
View File
@@ -1,104 +0,0 @@
#include <sophia/resource_registry.hpp>
#include <sophia/util/assert.hpp>
#include "mesh.hpp"
#include "pools/mesh_pool.hpp"
#include "pools/render_target_pool.hpp"
#include "render_target.hpp"
namespace sophia {
class ResourceRegistry::Impl {
public:
Impl(Device& device, vk::Format renderTargetFormat)
: meshPool{device}, renderTargetPool{device, renderTargetFormat} {}
MeshPool meshPool;
RenderTargetPool renderTargetPool;
// TexturePool texturePool;
// MaterialPool materialPool;
};
ResourceRegistry::ResourceRegistry(Device& device, u32 renderTargetFormat)
: impl{std::make_unique<Impl>(
device, static_cast<vk::Format>(renderTargetFormat))} {}
ResourceRegistry::~ResourceRegistry() = default;
template <>
MeshHandle ResourceRegistry::load<Mesh>(const std::string& path) {
(void)path;
SOPHIA_ASSERT(false,
"Mesh::load(path) is not implemented yet - no model file "
"format is supported; build one via Mesh::Builder for now");
return MeshHandle{};
}
template <typename T>
typename ResourceTraits<T>::Handle load(const std::string&) {
static_assert(sizeof(T) == 0,
"load<T> is not implemented for this resource type");
}
template <>
void ResourceRegistry::release<Mesh>(MeshHandle handle) {
impl->meshPool.release(handle);
}
template <>
void ResourceRegistry::release<RenderTarget>(RenderTargetHandle handle) {
impl->renderTargetPool.release(handle);
}
template <>
const Mesh& ResourceRegistry::get<Mesh>(MeshHandle handle) const {
return impl->meshPool.get(handle);
}
MeshHandle ResourceRegistry::createResource(std::type_identity<Mesh>,
std::vector<CanvasVertex> vertices,
std::vector<u32> indices) {
Mesh::Builder builder{};
builder.vertices.reserve(vertices.size());
for (const CanvasVertex& vertex : vertices) {
builder.vertices.push_back({vertex.position, vertex.color});
}
builder.indices = std::move(indices);
return impl->meshPool.create(builder);
}
RenderTargetHandle ResourceRegistry::createResource(
std::type_identity<RenderTarget>, u32 width, u32 height) {
return impl->renderTargetPool.create(width, height);
}
void ResourceRegistry::requestResize(RenderTargetHandle handle, u32 width,
u32 height) {
impl->renderTargetPool.get(handle).requestResize(width, height);
}
glm::vec2 ResourceRegistry::getExtent(RenderTargetHandle handle) const {
vk::Extent2D extent =
std::as_const(impl->renderTargetPool).get(handle).getExtent();
return {static_cast<float>(extent.width), static_cast<float>(extent.height)};
}
bool ResourceRegistry::isAlive(RenderTargetHandle handle) const {
return impl->renderTargetPool.isAlive(handle);
}
RenderTarget& ResourceRegistry::getRenderTarget(RenderTargetHandle handle) {
return impl->renderTargetPool.get(handle);
}
const RenderTarget& ResourceRegistry::getRenderTarget(
RenderTargetHandle handle) const {
return std::as_const(impl->renderTargetPool).get(handle);
}
void ResourceRegistry::applyPendingResizes() {
impl->renderTargetPool.applyPendingResizes();
}
} // namespace sophia
@@ -0,0 +1,29 @@
#pragma once
#include <sophia/handles.hpp>
#include "device.hpp"
#include "mesh.hpp"
#include "resources/pools/pool.hpp"
#include "resources/pools/render_target_pool.hpp"
#include "scene.hpp"
namespace sophia {
class ResourceRegistry {
public:
ResourceRegistry(const ResourceRegistry&) = delete;
ResourceRegistry& operator=(const ResourceRegistry&) = delete;
ResourceRegistry(Device& device, vk::Format renderTargetFormat)
: device{device}, renderTargetPool{device, renderTargetFormat} {}
Device& device;
Pool<Mesh, MeshHandle> meshPool{"Mesh"};
RenderTargetPool renderTargetPool;
Pool<Scene, SceneHandle> scenePool{"Scene"};
// TexturePool textures;
// MaterialPool materials;
};
} // namespace sophia
@@ -0,0 +1,72 @@
#include <sophia/resource_registry_ref.hpp>
#include <sophia/util/assert.hpp>
#include "resources/resource_registry.hpp"
namespace sophia {
template <>
MeshHandle ResourceRegistryRef::load<Mesh>(const std::string& path) {
(void)path;
SOPHIA_ASSERT(false,
"Mesh::load(path) is not implemented yet - no model file "
"format is supported; build one via Mesh::Builder for now");
return MeshHandle{};
}
template <>
void ResourceRegistryRef::release<Mesh>(MeshHandle handle) {
registry->meshPool.release(handle);
}
template <>
void ResourceRegistryRef::release<RenderTarget>(RenderTargetHandle handle) {
registry->renderTargetPool.release(handle);
}
template <>
void ResourceRegistryRef::release<Scene>(SceneHandle handle) {
registry->scenePool.release(handle);
}
MeshHandle ResourceRegistryRef::createResource(
std::type_identity<Mesh>, std::vector<CanvasVertex> vertices,
std::vector<u32> indices) {
Mesh::Builder builder{};
builder.vertices.reserve(vertices.size());
for (const CanvasVertex& vertex : vertices) {
builder.vertices.push_back({vertex.position, vertex.color});
}
builder.indices = std::move(indices);
return registry->meshPool.create(registry->device, builder);
}
RenderTargetHandle ResourceRegistryRef::createResource(
std::type_identity<RenderTarget>, u32 width, u32 height) {
return registry->renderTargetPool.create(width, height);
}
SceneHandle ResourceRegistryRef::createResource(std::type_identity<Scene>) {
return registry->scenePool.create();
}
void ResourceRegistryRef::requestResize(RenderTargetHandle handle, u32 width,
u32 height) {
registry->renderTargetPool.get(handle).requestResize(width, height);
}
glm::vec2 ResourceRegistryRef::getExtent(RenderTargetHandle handle) const {
vk::Extent2D extent = registry->renderTargetPool.get(handle).getExtent();
return {static_cast<float>(extent.width), static_cast<float>(extent.height)};
}
bool ResourceRegistryRef::isAlive(RenderTargetHandle handle) const {
return registry->renderTargetPool.isAlive(handle);
}
bool ResourceRegistryRef::isAlive(SceneHandle handle) const {
return registry->scenePool.isAlive(handle);
}
} // namespace sophia
-1
View File
@@ -1,4 +1,3 @@
#include <sophia/resource_registry.hpp>
#include <sophia/ui/ui.hpp> #include <sophia/ui/ui.hpp>
#include <sophia/util/assert.hpp> #include <sophia/util/assert.hpp>