Render Graph concept started!
This commit is contained in:
@@ -34,4 +34,36 @@ struct MeshComponent {
|
|||||||
MeshHandle handle;
|
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
|
} // namespace sophia
|
||||||
|
|||||||
+38
-26
@@ -21,6 +21,8 @@
|
|||||||
#include "pipelines/graphics_pipeline.hpp"
|
#include "pipelines/graphics_pipeline.hpp"
|
||||||
#include "render_context.hpp"
|
#include "render_context.hpp"
|
||||||
#include "render_target.hpp"
|
#include "render_target.hpp"
|
||||||
|
#include "rendering/render_graph.hpp"
|
||||||
|
#include "resources/resource_registry.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"
|
||||||
@@ -42,11 +44,12 @@ struct PushConstants {
|
|||||||
|
|
||||||
std::vector<std::string> commandLineArgs;
|
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,
|
void beginColorPass(vk::CommandBuffer commandBuffer, vk::ImageView view,
|
||||||
vk::Extent2D extent) {
|
vk::Extent2D extent) {
|
||||||
vk::ClearValue clearValue{};
|
vk::ClearValue clearValue{};
|
||||||
clearValue.color =
|
clearValue.color = vk::ClearColorValue{CLEAR_COLOR};
|
||||||
vk::ClearColorValue{std::array<float, 4>{0.01f, 0.01f, 0.02f, 1.0f}};
|
|
||||||
|
|
||||||
vk::RenderingAttachmentInfo colorAttachment{};
|
vk::RenderingAttachmentInfo colorAttachment{};
|
||||||
colorAttachment.imageView = view;
|
colorAttachment.imageView = view;
|
||||||
@@ -83,13 +86,14 @@ class Application::Impl {
|
|||||||
config{config},
|
config{config},
|
||||||
window{config.width, config.height, config.name},
|
window{config.width, config.height, config.name},
|
||||||
device{&window},
|
device{&window},
|
||||||
renderContext{window, device} {}
|
renderContext{window, device},
|
||||||
|
resourceRegistry{device, renderContext.getImageFormat()} {}
|
||||||
|
|
||||||
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->resourceRegistry};
|
||||||
this->application._ui.renderer = &imguiRenderer;
|
this->application._ui.renderer = &imguiRenderer;
|
||||||
|
|
||||||
this->application.consoleCommandRegistry.registerCommand(
|
this->application.consoleCommandRegistry.registerCommand(
|
||||||
@@ -100,8 +104,13 @@ class Application::Impl {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this->application.onCreate();
|
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 startTime = std::chrono::high_resolution_clock::now();
|
||||||
auto currentTime = startTime;
|
auto currentTime = startTime;
|
||||||
@@ -120,12 +129,11 @@ class Application::Impl {
|
|||||||
|
|
||||||
// Resizing waits for the GPU, so it has to happen before any of this
|
// 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.
|
// frame's work (or ImGui's references to the targets) is recorded.
|
||||||
this->application.resourceRegistry.applyPendingResizes();
|
renderTargetPool.applyPendingResizes();
|
||||||
|
|
||||||
RenderTarget* sceneTarget =
|
RenderTarget* sceneTarget =
|
||||||
this->sceneTargetHandle.isValid()
|
this->sceneTargetHandle.isValid()
|
||||||
? &this->application.resourceRegistry.getRenderTarget(
|
? &renderTargetPool.get(this->sceneTargetHandle)
|
||||||
this->sceneTargetHandle)
|
|
||||||
: nullptr;
|
: nullptr;
|
||||||
|
|
||||||
vk::Extent2D extent = renderContext.getExtent();
|
vk::Extent2D extent = renderContext.getExtent();
|
||||||
@@ -149,7 +157,7 @@ class Application::Impl {
|
|||||||
|
|
||||||
imguiRenderer.beginFrame();
|
imguiRenderer.beginFrame();
|
||||||
|
|
||||||
this->application.panelRegistry.update(frameInfo);
|
panelRegistry.update(frameInfo);
|
||||||
this->application.onRender();
|
this->application.onRender();
|
||||||
|
|
||||||
std::vector<ModelRenderSystem::MeshInstance> instances;
|
std::vector<ModelRenderSystem::MeshInstance> instances;
|
||||||
@@ -172,21 +180,25 @@ class Application::Impl {
|
|||||||
vk::ImageLayout::eShaderReadOnlyOptimal);
|
vk::ImageLayout::eShaderReadOnlyOptimal);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderContext.transitionCurrentImage(
|
RenderGraph graph;
|
||||||
commandBuffer, vk::ImageLayout::eColorAttachmentOptimal);
|
ImageRef backbuffer =
|
||||||
beginColorPass(commandBuffer, renderContext.getCurrentImageView(),
|
graph.importImage(renderContext.getCurrentImage(), Access::Present);
|
||||||
extent);
|
|
||||||
|
|
||||||
|
graph.addPass(
|
||||||
|
"main",
|
||||||
|
[&](RenderGraph::PassBuilder& pass) {
|
||||||
|
pass.writeColor(backbuffer, vk::AttachmentLoadOp::eClear,
|
||||||
|
CLEAR_COLOR);
|
||||||
|
},
|
||||||
|
[&](vk::CommandBuffer commandBuffer) {
|
||||||
if (!sceneTarget) {
|
if (!sceneTarget) {
|
||||||
modelRenderSystem.render(commandBuffer, instances, frameInfo);
|
modelRenderSystem.render(commandBuffer, instances, frameInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
imguiRenderer.endFrame(commandBuffer);
|
imguiRenderer.endFrame(commandBuffer);
|
||||||
|
});
|
||||||
|
|
||||||
commandBuffer.endRendering();
|
graph.execute(commandBuffer);
|
||||||
|
|
||||||
renderContext.transitionCurrentImage(commandBuffer,
|
|
||||||
vk::ImageLayout::ePresentSrcKHR);
|
|
||||||
|
|
||||||
renderContext.endFrame();
|
renderContext.endFrame();
|
||||||
|
|
||||||
@@ -232,11 +244,7 @@ class Application::Impl {
|
|||||||
this->sceneTargetHandle = handle;
|
this->sceneTargetHandle = handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 getColorFormat() const {
|
ResourceRegistry& getResourceRegistry() { return resourceRegistry; }
|
||||||
return static_cast<u32>(this->renderContext.getImageFormat());
|
|
||||||
}
|
|
||||||
|
|
||||||
Device& getDevice() { return device; }
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Application& application;
|
Application& application;
|
||||||
@@ -244,6 +252,7 @@ class Application::Impl {
|
|||||||
Window window;
|
Window window;
|
||||||
Device device;
|
Device device;
|
||||||
RenderContext renderContext;
|
RenderContext renderContext;
|
||||||
|
ResourceRegistry resourceRegistry;
|
||||||
|
|
||||||
std::vector<std::unique_ptr<Mesh>> meshes;
|
std::vector<std::unique_ptr<Mesh>> meshes;
|
||||||
std::vector<ModelRenderSystem::MeshInstance> drawCalls;
|
std::vector<ModelRenderSystem::MeshInstance> drawCalls;
|
||||||
@@ -259,14 +268,17 @@ 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)},
|
||||||
resourceRegistry{impl->getDevice(), impl->getColorFormat()},
|
_ui{ResourceRegistryRef{impl->getResourceRegistry()}},
|
||||||
_ui{resourceRegistry},
|
|
||||||
_debugUI{this->panelRegistry, this->consoleCommandRegistry} {}
|
_debugUI{this->panelRegistry, this->consoleCommandRegistry} {}
|
||||||
|
|
||||||
Application::~Application() = default;
|
Application::~Application() = default;
|
||||||
|
|
||||||
void Application::run() { impl->run(); }
|
void Application::run() { impl->run(); }
|
||||||
|
|
||||||
|
ResourceRegistryRef Application::resources() {
|
||||||
|
return ResourceRegistryRef{impl->getResourceRegistry()};
|
||||||
|
}
|
||||||
|
|
||||||
void Application::pushMesh(std::vector<CanvasVertex> vertices,
|
void Application::pushMesh(std::vector<CanvasVertex> vertices,
|
||||||
std::vector<u32> indices) {
|
std::vector<u32> indices) {
|
||||||
impl->pushMesh(std::move(vertices), std::move(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::clearMeshes() { impl->clearMeshes(); }
|
||||||
|
|
||||||
void Application::drawMesh(MeshHandle handle, const glm::mat4& transform) {
|
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
|
} // 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