169 lines
4.7 KiB
C++
169 lines
4.7 KiB
C++
#pragma once
|
|
|
|
#include <array>
|
|
#include <functional>
|
|
#include <optional>
|
|
#include <sophia/types.hpp>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <vulkan/vulkan.hpp>
|
|
// After Vulkan, which it expects to already be declared.
|
|
#include <tracy/TracyVulkan.hpp>
|
|
|
|
#include "vulkan/access.hpp"
|
|
#include "vulkan/image.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 or depth 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)>;
|
|
|
|
explicit RenderGraph(TracyVkCtx tracyContext = nullptr)
|
|
: tracyContext{tracyContext} {}
|
|
|
|
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 or depth) rendering begun. `name`
|
|
* labels the pass's commands in debugging tools such as RenderDoc.
|
|
*/
|
|
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 DepthAttachment {
|
|
ImageRef image;
|
|
vk::AttachmentLoadOp loadOp;
|
|
float clearDepth;
|
|
};
|
|
|
|
struct Pass {
|
|
std::string name;
|
|
std::vector<ImageUse> uses;
|
|
std::vector<ColorAttachment> colorAttachments;
|
|
std::optional<DepthAttachment> depthAttachment;
|
|
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::ImageMemoryBarrier2> barriers;
|
|
};
|
|
|
|
void transitionTo(ImageRef ref, const AccessState& next,
|
|
PendingBarriers& pending);
|
|
void flush(vk::CommandBuffer commandBuffer, PendingBarriers& pending);
|
|
void recordPass(vk::CommandBuffer commandBuffer, Pass& pass);
|
|
void recordRendering(vk::CommandBuffer commandBuffer, Pass& pass);
|
|
|
|
TracyVkCtx tracyContext;
|
|
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});
|
|
|
|
/**
|
|
* Renders with `image` as the depth attachment, which must be the same size
|
|
* as any color attachments. At most one per pass.
|
|
*/
|
|
PassBuilder& writeDepth(
|
|
ImageRef image,
|
|
vk::AttachmentLoadOp loadOp = vk::AttachmentLoadOp::eClear,
|
|
float clearDepth = 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
|