Add offscreen render targets and rename AssetRegistry to ResourceRegistry
Add a RenderTarget resource so the scene can render offscreen instead of straight to the swapchain. The editor uses it to show the scene inside the Viewport panel. - Rename AssetRegistry to ResourceRegistry (AssetTraits -> ResourceTraits, assets() -> resources()) and move the pools from engine/src/assets to engine/src/resources. It now covers non-asset GPU resources as well. - Replace loadMesh() with create<T>(...), and add create<RenderTarget>(), requestResize(), getExtent() and isAlive(). - Add RenderTarget and RenderTargetPool with RenderTargetHandle. Resizes are deferred and applied at the start of the next frame, when the GPU is idle. - Add Application::setSceneTarget(). With no target set, the scene still renders to the swapchain. With a target set, the scene renders offscreen and the swapchain receives only the UI. Frame extent and camera projection follow the target's size. - Add sophia::UI (Application::ui()) with image() and getTextureId() to draw a render target in an ImGui window, sizing the target to the available space. ImGuiRenderer now registers the target textures for this. - ViewportPanel takes the UI and a target, and draws the scene image in place of the flat background fill. - Update the testbed, the boids example, and the glossary for the rename.
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
#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
|
||||
@@ -0,0 +1,35 @@
|
||||
#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,81 @@
|
||||
#include "resources/pools/render_target_pool.hpp"
|
||||
|
||||
#include <sophia/util/assert.hpp>
|
||||
#include <utility>
|
||||
|
||||
namespace sophia {
|
||||
|
||||
namespace {
|
||||
constexpr u32 GENERATION_MASK = (1u << 12) - 1;
|
||||
} // namespace
|
||||
|
||||
RenderTargetPool::RenderTargetPool(Device& device, vk::Format format)
|
||||
: device{device}, format{format} {}
|
||||
|
||||
RenderTargetPool::~RenderTargetPool() = default;
|
||||
|
||||
RenderTargetHandle RenderTargetPool::create(u32 width, u32 height) {
|
||||
auto target = std::make_unique<RenderTarget>(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) {
|
||||
validate(handle);
|
||||
|
||||
// Earlier frames may still be rendering into or sampling the target.
|
||||
device.waitIdle();
|
||||
|
||||
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() {
|
||||
for (Slot& slot : slots) {
|
||||
if (slot.target) {
|
||||
slot.target->applyPendingResize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace sophia
|
||||
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <sophia/handles.hpp>
|
||||
#include <vector>
|
||||
|
||||
#include "device.hpp"
|
||||
#include "render_target.hpp"
|
||||
|
||||
namespace sophia {
|
||||
|
||||
class RenderTargetPool {
|
||||
public:
|
||||
RenderTargetPool(Device& device, vk::Format format);
|
||||
~RenderTargetPool();
|
||||
|
||||
RenderTargetPool(const RenderTargetPool&) = delete;
|
||||
RenderTargetPool& operator=(const RenderTargetPool&) = delete;
|
||||
|
||||
RenderTargetHandle create(u32 width, u32 height);
|
||||
RenderTarget& get(RenderTargetHandle handle);
|
||||
const RenderTarget& get(RenderTargetHandle handle) const;
|
||||
void release(RenderTargetHandle handle);
|
||||
|
||||
bool isAlive(RenderTargetHandle handle) const;
|
||||
|
||||
void applyPendingResizes();
|
||||
|
||||
private:
|
||||
struct Slot {
|
||||
std::unique_ptr<RenderTarget> target;
|
||||
u32 generation = 0;
|
||||
};
|
||||
|
||||
const Slot& validate(RenderTargetHandle handle) const;
|
||||
|
||||
Device& device;
|
||||
vk::Format format;
|
||||
|
||||
std::vector<Slot> slots;
|
||||
std::vector<u32> freeList;
|
||||
};
|
||||
|
||||
} // namespace sophia
|
||||
@@ -0,0 +1,104 @@
|
||||
#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
|
||||
Reference in New Issue
Block a user