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:
+8
-8
@@ -1,15 +1,15 @@
|
||||
# Glossary
|
||||
|
||||
## AssetRegistry
|
||||
## ResourceRegistry
|
||||
|
||||
An AssetRegistry owns GPU-backed assets 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.
|
||||
|
||||
Internally it's a collection of per-type pools, each responsible for one
|
||||
asset type:
|
||||
resource type:
|
||||
|
||||
```
|
||||
AssetRegistry
|
||||
ResourceRegistry
|
||||
├── MeshPool → MeshHandle
|
||||
├── TexturePool → TextureHandle (planned)
|
||||
└── MaterialPool → MaterialHandle (planned)
|
||||
@@ -18,19 +18,19 @@ AssetRegistry
|
||||
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
|
||||
kept around after release() is detected as stale instead of resolving to
|
||||
someone else's asset (or freed memory).
|
||||
someone else's resource (or freed memory).
|
||||
|
||||
Typical lifecycle:
|
||||
|
||||
```
|
||||
load<Mesh>(path) / loadMesh(...) → MeshHandle
|
||||
load<Mesh>(path) / create<Mesh>(...) → MeshHandle
|
||||
│
|
||||
get<Mesh>(handle) → const Mesh&
|
||||
│
|
||||
release<Mesh>(handle)
|
||||
```
|
||||
|
||||
Think: "Where do I get or release a GPU asset by handle, without owning its
|
||||
Think: "Where do I get or release a GPU resource by handle, without owning its
|
||||
lifetime directly?"
|
||||
|
||||
## Buffer
|
||||
@@ -256,7 +256,7 @@ 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 AssetRegistry's
|
||||
Internally it's a generation-checked slot pool, the same shape ResourceRegistry's
|
||||
per-type pools use:
|
||||
|
||||
```
|
||||
|
||||
+4
-1
@@ -18,7 +18,10 @@ class Editor : public sophia::Application {
|
||||
panels().emplace<SceneHierarchyPanel>();
|
||||
panels().emplace<InspectorPanel>();
|
||||
panels().emplace<AssetBrowserPanel>();
|
||||
panels().emplace<ViewportPanel>();
|
||||
sophia::RenderTargetHandle sceneTarget =
|
||||
resources().create<sophia::RenderTarget>();
|
||||
setSceneTarget(sceneTarget);
|
||||
panels().emplace<ViewportPanel>(ui(), sceneTarget);
|
||||
|
||||
panels().setDefaultLayout(
|
||||
{.left = {"Scene Hierarchy"},
|
||||
|
||||
@@ -24,8 +24,9 @@ void ViewportPanel::onUpdate(const sophia::FrameInfo&) {
|
||||
const ImVec2 size = ImGui::GetContentRegionAvail();
|
||||
const ImVec2 end(origin.x + size.x, origin.y + size.y);
|
||||
|
||||
ui.image(target);
|
||||
|
||||
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||
drawList->AddRectFilled(origin, end, IM_COL32(24, 26, 32, 255));
|
||||
|
||||
if (showGrid) {
|
||||
constexpr float GRID_STEP = 40.0f;
|
||||
|
||||
@@ -1,15 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <sophia/frame_info.hpp>
|
||||
#include <sophia/handles.hpp>
|
||||
#include <sophia/ui/panel.hpp>
|
||||
#include <sophia/ui/ui.hpp>
|
||||
|
||||
class ViewportPanel : public sophia::Panel {
|
||||
public:
|
||||
ViewportPanel() : Panel{{.name = "Viewport"}} {}
|
||||
ViewportPanel(sophia::UI& ui, sophia::RenderTargetHandle target)
|
||||
: Panel{{.name = "Viewport",
|
||||
.windowFlags = ImGuiWindowFlags_NoScrollbar |
|
||||
ImGuiWindowFlags_NoScrollWithMouse}},
|
||||
ui{ui},
|
||||
target{target} {}
|
||||
|
||||
void onUpdate(const sophia::FrameInfo& frameInfo) override;
|
||||
|
||||
private:
|
||||
sophia::UI& ui;
|
||||
sophia::RenderTargetHandle target;
|
||||
|
||||
enum class GizmoMode { Move, Rotate, Scale };
|
||||
|
||||
GizmoMode gizmoMode = GizmoMode::Move;
|
||||
|
||||
@@ -3,14 +3,15 @@
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <sophia/asset_registry.hpp>
|
||||
#include <sophia/camera_2d.hpp>
|
||||
#include <sophia/canvas.hpp>
|
||||
#include <sophia/console/console_command_registry.hpp>
|
||||
#include <sophia/frame_info.hpp>
|
||||
#include <sophia/resource_registry.hpp>
|
||||
#include <sophia/types.hpp>
|
||||
#include <sophia/ui/debug_ui.hpp>
|
||||
#include <sophia/ui/panel_registry.hpp>
|
||||
#include <sophia/ui/ui.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -44,8 +45,9 @@ class Application {
|
||||
virtual void onRender() {}
|
||||
virtual void onDestroy() {}
|
||||
|
||||
AssetRegistry& assets() { return this->assetRegistry; }
|
||||
ResourceRegistry& resources() { return this->resourceRegistry; }
|
||||
PanelRegistry& panels() { return this->panelRegistry; }
|
||||
UI& ui() { return this->_ui; }
|
||||
|
||||
DebugUI& debugUI() { return this->_debugUI; }
|
||||
|
||||
@@ -57,6 +59,15 @@ class Application {
|
||||
|
||||
void drawMesh(MeshHandle handle, const glm::mat4& transform);
|
||||
|
||||
/**
|
||||
* Chooses where the scene is rendered. An invalid handle (the default) means
|
||||
* the swapchain; a target created with
|
||||
* ResourceRegistry::create<RenderTarget>() redirects it offscreen, and the
|
||||
* swapchain then only receives the UI. FrameInfo::currentFramebufferExtent
|
||||
* and the camera projection follow the target's size.
|
||||
*/
|
||||
void setSceneTarget(RenderTargetHandle handle);
|
||||
|
||||
private:
|
||||
friend class Canvas;
|
||||
|
||||
@@ -66,8 +77,9 @@ class Application {
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> impl;
|
||||
|
||||
AssetRegistry assetRegistry;
|
||||
ResourceRegistry resourceRegistry;
|
||||
PanelRegistry panelRegistry;
|
||||
UI _ui;
|
||||
|
||||
DebugUI _debugUI;
|
||||
};
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <sophia/handles.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "sophia/canvas.hpp"
|
||||
#include "sophia/types.hpp"
|
||||
|
||||
namespace sophia {
|
||||
|
||||
class Device;
|
||||
class Mesh;
|
||||
|
||||
template <typename T>
|
||||
struct AssetTraits;
|
||||
|
||||
template <>
|
||||
struct AssetTraits<Mesh> {
|
||||
using Handle = MeshHandle;
|
||||
};
|
||||
|
||||
class AssetRegistry {
|
||||
public:
|
||||
~AssetRegistry();
|
||||
|
||||
AssetRegistry(const AssetRegistry&) = delete;
|
||||
AssetRegistry& operator=(const AssetRegistry&) = delete;
|
||||
|
||||
template <typename T>
|
||||
typename AssetTraits<T>::Handle load(const std::string& path);
|
||||
|
||||
/**
|
||||
* TEMPORARY: Remove once load<Mesh> can read real model files from disk.
|
||||
*/
|
||||
MeshHandle loadMesh(std::vector<CanvasVertex> vertices,
|
||||
std::vector<u32> indices);
|
||||
|
||||
template <typename T>
|
||||
void release(typename AssetTraits<T>::Handle handle);
|
||||
|
||||
private:
|
||||
friend class Application;
|
||||
|
||||
explicit AssetRegistry(Device& device);
|
||||
|
||||
template <typename T>
|
||||
const T& get(typename AssetTraits<T>::Handle handle) const;
|
||||
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> impl;
|
||||
};
|
||||
|
||||
template <>
|
||||
MeshHandle AssetRegistry::load<Mesh>(const std::string& path);
|
||||
|
||||
template <>
|
||||
void AssetRegistry::release<Mesh>(MeshHandle handle);
|
||||
|
||||
template <>
|
||||
const Mesh& AssetRegistry::get<Mesh>(MeshHandle handle) const;
|
||||
|
||||
} // namespace sophia
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <sophia/asset_registry.hpp>
|
||||
#include <sophia/resource_registry.hpp>
|
||||
#include <string>
|
||||
|
||||
#define GLM_FORCE_RADIANS
|
||||
|
||||
@@ -26,8 +26,10 @@ struct Handle {
|
||||
|
||||
struct MeshTag {};
|
||||
struct PanelTag {};
|
||||
struct RenderTargetTag {};
|
||||
|
||||
using MeshHandle = Handle<MeshTag>;
|
||||
using PanelHandle = Handle<PanelTag>;
|
||||
using RenderTargetHandle = Handle<RenderTargetTag>;
|
||||
|
||||
} // namespace sophia
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <sophia/handles.hpp>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "sophia/canvas.hpp"
|
||||
#include "sophia/types.hpp"
|
||||
|
||||
#define GLM_FORCE_RADIANS
|
||||
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
namespace sophia {
|
||||
|
||||
class Device;
|
||||
class Mesh;
|
||||
class RenderTarget;
|
||||
class ImGuiRenderer;
|
||||
|
||||
template <typename T>
|
||||
struct ResourceTraits;
|
||||
|
||||
template <>
|
||||
struct ResourceTraits<Mesh> {
|
||||
using Handle = MeshHandle;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct ResourceTraits<RenderTarget> {
|
||||
using Handle = RenderTargetHandle;
|
||||
};
|
||||
|
||||
class ResourceRegistry {
|
||||
public:
|
||||
~ResourceRegistry();
|
||||
|
||||
ResourceRegistry(const ResourceRegistry&) = delete;
|
||||
ResourceRegistry& operator=(const ResourceRegistry&) = delete;
|
||||
|
||||
template <typename T>
|
||||
typename ResourceTraits<T>::Handle load(const std::string& path);
|
||||
|
||||
template <typename T, typename... Args>
|
||||
typename ResourceTraits<T>::Handle create(Args&&... args) {
|
||||
return createResource(std::type_identity<T>{}, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
void requestResize(RenderTargetHandle handle, u32 width, u32 height);
|
||||
|
||||
glm::vec2 getExtent(RenderTargetHandle handle) const;
|
||||
|
||||
bool isAlive(RenderTargetHandle 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>
|
||||
void release(typename ResourceTraits<T>::Handle handle);
|
||||
|
||||
private:
|
||||
friend class Application;
|
||||
friend class ImGuiRenderer;
|
||||
|
||||
// `renderTargetFormat` is a VkFormat value; it must match the color format
|
||||
// the scene pipelines are built against.
|
||||
ResourceRegistry(Device& device, u32 renderTargetFormat);
|
||||
|
||||
MeshHandle createResource(std::type_identity<Mesh>,
|
||||
std::vector<CanvasVertex> vertices,
|
||||
std::vector<u32> indices = {});
|
||||
|
||||
RenderTargetHandle createResource(std::type_identity<RenderTarget>,
|
||||
u32 width = 1, u32 height = 1);
|
||||
|
||||
RenderTarget& getRenderTarget(RenderTargetHandle handle);
|
||||
const RenderTarget& getRenderTarget(RenderTargetHandle handle) const;
|
||||
void applyPendingResizes();
|
||||
|
||||
template <typename T>
|
||||
const T& get(typename ResourceTraits<T>::Handle handle) const;
|
||||
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> impl;
|
||||
};
|
||||
|
||||
template <>
|
||||
MeshHandle ResourceRegistry::load<Mesh>(const std::string& path);
|
||||
|
||||
template <>
|
||||
void ResourceRegistry::release<Mesh>(MeshHandle handle);
|
||||
|
||||
template <>
|
||||
void ResourceRegistry::release<RenderTarget>(RenderTargetHandle handle);
|
||||
|
||||
template <>
|
||||
const Mesh& ResourceRegistry::get<Mesh>(MeshHandle handle) const;
|
||||
|
||||
} // namespace sophia
|
||||
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include <sophia/handles.hpp>
|
||||
|
||||
namespace sophia {
|
||||
|
||||
class ImGuiRenderer;
|
||||
class ResourceRegistry;
|
||||
|
||||
class UI {
|
||||
public:
|
||||
UI(const UI&) = delete;
|
||||
UI& operator=(const UI&) = delete;
|
||||
|
||||
/**
|
||||
* Draws the target filling the space left in the current window, and keeps
|
||||
* the target sized to match. The target catches up on the next frame, so the
|
||||
* image may be a frame behind while the window is being resized.
|
||||
*/
|
||||
void image(RenderTargetHandle handle);
|
||||
|
||||
/**
|
||||
* Texture to hand to ImGui::Image() that shows the target's contents.
|
||||
* Changes when the target is resized, so fetch it each frame rather than
|
||||
* caching it.
|
||||
*/
|
||||
ImTextureID getTextureId(RenderTargetHandle handle);
|
||||
|
||||
private:
|
||||
friend class Application;
|
||||
|
||||
explicit UI(ResourceRegistry& resources) : resources{resources} {}
|
||||
|
||||
ResourceRegistry& resources;
|
||||
ImGuiRenderer* renderer = nullptr;
|
||||
};
|
||||
|
||||
} // namespace sophia
|
||||
+89
-39
@@ -20,6 +20,7 @@
|
||||
#include "mesh.hpp"
|
||||
#include "pipelines/graphics_pipeline.hpp"
|
||||
#include "render_context.hpp"
|
||||
#include "render_target.hpp"
|
||||
#include "shader.hpp"
|
||||
#include "systems/model_render_system.hpp"
|
||||
#include "window.hpp"
|
||||
@@ -41,6 +42,38 @@ struct PushConstants {
|
||||
|
||||
std::vector<std::string> commandLineArgs;
|
||||
|
||||
void beginColorPass(vk::CommandBuffer commandBuffer, vk::ImageView view,
|
||||
vk::Extent2D extent) {
|
||||
vk::ClearValue clearValue{};
|
||||
clearValue.color =
|
||||
vk::ClearColorValue{std::array<float, 4>{0.01f, 0.01f, 0.02f, 1.0f}};
|
||||
|
||||
vk::RenderingAttachmentInfo colorAttachment{};
|
||||
colorAttachment.imageView = view;
|
||||
colorAttachment.imageLayout = vk::ImageLayout::eColorAttachmentOptimal;
|
||||
colorAttachment.loadOp = vk::AttachmentLoadOp::eClear;
|
||||
colorAttachment.storeOp = vk::AttachmentStoreOp::eStore;
|
||||
colorAttachment.clearValue = clearValue;
|
||||
|
||||
vk::RenderingInfo renderingInfo{};
|
||||
renderingInfo.renderArea = vk::Rect2D{{0, 0}, extent};
|
||||
renderingInfo.layerCount = 1;
|
||||
renderingInfo.colorAttachmentCount = 1;
|
||||
renderingInfo.pColorAttachments = &colorAttachment;
|
||||
|
||||
commandBuffer.beginRendering(renderingInfo);
|
||||
|
||||
vk::Viewport viewport{0.0f,
|
||||
0.0f,
|
||||
static_cast<float>(extent.width),
|
||||
static_cast<float>(extent.height),
|
||||
0.0f,
|
||||
1.0f};
|
||||
vk::Rect2D scissor{{0, 0}, extent};
|
||||
commandBuffer.setViewport(0, viewport);
|
||||
commandBuffer.setScissor(0, scissor);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class Application::Impl {
|
||||
@@ -55,7 +88,9 @@ class Application::Impl {
|
||||
void run() {
|
||||
ModelRenderSystem modelRenderSystem{device, renderContext};
|
||||
ImGuiRenderer imguiRenderer{window, device, renderContext.getImageFormat(),
|
||||
renderContext.getImageCount()};
|
||||
renderContext.getImageCount(),
|
||||
this->application.resourceRegistry};
|
||||
this->application._ui.renderer = &imguiRenderer;
|
||||
|
||||
this->application.consoleCommandRegistry.registerCommand(
|
||||
"reset_layout", "Restore the default panel layout",
|
||||
@@ -83,10 +118,22 @@ class Application::Impl {
|
||||
double elapsedTime =
|
||||
std::chrono::duration<double>(currentTime - startTime).count();
|
||||
|
||||
// Resizing waits for the GPU, so it has to happen before any of this
|
||||
// frame's work (or ImGui's references to the targets) is recorded.
|
||||
this->application.resourceRegistry.applyPendingResizes();
|
||||
|
||||
RenderTarget* sceneTarget =
|
||||
this->sceneTargetHandle.isValid()
|
||||
? &this->application.resourceRegistry.getRenderTarget(
|
||||
this->sceneTargetHandle)
|
||||
: nullptr;
|
||||
|
||||
vk::Extent2D extent = renderContext.getExtent();
|
||||
vk::Extent2D sceneExtent =
|
||||
sceneTarget ? sceneTarget->getExtent() : extent;
|
||||
FrameInfo frameInfo{frameIndex, elapsedTime, deltaTime,
|
||||
glm::vec2(static_cast<float>(extent.width),
|
||||
static_cast<float>(extent.height)),
|
||||
glm::vec2(static_cast<float>(sceneExtent.width),
|
||||
static_cast<float>(sceneExtent.height)),
|
||||
this->application.camera};
|
||||
this->application.onUpdate(frameInfo);
|
||||
this->application.camera.setProjection(
|
||||
@@ -105,38 +152,6 @@ class Application::Impl {
|
||||
this->application.panelRegistry.update(frameInfo);
|
||||
this->application.onRender();
|
||||
|
||||
renderContext.transitionCurrentImage(
|
||||
commandBuffer, vk::ImageLayout::eColorAttachmentOptimal);
|
||||
|
||||
vk::ClearValue clearValue{};
|
||||
clearValue.color =
|
||||
vk::ClearColorValue{std::array<float, 4>{0.01f, 0.01f, 0.02f, 1.0f}};
|
||||
|
||||
vk::RenderingAttachmentInfo colorAttachment{};
|
||||
colorAttachment.imageView = renderContext.getCurrentImageView();
|
||||
colorAttachment.imageLayout = vk::ImageLayout::eColorAttachmentOptimal;
|
||||
colorAttachment.loadOp = vk::AttachmentLoadOp::eClear;
|
||||
colorAttachment.storeOp = vk::AttachmentStoreOp::eStore;
|
||||
colorAttachment.clearValue = clearValue;
|
||||
|
||||
vk::RenderingInfo renderingInfo{};
|
||||
renderingInfo.renderArea = vk::Rect2D{{0, 0}, extent};
|
||||
renderingInfo.layerCount = 1;
|
||||
renderingInfo.colorAttachmentCount = 1;
|
||||
renderingInfo.pColorAttachments = &colorAttachment;
|
||||
|
||||
commandBuffer.beginRendering(renderingInfo);
|
||||
|
||||
vk::Viewport viewport{0.0f,
|
||||
0.0f,
|
||||
static_cast<float>(extent.width),
|
||||
static_cast<float>(extent.height),
|
||||
0.0f,
|
||||
1.0f};
|
||||
vk::Rect2D scissor{{0, 0}, extent};
|
||||
commandBuffer.setViewport(0, viewport);
|
||||
commandBuffer.setScissor(0, scissor);
|
||||
|
||||
std::vector<ModelRenderSystem::MeshInstance> instances;
|
||||
instances.reserve(this->meshes.size() + this->drawCalls.size());
|
||||
for (const std::unique_ptr<Mesh>& mesh : this->meshes) {
|
||||
@@ -144,10 +159,28 @@ class Application::Impl {
|
||||
}
|
||||
instances.insert(instances.end(), this->drawCalls.begin(),
|
||||
this->drawCalls.end());
|
||||
|
||||
modelRenderSystem.render(commandBuffer, instances, frameInfo);
|
||||
this->drawCalls.clear();
|
||||
|
||||
if (sceneTarget) {
|
||||
Image& targetImage = sceneTarget->getImage();
|
||||
targetImage.transitionLayout(commandBuffer,
|
||||
vk::ImageLayout::eColorAttachmentOptimal);
|
||||
beginColorPass(commandBuffer, targetImage.getView(), sceneExtent);
|
||||
modelRenderSystem.render(commandBuffer, instances, frameInfo);
|
||||
commandBuffer.endRendering();
|
||||
targetImage.transitionLayout(commandBuffer,
|
||||
vk::ImageLayout::eShaderReadOnlyOptimal);
|
||||
}
|
||||
|
||||
renderContext.transitionCurrentImage(
|
||||
commandBuffer, vk::ImageLayout::eColorAttachmentOptimal);
|
||||
beginColorPass(commandBuffer, renderContext.getCurrentImageView(),
|
||||
extent);
|
||||
|
||||
if (!sceneTarget) {
|
||||
modelRenderSystem.render(commandBuffer, instances, frameInfo);
|
||||
}
|
||||
|
||||
imguiRenderer.endFrame(commandBuffer);
|
||||
|
||||
commandBuffer.endRendering();
|
||||
@@ -164,6 +197,8 @@ class Application::Impl {
|
||||
|
||||
device.waitIdle();
|
||||
|
||||
this->application._ui.renderer = nullptr;
|
||||
|
||||
auto endTime = std::chrono::high_resolution_clock::now();
|
||||
double totalRuntime =
|
||||
std::chrono::duration<double>(endTime - startTime).count();
|
||||
@@ -193,6 +228,14 @@ class Application::Impl {
|
||||
this->drawCalls.push_back({&mesh, transform});
|
||||
}
|
||||
|
||||
void setSceneTarget(RenderTargetHandle handle) {
|
||||
this->sceneTargetHandle = handle;
|
||||
}
|
||||
|
||||
u32 getColorFormat() const {
|
||||
return static_cast<u32>(this->renderContext.getImageFormat());
|
||||
}
|
||||
|
||||
Device& getDevice() { return device; }
|
||||
|
||||
private:
|
||||
@@ -204,6 +247,8 @@ class Application::Impl {
|
||||
|
||||
std::vector<std::unique_ptr<Mesh>> meshes;
|
||||
std::vector<ModelRenderSystem::MeshInstance> drawCalls;
|
||||
|
||||
RenderTargetHandle sceneTargetHandle = RenderTargetHandle::invalid();
|
||||
};
|
||||
|
||||
void Application::setCommandLineArgs(int argc, const char* const* argv) {
|
||||
@@ -214,7 +259,8 @@ Application::Application(const Config& config)
|
||||
: impl{std::make_unique<Impl>(*this, config)},
|
||||
canvas{this},
|
||||
args{std::move(commandLineArgs)},
|
||||
assetRegistry{impl->getDevice()},
|
||||
resourceRegistry{impl->getDevice(), impl->getColorFormat()},
|
||||
_ui{resourceRegistry},
|
||||
_debugUI{this->panelRegistry, this->consoleCommandRegistry} {}
|
||||
|
||||
Application::~Application() = default;
|
||||
@@ -226,10 +272,14 @@ void Application::pushMesh(std::vector<CanvasVertex> vertices,
|
||||
impl->pushMesh(std::move(vertices), std::move(indices));
|
||||
}
|
||||
|
||||
void Application::setSceneTarget(RenderTargetHandle handle) {
|
||||
impl->setSceneTarget(handle);
|
||||
}
|
||||
|
||||
void Application::clearMeshes() { impl->clearMeshes(); }
|
||||
|
||||
void Application::drawMesh(MeshHandle handle, const glm::mat4& transform) {
|
||||
impl->drawMesh(assetRegistry.get<Mesh>(handle), transform);
|
||||
impl->drawMesh(resourceRegistry.get<Mesh>(handle), transform);
|
||||
}
|
||||
|
||||
} // namespace sophia
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
#include <sophia/asset_registry.hpp>
|
||||
#include <sophia/util/assert.hpp>
|
||||
|
||||
#include "mesh.hpp"
|
||||
#include "pools/mesh_pool.hpp"
|
||||
|
||||
namespace sophia {
|
||||
|
||||
class AssetRegistry::Impl {
|
||||
public:
|
||||
explicit Impl(Device& device) : meshPool{device} {}
|
||||
|
||||
MeshPool meshPool;
|
||||
// TexturePool texturePool;
|
||||
// MaterialPool materialPool;
|
||||
};
|
||||
|
||||
AssetRegistry::AssetRegistry(Device& device)
|
||||
: impl{std::make_unique<Impl>(device)} {}
|
||||
|
||||
AssetRegistry::~AssetRegistry() = default;
|
||||
|
||||
template <>
|
||||
MeshHandle AssetRegistry::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 AssetTraits<T>::Handle load(const std::string&) {
|
||||
static_assert(sizeof(T) == 0,
|
||||
"load<T> is not implemented for this asset type");
|
||||
}
|
||||
|
||||
template <>
|
||||
void AssetRegistry::release<Mesh>(MeshHandle handle) {
|
||||
impl->meshPool.release(handle);
|
||||
}
|
||||
|
||||
template <>
|
||||
const Mesh& AssetRegistry::get<Mesh>(MeshHandle handle) const {
|
||||
return impl->meshPool.get(handle);
|
||||
}
|
||||
|
||||
MeshHandle AssetRegistry::loadMesh(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);
|
||||
}
|
||||
|
||||
} // namespace sophia
|
||||
@@ -63,6 +63,18 @@ void Image::transitionLayout(vk::CommandBuffer commandBuffer,
|
||||
barrier.dstAccessMask = {};
|
||||
srcStage = vk::PipelineStageFlagBits::eColorAttachmentOutput;
|
||||
dstStage = vk::PipelineStageFlagBits::eBottomOfPipe;
|
||||
} else if (this->layout == vk::ImageLayout::eColorAttachmentOptimal &&
|
||||
newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) {
|
||||
barrier.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite;
|
||||
barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
|
||||
srcStage = vk::PipelineStageFlagBits::eColorAttachmentOutput;
|
||||
dstStage = vk::PipelineStageFlagBits::eFragmentShader;
|
||||
} else if (this->layout == vk::ImageLayout::eShaderReadOnlyOptimal &&
|
||||
newLayout == vk::ImageLayout::eColorAttachmentOptimal) {
|
||||
barrier.srcAccessMask = vk::AccessFlagBits::eShaderRead;
|
||||
barrier.dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite;
|
||||
srcStage = vk::PipelineStageFlagBits::eFragmentShader;
|
||||
dstStage = vk::PipelineStageFlagBits::eColorAttachmentOutput;
|
||||
} else if (this->layout == vk::ImageLayout::ePresentSrcKHR &&
|
||||
newLayout == vk::ImageLayout::eColorAttachmentOptimal) {
|
||||
barrier.srcAccessMask = {};
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
#include <imgui_impl_vulkan.h>
|
||||
#include <imgui_internal.h>
|
||||
|
||||
#include <sophia/resource_registry.hpp>
|
||||
|
||||
#include "render_target.hpp"
|
||||
|
||||
namespace sophia {
|
||||
|
||||
namespace {
|
||||
@@ -20,7 +24,8 @@ void dockAll(const std::vector<std::string>& windows, ImGuiID nodeId) {
|
||||
|
||||
ImGuiRenderer::ImGuiRenderer(Window& window, Device& device,
|
||||
vk::Format colorAttachmentFormat,
|
||||
u32 minImageCount) {
|
||||
u32 minImageCount, ResourceRegistry& resources)
|
||||
: resources{resources} {
|
||||
// 1. Construct ImGui context
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
@@ -67,12 +72,17 @@ ImGuiRenderer::ImGuiRenderer(Window& window, Device& device,
|
||||
}
|
||||
|
||||
ImGuiRenderer::~ImGuiRenderer() {
|
||||
for (const auto& [index, texture] : textures) {
|
||||
ImGui_ImplVulkan_RemoveTexture(texture.descriptorSet);
|
||||
}
|
||||
ImGui_ImplVulkan_Shutdown();
|
||||
ImGui_ImplGlfw_Shutdown();
|
||||
ImGui::DestroyContext();
|
||||
}
|
||||
|
||||
void ImGuiRenderer::beginFrame() {
|
||||
releaseStaleTextures();
|
||||
|
||||
ImGui_ImplVulkan_NewFrame();
|
||||
ImGui_ImplGlfw_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
@@ -82,6 +92,45 @@ void ImGuiRenderer::beginFrame() {
|
||||
applyLayoutIfNeeded(dockspaceId, DOCKSPACE_FLAGS);
|
||||
}
|
||||
|
||||
ImTextureID ImGuiRenderer::getTextureId(RenderTargetHandle handle) {
|
||||
vk::ImageView view =
|
||||
std::as_const(resources).getRenderTarget(handle).getImage().getView();
|
||||
|
||||
auto it = textures.find(handle.index);
|
||||
if (it != textures.end()) {
|
||||
if (it->second.generation == handle.generation && it->second.view == view) {
|
||||
return (ImTextureID)it->second.descriptorSet;
|
||||
}
|
||||
ImGui_ImplVulkan_RemoveTexture(it->second.descriptorSet);
|
||||
textures.erase(it);
|
||||
}
|
||||
|
||||
// ImGui owns the sampler; it only needs the view and the layout the image is
|
||||
// left in once the scene pass has finished.
|
||||
VkDescriptorSet descriptorSet = ImGui_ImplVulkan_AddTexture(
|
||||
static_cast<VkImageView>(view), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
textures[handle.index] = {handle.generation, view, descriptorSet};
|
||||
return (ImTextureID)descriptorSet;
|
||||
}
|
||||
|
||||
// Resizing and releasing a target both wait for the GPU before destroying its
|
||||
// image, so nothing in flight can still be using a stale texture.
|
||||
void ImGuiRenderer::releaseStaleTextures() {
|
||||
for (auto it = textures.begin(); it != textures.end();) {
|
||||
RenderTargetHandle handle{it->first, it->second.generation};
|
||||
bool stale =
|
||||
!resources.isAlive(handle) ||
|
||||
std::as_const(resources).getRenderTarget(handle).getImage().getView() !=
|
||||
it->second.view;
|
||||
if (stale) {
|
||||
ImGui_ImplVulkan_RemoveTexture(it->second.descriptorSet);
|
||||
it = textures.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ImGuiRenderer::setDefaultLayout(DockLayout layout) {
|
||||
defaultLayout = std::move(layout);
|
||||
seedIfUnsaved = true;
|
||||
|
||||
@@ -2,31 +2,50 @@
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include <sophia/handles.hpp>
|
||||
#include <sophia/types.hpp>
|
||||
#include <sophia/ui/dock_layout.hpp>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "device.hpp"
|
||||
#include "window.hpp"
|
||||
|
||||
namespace sophia {
|
||||
|
||||
class ResourceRegistry;
|
||||
|
||||
class ImGuiRenderer {
|
||||
public:
|
||||
ImGuiRenderer(const ImGuiRenderer&) = delete;
|
||||
ImGuiRenderer& operator=(const ImGuiRenderer&) = delete;
|
||||
|
||||
ImGuiRenderer(Window& window, Device& device,
|
||||
vk::Format colorAttachmentFormat, u32 minImageCount);
|
||||
vk::Format colorAttachmentFormat, u32 minImageCount,
|
||||
ResourceRegistry& resources);
|
||||
~ImGuiRenderer();
|
||||
|
||||
void beginFrame();
|
||||
void endFrame(vk::CommandBuffer commandBuffer);
|
||||
|
||||
ImTextureID getTextureId(RenderTargetHandle handle);
|
||||
|
||||
void setDefaultLayout(DockLayout layout);
|
||||
void resetLayout();
|
||||
|
||||
private:
|
||||
void applyLayoutIfNeeded(ImGuiID dockspaceId, ImGuiDockNodeFlags flags);
|
||||
void releaseStaleTextures();
|
||||
|
||||
struct Texture {
|
||||
u32 generation;
|
||||
vk::ImageView view;
|
||||
VkDescriptorSet descriptorSet;
|
||||
};
|
||||
|
||||
ResourceRegistry& resources;
|
||||
|
||||
// Keyed by RenderTargetHandle::index.
|
||||
std::unordered_map<u32, Texture> textures;
|
||||
|
||||
DockLayout defaultLayout;
|
||||
bool seedIfUnsaved = false;
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <sophia/asset_registry.hpp>
|
||||
#include <sophia/resource_registry.hpp>
|
||||
#include <sophia/types.hpp>
|
||||
#include <vector>
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
#include "render_target.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace sophia {
|
||||
|
||||
RenderTarget::RenderTarget(Device& device, vk::Format format, u32 width,
|
||||
u32 height)
|
||||
: device{device}, format{format} {
|
||||
create(std::max(width, 1u), std::max(height, 1u));
|
||||
}
|
||||
|
||||
RenderTarget::~RenderTarget() { destroy(); }
|
||||
|
||||
vk::Extent2D RenderTarget::getExtent() const {
|
||||
vk::Extent3D extent = this->image->getExtent();
|
||||
return {extent.width, extent.height};
|
||||
}
|
||||
|
||||
void RenderTarget::requestResize(u32 width, u32 height) {
|
||||
this->pendingExtent = vk::Extent2D{std::max(width, 1u), std::max(height, 1u)};
|
||||
}
|
||||
|
||||
void RenderTarget::applyPendingResize() {
|
||||
if (!this->pendingExtent) {
|
||||
return;
|
||||
}
|
||||
|
||||
vk::Extent2D extent = *this->pendingExtent;
|
||||
this->pendingExtent.reset();
|
||||
if (extent == getExtent()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Earlier frames may still be sampling the old image (e.g. through the UI).
|
||||
this->device.waitIdle();
|
||||
destroy();
|
||||
create(extent.width, extent.height);
|
||||
}
|
||||
|
||||
void RenderTarget::create(u32 width, u32 height) {
|
||||
this->image = Image::Builder(this->device)
|
||||
.setExtent(width, height)
|
||||
.setFormat(this->format)
|
||||
.asColorAttachment()
|
||||
.withView()
|
||||
.build();
|
||||
}
|
||||
|
||||
void RenderTarget::destroy() { this->image.reset(); }
|
||||
|
||||
} // namespace sophia
|
||||
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <sophia/types.hpp>
|
||||
#include <vulkan/vulkan.hpp>
|
||||
|
||||
#include "device.hpp"
|
||||
#include "image.hpp"
|
||||
|
||||
namespace sophia {
|
||||
|
||||
/**
|
||||
* A place rendering can go. Currently always backed by an offscreen color
|
||||
* image.
|
||||
*
|
||||
* Owned by the ResourceRegistry and addressed through a RenderTargetHandle.
|
||||
*/
|
||||
class RenderTarget {
|
||||
public:
|
||||
RenderTarget(Device& device, vk::Format format, u32 width, u32 height);
|
||||
~RenderTarget();
|
||||
|
||||
RenderTarget(const RenderTarget&) = delete;
|
||||
RenderTarget& operator=(const RenderTarget&) = delete;
|
||||
|
||||
Image& getImage() const { return *this->image; }
|
||||
|
||||
vk::Extent2D getExtent() const;
|
||||
|
||||
void requestResize(u32 width, u32 height);
|
||||
|
||||
/**
|
||||
* Applies a pending resize. Waits for the GPU, so it must run before any of
|
||||
* the frame's work (or any UI reference to the image) is recorded.
|
||||
*/
|
||||
void applyPendingResize();
|
||||
|
||||
private:
|
||||
void create(u32 width, u32 height);
|
||||
void destroy();
|
||||
|
||||
Device& device;
|
||||
vk::Format format;
|
||||
|
||||
std::unique_ptr<Image> image;
|
||||
|
||||
std::optional<vk::Extent2D> pendingExtent;
|
||||
};
|
||||
|
||||
} // namespace sophia
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "assets/pools/mesh_pool.hpp"
|
||||
#include "resources/pools/mesh_pool.hpp"
|
||||
|
||||
#include <sophia/util/assert.hpp>
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,27 @@
|
||||
#include <sophia/resource_registry.hpp>
|
||||
#include <sophia/ui/ui.hpp>
|
||||
#include <sophia/util/assert.hpp>
|
||||
|
||||
#include "imgui_renderer.hpp"
|
||||
|
||||
namespace sophia {
|
||||
|
||||
void UI::image(RenderTargetHandle handle) {
|
||||
const ImVec2 size = ImGui::GetContentRegionAvail();
|
||||
if (size.x < 1.0f || size.y < 1.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
resources.requestResize(handle, static_cast<u32>(size.x),
|
||||
static_cast<u32>(size.y));
|
||||
ImTextureID textureId = getTextureId(handle);
|
||||
ImGui::Image(textureId, size);
|
||||
}
|
||||
|
||||
ImTextureID UI::getTextureId(RenderTargetHandle handle) {
|
||||
SOPHIA_ASSERT(renderer != nullptr,
|
||||
"UI::getTextureId() called before the UI was set up");
|
||||
return renderer->getTextureId(handle);
|
||||
}
|
||||
|
||||
} // namespace sophia
|
||||
@@ -91,7 +91,7 @@ class Boids2D : public sophia::Application {
|
||||
{{-BOID_LENGTH * 0.5f, BOID_WIDTH * 0.5f}, {1.0f, 1.0f, 1.0f}},
|
||||
{{-BOID_LENGTH * 0.5f, -BOID_WIDTH * 0.5f}, {1.0f, 1.0f, 1.0f}},
|
||||
};
|
||||
boidMesh = assets().loadMesh(std::move(vertices), {});
|
||||
boidMesh = resources().create<sophia::Mesh>(std::move(vertices));
|
||||
}
|
||||
|
||||
void onUpdate(const sophia::FrameInfo& frameInfo) override {
|
||||
|
||||
+24
-6
@@ -1,17 +1,35 @@
|
||||
#include <iostream>
|
||||
#include <imgui.h>
|
||||
|
||||
#include <sophia/application.hpp>
|
||||
#include <sophia/frame_info.hpp>
|
||||
#include <sophia/ui/panel.hpp>
|
||||
|
||||
class Viewport : public sophia::Panel {
|
||||
public:
|
||||
Viewport(sophia::UI& ui, sophia::RenderTargetHandle target)
|
||||
: Panel{{.name = "Viewport"}}, ui{ui}, target{target} {}
|
||||
|
||||
void onUpdate(const sophia::FrameInfo&) override {
|
||||
ImGui::Text("Test ViewPort");
|
||||
|
||||
ui.image(target);
|
||||
}
|
||||
|
||||
private:
|
||||
sophia::UI& ui;
|
||||
sophia::RenderTargetHandle target;
|
||||
};
|
||||
|
||||
class Testbed : public sophia::Application {
|
||||
public:
|
||||
Testbed() : Application{{.width = 750, .height = 1000, .name = "Testbed"}} {}
|
||||
|
||||
void onCreate() override {
|
||||
std::cout << "Testbed created" << std::endl;
|
||||
|
||||
debugUI().show();
|
||||
sophia::RenderTargetHandle target =
|
||||
resources().create<sophia::RenderTarget>();
|
||||
setSceneTarget(target);
|
||||
panels().emplace<Viewport>(ui(), target);
|
||||
}
|
||||
|
||||
void onUpdate(const sophia::FrameInfo& frameInfo) override {}
|
||||
};
|
||||
|
||||
SOPHIA_MAIN(Testbed)
|
||||
|
||||
Reference in New Issue
Block a user