diff --git a/include/inexor/vulkan-renderer/render-graph/buffer.hpp b/include/inexor/vulkan-renderer/render-graph/buffer.hpp index f99533798..963a03db9 100644 --- a/include/inexor/vulkan-renderer/render-graph/buffer.hpp +++ b/include/inexor/vulkan-renderer/render-graph/buffer.hpp @@ -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, @@ -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> 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> m_on_update; + + /// This is relevant for index buffers only + VkIndexType m_index_type; + /// This is relevant for vertex buffers only + std::vector 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> 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 vert_input_attr_descs, + std::optional> 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> on_update = std::nullopt); Buffer(const Buffer &) = delete; @@ -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; } diff --git a/include/inexor/vulkan-renderer/render-graph/graphics_pass_builder.hpp b/include/inexor/vulkan-renderer/render-graph/graphics_pass_builder.hpp index 20c89c9aa..5db2e2b2e 100644 --- a/include/inexor/vulkan-renderer/render-graph/graphics_pass_builder.hpp +++ b/include/inexor/vulkan-renderer/render-graph/graphics_pass_builder.hpp @@ -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 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 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>> 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: @@ -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 @@ -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 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(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 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) @@ -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) @@ -101,7 +110,7 @@ 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) { @@ -109,7 +118,7 @@ class GraphicsPassBuilder { 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) { @@ -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 on_record) { m_on_record = std::move(on_record); diff --git a/include/inexor/vulkan-renderer/render-graph/render_graph.hpp b/include/inexor/vulkan-renderer/render-graph/render_graph.hpp index 6670f8ade..4fb528e32 100644 --- a/include/inexor/vulkan-renderer/render-graph/render_graph.hpp +++ b/include/inexor/vulkan-renderer/render-graph/render_graph.hpp @@ -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); @@ -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 add_buffer(std::string name, BufferType type, - std::optional> 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 add_index_buffer(std::string name, + std::optional> 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 + add_uniform_buffer(std::string name, std::optional> 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 + add_vertex_buffer(std::string name, std::vector vert_input_attr_descs, + std::optional> 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 @@ -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 @@ -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 @@ -224,13 +248,13 @@ class RenderGraph { [[nodiscard]] std::weak_ptr add_texture(std::string name, TextureUsage usage, VkFormat format, std::optional> 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(); }; diff --git a/include/inexor/vulkan-renderer/wrapper/commands/command_buffer.hpp b/include/inexor/vulkan-renderer/wrapper/commands/command_buffer.hpp index 7f3ffad88..ff81a47f0 100644 --- a/include/inexor/vulkan-renderer/wrapper/commands/command_buffer.hpp +++ b/include/inexor/vulkan-renderer/wrapper/commands/command_buffer.hpp @@ -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(); } diff --git a/include/inexor/vulkan-renderer/wrapper/device.hpp b/include/inexor/vulkan-renderer/wrapper/device.hpp index dcdb8ea7a..9feea304a 100644 --- a/include/inexor/vulkan-renderer/wrapper/device.hpp +++ b/include/inexor/vulkan-renderer/wrapper/device.hpp @@ -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 diff --git a/src/vulkan-renderer/application.cpp b/src/vulkan-renderer/application.cpp index bf3996718..46a140b92 100644 --- a/src/vulkan-renderer/application.cpp +++ b/src/vulkan-renderer/application.cpp @@ -175,6 +175,7 @@ Application::Application(int argc, char **argv) { // TODO: Consistent design: object name must not be empty! // TODO: Uniform API style like this: m_vertex_shader = m_device->create_vertex(?)_shader("Octree", // "shaders/main.vert.spv"); + // TODO: Same idea here: make constructor of Shader private and allow friend class Rendergraph only? m_vertex_shader = std::make_unique(*m_device, VK_SHADER_STAGE_VERTEX_BIT, "Octree", "shaders/main.vert.spv"); m_fragment_shader = @@ -185,7 +186,6 @@ Application::Application(int argc, char **argv) { m_camera->set_movement_speed(5.0f); m_camera->set_rotation_speed(0.5f); - m_window->show(); recreate_swapchain(); } @@ -421,7 +421,6 @@ void Application::run() { while (!m_window->should_close()) { m_window->poll(); - // TODO: Incorporate this also into rendergraph? Might be a little too much though process_keyboard_input(); process_mouse_input(); m_camera->update(m_time_passed); @@ -447,7 +446,22 @@ void Application::setup_render_graph() { using render_graph::BufferType; - m_vertex_buffer = m_render_graph->add_buffer("Octree", BufferType::VERTEX_BUFFER, [&]() { + const std::vector vert_input_attr_desc{{ + { + .location = 0, + .binding = 0, + .format = VK_FORMAT_R32G32B32_SFLOAT, + .offset = offsetof(OctreeGpuVertex, position), + }, + { + .location = 1, + .binding = 0, + .format = VK_FORMAT_R32G32B32_SFLOAT, + .offset = offsetof(OctreeGpuVertex, color), + }, + }}; + + m_vertex_buffer = m_render_graph->add_vertex_buffer("Octree", vert_input_attr_desc, [&]() { // If the key N was pressed once, we generate a new octree if (m_input_data->was_key_pressed_once(GLFW_KEY_N)) { load_octree_geometry(false); @@ -460,15 +474,18 @@ void Application::setup_render_graph() { }); // Note that the index buffer is updated together with the vertex buffer to keep data consistent - m_index_buffer = m_render_graph->add_buffer("Octree", BufferType::INDEX_BUFFER); + m_index_buffer = m_render_graph->add_index_buffer("Octree"); + // ------------------------------------------------------------------------------------------------- + // TODO: Delete this if not required (improves simplicity) // Update the vertex buffer and index buffer at initialization - // Note that we update the vertex buffer together with the index buffer to keep data consistent + // NOTE: We must update the vertex buffer together with the index buffer to keep data consistent m_vertex_buffer.lock()->request_update(m_octree_vertices); m_index_buffer.lock()->request_update(m_octree_indices); + // ------------------------------------------------------------------------------------------------- - m_uniform_buffer = m_render_graph->add_buffer("Matrices", BufferType::UNIFORM_BUFFER, [&]() { - // The model matrix is constant and doesn't need to be updated + m_uniform_buffer = m_render_graph->add_uniform_buffer("Matrices", [&]() { + // TODO: Update model matrix m_mvp_matrices.view = m_camera->view_matrix(); m_mvp_matrices.proj = m_camera->perspective_matrix(); m_mvp_matrices.proj[1][1] *= -1; @@ -477,7 +494,6 @@ void Application::setup_render_graph() { using wrapper::pipelines::GraphicsPipelineBuilder; - // TODO: How to associate pipeline layouts with pipelines? m_render_graph->add_graphics_pipeline( [&](GraphicsPipelineBuilder &builder, const VkPipelineLayout pipeline_layout) { m_octree_pipeline = builder @@ -493,20 +509,7 @@ void Application::setup_render_graph() { .inputRate = VK_VERTEX_INPUT_RATE_VERTEX, }, }) - .set_vertex_input_attributes({ - { - .location = 0, - .binding = 0, - .format = VK_FORMAT_R32G32B32_SFLOAT, - .offset = offsetof(OctreeGpuVertex, position), - }, - { - .location = 1, - .binding = 0, - .format = VK_FORMAT_R32G32B32_SFLOAT, - .offset = offsetof(OctreeGpuVertex, color), - }, - }) + .set_vertex_input_attributes(vert_input_attr_desc) .set_viewport(m_swapchain->extent()) .set_scissor(m_swapchain->extent()) .set_pipeline_layout(pipeline_layout) @@ -520,6 +523,7 @@ void Application::setup_render_graph() { using wrapper::commands::CommandBuffer; m_render_graph->add_graphics_pass([&](GraphicsPassBuilder &builder) { + // TODO: Should graphics pass be owned by rendergraph or other code place? (shared_ptr or weak_ptr?) m_octree_pass = builder .set_clear_value({ .color = {1.0f, 0.0f, 0.0f}, diff --git a/src/vulkan-renderer/render-graph/buffer.cpp b/src/vulkan-renderer/render-graph/buffer.cpp index e30fcf512..a9226ab60 100644 --- a/src/vulkan-renderer/render-graph/buffer.cpp +++ b/src/vulkan-renderer/render-graph/buffer.cpp @@ -8,14 +8,30 @@ namespace inexor::vulkan_renderer::render_graph { -Buffer::Buffer(const Device &device, std::string name, const BufferType type, +Buffer::Buffer(const Device &device, std::string name, std::optional> on_update) + : m_device(device), m_name(std::move(name)), m_on_update(std::move(on_update)), + m_buffer_type(BufferType::UNIFORM_BUFFER) { + // TODO: Set buffer usage flags! +} + +Buffer::Buffer(const Device &device, std::string name, + std::vector vert_input_attr_descs, std::optional> on_update) - : m_device(device), m_name(std::move(name)), m_buffer_type(type), m_on_update(std::move(on_update)) { - // TODO: Set VMA memory usage and Vulkan buffer usage flags + : m_device(device), m_name(std::move(name)), m_on_update(std::move(on_update)), + m_buffer_type(BufferType::VERTEX_BUFFER) { + // TODO: Set buffer usage flags! +} + +Buffer::Buffer(const Device &device, std::string name, VkIndexType index_type, + std::optional> on_update) + : m_device(device), m_name(std::move(name)), m_on_update(std::move(on_update)), m_index_type(index_type), + m_buffer_type(BufferType::INDEX_BUFFER) { + // TODO: Set buffer usage flags! } Buffer::Buffer(Buffer &&other) noexcept : m_device(other.m_device), m_buffer_usage(other.m_buffer_usage), m_mem_usage(other.m_mem_usage) { + // TODO: Fix me! m_name = std::move(other.m_name); m_buffer_type = other.m_buffer_type; m_on_update = std::move(other.m_on_update); @@ -74,12 +90,14 @@ void Buffer::update_buffer() { // TODO: Batching of pipeline memory barriers for staging buffer through rendergraph? break; } - case BufferType::STAGING_BUFFER: { - // A staging buffer does not require updates, as it itself is used to update other buffers - // Note that staging buffers are managed inside of device wrapper, not rendergraph, and we currently do not - // re-use staging buffers - break; - } + /* + case BufferType::STAGING_BUFFER: { + // A staging buffer does not require updates, as it itself is used to update other buffers + // Note that staging buffers are managed inside of device wrapper, not rendergraph, and we currently do not + // re-use staging buffers + break; + } + */ } } diff --git a/src/vulkan-renderer/render-graph/graphics_pass_builder.cpp b/src/vulkan-renderer/render-graph/graphics_pass_builder.cpp index a71c3f221..96a04fc26 100644 --- a/src/vulkan-renderer/render-graph/graphics_pass_builder.cpp +++ b/src/vulkan-renderer/render-graph/graphics_pass_builder.cpp @@ -6,12 +6,6 @@ GraphicsPassBuilder::GraphicsPassBuilder() { reset(); } -std::shared_ptr GraphicsPassBuilder::build(std::string name) { - return std::make_shared(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)); -} - void GraphicsPassBuilder::reset() { m_clear_value = std::nullopt; m_on_record = [](auto &) {}; diff --git a/src/vulkan-renderer/render-graph/render_graph.cpp b/src/vulkan-renderer/render-graph/render_graph.cpp index 8dae0183d..2276c3a7e 100644 --- a/src/vulkan-renderer/render-graph/render_graph.cpp +++ b/src/vulkan-renderer/render-graph/render_graph.cpp @@ -15,9 +15,22 @@ namespace inexor::vulkan_renderer::render_graph { RenderGraph::RenderGraph(Device &device, Swapchain &swapchain) : m_device(device), m_swapchain(swapchain), m_graphics_pipeline_builder(device) {} -std::weak_ptr RenderGraph::add_buffer(std::string name, const BufferType type, - std::optional> on_update) { - m_buffers.emplace_back(std::make_shared(m_device, std::move(name), type, std::move(on_update))); +std::weak_ptr RenderGraph::add_index_buffer(std::string name, std::optional> on_update) { + m_buffers.emplace_back( + std::make_shared(m_device, std::move(name), VK_INDEX_TYPE_UINT32, std::move(on_update))); + return m_buffers.back(); +} + +std::weak_ptr RenderGraph::add_uniform_buffer(std::string name, + std::optional> on_update) { + m_buffers.emplace_back(std::make_shared(m_device, std::move(name), std::move(on_update))); + return m_buffers.back(); +} + +std::weak_ptr +RenderGraph::add_vertex_buffer(std::string name, std::vector vert_input_attr_descs, + std::optional> on_update) { + m_buffers.emplace_back(std::make_shared(m_device, std::move(name), std::move(vert_input_attr_descs))); return m_buffers.back(); } @@ -54,7 +67,9 @@ void RenderGraph::compile() { void RenderGraph::create_buffers() { for (const auto &buffer : m_buffers) { // Call the update lambda of the buffer - std::invoke(buffer->m_on_update.value()); + if (buffer->m_on_update) { + std::invoke(buffer->m_on_update.value()); + } // Create the buffer buffer->create_buffer(); } @@ -127,8 +142,7 @@ void RenderGraph::determine_pass_order() { void RenderGraph::record_command_buffer_for_pass(const CommandBuffer &cmd_buf, const GraphicsPass &pass, const bool is_first_pass, const bool is_last_pass, const std::uint32_t img_index) { - // Start a new debug label for this graphics pass - // These debug labels are visible in RenderDoc + // Start a new debug label for this graphics pass (debug labels are visible in graphics debuggers like RenderDoc) // TODO: Generate color gradient depending on the number of passes? (Interpolate e.g. in 12 steps for 12 passes) cmd_buf.begin_debug_label_region(pass.m_name, {1.0f, 0.0f, 0.0f, 1.0f}); @@ -206,23 +220,25 @@ void RenderGraph::record_command_buffer_for_pass(const CommandBuffer &cmd_buf, c } #endif - // Call the user-defined on_record lambda of the graphics pass + // Call the user-defined on_record function of the graphics pass // This is where the real rendering of the pass is happening - // Note that it is the responsibility of the programmer to bind the pipeline in the on_record lambda! + // NOTE: It is the responsibility of the programmer to bind the correct pipeline in the on_record lambda! This is + // not part of the rendergraph abstraction because during rendering binding pipelines can be arbitrarily complex. + // TODO: Implement binding only one (or several) pipelines at beginning of rendering, so there is partial + // abstraction std::invoke(pass.m_on_record, cmd_buf); // End dynamic rendering cmd_buf.end_rendering(); - // If this is the last pass, transition the image layout the back buffer for presenting + // If this is the last graphics pass, transition the image layout of the back buffer for presenting if (is_last_pass) { cmd_buf.change_image_layout(m_swapchain.image(img_index), // VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, // VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); } - // End the debug label for this graphics pass - // These debug labels are visible in RenderDoc + // End the debug label for this graphics pass (debug labels are visible in graphics debuggers like RenderDoc) cmd_buf.end_debug_label_region(); } @@ -230,9 +246,9 @@ void RenderGraph::record_command_buffers(const CommandBuffer &cmd_buf, const std // TODO: Support multiple passes per command buffer, not just recording everything into one command buffer // TODO: Record command buffers in parallel - // Loop through all passes and record the command buffer for that pass + // Loop through all graphics passes and record their command buffer for (std::size_t pass_index = 0; pass_index < m_graphics_passes.size(); pass_index++) { - // It's important to know if it's the first or last pass because of image layout transition for back buffer + // This is important to know because of image layout transitions for back buffer for example const bool is_first_pass = (pass_index == 0); const bool is_last_pass = (pass_index == m_graphics_passes.size()); record_command_buffer_for_pass(cmd_buf, *m_graphics_passes[pass_index], is_first_pass, is_last_pass, img_index); @@ -240,7 +256,6 @@ void RenderGraph::record_command_buffers(const CommandBuffer &cmd_buf, const std } void RenderGraph::render() { - // TODO: Can't we turn this into an m_device.execute(); ? const auto &cmd_buf = m_device.request_command_buffer("RenderGraph::render"); record_command_buffers(cmd_buf, m_swapchain.acquire_next_image_index()); const std::array stage_mask{VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; @@ -256,7 +271,7 @@ void RenderGraph::render() { void RenderGraph::update_buffers() { for (const auto &buffer : m_buffers) { - // Call the update lambda of the buffer, if specified + // Call the update lambda of the buffer (if specified) if (buffer->m_on_update) { std::invoke(buffer->m_on_update.value()); } @@ -266,8 +281,8 @@ void RenderGraph::update_buffers() { void RenderGraph::update_textures() { for (const auto &texture : m_textures) { + // Call the update lambda of the texture (if specified) if (texture->m_on_update) { - // Call the update lambda of the texture std::invoke(texture->m_on_update.value()); } } @@ -283,8 +298,9 @@ void RenderGraph::update_descriptor_sets() { } void RenderGraph::update_push_constant_ranges() { + // TODO: Update push constant ranges in parallel + // Loop through all push constant ranges and call their update function for (const auto &push_constant : m_push_constant_ranges) { - // Call the update lambda of the push constant range std::invoke(push_constant->m_on_update); } } diff --git a/src/vulkan-renderer/renderers/imgui.cpp b/src/vulkan-renderer/renderers/imgui.cpp index 403d0b8c9..0d81de1af 100644 --- a/src/vulkan-renderer/renderers/imgui.cpp +++ b/src/vulkan-renderer/renderers/imgui.cpp @@ -32,7 +32,26 @@ ImGuiRenderer::ImGuiRenderer(const wrapper::Device &device, render_graph::Render // TODO: Do we need this here? using render_graph::BufferType; - m_vertex_buffer = render_graph.add_buffer("ImGui", BufferType::VERTEX_BUFFER, [&]() { + // This is required for both vertex buffer and the graphics pipeline + const std::vector vert_input_attr_descs{{ + { + .location = 0, + .format = VK_FORMAT_R32G32_SFLOAT, + .offset = offsetof(ImDrawVert, pos), + }, + { + .location = 1, + .format = VK_FORMAT_R32G32_SFLOAT, + .offset = offsetof(ImDrawVert, uv), + }, + { + .location = 2, + .format = VK_FORMAT_R8G8B8A8_UNORM, + .offset = offsetof(ImDrawVert, col), + }, + }}; + + m_vertex_buffer = render_graph.add_vertex_buffer("ImGui", vert_input_attr_descs, [&]() { // Update the user ImGui data m_on_update_user_data(); @@ -63,11 +82,7 @@ ImGuiRenderer::ImGuiRenderer(const wrapper::Device &device, render_graph::Render m_index_buffer.lock()->request_update(&m_index_data, sizeof(m_index_data)); }); - // Note that the index buffer is updated together with the vertex buffer to keep data consistent - // This means there is no update lambda here - - // TODO: FIX ME! - m_index_buffer = render_graph.add_buffer("ImGui", BufferType::INDEX_BUFFER, [] {}); + m_index_buffer = render_graph.add_index_buffer("ImGui"); render_graph.add_graphics_pipeline( [&](wrapper::pipelines::GraphicsPipelineBuilder &builder, const VkPipelineLayout pipeline_layout) { @@ -91,23 +106,7 @@ ImGuiRenderer::ImGuiRenderer(const wrapper::Device &device, render_graph::Render .inputRate = VK_VERTEX_INPUT_RATE_VERTEX, }, }) - .set_vertex_input_attributes({ - { - .location = 0, - .format = VK_FORMAT_R32G32_SFLOAT, - .offset = offsetof(ImDrawVert, pos), - }, - { - .location = 1, - .format = VK_FORMAT_R32G32_SFLOAT, - .offset = offsetof(ImDrawVert, uv), - }, - { - .location = 2, - .format = VK_FORMAT_R8G8B8A8_UNORM, - .offset = offsetof(ImDrawVert, col), - }, - }) + .set_vertex_input_attributes(vert_input_attr_descs) .add_shader(m_vertex_shader) .add_shader(m_fragment_shader) .set_pipeline_layout(pipeline_layout) @@ -115,14 +114,11 @@ ImGuiRenderer::ImGuiRenderer(const wrapper::Device &device, render_graph::Render return m_imgui_pipeline; }); - m_imgui_texture = render_graph.add_texture("ImGui font", // - render_graph::TextureUsage::NORMAL, // - VK_FORMAT_R8G8B8A8_UNORM, // - [&]() { - // Initialize the font texture - m_imgui_texture.lock()->request_update(m_font_texture_data, - m_font_texture_data_size); - }); + m_imgui_texture = + render_graph.add_texture("ImGui-font", render_graph::TextureUsage::NORMAL, VK_FORMAT_R8G8B8A8_UNORM, [&]() { + // Initialize the font texture + m_imgui_texture.lock()->request_update(m_font_texture_data, m_font_texture_data_size); + }); render_graph.add_graphics_pass([&](render_graph::GraphicsPassBuilder &builder) { m_imgui_pass = builder.reads_from_buffer(m_index_buffer) diff --git a/src/vulkan-renderer/wrapper/device.cpp b/src/vulkan-renderer/wrapper/device.cpp index cb601471b..44cc045d8 100644 --- a/src/vulkan-renderer/wrapper/device.cpp +++ b/src/vulkan-renderer/wrapper/device.cpp @@ -477,7 +477,7 @@ commands::CommandPool &Device::thread_graphics_pool() const { // Note that thread_graphics_pool is implicitely static! thread_local commands::CommandPool *thread_graphics_pool = nullptr; // NOLINT if (thread_graphics_pool == nullptr) { - auto cmd_pool = std::make_unique(*this, "Graphics Pool"); + auto cmd_pool = std::make_unique(*this, "Graphics"); std::scoped_lock locker(m_mutex); thread_graphics_pool = m_cmd_pools.emplace_back(std::move(cmd_pool)).get(); }