Program Listing for File render_graph.hpp¶
↰ Return to documentation for file (inexor/vulkan-renderer/render_graph.hpp
)
#pragma once
#include "inexor/vulkan-renderer/wrapper/device.hpp"
#include "inexor/vulkan-renderer/wrapper/framebuffer.hpp"
#include "inexor/vulkan-renderer/wrapper/swapchain.hpp"
#include <spdlog/spdlog.h>
#include <functional>
#include <memory>
#include <string>
#include <type_traits>
#include <unordered_map>
#include <vector>
// TODO: Compute stages.
// TODO: Uniform buffers.
// Forward declarations
namespace inexor::vulkan_renderer::wrapper {
class CommandBuffer;
class Shader;
}; // namespace inexor::vulkan_renderer::wrapper
namespace inexor::vulkan_renderer {
// Forward declarations
class PhysicalResource;
class PhysicalStage;
class RenderGraph;
struct RenderGraphObject {
RenderGraphObject() = default;
RenderGraphObject(const RenderGraphObject &) = delete;
RenderGraphObject(RenderGraphObject &&) = delete;
virtual ~RenderGraphObject() = default;
RenderGraphObject &operator=(const RenderGraphObject &) = delete;
RenderGraphObject &operator=(RenderGraphObject &&) = delete;
template <typename T>
[[nodiscard]] T *as();
template <typename T>
[[nodiscard]] const T *as() const;
};
class RenderResource : public RenderGraphObject {
friend RenderGraph;
private:
const std::string m_name;
std::shared_ptr<PhysicalResource> m_physical;
protected:
explicit RenderResource(std::string name) : m_name(std::move(name)) {}
public:
RenderResource(const RenderResource &) = delete;
RenderResource(RenderResource &&) = delete;
~RenderResource() override = default;
RenderResource &operator=(const RenderResource &) = delete;
RenderResource &operator=(RenderResource &&) = delete;
[[nodiscard]] const std::string &name() const {
return m_name;
}
};
enum class BufferUsage {
INDEX_BUFFER,
VERTEX_BUFFER,
};
class BufferResource : public RenderResource {
friend RenderGraph;
private:
const BufferUsage m_usage;
std::vector<VkVertexInputAttributeDescription> m_vertex_attributes;
// Data to upload during render graph compilation.
const void *m_data{nullptr};
std::size_t m_data_size{0};
bool m_data_upload_needed{false};
std::size_t m_element_size{0};
public:
BufferResource(std::string &&name, BufferUsage usage) : RenderResource(name), m_usage(usage) {}
void add_vertex_attribute(VkFormat format, std::uint32_t offset);
void set_element_size(std::size_t element_size) {
m_element_size = element_size;
}
// TODO: Use std::span when we switch to C++ 20.
template <typename T>
void upload_data(const T *data, std::size_t count);
template <typename T>
void upload_data(const std::vector<T> &data);
};
enum class TextureUsage {
// TODO: Refactor back buffer system more (remove need for BACK_BUFFER texture usage)
BACK_BUFFER,
DEPTH_STENCIL_BUFFER,
NORMAL,
};
class TextureResource : public RenderResource {
friend RenderGraph;
private:
const TextureUsage m_usage;
VkFormat m_format{VK_FORMAT_UNDEFINED};
public:
TextureResource(std::string &&name, TextureUsage usage) : RenderResource(name), m_usage(usage) {}
void set_format(VkFormat format) {
m_format = format;
}
};
class RenderStage : public RenderGraphObject {
friend RenderGraph;
private:
const std::string m_name;
std::unique_ptr<PhysicalStage> m_physical;
std::vector<const RenderResource *> m_writes;
std::vector<const RenderResource *> m_reads;
std::vector<VkDescriptorSetLayout> m_descriptor_layouts;
std::vector<VkPushConstantRange> m_push_constant_ranges;
std::function<void(const PhysicalStage &, const wrapper::CommandBuffer &)> m_on_record{[](auto &, auto &) {}};
protected:
explicit RenderStage(std::string name) : m_name(std::move(name)) {}
public:
RenderStage(const RenderStage &) = delete;
RenderStage(RenderStage &&) = delete;
~RenderStage() override = default;
RenderStage &operator=(const RenderStage &) = delete;
RenderStage &operator=(RenderStage &&) = delete;
void writes_to(const RenderResource *resource);
void reads_from(const RenderResource *resource);
// TODO: Refactor descriptor management in the render graph
void add_descriptor_layout(VkDescriptorSetLayout layout) {
m_descriptor_layouts.push_back(layout);
}
void add_push_constant_range(VkPushConstantRange range) {
m_push_constant_ranges.push_back(range);
}
[[nodiscard]] const std::string &name() const {
return m_name;
}
void set_on_record(std::function<void(const PhysicalStage &, const wrapper::CommandBuffer &)> on_record) {
m_on_record = std::move(on_record);
}
};
class GraphicsStage : public RenderStage {
friend RenderGraph;
private:
bool m_clears_screen{false};
bool m_depth_test{false};
bool m_depth_write{false};
VkPipelineColorBlendAttachmentState m_blend_attachment{};
std::unordered_map<const BufferResource *, std::uint32_t> m_buffer_bindings;
std::vector<VkPipelineShaderStageCreateInfo> m_shaders;
public:
explicit GraphicsStage(std::string &&name) : RenderStage(name) {}
GraphicsStage(const GraphicsStage &) = delete;
GraphicsStage(GraphicsStage &&) = delete;
~GraphicsStage() override = default;
GraphicsStage &operator=(const GraphicsStage &) = delete;
GraphicsStage &operator=(GraphicsStage &&) = delete;
void set_clears_screen(bool clears_screen) {
m_clears_screen = clears_screen;
}
void set_depth_options(bool depth_test, bool depth_write) {
m_depth_test = depth_test;
m_depth_write = depth_write;
}
void set_blend_attachment(VkPipelineColorBlendAttachmentState blend_attachment) {
m_blend_attachment = blend_attachment;
}
void bind_buffer(const BufferResource *buffer, std::uint32_t binding);
void uses_shader(const wrapper::Shader &shader);
};
// TODO: Add wrapper::Allocation that can be made by doing `device->make<Allocation>(...)`.
class PhysicalResource : public RenderGraphObject {
friend RenderGraph;
protected:
const wrapper::Device &m_device;
VmaAllocation m_allocation{VK_NULL_HANDLE};
explicit PhysicalResource(const wrapper::Device &device) : m_device(device) {}
public:
PhysicalResource(const PhysicalResource &) = delete;
PhysicalResource(PhysicalResource &&) = delete;
~PhysicalResource() override = default;
PhysicalResource &operator=(const PhysicalResource &) = delete;
PhysicalResource &operator=(PhysicalResource &&) = delete;
};
class PhysicalBuffer : public PhysicalResource {
friend RenderGraph;
private:
VmaAllocationInfo m_alloc_info{};
VkBuffer m_buffer{VK_NULL_HANDLE};
public:
explicit PhysicalBuffer(const wrapper::Device &device) : PhysicalResource(device) {}
PhysicalBuffer(const PhysicalBuffer &) = delete;
PhysicalBuffer(PhysicalBuffer &&) = delete;
~PhysicalBuffer() override;
PhysicalBuffer &operator=(const PhysicalBuffer &) = delete;
PhysicalBuffer &operator=(PhysicalBuffer &&) = delete;
};
class PhysicalImage : public PhysicalResource {
friend RenderGraph;
private:
VkImage m_image{VK_NULL_HANDLE};
VkImageView m_image_view{VK_NULL_HANDLE};
public:
explicit PhysicalImage(const wrapper::Device &device) : PhysicalResource(device) {}
PhysicalImage(const PhysicalImage &) = delete;
PhysicalImage(PhysicalImage &&) = delete;
~PhysicalImage() override;
PhysicalImage &operator=(const PhysicalImage &) = delete;
PhysicalImage &operator=(PhysicalImage &&) = delete;
};
class PhysicalBackBuffer : public PhysicalResource {
friend RenderGraph;
private:
const wrapper::Swapchain &m_swapchain;
public:
PhysicalBackBuffer(const wrapper::Device &device, const wrapper::Swapchain &swapchain)
: PhysicalResource(device), m_swapchain(swapchain) {}
PhysicalBackBuffer(const PhysicalBackBuffer &) = delete;
PhysicalBackBuffer(PhysicalBackBuffer &&) = delete;
~PhysicalBackBuffer() override = default;
PhysicalBackBuffer &operator=(const PhysicalBackBuffer &) = delete;
PhysicalBackBuffer &operator=(PhysicalBackBuffer &&) = delete;
};
class PhysicalStage : public RenderGraphObject {
friend RenderGraph;
private:
VkPipeline m_pipeline{VK_NULL_HANDLE};
VkPipelineLayout m_pipeline_layout{VK_NULL_HANDLE};
protected:
const wrapper::Device &m_device;
public:
explicit PhysicalStage(const wrapper::Device &device) : m_device(device) {}
PhysicalStage(const PhysicalStage &) = delete;
PhysicalStage(PhysicalStage &&) = delete;
~PhysicalStage() override;
PhysicalStage &operator=(const PhysicalStage &) = delete;
PhysicalStage &operator=(PhysicalStage &&) = delete;
// TODO: This can be removed once descriptors are properly implemented in the render graph.
[[nodiscard]] VkPipelineLayout pipeline_layout() const {
return m_pipeline_layout;
}
};
class PhysicalGraphicsStage : public PhysicalStage {
friend RenderGraph;
private:
VkRenderPass m_render_pass{VK_NULL_HANDLE};
std::vector<wrapper::Framebuffer> m_framebuffers;
public:
explicit PhysicalGraphicsStage(const wrapper::Device &device) : PhysicalStage(device) {}
PhysicalGraphicsStage(const PhysicalGraphicsStage &) = delete;
PhysicalGraphicsStage(PhysicalGraphicsStage &&) = delete;
~PhysicalGraphicsStage() override;
PhysicalGraphicsStage &operator=(const PhysicalGraphicsStage &) = delete;
PhysicalGraphicsStage &operator=(PhysicalGraphicsStage &&) = delete;
};
class RenderGraph {
private:
wrapper::Device &m_device;
const wrapper::Swapchain &m_swapchain;
std::shared_ptr<spdlog::logger> m_log{spdlog::default_logger()->clone("render-graph")};
// Vectors of render resources and stages.
std::vector<std::unique_ptr<BufferResource>> m_buffer_resources;
std::vector<std::unique_ptr<TextureResource>> m_texture_resources;
std::vector<std::unique_ptr<RenderStage>> m_stages;
// Stage execution order.
std::vector<RenderStage *> m_stage_stack;
// Functions for building resource related vulkan objects.
void build_buffer(const BufferResource &, PhysicalBuffer &) const;
void build_image(const TextureResource &, PhysicalImage &, VmaAllocationCreateInfo *) const;
void build_image_view(const TextureResource &, PhysicalImage &) const;
// Functions for building stage related vulkan objects.
void build_pipeline_layout(const RenderStage *, PhysicalStage &) const;
void record_command_buffer(const RenderStage *, const wrapper::CommandBuffer &cmd_buf,
std::uint32_t image_index) const;
// Functions for building graphics stage related vulkan objects.
void build_render_pass(const GraphicsStage *, PhysicalGraphicsStage &) const;
void build_graphics_pipeline(const GraphicsStage *, PhysicalGraphicsStage &) const;
public:
RenderGraph(wrapper::Device &device, const wrapper::Swapchain &swapchain)
: m_device(device), m_swapchain(swapchain) {}
template <typename T, typename... Args>
T *add(Args &&...args) {
auto ptr = std::make_unique<T>(std::forward<Args>(args)...);
if constexpr (std::is_same_v<T, BufferResource>) {
return static_cast<T *>(m_buffer_resources.emplace_back(std::move(ptr)).get());
} else if constexpr (std::is_same_v<T, TextureResource>) {
return static_cast<T *>(m_texture_resources.emplace_back(std::move(ptr)).get());
} else if constexpr (std::is_base_of_v<RenderStage, T>) {
return static_cast<T *>(m_stages.emplace_back(std::move(ptr)).get());
} else {
static_assert(!std::is_same_v<T, T>, "T must be a RenderResource or RenderStage");
}
}
void compile(const RenderResource *target);
void render(std::uint32_t image_index, const wrapper::CommandBuffer &cmd_buf);
};
template <typename T>
[[nodiscard]] T *RenderGraphObject::as() {
return dynamic_cast<T *>(this);
}
template <typename T>
[[nodiscard]] const T *RenderGraphObject::as() const {
return dynamic_cast<const T *>(this);
}
template <typename T>
void BufferResource::upload_data(const T *data, std::size_t count) {
m_data = data;
m_data_size = count * (m_element_size = sizeof(T));
m_data_upload_needed = true;
}
template <typename T>
void BufferResource::upload_data(const std::vector<T> &data) {
upload_data(data.data(), data.size());
}
} // namespace inexor::vulkan_renderer