Add offscreen render targets and rename AssetRegistry to ResourceRegistry #8
@@ -34,4 +34,36 @@ struct MeshComponent {
|
||||
MeshHandle handle;
|
||||
};
|
||||
|
||||
struct CameraComponent {
|
||||
struct Orthographic {
|
||||
float height = 10.f;
|
||||
float nearPlane = -1.f;
|
||||
float farPlane = 1.f;
|
||||
};
|
||||
|
||||
struct Perspective {
|
||||
float fov = glm::radians(60.f);
|
||||
float nearPlane = 0.1f;
|
||||
float farPlane = 100.f;
|
||||
};
|
||||
|
||||
enum class Projection { Orthographic, Perspective };
|
||||
|
||||
Projection projection = Projection::Orthographic;
|
||||
Orthographic ortho;
|
||||
Perspective perspective;
|
||||
|
||||
glm::mat4 getProjection(float aspect) const {
|
||||
if (projection == Projection::Perspective) {
|
||||
return glm::perspective(perspective.fov, aspect, perspective.nearPlane,
|
||||
perspective.farPlane);
|
||||
}
|
||||
|
||||
float halfHeight = ortho.height / 2.f;
|
||||
float halfWidth = halfHeight * aspect;
|
||||
return glm::ortho(-halfWidth, halfWidth, -halfHeight, halfHeight,
|
||||
ortho.nearPlane, ortho.farPlane);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace sophia
|
||||
|
||||
+42
-30
@@ -21,6 +21,8 @@
|
||||
#include "pipelines/graphics_pipeline.hpp"
|
||||
#include "render_context.hpp"
|
||||
#include "render_target.hpp"
|
||||
#include "rendering/render_graph.hpp"
|
||||
#include "resources/resource_registry.hpp"
|
||||
#include "shader.hpp"
|
||||
#include "systems/model_render_system.hpp"
|
||||
#include "window.hpp"
|
||||
@@ -42,11 +44,12 @@ struct PushConstants {
|
||||
|
||||
std::vector<std::string> commandLineArgs;
|
||||
|
||||
constexpr ClearColor CLEAR_COLOR = {0.01f, 0.01f, 0.02f, 1.0f};
|
||||
|
||||
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}};
|
||||
clearValue.color = vk::ClearColorValue{CLEAR_COLOR};
|
||||
|
||||
vk::RenderingAttachmentInfo colorAttachment{};
|
||||
colorAttachment.imageView = view;
|
||||
@@ -83,13 +86,14 @@ class Application::Impl {
|
||||
config{config},
|
||||
window{config.width, config.height, config.name},
|
||||
device{&window},
|
||||
renderContext{window, device} {}
|
||||
renderContext{window, device},
|
||||
resourceRegistry{device, renderContext.getImageFormat()} {}
|
||||
|
||||
void run() {
|
||||
ModelRenderSystem modelRenderSystem{device, renderContext};
|
||||
ImGuiRenderer imguiRenderer{window, device, renderContext.getImageFormat(),
|
||||
renderContext.getImageCount(),
|
||||
this->application.resourceRegistry};
|
||||
this->resourceRegistry};
|
||||
this->application._ui.renderer = &imguiRenderer;
|
||||
|
||||
this->application.consoleCommandRegistry.registerCommand(
|
||||
@@ -100,8 +104,13 @@ class Application::Impl {
|
||||
});
|
||||
|
||||
this->application.onCreate();
|
||||
imguiRenderer.setDefaultLayout(
|
||||
this->application.panelRegistry.getDefaultLayout());
|
||||
|
||||
auto& meshPool = this->resourceRegistry.meshPool;
|
||||
auto& renderTargetPool = this->resourceRegistry.renderTargetPool;
|
||||
|
||||
PanelRegistry& panelRegistry = this->application.panelRegistry;
|
||||
|
||||
imguiRenderer.setDefaultLayout(panelRegistry.getDefaultLayout());
|
||||
|
||||
auto startTime = std::chrono::high_resolution_clock::now();
|
||||
auto currentTime = startTime;
|
||||
@@ -120,12 +129,11 @@ class Application::Impl {
|
||||
|
||||
// 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();
|
||||
renderTargetPool.applyPendingResizes();
|
||||
|
||||
RenderTarget* sceneTarget =
|
||||
this->sceneTargetHandle.isValid()
|
||||
? &this->application.resourceRegistry.getRenderTarget(
|
||||
this->sceneTargetHandle)
|
||||
? &renderTargetPool.get(this->sceneTargetHandle)
|
||||
: nullptr;
|
||||
|
||||
vk::Extent2D extent = renderContext.getExtent();
|
||||
@@ -149,7 +157,7 @@ class Application::Impl {
|
||||
|
||||
imguiRenderer.beginFrame();
|
||||
|
||||
this->application.panelRegistry.update(frameInfo);
|
||||
panelRegistry.update(frameInfo);
|
||||
this->application.onRender();
|
||||
|
||||
std::vector<ModelRenderSystem::MeshInstance> instances;
|
||||
@@ -172,21 +180,25 @@ class Application::Impl {
|
||||
vk::ImageLayout::eShaderReadOnlyOptimal);
|
||||
}
|
||||
|
||||
renderContext.transitionCurrentImage(
|
||||
commandBuffer, vk::ImageLayout::eColorAttachmentOptimal);
|
||||
beginColorPass(commandBuffer, renderContext.getCurrentImageView(),
|
||||
extent);
|
||||
RenderGraph graph;
|
||||
ImageRef backbuffer =
|
||||
graph.importImage(renderContext.getCurrentImage(), Access::Present);
|
||||
|
||||
if (!sceneTarget) {
|
||||
modelRenderSystem.render(commandBuffer, instances, frameInfo);
|
||||
}
|
||||
graph.addPass(
|
||||
"main",
|
||||
[&](RenderGraph::PassBuilder& pass) {
|
||||
pass.writeColor(backbuffer, vk::AttachmentLoadOp::eClear,
|
||||
CLEAR_COLOR);
|
||||
},
|
||||
[&](vk::CommandBuffer commandBuffer) {
|
||||
if (!sceneTarget) {
|
||||
modelRenderSystem.render(commandBuffer, instances, frameInfo);
|
||||
}
|
||||
|
||||
imguiRenderer.endFrame(commandBuffer);
|
||||
imguiRenderer.endFrame(commandBuffer);
|
||||
});
|
||||
|
||||
commandBuffer.endRendering();
|
||||
|
||||
renderContext.transitionCurrentImage(commandBuffer,
|
||||
vk::ImageLayout::ePresentSrcKHR);
|
||||
graph.execute(commandBuffer);
|
||||
|
||||
renderContext.endFrame();
|
||||
|
||||
@@ -232,11 +244,7 @@ class Application::Impl {
|
||||
this->sceneTargetHandle = handle;
|
||||
}
|
||||
|
||||
u32 getColorFormat() const {
|
||||
return static_cast<u32>(this->renderContext.getImageFormat());
|
||||
}
|
||||
|
||||
Device& getDevice() { return device; }
|
||||
ResourceRegistry& getResourceRegistry() { return resourceRegistry; }
|
||||
|
||||
private:
|
||||
Application& application;
|
||||
@@ -244,6 +252,7 @@ class Application::Impl {
|
||||
Window window;
|
||||
Device device;
|
||||
RenderContext renderContext;
|
||||
ResourceRegistry resourceRegistry;
|
||||
|
||||
std::vector<std::unique_ptr<Mesh>> meshes;
|
||||
std::vector<ModelRenderSystem::MeshInstance> drawCalls;
|
||||
@@ -259,14 +268,17 @@ Application::Application(const Config& config)
|
||||
: impl{std::make_unique<Impl>(*this, config)},
|
||||
canvas{this},
|
||||
args{std::move(commandLineArgs)},
|
||||
resourceRegistry{impl->getDevice(), impl->getColorFormat()},
|
||||
_ui{resourceRegistry},
|
||||
_ui{ResourceRegistryRef{impl->getResourceRegistry()}},
|
||||
_debugUI{this->panelRegistry, this->consoleCommandRegistry} {}
|
||||
|
||||
Application::~Application() = default;
|
||||
|
||||
void Application::run() { impl->run(); }
|
||||
|
||||
ResourceRegistryRef Application::resources() {
|
||||
return ResourceRegistryRef{impl->getResourceRegistry()};
|
||||
}
|
||||
|
||||
void Application::pushMesh(std::vector<CanvasVertex> vertices,
|
||||
std::vector<u32> indices) {
|
||||
impl->pushMesh(std::move(vertices), std::move(indices));
|
||||
@@ -279,7 +291,7 @@ void Application::setSceneTarget(RenderTargetHandle handle) {
|
||||
void Application::clearMeshes() { impl->clearMeshes(); }
|
||||
|
||||
void Application::drawMesh(MeshHandle handle, const glm::mat4& transform) {
|
||||
impl->drawMesh(resourceRegistry.get<Mesh>(handle), transform);
|
||||
impl->drawMesh(impl->getResourceRegistry().meshPool.get(handle), transform);
|
||||
}
|
||||
|
||||
} // namespace sophia
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
#include "rendering/render_graph.hpp"
|
||||
|
||||
#include <sophia/util/assert.hpp>
|
||||
#include <utility>
|
||||
|
||||
namespace sophia {
|
||||
|
||||
ImageRef RenderGraph::importImage(Image& image, Access finalAccess) {
|
||||
ImageRef ref{static_cast<u32>(this->images.size())};
|
||||
this->images.push_back({&image, finalAccess, image.getAccessState()});
|
||||
return ref;
|
||||
}
|
||||
|
||||
void RenderGraph::addPass(std::string name, const SetupFn& setup,
|
||||
ExecuteFn execute) {
|
||||
Pass& pass = this->passes.emplace_back();
|
||||
pass.name = std::move(name);
|
||||
pass.execute = std::move(execute);
|
||||
|
||||
PassBuilder builder{*this, pass};
|
||||
setup(builder);
|
||||
}
|
||||
|
||||
void RenderGraph::execute(vk::CommandBuffer commandBuffer) {
|
||||
for (Pass& pass : this->passes) {
|
||||
this->recordPass(commandBuffer, pass);
|
||||
}
|
||||
|
||||
PendingBarriers pending;
|
||||
for (u32 i = 0; i < this->images.size(); i++) {
|
||||
this->transitionTo(ImageRef{i}, getAccessState(this->images[i].finalAccess),
|
||||
pending);
|
||||
}
|
||||
this->flush(commandBuffer, pending);
|
||||
|
||||
for (ImportedImage& imported : this->images) {
|
||||
imported.image->recordLayout(imported.state.layout);
|
||||
}
|
||||
|
||||
this->passes.clear();
|
||||
}
|
||||
|
||||
void RenderGraph::transitionTo(ImageRef ref, const AccessState& next,
|
||||
PendingBarriers& pending) {
|
||||
SOPHIA_ASSERT(ref.isValid() && ref.index < this->images.size(),
|
||||
"ImageRef doesn't belong to this graph");
|
||||
|
||||
ImportedImage& imported = this->images[ref.index];
|
||||
|
||||
if (needsBarrier(imported.state, next)) {
|
||||
pending.barriers.push_back(makeImageBarrier(
|
||||
imported.image->get(), imported.image->getSubresourceRange(),
|
||||
imported.state, next));
|
||||
pending.srcStages |= imported.state.stage;
|
||||
pending.dstStages |= next.stage;
|
||||
imported.state = next;
|
||||
} else {
|
||||
// Same-layout read after read. Nothing to record, but a later write has
|
||||
// to wait on this reader too.
|
||||
imported.state.stage |= next.stage;
|
||||
imported.state.access |= next.access;
|
||||
}
|
||||
}
|
||||
|
||||
void RenderGraph::flush(vk::CommandBuffer commandBuffer,
|
||||
PendingBarriers& pending) {
|
||||
if (pending.barriers.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
commandBuffer.pipelineBarrier(pending.srcStages, pending.dstStages, {},
|
||||
nullptr, nullptr, pending.barriers);
|
||||
pending = PendingBarriers{};
|
||||
}
|
||||
|
||||
void RenderGraph::recordPass(vk::CommandBuffer commandBuffer, Pass& pass) {
|
||||
PendingBarriers pending;
|
||||
for (const ImageUse& use : pass.uses) {
|
||||
this->transitionTo(use.image, getAccessState(use.access), pending);
|
||||
}
|
||||
this->flush(commandBuffer, pending);
|
||||
|
||||
if (pass.colorAttachments.empty()) {
|
||||
pass.execute(commandBuffer);
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<vk::RenderingAttachmentInfo> attachments;
|
||||
attachments.reserve(pass.colorAttachments.size());
|
||||
|
||||
vk::Extent3D imageExtent =
|
||||
this->images[pass.colorAttachments.front().image.index]
|
||||
.image->getExtent();
|
||||
vk::Extent2D extent{imageExtent.width, imageExtent.height};
|
||||
|
||||
for (const ColorAttachment& color : pass.colorAttachments) {
|
||||
const Image& image = *this->images[color.image.index].image;
|
||||
SOPHIA_ASSERT(image.getExtent().width == extent.width &&
|
||||
image.getExtent().height == extent.height,
|
||||
"Pass", pass.name,
|
||||
"has color attachments of different sizes");
|
||||
|
||||
vk::RenderingAttachmentInfo attachment{};
|
||||
attachment.imageView = image.getView();
|
||||
attachment.imageLayout = vk::ImageLayout::eColorAttachmentOptimal;
|
||||
attachment.loadOp = color.loadOp;
|
||||
attachment.storeOp = vk::AttachmentStoreOp::eStore;
|
||||
attachment.clearValue.color = vk::ClearColorValue{color.clearColor};
|
||||
attachments.push_back(attachment);
|
||||
}
|
||||
|
||||
vk::RenderingInfo renderingInfo{};
|
||||
renderingInfo.renderArea = vk::Rect2D{{0, 0}, extent};
|
||||
renderingInfo.layerCount = 1;
|
||||
renderingInfo.colorAttachmentCount = static_cast<u32>(attachments.size());
|
||||
renderingInfo.pColorAttachments = attachments.data();
|
||||
|
||||
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);
|
||||
|
||||
pass.execute(commandBuffer);
|
||||
|
||||
commandBuffer.endRendering();
|
||||
}
|
||||
|
||||
RenderGraph::PassBuilder& RenderGraph::PassBuilder::writeColor(
|
||||
ImageRef image, vk::AttachmentLoadOp loadOp, ClearColor clearColor) {
|
||||
this->use(image, Access::ColorWrite);
|
||||
this->pass.colorAttachments.push_back({image, loadOp, clearColor});
|
||||
return *this;
|
||||
}
|
||||
|
||||
RenderGraph::PassBuilder& RenderGraph::PassBuilder::sample(ImageRef image) {
|
||||
return this->use(image, Access::SampledRead);
|
||||
}
|
||||
|
||||
RenderGraph::PassBuilder& RenderGraph::PassBuilder::use(ImageRef image,
|
||||
Access access) {
|
||||
SOPHIA_ASSERT(image.isValid() && image.index < this->graph.images.size(),
|
||||
"ImageRef doesn't belong to this graph");
|
||||
for (const ImageUse& existing : this->pass.uses) {
|
||||
SOPHIA_ASSERT(existing.image.index != image.index, "Pass", this->pass.name,
|
||||
"uses the same image twice");
|
||||
}
|
||||
|
||||
this->pass.uses.push_back({image, access});
|
||||
return *this;
|
||||
}
|
||||
|
||||
} // namespace sophia
|
||||
@@ -0,0 +1,147 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <sophia/types.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <vulkan/vulkan.hpp>
|
||||
|
||||
#include "image.hpp"
|
||||
#include "rendering/access.hpp"
|
||||
|
||||
namespace sophia {
|
||||
|
||||
/**
|
||||
* Names an image within one RenderGraph. Only valid for the graph that
|
||||
* returned it.
|
||||
*/
|
||||
struct ImageRef {
|
||||
u32 index = INVALID_INDEX;
|
||||
|
||||
static constexpr u32 INVALID_INDEX = 0xFFFFFFFF;
|
||||
|
||||
bool isValid() const { return this->index != INVALID_INDEX; }
|
||||
};
|
||||
|
||||
using ClearColor = std::array<float, 4>;
|
||||
|
||||
/**
|
||||
* A frame's passes and the images they touch, recorded into a single command
|
||||
* buffer. Passes run in the order they were added; the graph's job is to
|
||||
* work out the barriers and layout transitions between them from what each
|
||||
* pass says it does to each image, and to begin/end rendering for passes that
|
||||
* write color attachments.
|
||||
*
|
||||
* Built fresh every frame:
|
||||
*
|
||||
* RenderGraph graph;
|
||||
* ImageRef backbuffer = graph.importImage(swapchainImage, Access::Present);
|
||||
* graph.addPass(
|
||||
* "main", [&](PassBuilder& pass) { pass.writeColor(backbuffer); },
|
||||
* [&](vk::CommandBuffer cb) { draw(cb); });
|
||||
* graph.execute(commandBuffer);
|
||||
*
|
||||
* Imported images start in whatever layout Image reports and are left in
|
||||
* their final access when execute() returns, with the Image's tracked layout
|
||||
* updated to match.
|
||||
*/
|
||||
class RenderGraph {
|
||||
public:
|
||||
class PassBuilder;
|
||||
|
||||
using SetupFn = std::function<void(PassBuilder&)>;
|
||||
using ExecuteFn = std::function<void(vk::CommandBuffer)>;
|
||||
|
||||
RenderGraph() = default;
|
||||
|
||||
RenderGraph(const RenderGraph&) = delete;
|
||||
RenderGraph& operator=(const RenderGraph&) = delete;
|
||||
|
||||
/**
|
||||
* Brings an image the graph doesn't own into it. It must outlive
|
||||
* execute(). After the last pass it is transitioned to `finalAccess`.
|
||||
*/
|
||||
ImageRef importImage(Image& image, Access finalAccess);
|
||||
|
||||
/**
|
||||
* Adds a pass. `setup` runs immediately and declares what the pass does to
|
||||
* images; `execute` runs later, from execute(), with the barriers already
|
||||
* recorded and (if the pass writes color) rendering begun.
|
||||
*/
|
||||
void addPass(std::string name, const SetupFn& setup, ExecuteFn execute);
|
||||
|
||||
/**
|
||||
* Records every pass, and the barriers between them, into `commandBuffer`.
|
||||
*/
|
||||
void execute(vk::CommandBuffer commandBuffer);
|
||||
|
||||
private:
|
||||
struct ImageUse {
|
||||
ImageRef image;
|
||||
Access access;
|
||||
};
|
||||
|
||||
struct ColorAttachment {
|
||||
ImageRef image;
|
||||
vk::AttachmentLoadOp loadOp;
|
||||
ClearColor clearColor;
|
||||
};
|
||||
|
||||
struct Pass {
|
||||
std::string name;
|
||||
std::vector<ImageUse> uses;
|
||||
std::vector<ColorAttachment> colorAttachments;
|
||||
ExecuteFn execute;
|
||||
};
|
||||
|
||||
struct ImportedImage {
|
||||
Image* image;
|
||||
Access finalAccess;
|
||||
// What the image was last left in, as of the barriers recorded so far.
|
||||
AccessState state;
|
||||
};
|
||||
|
||||
struct PendingBarriers {
|
||||
std::vector<vk::ImageMemoryBarrier> barriers;
|
||||
vk::PipelineStageFlags srcStages;
|
||||
vk::PipelineStageFlags dstStages;
|
||||
};
|
||||
|
||||
void transitionTo(ImageRef ref, const AccessState& next,
|
||||
PendingBarriers& pending);
|
||||
void flush(vk::CommandBuffer commandBuffer, PendingBarriers& pending);
|
||||
void recordPass(vk::CommandBuffer commandBuffer, Pass& pass);
|
||||
|
||||
std::vector<ImportedImage> images;
|
||||
std::vector<Pass> passes;
|
||||
};
|
||||
|
||||
class RenderGraph::PassBuilder {
|
||||
public:
|
||||
/**
|
||||
* Renders into `image` as a color attachment. Passes with one or more of
|
||||
* these get rendering begun (and viewport/scissor set to the attachment's
|
||||
* extent) before their execute callback runs.
|
||||
*/
|
||||
PassBuilder& writeColor(
|
||||
ImageRef image,
|
||||
vk::AttachmentLoadOp loadOp = vk::AttachmentLoadOp::eClear,
|
||||
ClearColor clearColor = {0.f, 0.f, 0.f, 1.f});
|
||||
|
||||
/** Samples `image` in a shader. */
|
||||
PassBuilder& sample(ImageRef image);
|
||||
|
||||
/** Any other use, e.g. storage or transfer access. */
|
||||
PassBuilder& use(ImageRef image, Access access);
|
||||
|
||||
private:
|
||||
friend class RenderGraph;
|
||||
|
||||
PassBuilder(RenderGraph& graph, Pass& pass) : graph{graph}, pass{pass} {}
|
||||
|
||||
RenderGraph& graph;
|
||||
Pass& pass;
|
||||
};
|
||||
|
||||
} // namespace sophia
|
||||
Reference in New Issue
Block a user