Skip to content

Commit

Permalink
[WIP] Work in progress
Browse files Browse the repository at this point in the history
  • Loading branch information
IAmNotHanni committed Jun 26, 2024
1 parent a4bbfc6 commit 638e2e1
Show file tree
Hide file tree
Showing 11 changed files with 245 additions and 140 deletions.
81 changes: 60 additions & 21 deletions include/inexor/vulkan-renderer/render-graph/buffer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ namespace inexor::vulkan_renderer::render_graph {

/// The buffer type describes the internal usage of the buffer resource inside of the rendergraph
enum class BufferType {
STAGING_BUFFER,
VERTEX_BUFFER,
INDEX_BUFFER,
UNIFORM_BUFFER,
Expand All @@ -30,47 +29,85 @@ using wrapper::Device;
// TODO: Store const reference to rendergraph and retrieve the swapchain image index for automatic buffer tripling

/// RAII wrapper for buffer resources inside of the rendergraph
/// A buffer resource can be a vertex, index, staging, or uniform buffer
/// A buffer resource can be a vertex buffer, index buffer, or uniform buffer
class Buffer {
private:
friend class RenderGraph;

/// The device wrapper
const Device &m_device;
/// The internal name of this buffer resource inside of the rendergraph
/// The internal debug name of the buffer resource
std::string m_name;
/// The buffer type
BufferType m_buffer_type;
/// An optional update function to update the data of the buffer resource
std::optional<std::function<void()>> m_on_update{std::nullopt};

VkBuffer m_buffer{VK_NULL_HANDLE};
VmaAllocation m_alloc{VK_NULL_HANDLE};
VmaAllocationInfo m_alloc_info;
VkBufferUsageFlags m_buffer_usage;
VmaMemoryUsage m_mem_usage;

// TODO: Maybe buffer updates should be done immediately, and no m_src_data should be stored!
/// The buffer type will be set depending on which constructor of the Buffer wrapper is called by rendergraph. The
/// engine currently supports three different types of buffers in the Buffer wrapper class: vertex buffers, index
/// buffers, and uniform buffers. The instances of the Buffer wrapper class are managed by rendergraph only. One
/// solution to deal with the different buffer types would be to use a BufferBase class and to make three distinct
/// classes VertexBuffer, IndexBuffer, and UniformBuffer. However, we aimed for simplicity and wanted to avoid
/// polymorphism in the rendergraph for performance reasons. Therefore, we have chosen to use only one Buffer
/// wrapper class which contains members for all three different buffer types. The type of the buffer will be set
/// depending on which Buffer constructor is called by rendergraph. The actual memory management for the buffers is
/// done by Vulkan Memory Allocator (VMA) internally.
BufferType m_buffer_type;

/// The buffer update function which is called by rendergraph to update the buffer's data. This update function is
/// called, no matter what the type of the buffer is. With the currently supported buffer types (vertex-, index-,
/// and uniform buffers) there is always a discussion about whether some update lambdas can be made std::optional.
/// For example we could have one vertex buffer with an index buffer and the index buffer is updated together with
/// the vertex buffer in the update function of the vertex buffer. From the design of the engine there is no
/// limitation which buffer is updated in which update function, as long as the handle to that buffer has been
/// created in rendergraph. In our exxample, the update function of the index buffer could be std::nullopt. In this
/// case, rendergraph could separate all buffers into those which require an update and those who do not. For
/// simplicity however, we made the update function not std::optional.
std::optional<std::function<void()>> m_on_update;

/// This is relevant for index buffers only
VkIndexType m_index_type;
/// This is relevant for vertex buffers only
std::vector<VkVertexInputAttributeDescription> m_vert_input_attr_descs;
/// TODO: Is this is relevant for uniform buffers only?
/// TODO: Maybe buffer updates should be done immediately, and no m_src_data should be stored!
/// It's the responsibility of the programmer to make sure the data m_src_data points to is still valid when
/// update_buffer() is called!
void *m_src_data{nullptr};
std::size_t m_src_data_size{0};

/// The resources for actual memory management of the buffer
VkBuffer m_buffer{VK_NULL_HANDLE};
VmaAllocation m_alloc{VK_NULL_HANDLE};
VmaAllocationInfo m_alloc_info{};
VkBufferUsageFlags m_buffer_usage{};
VmaMemoryUsage m_mem_usage{};

/// Create the buffer using Vulkan Memory Allocator (VMA) library
void create_buffer();

// TODO: Only necessary for uniform buffers(?)
void update_buffer();

/// Call vmaDestroyBuffer
void destroy_buffer();

public:
// TODO: Put default constructor into private section?
/// Constructor for uniform buffers
/// @param device The device wrapper
/// @param name The name of the buffer
/// @param on_update The buffer update function (``std::nullopt`` by default)
Buffer(const Device &device, std::string name, std::optional<std::function<void()>> on_update = std::nullopt);

/// Constructor for vertex buffers
/// @param device The device wrapper
/// @param vert_input_attr_descs A vertex of vertex input attribute descriptions
/// @param on_update The buffer update function (``std::nullopt`` by default)
Buffer(const Device &device, std::string name, std::vector<VkVertexInputAttributeDescription> vert_input_attr_descs,
std::optional<std::function<void()>> on_update = std::nullopt);

/// Default constructor
/// Constructor for index buffers
/// @param device The device wrapper
/// @param name The internal debug name of the buffer (must not be empty)
/// @param usage The internal usage of the buffer in the rendergraph
/// @note The update frequency of a buffer will only be respected when grouping uniform buffers into descriptor sets
/// @param on_update An optional update function (``std::nullopt`` by default, meaning no updates to this buffer)
Buffer(const Device &device, std::string name, BufferType type,
/// @param name The name of the buffer
/// @param index_type The Vulkan index type
/// @param on_update The buffer update function (``std::nullopt`` by default)
Buffer(const Device &device, std::string name, VkIndexType index_type,
std::optional<std::function<void()>> on_update = std::nullopt);

Buffer(const Buffer &) = delete;
Expand All @@ -80,10 +117,12 @@ class Buffer {
Buffer &operator=(const Buffer &) = delete;
Buffer &operator=(Buffer &&) = delete;

// TODO: Remove get methods and allow access to private fields through friend class declaration only?
[[nodiscard]] auto &buffer() const {
return m_buffer;
}

// TODO: Remove get methods and allow access to private fields through friend class declaration only?
[[nodiscard]] auto &name() const {
return m_name;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,33 +16,33 @@ class CommandBuffer;

namespace inexor::vulkan_renderer::render_graph {

/// A builder class for graphics stages in the rendergraph
/// A builder class for graphics passes in the rendergraph
/// @warning Make sure that the order or add calls for buffers and textures matches the binding order!
class GraphicsPassBuilder {
private:
/// Indicates if the screen is cleared at the beginning of this stage
/// Indicates if the screen is cleared at the beginning of this pass
std::optional<VkClearValue> m_clear_value;
/// Add members which describe data related to graphics stages here
/// Add members which describe data related to graphics passes here
std::function<void(const wrapper::commands::CommandBuffer &)> m_on_record;
/// Depth testing
bool m_depth_test;

/// The buffers the graphics stage reads from
/// The buffers which are read by the graphics pass
/// If the buffer's ``BufferType`` is ``UNIFORM_BUFFER``, a value for the shader stage flag must be specified,
/// because uniform buffers can be read from vertex or fragment stage bit.
BufferReads m_buffer_reads;
/// The textures the graphics stage reads from
/// The textures the graphics pass reads from
TextureReads m_texture_reads;
/// The textures the graphics stage writes to
/// The textures the graphics pass writes to
TextureWrites m_texture_writes;
/// The push constant ranges of the graphics stage
/// The push constant ranges of the graphics pass
std::vector<std::pair<VkPushConstantRange, std::function<void()>>> m_push_constant_ranges;

// TODO: Merge push constant ranges into one block and put it as member here?
// TODO: Copy all data into one piece of memory and call vkCmdPushConstants only once?
void compile_push_constants();

/// Reset all data of the graphics stage builder
/// Reset all data of the graphics pass builder
void reset();

public:
Expand All @@ -54,7 +54,7 @@ class GraphicsPassBuilder {
GraphicsPassBuilder &operator=(const GraphicsPassBuilder &) = delete;
GraphicsPassBuilder &operator=(GraphicsPassBuilder &&) noexcept;

/// Add a push constant range to the graphics stage
/// Add a push constant range to the graphics pass
/// @param shader_stage The shader stage for the push constant range
/// @param push_constant The push constant data
/// @param on_update The update callable
Expand All @@ -74,14 +74,23 @@ class GraphicsPassBuilder {
return *this;
}

/// Build the graphics stage and reset the builder's data
/// @param name The name of the graphics stage
/// @return The graphics stage which was created
[[nodiscard]] std::shared_ptr<GraphicsPass> build(std::string name);
/// Build the graphics pass
/// @param name The name of the graphics pass
/// @return The graphics pass that was just created
[[nodiscard]] auto build(std::string name) {
auto graphics_pass = std::make_shared<GraphicsPass>(std::move(name), std::move(m_buffer_reads),
std::move(m_texture_reads), std::move(m_texture_writes),
std::move(m_on_record), std::move(m_clear_value));
// Don't forget to reset the builder automatically before returning the graphics pass that was just created
reset();
// Return the graphics pass that was created
return graphics_pass;
}

// TODO: We must specify buffer reads for vertex and index buffers, but bind manually... is that good?
// TODO: std::optional<VkShaderStageFlagBits> or better default VkShaderStageFlagBits to VK_SHADER_STAGE_VERTEX_BIT?

/// Specifies that this pass reads from a buffer
/// Specify that the pass reads from a buffer
/// @param buffer The buffer the pass reads from
/// @param shader_stage The shader stage the buffer is read from
/// @return A const reference to the this pointer (allowing method calls to be chained)
Expand All @@ -91,7 +100,7 @@ class GraphicsPassBuilder {
return *this;
}

/// Specifies that this pass reads from a texture
/// Specify that the pass reads from a texture
/// @param texture The texture the pass reads from
/// @param shader_stage The shader stage the texture is read from
/// @return A const reference to the this pointer (allowing method calls to be chained)
Expand All @@ -101,15 +110,15 @@ class GraphicsPassBuilder {
return *this;
}

/// Specifies that this pass writes to a texture
/// Specify that the pass writes to a texture
/// @param texture The texture the pass writes to
/// @return A const reference to the this pointer (allowing method calls to be chained)
[[nodiscard]] auto &writes_to_texture(std::weak_ptr<Texture> texture) {
m_texture_writes.emplace_back(texture);
return *this;
}

/// Set the clear status for the stage
/// Set the clear status for the pass
/// @param clear_value The clear value for color and depth
/// @return A const reference to the this pointer (allowing method calls to be chained)
[[nodiscard]] auto &set_clear_value(VkClearValue clear_value) {
Expand All @@ -118,15 +127,15 @@ class GraphicsPassBuilder {
}

/// Enable or disable depth testing
/// @param depth_test ``true`` if depth testing is enabled for this stage
/// @param depth_test ``true`` if depth testing is enabled for this pass
/// @return A const reference to the this pointer (allowing method calls to be chained)
[[nodiscard]] auto &set_depth_test(bool depth_test) {
m_depth_test = depth_test;
return *this;
}

/// Set the function which will be called when the stage's command buffer is being recorded
/// @param on_record The function which will be called when the stage's command buffer is being recorded
/// Set the function which will be called when the command buffer for rendering of the pass is being recorded
/// @param on_record The command buffer recording function
/// @return A const reference to the this pointer (allowing method calls to be chained)
[[nodiscard]] auto &set_on_record(std::function<void(const wrapper::commands::CommandBuffer &)> on_record) {
m_on_record = std::move(on_record);
Expand Down
48 changes: 36 additions & 12 deletions include/inexor/vulkan-renderer/render-graph/render_graph.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ class RenderGraph {

public:
/// Default constructor
/// @note device and swapchain are not a const reference because rendergraph needs to modify both
/// @param device The device wrapper
/// @param swapchain The swapchain wrapper
RenderGraph(Device &device, Swapchain &swapchain);
Expand All @@ -175,15 +176,36 @@ class RenderGraph {
RenderGraph &operator=(const RenderGraph &) = delete;
RenderGraph &operator=(RenderGraph &&) = delete;

/// Add a buffer (vertex, index, or uniform buffer) resource to the rendergraph
/// @param name The internal name of the buffer resource (must not be empty)
/// @param type The internal buffer usage of the buffer
/// @param on_update An optional buffer resource update function (``std::nullopt`` by default)
/// @note Not every buffer must have an update function because index buffers should be updated with vertex buffers
/// @exception std::runtime_error Internal debug name is empty
/// @return A weak pointer to the buffer resource that was just created
[[nodiscard]] std::weak_ptr<Buffer> add_buffer(std::string name, BufferType type,
std::optional<std::function<void()>> on_update = std::nullopt);
/// Add an index buffer to rendergraph
/// @note The Vulkan index type is set to ``VK_INDEX_TYPE_UINT32`` by default and it not exposed as parameter
/// @param name The name of the index buffer
/// @param on_update The update function of the index buffer
/// @return A weak pointer to the buffer resource that was created
/// @note The rendergraph owns the memory of the buffer resource, hence we return a weak pointer
[[nodiscard]] std::weak_ptr<Buffer> add_index_buffer(std::string name,
std::optional<std::function<void()>> on_update = std::nullopt);

/// Add a uniform buffer to rendergraph
/// @param name The name of the uniform buffer
/// @param on_update The update function of the uniform buffer
/// @return A weak pointer to the buffer resource that was created
/// @note The rendergraph owns the memory of the buffer resource, hence we return a weak pointer
[[nodiscard]] std::weak_ptr<Buffer>
add_uniform_buffer(std::string name, std::optional<std::function<void()>> on_update = std::nullopt);

/// Add a vertex buffer to rendergraph
/// @param name The name of the vertex buffer
/// @param vert_input_attr_descs The vertex input attribute descriptions
/// @note You might cleverly noticed that ``VkVertexInputAttributeDescription`` is not required to create a buffer.
/// Why then is it a parameter here? The vertex input attribute description is stored in the buffer so that when
/// rendergraph gets compiled and builds the graphics pipelines, it can read ``VkVertexInputAttributeDescription``
/// from the buffers to create the graphics pipelines.
/// @param on_update The update function of the vertex buffer
/// @return A weak pointer to the buffer resource that was created
/// @note The rendergraph owns the memory of the buffer resource, hence we return a weak pointer
[[nodiscard]] std::weak_ptr<Buffer>
add_vertex_buffer(std::string name, std::vector<VkVertexInputAttributeDescription> vert_input_attr_descs,
std::optional<std::function<void()>> on_update = std::nullopt);

/// Add a new graphics pass to the rendergraph
/// @param on_pass_create A callable to create the graphics pass using GraphicsPassBuilder
Expand All @@ -199,7 +221,7 @@ class RenderGraph {
/// @tparam T The data type of the push constant range
/// @param data A pointer to the data of the push constant range
/// @param on_update The update function of the push constant range
/// @param stage_flags The shader stage flags
/// @param stage_flags The shader stage flags (``VK_SHADER_STAGE_VERTEX_BIT`` by default)
/// @param offset The offset in bytes (``0`` by default)
/// @return The this pointer, allowing for methods to be chained as a builder pattern
template <typename PushConstantDataType>
Expand All @@ -215,6 +237,8 @@ class RenderGraph {
data, std::move(on_update));
}

// TODO: Implement dynamic textures!

/// Add a texture resource to the rendergraph
/// @param name The name of the texture (must not be empty)
/// @param uage The texture usage inside of rendergraph
Expand All @@ -224,13 +248,13 @@ class RenderGraph {
[[nodiscard]] std::weak_ptr<Texture> add_texture(std::string name, TextureUsage usage, VkFormat format,
std::optional<std::function<void()>> on_update = std::nullopt);

/// Compile the entire rendergraph
/// Compile the rendergraph
void compile();

/// Render a frame
void render();

/// Update all the rendering data (buffers, textures...)
/// Update rendering data like buffers or textures
void update_data();
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,10 @@ class CommandBuffer {
[[nodiscard]] VkBuffer create_staging_buffer(const void *data, const VkDeviceSize data_size,
const std::string &name) const {
// Create a staging buffer for the copy operation and keep it until the CommandBuffer exceeds its lifetime
m_staging_bufs.emplace_back(m_device, name, render_graph::BufferType::STAGING_BUFFER);

// TODO: Create/Implement staging buffers!

// m_staging_bufs.emplace_back(m_device, name, render_graph::BufferType::STAGING_BUFFER);
return m_staging_bufs.back().buffer();
}

Expand Down
2 changes: 2 additions & 0 deletions include/inexor/vulkan-renderer/wrapper/device.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ class Device {
/// @note This method will create a command pool for the thread if it doesn't already exist
commands::CommandPool &thread_graphics_pool() const;

/// TODO: Implement thread_local command pools for other queue types (transfer, compute, sparse binding..?)

public:
/// Pick the best physical device automatically
/// @param physical_device_infos The data of the physical devices
Expand Down
Loading

0 comments on commit 638e2e1

Please sign in to comment.