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
|
# 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.
|
lightweight Handles instead of pointers or ownership.
|
||||||
|
|
||||||
Internally it's a collection of per-type pools, each responsible for one
|
Internally it's a collection of per-type pools, each responsible for one
|
||||||
asset type:
|
resource type:
|
||||||
|
|
||||||
```
|
```
|
||||||
AssetRegistry
|
ResourceRegistry
|
||||||
├── MeshPool → MeshHandle
|
├── MeshPool → MeshHandle
|
||||||
├── TexturePool → TextureHandle (planned)
|
├── TexturePool → TextureHandle (planned)
|
||||||
└── MaterialPool → MaterialHandle (planned)
|
└── MaterialPool → MaterialHandle (planned)
|
||||||
@@ -18,19 +18,19 @@ AssetRegistry
|
|||||||
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 asset (or freed memory).
|
someone else's resource (or freed memory).
|
||||||
|
|
||||||
Typical lifecycle:
|
Typical lifecycle:
|
||||||
|
|
||||||
```
|
```
|
||||||
load<Mesh>(path) / loadMesh(...) → MeshHandle
|
load<Mesh>(path) / create<Mesh>(...) → MeshHandle
|
||||||
│
|
│
|
||||||
get<Mesh>(handle) → const Mesh&
|
get<Mesh>(handle) → const Mesh&
|
||||||
│
|
│
|
||||||
release<Mesh>(handle)
|
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?"
|
lifetime directly?"
|
||||||
|
|
||||||
## Buffer
|
## 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
|
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 AssetRegistry's
|
Internally it's a generation-checked slot pool, the same shape ResourceRegistry's
|
||||||
per-type pools use:
|
per-type pools use:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
+4
-1
@@ -18,7 +18,10 @@ class Editor : public sophia::Application {
|
|||||||
panels().emplace<SceneHierarchyPanel>();
|
panels().emplace<SceneHierarchyPanel>();
|
||||||
panels().emplace<InspectorPanel>();
|
panels().emplace<InspectorPanel>();
|
||||||
panels().emplace<AssetBrowserPanel>();
|
panels().emplace<AssetBrowserPanel>();
|
||||||
panels().emplace<ViewportPanel>();
|
sophia::RenderTargetHandle sceneTarget =
|
||||||
|
resources().create<sophia::RenderTarget>();
|
||||||
|
setSceneTarget(sceneTarget);
|
||||||
|
panels().emplace<ViewportPanel>(ui(), sceneTarget);
|
||||||
|
|
||||||
panels().setDefaultLayout(
|
panels().setDefaultLayout(
|
||||||
{.left = {"Scene Hierarchy"},
|
{.left = {"Scene Hierarchy"},
|
||||||
|
|||||||
@@ -24,8 +24,9 @@ void ViewportPanel::onUpdate(const sophia::FrameInfo&) {
|
|||||||
const ImVec2 size = ImGui::GetContentRegionAvail();
|
const ImVec2 size = ImGui::GetContentRegionAvail();
|
||||||
const ImVec2 end(origin.x + size.x, origin.y + size.y);
|
const ImVec2 end(origin.x + size.x, origin.y + size.y);
|
||||||
|
|
||||||
|
ui.image(target);
|
||||||
|
|
||||||
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||||
drawList->AddRectFilled(origin, end, IM_COL32(24, 26, 32, 255));
|
|
||||||
|
|
||||||
if (showGrid) {
|
if (showGrid) {
|
||||||
constexpr float GRID_STEP = 40.0f;
|
constexpr float GRID_STEP = 40.0f;
|
||||||
|
|||||||
@@ -1,15 +1,25 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <sophia/frame_info.hpp>
|
#include <sophia/frame_info.hpp>
|
||||||
|
#include <sophia/handles.hpp>
|
||||||
#include <sophia/ui/panel.hpp>
|
#include <sophia/ui/panel.hpp>
|
||||||
|
#include <sophia/ui/ui.hpp>
|
||||||
|
|
||||||
class ViewportPanel : public sophia::Panel {
|
class ViewportPanel : public sophia::Panel {
|
||||||
public:
|
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;
|
void onUpdate(const sophia::FrameInfo& frameInfo) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
sophia::UI& ui;
|
||||||
|
sophia::RenderTargetHandle target;
|
||||||
|
|
||||||
enum class GizmoMode { Move, Rotate, Scale };
|
enum class GizmoMode { Move, Rotate, Scale };
|
||||||
|
|
||||||
GizmoMode gizmoMode = GizmoMode::Move;
|
GizmoMode gizmoMode = GizmoMode::Move;
|
||||||
|
|||||||
@@ -3,14 +3,15 @@
|
|||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <sophia/asset_registry.hpp>
|
|
||||||
#include <sophia/camera_2d.hpp>
|
#include <sophia/camera_2d.hpp>
|
||||||
#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/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>
|
||||||
|
#include <sophia/ui/ui.hpp>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -44,8 +45,9 @@ class Application {
|
|||||||
virtual void onRender() {}
|
virtual void onRender() {}
|
||||||
virtual void onDestroy() {}
|
virtual void onDestroy() {}
|
||||||
|
|
||||||
AssetRegistry& assets() { return this->assetRegistry; }
|
ResourceRegistry& resources() { return this->resourceRegistry; }
|
||||||
PanelRegistry& panels() { return this->panelRegistry; }
|
PanelRegistry& panels() { return this->panelRegistry; }
|
||||||
|
UI& ui() { return this->_ui; }
|
||||||
|
|
||||||
DebugUI& debugUI() { return this->_debugUI; }
|
DebugUI& debugUI() { return this->_debugUI; }
|
||||||
|
|
||||||
@@ -57,6 +59,15 @@ class Application {
|
|||||||
|
|
||||||
void drawMesh(MeshHandle handle, const glm::mat4& transform);
|
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:
|
private:
|
||||||
friend class Canvas;
|
friend class Canvas;
|
||||||
|
|
||||||
@@ -66,8 +77,9 @@ class Application {
|
|||||||
class Impl;
|
class Impl;
|
||||||
std::unique_ptr<Impl> impl;
|
std::unique_ptr<Impl> impl;
|
||||||
|
|
||||||
AssetRegistry assetRegistry;
|
ResourceRegistry resourceRegistry;
|
||||||
PanelRegistry panelRegistry;
|
PanelRegistry panelRegistry;
|
||||||
|
UI _ui;
|
||||||
|
|
||||||
DebugUI _debugUI;
|
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
|
#pragma once
|
||||||
|
|
||||||
#include <sophia/asset_registry.hpp>
|
#include <sophia/resource_registry.hpp>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#define GLM_FORCE_RADIANS
|
#define GLM_FORCE_RADIANS
|
||||||
|
|||||||
@@ -26,8 +26,10 @@ struct Handle {
|
|||||||
|
|
||||||
struct MeshTag {};
|
struct MeshTag {};
|
||||||
struct PanelTag {};
|
struct PanelTag {};
|
||||||
|
struct RenderTargetTag {};
|
||||||
|
|
||||||
using MeshHandle = Handle<MeshTag>;
|
using MeshHandle = Handle<MeshTag>;
|
||||||
using PanelHandle = Handle<PanelTag>;
|
using PanelHandle = Handle<PanelTag>;
|
||||||
|
using RenderTargetHandle = Handle<RenderTargetTag>;
|
||||||
|
|
||||||
} // namespace sophia
|
} // 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 "mesh.hpp"
|
||||||
#include "pipelines/graphics_pipeline.hpp"
|
#include "pipelines/graphics_pipeline.hpp"
|
||||||
#include "render_context.hpp"
|
#include "render_context.hpp"
|
||||||
|
#include "render_target.hpp"
|
||||||
#include "shader.hpp"
|
#include "shader.hpp"
|
||||||
#include "systems/model_render_system.hpp"
|
#include "systems/model_render_system.hpp"
|
||||||
#include "window.hpp"
|
#include "window.hpp"
|
||||||
@@ -41,6 +42,38 @@ struct PushConstants {
|
|||||||
|
|
||||||
std::vector<std::string> commandLineArgs;
|
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
|
} // namespace
|
||||||
|
|
||||||
class Application::Impl {
|
class Application::Impl {
|
||||||
@@ -55,7 +88,9 @@ class Application::Impl {
|
|||||||
void run() {
|
void run() {
|
||||||
ModelRenderSystem modelRenderSystem{device, renderContext};
|
ModelRenderSystem modelRenderSystem{device, renderContext};
|
||||||
ImGuiRenderer imguiRenderer{window, device, renderContext.getImageFormat(),
|
ImGuiRenderer imguiRenderer{window, device, renderContext.getImageFormat(),
|
||||||
renderContext.getImageCount()};
|
renderContext.getImageCount(),
|
||||||
|
this->application.resourceRegistry};
|
||||||
|
this->application._ui.renderer = &imguiRenderer;
|
||||||
|
|
||||||
this->application.consoleCommandRegistry.registerCommand(
|
this->application.consoleCommandRegistry.registerCommand(
|
||||||
"reset_layout", "Restore the default panel layout",
|
"reset_layout", "Restore the default panel layout",
|
||||||
@@ -83,10 +118,22 @@ class Application::Impl {
|
|||||||
double elapsedTime =
|
double elapsedTime =
|
||||||
std::chrono::duration<double>(currentTime - startTime).count();
|
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 extent = renderContext.getExtent();
|
||||||
|
vk::Extent2D sceneExtent =
|
||||||
|
sceneTarget ? sceneTarget->getExtent() : extent;
|
||||||
FrameInfo frameInfo{frameIndex, elapsedTime, deltaTime,
|
FrameInfo frameInfo{frameIndex, elapsedTime, deltaTime,
|
||||||
glm::vec2(static_cast<float>(extent.width),
|
glm::vec2(static_cast<float>(sceneExtent.width),
|
||||||
static_cast<float>(extent.height)),
|
static_cast<float>(sceneExtent.height)),
|
||||||
this->application.camera};
|
this->application.camera};
|
||||||
this->application.onUpdate(frameInfo);
|
this->application.onUpdate(frameInfo);
|
||||||
this->application.camera.setProjection(
|
this->application.camera.setProjection(
|
||||||
@@ -105,38 +152,6 @@ class Application::Impl {
|
|||||||
this->application.panelRegistry.update(frameInfo);
|
this->application.panelRegistry.update(frameInfo);
|
||||||
this->application.onRender();
|
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;
|
std::vector<ModelRenderSystem::MeshInstance> instances;
|
||||||
instances.reserve(this->meshes.size() + this->drawCalls.size());
|
instances.reserve(this->meshes.size() + this->drawCalls.size());
|
||||||
for (const std::unique_ptr<Mesh>& mesh : this->meshes) {
|
for (const std::unique_ptr<Mesh>& mesh : this->meshes) {
|
||||||
@@ -144,10 +159,28 @@ class Application::Impl {
|
|||||||
}
|
}
|
||||||
instances.insert(instances.end(), this->drawCalls.begin(),
|
instances.insert(instances.end(), this->drawCalls.begin(),
|
||||||
this->drawCalls.end());
|
this->drawCalls.end());
|
||||||
|
|
||||||
modelRenderSystem.render(commandBuffer, instances, frameInfo);
|
|
||||||
this->drawCalls.clear();
|
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);
|
imguiRenderer.endFrame(commandBuffer);
|
||||||
|
|
||||||
commandBuffer.endRendering();
|
commandBuffer.endRendering();
|
||||||
@@ -164,6 +197,8 @@ class Application::Impl {
|
|||||||
|
|
||||||
device.waitIdle();
|
device.waitIdle();
|
||||||
|
|
||||||
|
this->application._ui.renderer = nullptr;
|
||||||
|
|
||||||
auto endTime = std::chrono::high_resolution_clock::now();
|
auto endTime = std::chrono::high_resolution_clock::now();
|
||||||
double totalRuntime =
|
double totalRuntime =
|
||||||
std::chrono::duration<double>(endTime - startTime).count();
|
std::chrono::duration<double>(endTime - startTime).count();
|
||||||
@@ -193,6 +228,14 @@ class Application::Impl {
|
|||||||
this->drawCalls.push_back({&mesh, transform});
|
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; }
|
Device& getDevice() { return device; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -204,6 +247,8 @@ class Application::Impl {
|
|||||||
|
|
||||||
std::vector<std::unique_ptr<Mesh>> meshes;
|
std::vector<std::unique_ptr<Mesh>> meshes;
|
||||||
std::vector<ModelRenderSystem::MeshInstance> drawCalls;
|
std::vector<ModelRenderSystem::MeshInstance> drawCalls;
|
||||||
|
|
||||||
|
RenderTargetHandle sceneTargetHandle = RenderTargetHandle::invalid();
|
||||||
};
|
};
|
||||||
|
|
||||||
void Application::setCommandLineArgs(int argc, const char* const* argv) {
|
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)},
|
: impl{std::make_unique<Impl>(*this, config)},
|
||||||
canvas{this},
|
canvas{this},
|
||||||
args{std::move(commandLineArgs)},
|
args{std::move(commandLineArgs)},
|
||||||
assetRegistry{impl->getDevice()},
|
resourceRegistry{impl->getDevice(), impl->getColorFormat()},
|
||||||
|
_ui{resourceRegistry},
|
||||||
_debugUI{this->panelRegistry, this->consoleCommandRegistry} {}
|
_debugUI{this->panelRegistry, this->consoleCommandRegistry} {}
|
||||||
|
|
||||||
Application::~Application() = default;
|
Application::~Application() = default;
|
||||||
@@ -226,10 +272,14 @@ void Application::pushMesh(std::vector<CanvasVertex> vertices,
|
|||||||
impl->pushMesh(std::move(vertices), std::move(indices));
|
impl->pushMesh(std::move(vertices), std::move(indices));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Application::setSceneTarget(RenderTargetHandle handle) {
|
||||||
|
impl->setSceneTarget(handle);
|
||||||
|
}
|
||||||
|
|
||||||
void Application::clearMeshes() { impl->clearMeshes(); }
|
void Application::clearMeshes() { impl->clearMeshes(); }
|
||||||
|
|
||||||
void Application::drawMesh(MeshHandle handle, const glm::mat4& transform) {
|
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
|
} // 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 = {};
|
barrier.dstAccessMask = {};
|
||||||
srcStage = vk::PipelineStageFlagBits::eColorAttachmentOutput;
|
srcStage = vk::PipelineStageFlagBits::eColorAttachmentOutput;
|
||||||
dstStage = vk::PipelineStageFlagBits::eBottomOfPipe;
|
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 &&
|
} else if (this->layout == vk::ImageLayout::ePresentSrcKHR &&
|
||||||
newLayout == vk::ImageLayout::eColorAttachmentOptimal) {
|
newLayout == vk::ImageLayout::eColorAttachmentOptimal) {
|
||||||
barrier.srcAccessMask = {};
|
barrier.srcAccessMask = {};
|
||||||
|
|||||||
@@ -5,6 +5,10 @@
|
|||||||
#include <imgui_impl_vulkan.h>
|
#include <imgui_impl_vulkan.h>
|
||||||
#include <imgui_internal.h>
|
#include <imgui_internal.h>
|
||||||
|
|
||||||
|
#include <sophia/resource_registry.hpp>
|
||||||
|
|
||||||
|
#include "render_target.hpp"
|
||||||
|
|
||||||
namespace sophia {
|
namespace sophia {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -20,7 +24,8 @@ void dockAll(const std::vector<std::string>& windows, ImGuiID nodeId) {
|
|||||||
|
|
||||||
ImGuiRenderer::ImGuiRenderer(Window& window, Device& device,
|
ImGuiRenderer::ImGuiRenderer(Window& window, Device& device,
|
||||||
vk::Format colorAttachmentFormat,
|
vk::Format colorAttachmentFormat,
|
||||||
u32 minImageCount) {
|
u32 minImageCount, ResourceRegistry& resources)
|
||||||
|
: resources{resources} {
|
||||||
// 1. Construct ImGui context
|
// 1. Construct ImGui context
|
||||||
IMGUI_CHECKVERSION();
|
IMGUI_CHECKVERSION();
|
||||||
ImGui::CreateContext();
|
ImGui::CreateContext();
|
||||||
@@ -67,12 +72,17 @@ ImGuiRenderer::ImGuiRenderer(Window& window, Device& device,
|
|||||||
}
|
}
|
||||||
|
|
||||||
ImGuiRenderer::~ImGuiRenderer() {
|
ImGuiRenderer::~ImGuiRenderer() {
|
||||||
|
for (const auto& [index, texture] : textures) {
|
||||||
|
ImGui_ImplVulkan_RemoveTexture(texture.descriptorSet);
|
||||||
|
}
|
||||||
ImGui_ImplVulkan_Shutdown();
|
ImGui_ImplVulkan_Shutdown();
|
||||||
ImGui_ImplGlfw_Shutdown();
|
ImGui_ImplGlfw_Shutdown();
|
||||||
ImGui::DestroyContext();
|
ImGui::DestroyContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImGuiRenderer::beginFrame() {
|
void ImGuiRenderer::beginFrame() {
|
||||||
|
releaseStaleTextures();
|
||||||
|
|
||||||
ImGui_ImplVulkan_NewFrame();
|
ImGui_ImplVulkan_NewFrame();
|
||||||
ImGui_ImplGlfw_NewFrame();
|
ImGui_ImplGlfw_NewFrame();
|
||||||
ImGui::NewFrame();
|
ImGui::NewFrame();
|
||||||
@@ -82,6 +92,45 @@ void ImGuiRenderer::beginFrame() {
|
|||||||
applyLayoutIfNeeded(dockspaceId, DOCKSPACE_FLAGS);
|
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) {
|
void ImGuiRenderer::setDefaultLayout(DockLayout layout) {
|
||||||
defaultLayout = std::move(layout);
|
defaultLayout = std::move(layout);
|
||||||
seedIfUnsaved = true;
|
seedIfUnsaved = true;
|
||||||
|
|||||||
@@ -2,31 +2,50 @@
|
|||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
|
||||||
|
#include <sophia/handles.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 "device.hpp"
|
#include "device.hpp"
|
||||||
#include "window.hpp"
|
#include "window.hpp"
|
||||||
|
|
||||||
namespace sophia {
|
namespace sophia {
|
||||||
|
|
||||||
|
class ResourceRegistry;
|
||||||
|
|
||||||
class ImGuiRenderer {
|
class ImGuiRenderer {
|
||||||
public:
|
public:
|
||||||
ImGuiRenderer(const ImGuiRenderer&) = delete;
|
ImGuiRenderer(const ImGuiRenderer&) = delete;
|
||||||
ImGuiRenderer& operator=(const ImGuiRenderer&) = delete;
|
ImGuiRenderer& operator=(const ImGuiRenderer&) = delete;
|
||||||
|
|
||||||
ImGuiRenderer(Window& window, Device& device,
|
ImGuiRenderer(Window& window, Device& device,
|
||||||
vk::Format colorAttachmentFormat, u32 minImageCount);
|
vk::Format colorAttachmentFormat, u32 minImageCount,
|
||||||
|
ResourceRegistry& resources);
|
||||||
~ImGuiRenderer();
|
~ImGuiRenderer();
|
||||||
|
|
||||||
void beginFrame();
|
void beginFrame();
|
||||||
void endFrame(vk::CommandBuffer commandBuffer);
|
void endFrame(vk::CommandBuffer commandBuffer);
|
||||||
|
|
||||||
|
ImTextureID getTextureId(RenderTargetHandle handle);
|
||||||
|
|
||||||
void setDefaultLayout(DockLayout layout);
|
void setDefaultLayout(DockLayout layout);
|
||||||
void resetLayout();
|
void resetLayout();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void applyLayoutIfNeeded(ImGuiID dockspaceId, ImGuiDockNodeFlags flags);
|
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;
|
DockLayout defaultLayout;
|
||||||
bool seedIfUnsaved = false;
|
bool seedIfUnsaved = false;
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <sophia/asset_registry.hpp>
|
#include <sophia/resource_registry.hpp>
|
||||||
#include <sophia/types.hpp>
|
#include <sophia/types.hpp>
|
||||||
#include <vector>
|
#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>
|
#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}},
|
||||||
{{-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 {
|
void onUpdate(const sophia::FrameInfo& frameInfo) override {
|
||||||
|
|||||||
+24
-6
@@ -1,17 +1,35 @@
|
|||||||
#include <iostream>
|
#include <imgui.h>
|
||||||
|
|
||||||
#include <sophia/application.hpp>
|
#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 {
|
class Testbed : public sophia::Application {
|
||||||
public:
|
public:
|
||||||
Testbed() : Application{{.width = 750, .height = 1000, .name = "Testbed"}} {}
|
Testbed() : Application{{.width = 750, .height = 1000, .name = "Testbed"}} {}
|
||||||
|
|
||||||
void onCreate() override {
|
void onCreate() override {
|
||||||
std::cout << "Testbed created" << std::endl;
|
sophia::RenderTargetHandle target =
|
||||||
|
resources().create<sophia::RenderTarget>();
|
||||||
debugUI().show();
|
setSceneTarget(target);
|
||||||
|
panels().emplace<Viewport>(ui(), target);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onUpdate(const sophia::FrameInfo& frameInfo) override {}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
SOPHIA_MAIN(Testbed)
|
SOPHIA_MAIN(Testbed)
|
||||||
|
|||||||
Reference in New Issue
Block a user