Upgrading pool pattern
This commit is contained in:
+31
-10
@@ -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
|
||||||
|
|||||||
@@ -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,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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
+19
-32
@@ -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{®istry} {}
|
||||||
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,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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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,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>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user