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:
+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
|
||||
|
||||
Reference in New Issue
Block a user