diff --git a/include/inexor/vulkan-renderer/render-graph/graphics_pass.hpp b/include/inexor/vulkan-renderer/render-graph/graphics_pass.hpp index 92e9bd13c..c827c8ebb 100644 --- a/include/inexor/vulkan-renderer/render-graph/graphics_pass.hpp +++ b/include/inexor/vulkan-renderer/render-graph/graphics_pass.hpp @@ -23,7 +23,7 @@ namespace inexor::vulkan_renderer::render_graph { // Forward declaration class RenderGraph; -// Using declaration +// Using declarations using wrapper::Swapchain; using wrapper::descriptors::DescriptorSetLayout; @@ -55,9 +55,11 @@ enum class DebugLabelColor { /// @return An array of RGBA float values to be passed into vkCmdBeginDebugUtilsLabelEXT [[nodiscard]] std::array get_debug_label_color(const DebugLabelColor color); +/// Using declaration +using OnRecordCommandBufferForPass = std::function; + /// A wrapper for graphics passes inside of rendergraph class GraphicsPass { - // friend class RenderGraph; private: @@ -65,7 +67,6 @@ class GraphicsPass { std::string m_name; /// The command buffer recording function of the graphics pass - using OnRecordCommandBufferForPass = std::function; OnRecordCommandBufferForPass m_on_record_cmd_buffer{[](auto &) {}}; /// The descriptor set layout of the pass (this will be created by rendergraph) @@ -76,12 +77,15 @@ class GraphicsPass { /// The color of the debug label region (visible in graphics debuggers like RenderDoc) std::array m_debug_label_color; + /// The extent + VkExtent2D m_extent{0, 0}; /// The graphics passes this pass reads from std::vector> m_graphics_pass_reads; + /// The texture attachments of this pass (unified means color, depth, stencil attachment or a swapchain) - std::vector> m_write_attachments{}; + std::vector, std::optional>> m_write_attachments{}; /// The swapchains this graphics pass writes to - std::vector> m_write_swapchains{}; + std::vector, std::optional>> m_write_swapchains{}; // All the data below will be filled and used by rendergraph only @@ -90,11 +94,18 @@ class GraphicsPass { /// which is why we store them as members here. VkRenderingInfo m_rendering_info{}; /// The color attachments inside of m_rendering_info - std::vector m_color_attachment_infos{}; + std::vector m_color_attachments{}; + /// Does this graphics pass have any depth attachment? + bool m_has_depth_attachment{false}; /// The depth attachment inside of m_rendering_info - VkRenderingAttachmentInfo m_depth_attachment_info{}; + VkRenderingAttachmentInfo m_depth_attachment{}; + /// Does this graphics pass have any stencil attachment? + bool m_has_stencil_attachment{false}; /// The stencil attachment inside of m_rendering_info - VkRenderingAttachmentInfo m_stencil_attachment_info{}; + VkRenderingAttachmentInfo m_stencil_attachment{}; + + /// Reset the rendering info + void reset_rendering_info(); public: /// Default constructor @@ -107,8 +118,8 @@ class GraphicsPass { GraphicsPass(std::string name, OnRecordCommandBufferForPass on_record_cmd_buffer, std::vector> graphics_pass_reads, - std::vector> write_attachments, - std::vector> write_swapchains, + std::vector, std::optional>> write_attachments, + std::vector, std::optional>> write_swapchains, DebugLabelColor pass_debug_label_color); GraphicsPass(const GraphicsPass &) = delete; 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 74765acea..7a3938084 100644 --- a/include/inexor/vulkan-renderer/render-graph/graphics_pass_builder.hpp +++ b/include/inexor/vulkan-renderer/render-graph/graphics_pass_builder.hpp @@ -25,13 +25,13 @@ using wrapper::commands::CommandBuffer; class GraphicsPassBuilder { private: /// Add members which describe data related to graphics passes here - std::function m_on_record_cmd_buffer{}; + OnRecordCommandBufferForPass m_on_record_cmd_buffer{}; /// The graphics passes which are read by this graphics pass std::vector> m_graphics_pass_reads{}; /// The texture resources this graphics pass writes to - std::vector> m_write_attachments{}; + std::vector, std::optional>> m_write_attachments{}; /// The swapchain this graphics pass writes to - std::vector> m_write_swapchains{}; + std::vector, std::optional>> m_write_swapchains{}; /// Reset the data of the graphics pass builder void reset(); @@ -68,7 +68,7 @@ class GraphicsPassBuilder { /// Set the function which will be called when the command buffer for rendering of the pass is being recorded /// @param on_record_cmd_buffer The command buffer recording function /// @return A const reference to the this pointer (allowing method calls to be chained) - [[nodiscard]] GraphicsPassBuilder &set_on_record(std::function on_record_cmd_buffer); + [[nodiscard]] GraphicsPassBuilder &set_on_record(OnRecordCommandBufferForPass on_record_cmd_buffer); /// Specify that this graphics pass writes to an attachment /// @param attachment The attachment @@ -81,8 +81,10 @@ class GraphicsPassBuilder { /// Specify that this graphics pass writes to a swapchain /// @param swapchain The swapchain this pass writes to + /// @param clear_value The optional clear value of the swapchain (``std::nullopt`` by default) /// @return A const reference to the this pointer (allowing method calls to be chained) - [[nodiscard]] GraphicsPassBuilder &writes_to(std::weak_ptr swapchain); + [[nodiscard]] GraphicsPassBuilder &writes_to(std::weak_ptr swapchain, + std::optional clear_value = std::nullopt); }; } // namespace inexor::vulkan_renderer::render_graph diff --git a/include/inexor/vulkan-renderer/render-graph/render_graph.hpp b/include/inexor/vulkan-renderer/render-graph/render_graph.hpp index cc83c9238..211273dc8 100644 --- a/include/inexor/vulkan-renderer/render-graph/render_graph.hpp +++ b/include/inexor/vulkan-renderer/render-graph/render_graph.hpp @@ -96,7 +96,7 @@ class RenderGraph { /// ----------------------------------------------------------------------------------------------------------------- // TODO - std::vector> m_swapchains; + std::vector m_swapchains_imgs_available; /// ----------------------------------------------------------------------------------------------------------------- /// GRAPHICS PIPELINES @@ -239,9 +239,8 @@ class RenderGraph { /// @exception std::logic_error The rendergraph is not acyclic! void check_for_cycles(); - /// Create the buffers of every buffer resource in the rendergraph - /// @param cmd_buf The command buffer to record into - void create_buffers(); + /// Collect all image available semaphores of all swapchains which are used into one std::vector + void collect_swapchain_image_available_semaphores(); /// Create the descriptor set layouts void create_descriptor_set_layouts(); @@ -281,8 +280,9 @@ class RenderGraph { /// @note If a uniform buffer has been updated, an update of the associated descriptor set will be performed void update_buffers(); - /// Update the descriptor sets - void update_descriptor_sets(); + /// Update the write descriptor sets + /// @note This function must only be called once during rendergraph compilation, not for every frame! + void update_write_descriptor_sets(); /// Update dynamic textures void update_textures(); diff --git a/include/inexor/vulkan-renderer/render-graph/texture.hpp b/include/inexor/vulkan-renderer/render-graph/texture.hpp index 1524dc02c..b81b0b4f8 100644 --- a/include/inexor/vulkan-renderer/render-graph/texture.hpp +++ b/include/inexor/vulkan-renderer/render-graph/texture.hpp @@ -30,8 +30,9 @@ namespace inexor::vulkan_renderer::render_graph { /// NOTE: All usages which are not TextureUsage::NORMAL are for internal usage inside of rendergraph only enum class TextureUsage { NORMAL, - BACK_BUFFER, - DEPTH_STENCIL_BUFFER, + COLOR_ATTACHMENT, + DEPTH_ATTACHMENT, + STENCIL_ATTACHMENT, }; // Forward declaration diff --git a/include/inexor/vulkan-renderer/wrapper/commands/command_buffer.hpp b/include/inexor/vulkan-renderer/wrapper/commands/command_buffer.hpp index b13d2811b..b6b3b6884 100644 --- a/include/inexor/vulkan-renderer/wrapper/commands/command_buffer.hpp +++ b/include/inexor/vulkan-renderer/wrapper/commands/command_buffer.hpp @@ -62,7 +62,10 @@ class CommandBuffer { const CommandBuffer &end_command_buffer() const; // NOLINT /// Call vkQueueSubmit - void submit_and_wait() const; + /// @param wait_semaphores The semaphores to wait for + /// @param signal_semaphores The semaphores to signal + void submit_and_wait(std::span wait_semaphores = {}, + std::span signal_semaphores = {}) const; public: /// Default constructor @@ -405,7 +408,8 @@ class CommandBuffer { const T &data, // NOLINT const VkShaderStageFlags stage, const VkDeviceSize offset = 0) const { - return push_constants(pipeline.lock()->m_pipeline_layout, stage, sizeof(data), &data, offset); + return push_constants(pipeline.lock()->m_pipeline_layout->m_pipeline_layout, stage, sizeof(data), &data, + offset); } /// Set the name of a command buffer during recording of a specific command in the current command buffer diff --git a/include/inexor/vulkan-renderer/wrapper/device.hpp b/include/inexor/vulkan-renderer/wrapper/device.hpp index 3e063afd6..39caac7f8 100644 --- a/include/inexor/vulkan-renderer/wrapper/device.hpp +++ b/include/inexor/vulkan-renderer/wrapper/device.hpp @@ -150,9 +150,13 @@ class Device { /// the given command pool, begins the command buffer, executes the lambda, ends recording the command buffer, /// submits it and waits for it. /// @param name The internal debug name of the command buffer (must not be empty) - /// @param cmd_lambda The command lambda to execute + /// @param cmd_lambda The command buffer recording function to execute + /// @param wait_semaphores + /// @param signal_semaphores void execute(const std::string &name, - const std::function &cmd_lambda) const; + const std::function &cmd_buf_recording_func, + std::span wait_semaphores = {}, + std::span signal_semaphores = {}) const; /// Find a queue family index that suits a specific criteria /// @param criteria_lambda The lambda to sort out unsuitable queue families diff --git a/include/inexor/vulkan-renderer/wrapper/pipelines/pipeline.hpp b/include/inexor/vulkan-renderer/wrapper/pipelines/pipeline.hpp index c37f0f8f9..00cb36442 100644 --- a/include/inexor/vulkan-renderer/wrapper/pipelines/pipeline.hpp +++ b/include/inexor/vulkan-renderer/wrapper/pipelines/pipeline.hpp @@ -1,5 +1,7 @@ #pragma once +#include "inexor/vulkan-renderer/wrapper/pipelines/pipeline_layout.hpp" + #include #include @@ -31,10 +33,8 @@ class GraphicsPipeline { private: const Device &m_device; - std::vector m_descriptor_set_layouts; - std::vector m_push_constant_ranges; + std::unique_ptr m_pipeline_layout{nullptr}; VkPipeline m_pipeline{VK_NULL_HANDLE}; - VkPipelineLayout m_pipeline_layout{VK_NULL_HANDLE}; std::string m_name; public: diff --git a/include/inexor/vulkan-renderer/wrapper/pipelines/pipeline_builder.hpp b/include/inexor/vulkan-renderer/wrapper/pipelines/pipeline_builder.hpp index 43f6668c7..4ade8ff70 100644 --- a/include/inexor/vulkan-renderer/wrapper/pipelines/pipeline_builder.hpp +++ b/include/inexor/vulkan-renderer/wrapper/pipelines/pipeline_builder.hpp @@ -27,6 +27,10 @@ class RenderGraph; namespace inexor::vulkan_renderer::wrapper::pipelines { +// Forward declaration +class render_graph::RenderGraph; +class wrapper::Shader; + // TODO: ComputePipelineBuilder /// Builder class for VkPipelineCreateInfo for graphics pipelines which use dynamic rendering @@ -34,9 +38,9 @@ namespace inexor::vulkan_renderer::wrapper::pipelines { /// This means if you forget to specify viewport for example, creation of the graphics pipeline will fail. /// It is the reponsibility of the programmer to use validation layers to check for problems. class GraphicsPipelineBuilder { -private: - friend class render_graph::RenderGraph; + friend class RenderGraph; +private: /// The device wrapper reference const Device &m_device; @@ -45,61 +49,60 @@ class GraphicsPipelineBuilder { // This method is also called after the graphics pipeline has been created, // allowing one instance of GraphicsPipelineBuilder to be reused - /// This is used for dynamic rendering - VkFormat m_depth_attachment_format; - VkFormat m_stencil_attachment_format; - std::vector m_color_attachments; - - VkPipelineRenderingCreateInfo m_pipeline_rendering_ci; - - std::vector m_vertex_input_binding_descriptions; - std::vector m_vertex_input_attribute_descriptions; + // With the builder we can either call add_shader or set_shaders + std::vector m_shader_stages{}; + std::vector m_vertex_input_binding_descriptions{}; + std::vector m_vertex_input_attribute_descriptions{}; // With the builder we can fill vertex binding descriptions and vertex attribute descriptions in here - VkPipelineVertexInputStateCreateInfo m_vertex_input_sci; + VkPipelineVertexInputStateCreateInfo m_vertex_input_sci{}; // With the builder we can set topology in here - VkPipelineInputAssemblyStateCreateInfo m_input_assembly_sci; + VkPipelineInputAssemblyStateCreateInfo m_input_assembly_sci{}; // With the builder we can set the patch control point count in here - VkPipelineTessellationStateCreateInfo m_tesselation_sci; + VkPipelineTessellationStateCreateInfo m_tesselation_sci{}; - std::vector m_viewports; - std::vector m_scissors; + std::vector m_viewports{}; + std::vector m_scissors{}; // With the builder we can set viewport(s) and scissor(s) in here - VkPipelineViewportStateCreateInfo m_viewport_sci; + VkPipelineViewportStateCreateInfo m_viewport_sci{}; // With the builder we can set polygon mode, cull mode, front face, and line width // TODO: Implement methods to enable depth bias and for setting the depth bias parameters - VkPipelineRasterizationStateCreateInfo m_rasterization_sci; - - // With the builder we can set rasterization samples and min sample shading - // TODO: Expose more multisampling parameters if desired - VkPipelineMultisampleStateCreateInfo m_multisample_sci; + VkPipelineRasterizationStateCreateInfo m_rasterization_sci{}; // With the builder we can't set individial fields of this struct, // since it's easier to specify an entire VkPipelineDepthStencilStateCreateInfo struct to the builder instead - VkPipelineDepthStencilStateCreateInfo m_depth_stencil_sci; + VkPipelineDepthStencilStateCreateInfo m_depth_stencil_sci{}; + + /// This is used for dynamic rendering + VkFormat m_depth_attachment_format{}; + VkFormat m_stencil_attachment_format{}; + std::vector m_color_attachments{}; + + VkPipelineRenderingCreateInfo m_pipeline_rendering_ci{}; + + // With the builder we can set rasterization samples and min sample shading + // TODO: Expose more multisampling parameters if desired + VkPipelineMultisampleStateCreateInfo m_multisample_sci{}; // With the builder we can't set individial fields of this struct, // since it's easier to specify an entire VkPipelineColorBlendStateCreateInfo struct to the builder instead - VkPipelineColorBlendStateCreateInfo m_color_blend_sci; + VkPipelineColorBlendStateCreateInfo m_color_blend_sci{}; - std::vector m_dynamic_states; + std::vector m_dynamic_states{}; // This will be filled in the build() method - VkPipelineDynamicStateCreateInfo m_dynamic_states_sci; + VkPipelineDynamicStateCreateInfo m_dynamic_states_sci{}; /// The layout of the graphics pipeline - VkPipelineLayout m_pipeline_layout; - - // With the builder we can either call add_shader or set_shaders - std::vector m_shader_stages; + VkPipelineLayout m_pipeline_layout{VK_NULL_HANDLE}; // With the builder we can either call add_color_blend_attachment or set_color_blend_attachments - std::vector m_color_blend_attachment_states; + std::vector m_color_blend_attachment_states{}; /// The push constant ranges of the graphics pass - std::vector m_push_constant_ranges; + std::vector m_push_constant_ranges{}; VkDescriptorSetLayout m_descriptor_set_layout{VK_NULL_HANDLE}; @@ -119,66 +122,33 @@ class GraphicsPipelineBuilder { GraphicsPipelineBuilder &operator=(const GraphicsPipelineBuilder &) = delete; GraphicsPipelineBuilder &operator=(GraphicsPipelineBuilder &&) = delete; - /// Add a shader to the graphics pipeline - /// @param shader The shader - /// @return A reference to the dereferenced this pointer (allows method calls to be chained) - [[nodiscard]] auto &uses_shader(std::weak_ptr shader) { - m_shader_stages.push_back(make_info({ - .stage = shader.lock()->m_shader_stage, - .module = shader.lock()->m_shader_module, - .pName = "main", - - })); - return *this; - } - /// Adds a color attachment /// @param format The format of the color attachment /// @return A reference to the dereferenced this pointer (allows method calls to be chained) - [[nodiscard]] auto &add_color_attachment(const VkFormat format) { - m_color_attachments.push_back(format); - return *this; - } + [[nodiscard]] GraphicsPipelineBuilder &add_color_attachment(VkFormat format); /// Add a color blend attachment /// @param attachment The color blend attachment /// @return A reference to the dereferenced this pointer (allows method calls to be chained) - [[nodiscard]] auto &add_color_blend_attachment(const VkPipelineColorBlendAttachmentState &attachment) { - m_color_blend_attachment_states.push_back(attachment); - return *this; - } + [[nodiscard]] GraphicsPipelineBuilder & + add_color_blend_attachment(const VkPipelineColorBlendAttachmentState &attachment); /// Add the default color blend attachment /// @return A reference to the dereferenced this pointer (allows method calls to be chained) - [[nodiscard]] auto &add_default_color_blend_attachment() { - return add_color_blend_attachment({ - .blendEnable = VK_TRUE, - .srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA, - .dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, - .colorBlendOp = VK_BLEND_OP_ADD, - .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE, - .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO, - .alphaBlendOp = VK_BLEND_OP_ADD, - .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | - VK_COLOR_COMPONENT_A_BIT, - }); - } + [[nodiscard]] GraphicsPipelineBuilder &add_default_color_blend_attachment(); /// Add a push constant range to the graphics pass /// @param shader_stage The shader stage for the push constant range /// @param size The size of the push constant /// @param offset The offset in the push constant range /// @return A const reference to the this pointer (allowing method calls to be chained) - [[nodiscard]] auto &add_push_constant_range(const VkShaderStageFlags shader_stage, - const std::uint32_t size, - const std::uint32_t offset = 0) { - m_push_constant_ranges.emplace_back(VkPushConstantRange{ - .stageFlags = shader_stage, - .offset = offset, - .size = size, - }); - return *this; - } + [[nodiscard]] GraphicsPipelineBuilder & + add_push_constant_range(VkShaderStageFlags shader_stage, std::uint32_t size, std::uint32_t offset = 0); + + /// Add a shader to the graphics pipeline + /// @param shader The shader + /// @return A reference to the dereferenced this pointer (allows method calls to be chained) + [[nodiscard]] GraphicsPipelineBuilder &add_shader(std::weak_ptr shader); /// Build the graphics pipeline with specified pipeline create flags /// @param name The debug name of the graphics pipeline @@ -188,75 +158,47 @@ class GraphicsPipelineBuilder { /// Set the color blend manually /// @param color_blend The color blend /// @return A reference to the dereferenced this pointer (allows method calls to be chained) - [[nodiscard]] auto &set_color_blend(const VkPipelineColorBlendStateCreateInfo &color_blend) { - m_color_blend_sci = color_blend; - return *this; - } + [[nodiscard]] GraphicsPipelineBuilder &set_color_blend(const VkPipelineColorBlendStateCreateInfo &color_blend); /// Set all color blend attachments manually /// @note You should prefer to use ``add_color_blend_attachment`` instead /// @param attachments The color blend attachments /// @return A reference to the dereferenced this pointer (allows method calls to be chained) - [[nodiscard]] auto & - set_color_blend_attachments(const std::vector &attachments) { - m_color_blend_attachment_states = attachments; - return *this; - } + [[nodiscard]] GraphicsPipelineBuilder & + set_color_blend_attachments(const std::vector &attachments); /// Enable or disable culling /// @warning Disabling culling will have a significant performance impact /// @param culling_enabled ``true`` if culling is enabled /// @return A reference to the dereferenced this pointer (allows method calls to be chained) - [[nodiscard]] auto &set_culling_mode(const VkBool32 culling_enabled) { - if (culling_enabled == VK_FALSE) { - spdlog::warn("Culling is disabled, which could have negative effects on the performance!"); - } - m_rasterization_sci.cullMode = culling_enabled == VK_TRUE ? VK_CULL_MODE_BACK_BIT : VK_CULL_MODE_NONE; - return *this; - } + [[nodiscard]] GraphicsPipelineBuilder &set_culling_mode(VkBool32 culling_enabled); /// Set the deptch attachment format /// @param format The format of the depth attachment /// @return A const reference to the this pointer (allowing method calls to be chained) - [[nodiscard]] auto &set_depth_attachment_format(const VkFormat format) { - m_depth_attachment_format = format; - return *this; - } + [[nodiscard]] GraphicsPipelineBuilder &set_depth_attachment_format(VkFormat format); + + /// Set the descriptor set layout + /// @param descriptor_set_layout The descriptor set layout + /// @return A reference to the dereferenced this pointer (allows method calls to be chained) + [[nodiscard]] GraphicsPipelineBuilder &set_descriptor_set_layout(VkDescriptorSetLayout descriptor_set_layout); /// Set the depth stencil /// @warning Disabling culling can have performance impacts! /// @param depth_stencil The depth stencil /// @return A reference to the dereferenced this pointer (allows method calls to be chained) - [[nodiscard]] auto &set_depth_stencil(const VkPipelineDepthStencilStateCreateInfo &depth_stencil) { - m_depth_stencil_sci = depth_stencil; - return *this; - } - - /// Set the stencil attachment format - /// @param format The format of the stencil attachment - /// @return A const reference to the this pointer (allowing method calls to be chained) - [[nodiscard]] auto &set_stencil_attachment_format(const VkFormat format) { - m_stencil_attachment_format = format; - return *this; - } - - /// Set the descriptor set layout - /// @param descriptor_set_layout The descriptor set layout - /// @return A reference to the dereferenced this pointer (allows method calls to be chained) - [[nodiscard]] auto &set_descriptor_set_layout(const VkDescriptorSetLayout descriptor_set_layout) { - assert(descriptor_set_layout); - m_descriptor_set_layout = descriptor_set_layout; - return *this; - } + [[nodiscard]] GraphicsPipelineBuilder & + set_depth_stencil(const VkPipelineDepthStencilStateCreateInfo &depth_stencil); /// Set the dynamic states /// @param dynamic_states The dynamic states /// @return A reference to the dereferenced this pointer (allows method calls to be chained) - [[nodiscard]] auto &set_dynamic_states(const std::vector &dynamic_states) { - assert(!dynamic_states.empty()); - m_dynamic_states = dynamic_states; - return *this; - } + [[nodiscard]] GraphicsPipelineBuilder &set_dynamic_states(const std::vector &dynamic_states); + + /// Set the stencil attachment format + /// @param format The format of the stencil attachment + /// @return A const reference to the this pointer (allowing method calls to be chained) + [[nodiscard]] GraphicsPipelineBuilder &set_stencil_attachment_format(VkFormat format); /// Set the input assembly state create info /// @note If you just want to set the triangle topology, call ``set_triangle_topology`` instead, because this is the @@ -269,158 +211,77 @@ class GraphicsPipelineBuilder { /// Set the line width of rasterization /// @param line_width The line width used in rasterization /// @return A reference to the dereferenced this pointer (allows method calls to be chained) - [[nodiscard]] auto &set_line_width(const float width) { - m_rasterization_sci.lineWidth = width; - return *this; - } + [[nodiscard]] GraphicsPipelineBuilder &set_line_width(float width); /// Set the most important MSAA settings /// @param sample_count The number of samples used in rasterization /// @param min_sample_shading A minimum fraction of sample shading /// @return A reference to the dereferenced this pointer (allows method calls to be chained) - [[nodiscard]] auto &set_multisampling(const VkSampleCountFlagBits sample_count, - const std::optional min_sample_shading) { - m_multisample_sci.rasterizationSamples = sample_count; - if (min_sample_shading) { - m_multisample_sci.minSampleShading = min_sample_shading.value(); - } - return *this; - } + [[nodiscard]] GraphicsPipelineBuilder &set_multisampling(VkSampleCountFlagBits sample_count, + std::optional min_sample_shading); /// Store the pipeline layout /// @param layout The pipeline layout /// @return A reference to the dereferenced this pointer (allows method calls to be chained) - [[nodiscard]] auto &set_pipeline_layout(const VkPipelineLayout layout) { - assert(layout); - m_pipeline_layout = layout; - return *this; - } + [[nodiscard]] GraphicsPipelineBuilder &set_pipeline_layout(VkPipelineLayout layout); /// Set the triangle topology /// @param topology the primitive topology /// @return A reference to the dereferenced this pointer (allows method calls to be chained) - [[nodiscard]] auto &set_primitive_topology(const VkPrimitiveTopology topology) { - m_input_assembly_sci.topology = topology; - return *this; - } + [[nodiscard]] GraphicsPipelineBuilder &set_primitive_topology(VkPrimitiveTopology topology); /// Set the rasterization state of the graphics pipeline manually /// @param rasterization The rasterization state /// @return A reference to the dereferenced this pointer (allows method calls to be chained) - [[nodiscard]] auto &set_rasterization(const VkPipelineRasterizationStateCreateInfo &rasterization) { - m_rasterization_sci = rasterization; - return *this; - } + [[nodiscard]] GraphicsPipelineBuilder & + set_rasterization(const VkPipelineRasterizationStateCreateInfo &rasterization); /// Set the scissor data in VkPipelineViewportStateCreateInfo /// There is another method called set_scissors in case multiple scissors will be used /// @param scissors The scissors in in VkPipelineViewportStateCreateInfo /// @return A reference to the dereferenced this pointer (allows method calls to be chained) - [[nodiscard]] auto &set_scissor(const VkRect2D &scissor) { - m_scissors = {scissor}; - m_viewport_sci.scissorCount = 1; - m_viewport_sci.pScissors = m_scissors.data(); - return *this; - } + [[nodiscard]] GraphicsPipelineBuilder &set_scissor(const VkRect2D &scissor); /// Set the scissor data in VkPipelineViewportStateCreateInfo (convert VkExtent2D to VkRect2D) /// @param extent The extent of the scissor /// @return A reference to the dereferenced this pointer (allows method calls to be chained) - [[nodiscard]] auto &set_scissor(const VkExtent2D &extent) { - return set_scissor({ - // Convert VkExtent2D to VkRect2D - .extent = extent, - }); - } - - /// Set the viewport data in VkPipelineViewportStateCreateInfo - /// There is another method called set_scissors in case multiple scissors will be used - /// @param scissor The scissor in in VkPipelineViewportStateCreateInfo - /// @return A reference to the dereferenced this pointer (allows method calls to be chained) - [[nodiscard]] auto &set_scissors(const std::vector &scissors) { - assert(!scissors.empty()); - m_scissors = scissors; - m_viewport_sci.scissorCount = static_cast(scissors.size()); - m_viewport_sci.pScissors = scissors.data(); - return *this; - } - - /// Set the shader stage - /// @param shader_stages The shader stages - /// @return A reference to the dereferenced this pointer (allows method calls to be chained) - [[nodiscard]] GraphicsPipelineBuilder &set_shaders(const std::vector &shaders); + [[nodiscard]] GraphicsPipelineBuilder &set_scissor(const VkExtent2D &extent); /// Set the tesselation control point count /// @note This is not used in the code so far, because we are not using tesselation /// @param control_point_count The tesselation control point count /// @return A reference to the dereferenced this pointer (allows method calls to be chained) - [[nodiscard]] auto &set_tesselation_control_point_count(const std::uint32_t control_point_count) { - m_tesselation_sci.patchControlPoints = control_point_count; - return *this; - } + [[nodiscard]] GraphicsPipelineBuilder &set_tesselation_control_point_count(std::uint32_t control_point_count); /// Set the vertex input attribute descriptions manually /// @note As of C++23, there is no mechanism to do so called reflection in C++, meaning we can't get any information /// about the members of a struct, which would allow us to determine vertex input attributes automatically. /// @param descriptions The vertex input attribute descriptions /// @return A reference to the dereferenced this pointer (allows method calls to be chained) - [[nodiscard]] auto & - set_vertex_input_attributes(const std::vector &descriptions) { - assert(!descriptions.empty()); - m_vertex_input_attribute_descriptions = descriptions; - return *this; - } + [[nodiscard]] GraphicsPipelineBuilder & + set_vertex_input_attributes(const std::vector &descriptions); /// Set the vertex input binding descriptions manually /// @param descriptions The vertex input binding descriptions /// @return A reference to the dereferenced this pointer (allows method calls to be chained) - [[nodiscard]] auto &set_vertex_input_bindings(const std::vector &descriptions) { - assert(!descriptions.empty()); - m_vertex_input_binding_descriptions = descriptions; - return *this; - } + [[nodiscard]] GraphicsPipelineBuilder & + set_vertex_input_bindings(const std::vector &descriptions); /// Set the viewport in VkPipelineViewportStateCreateInfo /// There is another method called set_viewports in case multiple viewports will be used /// @param viewport The viewport in VkPipelineViewportStateCreateInfo /// @return A reference to the dereferenced this pointer (allows method calls to be chained) - [[nodiscard]] auto &set_viewport(const VkViewport &viewport) { - m_viewports = {viewport}; - m_viewport_sci.viewportCount = 1; - m_viewport_sci.pViewports = m_viewports.data(); - return *this; - } + [[nodiscard]] GraphicsPipelineBuilder &set_viewport(const VkViewport &viewport); /// Set the viewport in VkPipelineViewportStateCreateInfo (convert VkExtent2D to VkViewport) /// @param extent The extent of the viewport /// @return A reference to the dereferenced this pointer (allows method calls to be chained) - [[nodiscard]] auto &set_viewport(const VkExtent2D &extent) { - return set_viewport({ - // Convert VkExtent2D to VkViewport - .width = static_cast(extent.width), - .height = static_cast(extent.height), - .maxDepth = 1.0f, - }); - } - - /// Set the viewport in VkPipelineViewportStateCreateInfo - /// @param viewports The viewports in VkPipelineViewportStateCreateInfo - /// @return A reference to the dereferenced this pointer (allows method calls to be chained) - [[nodiscard]] auto &set_viewports(const std::vector &viewports) { - assert(!viewports.empty()); - m_viewports = viewports; - m_viewport_sci.viewportCount = static_cast(m_viewports.size()); - m_viewport_sci.pViewports = m_viewports.data(); - return *this; - } + [[nodiscard]] GraphicsPipelineBuilder &set_viewport(const VkExtent2D &extent); /// Set the wireframe mode /// @param wireframe ``true`` if wireframe is enabled /// @return A reference to the dereferenced this pointer (allows method calls to be chained) - [[nodiscard]] auto &set_wireframe(const VkBool32 wireframe) { - m_rasterization_sci.polygonMode = (wireframe == VK_TRUE) ? VK_POLYGON_MODE_LINE : VK_POLYGON_MODE_FILL; - return *this; - } + [[nodiscard]] GraphicsPipelineBuilder &set_wireframe(VkBool32 wireframe); }; } // namespace inexor::vulkan_renderer::wrapper::pipelines diff --git a/include/inexor/vulkan-renderer/wrapper/pipelines/pipeline_layout.hpp b/include/inexor/vulkan-renderer/wrapper/pipelines/pipeline_layout.hpp index f5b0b8b7c..8192c14f1 100644 --- a/include/inexor/vulkan-renderer/wrapper/pipelines/pipeline_layout.hpp +++ b/include/inexor/vulkan-renderer/wrapper/pipelines/pipeline_layout.hpp @@ -9,35 +9,45 @@ namespace inexor::vulkan_renderer::render_graph { class RenderGraph; } // namespace inexor::vulkan_renderer::render_graph +namespace inexor::vulkan_renderer::wrapper::commands { +// Forward declaration +class CommandBuffer; +} // namespace inexor::vulkan_renderer::wrapper::commands + namespace inexor::vulkan_renderer::wrapper::pipelines { +// Forward declaration +class GraphicsPipeline; + // Using declaration +using commands::CommandBuffer; using render_graph::RenderGraph; /// RAII wrapper class for VkPipelineLayout class PipelineLayout { -private: - // Rendergraph needs to read from m_pipeline_layout - friend RenderGraph; + friend class RenderGraph; + friend class GraphicsPipeline; + friend class CommandBuffer; +private: const Device &m_device; std::string m_name; // There is no get method for this because only rendergraph needs to access it through friend class VkPipelineLayout m_pipeline_layout{VK_NULL_HANDLE}; +public: /// Call vkCreatePipelineLayout /// @note The constructor is private because only friend class RenderGraph needs access to it /// @param device The device wrapper - /// @param desc_set_layouts The descriptor set layouts of the pipeline layout - /// @param push_constant_ranges The push constant ranges of the pipeline layout /// @param name The name of the pipeline layout + /// @param descriptor_set_layouts The descriptor set layouts of the pipeline layout + /// @param push_constant_ranges The push constant ranges of the pipeline layout PipelineLayout(const Device &device, - std::span desc_set_layouts, - std::span push_constant_ranges, - std::string name); + std::string name, + std::span descriptor_set_layouts, + std::span push_constant_ranges); -public: PipelineLayout(const PipelineLayout &) = delete; PipelineLayout(PipelineLayout &&) noexcept; diff --git a/include/inexor/vulkan-renderer/wrapper/swapchain.hpp b/include/inexor/vulkan-renderer/wrapper/swapchain.hpp index 1dd3556df..215162f60 100644 --- a/include/inexor/vulkan-renderer/wrapper/swapchain.hpp +++ b/include/inexor/vulkan-renderer/wrapper/swapchain.hpp @@ -16,6 +16,7 @@ class Semaphore; namespace inexor::vulkan_renderer::render_graph { // Forward declaration class RenderGraph; +class GraphicsPass; class GraphicsPassBuilder; } // namespace inexor::vulkan_renderer::render_graph @@ -25,6 +26,7 @@ namespace inexor::vulkan_renderer::wrapper { class Device; // Using declarations +using render_graph::GraphicsPass; using render_graph::GraphicsPassBuilder; using render_graph::RenderGraph; @@ -33,9 +35,11 @@ class Swapchain { // friend class RenderGraph; friend class GraphicsPassBuilder; + friend class GraphicsPass; private: Device &m_device; + std::string m_name; VkSwapchainKHR m_swapchain{VK_NULL_HANDLE}; VkSurfaceKHR m_surface{VK_NULL_HANDLE}; std::optional m_surface_format{}; @@ -56,11 +60,17 @@ class Swapchain { public: /// Default constructor /// @param device The device wrapper + /// @param name The name of the swapchain /// @param surface The surface /// @param width The swapchain image width /// @param height The swapchain image height /// @param vsync_enabled ``true`` if vertical synchronization is enabled - Swapchain(Device &device, VkSurfaceKHR surface, std::uint32_t width, std::uint32_t height, bool vsync_enabled); + Swapchain(Device &device, + std::string name, + VkSurfaceKHR surface, + std::uint32_t width, + std::uint32_t height, + bool vsync_enabled); Swapchain(const Swapchain &) = delete; Swapchain(Swapchain &&) noexcept; @@ -122,14 +132,6 @@ class Swapchain { return m_extent; } - [[nodiscard]] const VkSemaphore *image_available_semaphore() const { - return m_img_available->semaphore(); - } - - [[nodiscard]] VkImage image(const std::size_t img_index) const { - return m_imgs.at(img_index); - } - [[nodiscard]] std::uint32_t image_count() const { return static_cast(m_imgs.size()); } @@ -138,14 +140,6 @@ class Swapchain { return m_surface_format.value().format; } - [[nodiscard]] const std::vector &image_views() const { - return m_img_views; - } - - [[nodiscard]] VkImageView image_view(const std::size_t img_index) const { - return m_img_views.at(img_index); - } - /// Call vkQueuePresentKHR with the current image index /// @exception VulkanException vkQueuePresentKHR call failed void present(); @@ -157,10 +151,6 @@ class Swapchain { /// @exception VulkanException vkCreateSwapchainKHR call failed /// @exception VulkanException vkGetPhysicalDeviceSurfaceSupportKHR call failed void setup(std::uint32_t width, std::uint32_t height, bool vsync_enabled); - - [[nodiscard]] const VkSwapchainKHR *swapchain() const { - return &m_swapchain; - } }; } // namespace inexor::vulkan_renderer::wrapper diff --git a/include/inexor/vulkan-renderer/wrapper/synchronization/fence.hpp b/include/inexor/vulkan-renderer/wrapper/synchronization/fence.hpp index 9b26bcd61..85d2e5582 100644 --- a/include/inexor/vulkan-renderer/wrapper/synchronization/fence.hpp +++ b/include/inexor/vulkan-renderer/wrapper/synchronization/fence.hpp @@ -33,15 +33,14 @@ class Fence { public: /// Default constructor - /// @param device The const reference to a device RAII wrapper instance. - /// @param name The internal debug marker name of the VkFence. - /// @param in_signaled_state True if the VkFence will be constructed in signaled state, false otherwise. - /// @warning Make sure to specify in_signaled_state correctly as needed, otherwise synchronization problems occur. + /// @param device The device wrapper + /// @param name The internal debug name of the Vulkan object + /// @param in_signaled_state ``true`` if the VkFence will be constructed in signaled state + /// @warning Make sure to specify in_signaled_state correctly as needed to avoid synchronization problems! Fence(const Device &device, const std::string &name, bool in_signaled_state); Fence(const Fence &) = delete; Fence(Fence &&) noexcept; - ~Fence(); Fence &operator=(const Fence &) = delete; @@ -52,8 +51,8 @@ class Fence { void wait(std::uint64_t timeout_limit = std::numeric_limits::max()) const; /// Call vkResetFences - /// @note This is deliberately called reset_fences and not reset because it is easy to confuse this with the reset - /// method of smart pointers, which could end up in a lot of bugs. + /// @note This is deliberately called ``reset_fences`` and not ``reset`` because ``reset`` is very easy to confuse + /// this with the reset method a smart pointer itself, which could end up in horrible bugs. void reset_fence() const; /// Call vkGetFenceStatus diff --git a/include/inexor/vulkan-renderer/wrapper/synchronization/semaphore.hpp b/include/inexor/vulkan-renderer/wrapper/synchronization/semaphore.hpp index 509c28a78..326faacb2 100644 --- a/include/inexor/vulkan-renderer/wrapper/synchronization/semaphore.hpp +++ b/include/inexor/vulkan-renderer/wrapper/synchronization/semaphore.hpp @@ -7,12 +7,26 @@ namespace inexor::vulkan_renderer::wrapper { // Forward declaration class Device; +class Swapchain; } // namespace inexor::vulkan_renderer::wrapper +namespace inexor::vulkan_renderer::render_graph { +// Forward declaration +class RenderGraph; +} // namespace inexor::vulkan_renderer::render_graph + namespace inexor::vulkan_renderer::wrapper::synchronization { +// Using declaration +using render_graph::RenderGraph; +using wrapper::Swapchain; + /// RAII wrapper class for VkSemaphore class Semaphore { + friend class RenderGraph; + friend class Swapchain; + +private: const Device &m_device; VkSemaphore m_semaphore{VK_NULL_HANDLE}; std::string m_name; @@ -28,12 +42,6 @@ class Semaphore { Semaphore &operator=(const Semaphore &) = delete; Semaphore &operator=(Semaphore &&) = delete; - - // TOOD: Either rename to get() or remove so only friend class can access? - // Probably not going to work because semaphores need to be accessed in any part of the program - [[nodiscard]] const VkSemaphore *semaphore() const { - return &m_semaphore; - } }; } // namespace inexor::vulkan_renderer::wrapper::synchronization diff --git a/src/vulkan-renderer/application.cpp b/src/vulkan-renderer/application.cpp index d4fe7cf2f..89a7a0a40 100644 --- a/src/vulkan-renderer/application.cpp +++ b/src/vulkan-renderer/application.cpp @@ -163,7 +163,7 @@ Application::Application(int argc, char **argv) { // TODO: Replace ->get() methods with private fields and friend class declaration! // TODO: API style like this: m_swapchain = m_device->create_swapchain(m_surface, m_window, m_vsync_enabled); - m_swapchain = std::make_unique(*m_device, m_surface->get(), m_window->width(), + m_swapchain = std::make_unique(*m_device, "Default", m_surface->get(), m_window->width(), m_window->height(), m_vsync_enabled); load_octree_geometry(true); @@ -395,8 +395,13 @@ void Application::render_frame() { recreate_swapchain(); return; } + + m_swapchain->acquire_next_image_index(); + m_render_graph->render(); + m_swapchain->present(); + if (auto fps_value = m_fps_counter.update()) { m_window->set_title("Inexor Vulkan API renderer demo - " + std::to_string(*fps_value) + " FPS"); spdlog::trace("FPS: {}, window size: {} x {}", *fps_value, m_window->width(), m_window->height()); @@ -421,14 +426,14 @@ void Application::setup_render_graph() { const auto swapchain_extent = m_swapchain->extent(); m_color_attachment = m_render_graph->add_texture( - "Color", render_graph::TextureUsage::BACK_BUFFER, m_swapchain->image_format(), swapchain_extent.width, + "Color", render_graph::TextureUsage::COLOR_ATTACHMENT, m_swapchain->image_format(), swapchain_extent.width, swapchain_extent.height /*, m_device->get_max_usable_sample_count() */); m_depth_attachment = m_render_graph->add_texture( - "Depth", render_graph::TextureUsage::DEPTH_STENCIL_BUFFER, VK_FORMAT_D32_SFLOAT_S8_UINT, swapchain_extent.width, + "Depth", render_graph::TextureUsage::DEPTH_ATTACHMENT, VK_FORMAT_D32_SFLOAT_S8_UINT, swapchain_extent.width, swapchain_extent.height /*, m_device->get_max_usable_sample_count()*/); - m_vertex_buffer = m_render_graph->add_buffer("Octree", render_graph::BufferType::VERTEX_BUFFER, [&]() { + m_vertex_buffer = m_render_graph->add_buffer("Octree|Vertex", render_graph::BufferType::VERTEX_BUFFER, [&]() { // If the key N was pressed once, generate a new octree if (m_input_data->was_key_pressed_once(GLFW_KEY_N)) { load_octree_geometry(false); @@ -438,14 +443,14 @@ void Application::setup_render_graph() { m_vertex_buffer.lock()->request_update(m_octree_vertices); }); - m_octree_vert = - std::make_shared(*m_device, "Octree", VK_SHADER_STAGE_VERTEX_BIT, "shaders/main.vert.spv"); - m_octree_frag = - std::make_shared(*m_device, "Octree", VK_SHADER_STAGE_FRAGMENT_BIT, "shaders/main.frag.spv"); + m_octree_vert = std::make_shared(*m_device, "Octree|Vert", VK_SHADER_STAGE_VERTEX_BIT, + "shaders/main.vert.spv"); + m_octree_frag = std::make_shared(*m_device, "Octree|Frag", VK_SHADER_STAGE_FRAGMENT_BIT, + "shaders/main.frag.spv"); // Note that the index buffer is updated together with the vertex buffer to keep data consistent // This means for m_index_buffer, on_init and on_update are defaulted to std::nullopt here! - m_index_buffer = m_render_graph->add_buffer("Octree", render_graph::BufferType::INDEX_BUFFER, [&]() { + m_index_buffer = m_render_graph->add_buffer("Octree|Index", render_graph::BufferType::INDEX_BUFFER, [&]() { // Request update of the octree index buffer m_index_buffer.lock()->request_update(m_octree_indices); }); @@ -457,7 +462,7 @@ void Application::setup_render_graph() { 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", render_graph::BufferType::UNIFORM_BUFFER, [&]() { + m_uniform_buffer = m_render_graph->add_buffer("Octree|Uniform", render_graph::BufferType::UNIFORM_BUFFER, [&]() { m_mvp_matrices.view = m_camera->view_matrix(); m_mvp_matrices.proj = m_camera->perspective_matrix(); m_mvp_matrices.proj[1][1] *= -1; @@ -506,8 +511,8 @@ void Application::setup_render_graph() { .set_viewport(m_swapchain->extent()) .set_scissor(m_swapchain->extent()) .set_descriptor_set_layout(m_descriptor_set_layout) - .uses_shader(m_octree_vert) - .uses_shader(m_octree_frag) + .add_shader(m_octree_vert) + .add_shader(m_octree_frag) .build("Octree"); return m_octree_pipeline; }); diff --git a/src/vulkan-renderer/render-graph/buffer.cpp b/src/vulkan-renderer/render-graph/buffer.cpp index 7e673028c..2fac34b67 100644 --- a/src/vulkan-renderer/render-graph/buffer.cpp +++ b/src/vulkan-renderer/render-graph/buffer.cpp @@ -158,6 +158,7 @@ void Buffer::create(const CommandBuffer &cmd_buf) { } void Buffer::destroy_all() { + destroy_buffer(); destroy_staging_buffer(); } diff --git a/src/vulkan-renderer/render-graph/graphics_pass.cpp b/src/vulkan-renderer/render-graph/graphics_pass.cpp index c7556652e..08b929972 100644 --- a/src/vulkan-renderer/render-graph/graphics_pass.cpp +++ b/src/vulkan-renderer/render-graph/graphics_pass.cpp @@ -1,5 +1,7 @@ #include "inexor/vulkan-renderer/render-graph/graphics_pass.hpp" +#include "inexor/vulkan-renderer/wrapper/make_info.hpp" + #include namespace inexor::vulkan_renderer::render_graph { @@ -46,12 +48,68 @@ std::array get_debug_label_color(const DebugLabelColor color) { } } -GraphicsPass::GraphicsPass(std::string name, - OnRecordCommandBufferForPass on_record_cmd_buffer, - std::vector> graphics_pass_reads, - std::vector> write_attachments, - std::vector> write_swapchains, - const DebugLabelColor pass_debug_label_color) { +GraphicsPass::GraphicsPass( + std::string name, + OnRecordCommandBufferForPass on_record_cmd_buffer, + std::vector> graphics_pass_reads, + std::vector, std::optional>> write_attachments, + std::vector, std::optional>> write_swapchains, + const DebugLabelColor pass_debug_label_color) { + // If an extent has already been specified, all attachments must match this! + if (m_extent.width != 0 && m_extent.height != 0) { + for (const auto &write_attachment : write_attachments) { + const auto &attachment = write_attachment.first.lock(); + if (attachment->m_width != m_extent.width) { + throw std::invalid_argument("[GraphicsPass::GraphicsPass] Error: Width of graphics pass " + m_name + + " is already specified (" + std::to_string(m_extent.width) + + "), but width of write attachment " + attachment->m_name + " (" + + std::to_string(attachment->m_width) + ") does not match!"); + } + if (attachment->m_height != m_extent.height) { + throw std::invalid_argument("[GraphicsPass::GraphicsPass] Error: Height of graphics pass " + m_name + + " is already specified (" + std::to_string(m_extent.height) + + "), but width of write attachment " + attachment->m_name + " (" + + std::to_string(attachment->m_height) + ") does not match!"); + } + } + for (const auto &write_swapchain : write_swapchains) { + const auto &swapchain = write_swapchain.first.lock(); + if (swapchain->m_extent.width != m_extent.width) { + throw std::invalid_argument("[GraphicsPass::GraphicsPass] Error: Width of graphics pass " + m_name + + " is already specified (" + std::to_string(m_extent.width) + + "), but width of write swapchain " + swapchain->m_name + " (" + + std::to_string(swapchain->m_extent.width) + ") does not match!"); + } + if (swapchain->m_extent.height != m_extent.height) { + throw std::invalid_argument("[GraphicsPass::GraphicsPass] Error: Height of graphics pass " + m_name + + " is already specified (" + std::to_string(m_extent.height) + + "), but width of write swapchain " + swapchain->m_name + " (" + + std::to_string(swapchain->m_extent.height) + ") does not match!"); + } + } + } + + // Pick any extent and store it, they must be all the same at this point + if (!write_attachments.empty()) { + const auto &attachment = write_attachments[0].first.lock(); + m_extent = { + .width = attachment->m_width, + .height = attachment->m_height, + }; + } else if (!write_swapchains.empty()) { + const auto &swapchain = write_swapchains[0].first.lock(); + m_extent = swapchain->m_extent; + } + + // Check if either width or height is 0 + if (m_extent.width == 0) { + throw std::runtime_error("[GraphicsPass::GraphicsPass] Error: m_extent.width is 0!"); + } + if (m_extent.height == 0) { + throw std::runtime_error("[GraphicsPass::GraphicsPass] Error: m_extent.height is 0!"); + } + + // Store the other data m_name = std::move(name); m_on_record_cmd_buffer = std::move(on_record_cmd_buffer); m_debug_label_color = get_debug_label_color(pass_debug_label_color); @@ -69,11 +127,21 @@ GraphicsPass::GraphicsPass(GraphicsPass &&other) noexcept { m_rendering_info = std::move(other.m_rendering_info); m_write_attachments = std::move(other.m_write_attachments); m_write_swapchains = std::move(other.m_write_swapchains); - m_color_attachment_infos = std::move(other.m_color_attachment_infos); - m_depth_attachment_info = std::move(other.m_depth_attachment_info); - m_stencil_attachment_info = std::move(other.m_stencil_attachment_info); + m_color_attachments = std::move(other.m_color_attachments); + m_depth_attachment = std::move(other.m_depth_attachment); + m_stencil_attachment = std::move(other.m_stencil_attachment); m_graphics_pass_reads = std::move(other.m_graphics_pass_reads); m_debug_label_color = other.m_debug_label_color; } +/// Reset the rendering info +void GraphicsPass::reset_rendering_info() { + m_rendering_info = wrapper::make_info(); + m_color_attachments.clear(); + m_has_depth_attachment = false; + m_depth_attachment = wrapper::make_info(); + m_has_stencil_attachment = false; + m_stencil_attachment = wrapper::make_info(); +} + } // namespace inexor::vulkan_renderer::render_graph diff --git a/src/vulkan-renderer/render-graph/graphics_pass_builder.cpp b/src/vulkan-renderer/render-graph/graphics_pass_builder.cpp index 0696df079..0c9188a77 100644 --- a/src/vulkan-renderer/render-graph/graphics_pass_builder.cpp +++ b/src/vulkan-renderer/render-graph/graphics_pass_builder.cpp @@ -32,7 +32,7 @@ GraphicsPassBuilder &GraphicsPassBuilder::conditionally_reads_from(std::weak_ptr return *this; } -GraphicsPassBuilder &GraphicsPassBuilder::reads_from(std::weak_ptr graphics_pass) { +GraphicsPassBuilder &GraphicsPassBuilder::reads_from(const std::weak_ptr graphics_pass) { if (graphics_pass.expired()) { throw std::invalid_argument("[GraphicsPassBuilder::reads_from] Error: 'graphics_pass' is an invalid pointer!"); } @@ -46,26 +46,26 @@ void GraphicsPassBuilder::reset() { m_write_attachments.clear(); } -GraphicsPassBuilder & -GraphicsPassBuilder::set_on_record(std::function on_record_cmd_buffer) { +GraphicsPassBuilder &GraphicsPassBuilder::set_on_record(OnRecordCommandBufferForPass on_record_cmd_buffer) { m_on_record_cmd_buffer = std::move(on_record_cmd_buffer); return *this; } -GraphicsPassBuilder &GraphicsPassBuilder::writes_to(std::weak_ptr attachment, - std::optional clear_value) { +GraphicsPassBuilder &GraphicsPassBuilder::writes_to(const std::weak_ptr attachment, + const std::optional clear_value) { if (attachment.expired()) { throw std::invalid_argument("[GraphicsPassBuilder::writes_to] Error: 'attachment' is an invalid pointer!"); } - m_write_attachments.push_back(std::move(attachment)); + m_write_attachments.emplace_back(std::make_pair(std::move(attachment), std::move(clear_value))); return *this; } -GraphicsPassBuilder &GraphicsPassBuilder::writes_to(std::weak_ptr swapchain) { +GraphicsPassBuilder &GraphicsPassBuilder::writes_to(const std::weak_ptr swapchain, + const std::optional clear_value) { if (swapchain.expired()) { throw std::invalid_argument("[GraphicsPassBuilder::writes_to] Error: 'swapchain' is an invalid pointer!"); } - m_write_swapchains.push_back(std::move(swapchain)); + m_write_swapchains.emplace_back(std::make_pair(std::move(swapchain), std::move(clear_value))); return *this; } diff --git a/src/vulkan-renderer/render-graph/render_graph.cpp b/src/vulkan-renderer/render-graph/render_graph.cpp index 481f65364..f1b20939b 100644 --- a/src/vulkan-renderer/render-graph/render_graph.cpp +++ b/src/vulkan-renderer/render-graph/render_graph.cpp @@ -88,30 +88,30 @@ void RenderGraph::check_for_cycles() { } } +void RenderGraph::collect_swapchain_image_available_semaphores() { + m_swapchains_imgs_available.clear(); + for (const auto &pass : m_graphics_passes) { + for (const auto &swapchain : pass->m_write_swapchains) { + m_swapchains_imgs_available.push_back(swapchain.first.lock()->m_img_available->m_semaphore); + } + } +} + void RenderGraph::compile() { // TODO: What needs to be re-done when swapchain is recreated? validate_render_graph(); determine_pass_order(); - create_buffers(); + update_buffers(); create_textures(); create_descriptor_set_layouts(); allocate_descriptor_sets(); - update_descriptor_sets(); + update_write_descriptor_sets(); create_graphics_passes(); create_graphics_pipelines(); + collect_swapchain_image_available_semaphores(); check_for_cycles(); } -void RenderGraph::create_buffers() { - m_device.execute("[RenderGraph::create_buffers]", [&](const CommandBuffer &cmd_buf) { - for (const auto &buffer : m_buffers) { - buffer->m_on_check_for_update(); - cmd_buf.set_suboperation_debug_name("[Buffer:" + buffer->m_name + "]"); - buffer->create(cmd_buf); - } - }); -} - void RenderGraph::create_descriptor_set_layouts() { for (const auto &descriptor : m_resource_descriptors) { // Call descriptor set layout create function for each descriptor @@ -157,143 +157,107 @@ void RenderGraph::determine_pass_order() { } void RenderGraph::fill_graphics_pass_rendering_info(GraphicsPass &pass) { -#if 0 - /// Fill VkRenderingAttachmentInfo for a given render_graph::Attachment - /// @param attachment The attachment (color, depth, or stencil) - /// @return VkRenderingAttachmentInfo The filled rendering info struct - auto fill_rendering_info = [&](const RenderingAttachment &attachment) { - const auto attach_ptr = attachment.first.lock(); - const auto img_layout = [&]() -> VkImageLayout { - switch (attach_ptr->m_usage) { - case TextureUsage::BACK_BUFFER: + pass.reset_rendering_info(); + + /// + /// + /// + auto fill_rendering_info_for_attachment = [&](const std::weak_ptr &write_attachment, + const std::optional &clear_value) { + const auto &attachment = write_attachment.lock(); + auto get_image_layout = [&]() { + switch (attachment->m_usage) { + case TextureUsage::COLOR_ATTACHMENT: case TextureUsage::NORMAL: { return VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; } - case TextureUsage::DEPTH_STENCIL_BUFFER: { + case TextureUsage::DEPTH_ATTACHMENT: + case TextureUsage::STENCIL_ATTACHMENT: { return VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; } default: return VK_IMAGE_LAYOUT_UNDEFINED; } - }(); // Invoke the lambda, not just define it! - - // TODO: Is there no easier way to determine this? - auto is_integer_format = [](const VkFormat format) { - switch (format) { - // 8-bit integer formats - case VK_FORMAT_R8_UINT: - case VK_FORMAT_R8_SINT: - case VK_FORMAT_R8G8_UINT: - case VK_FORMAT_R8G8_SINT: - case VK_FORMAT_R8G8B8_UINT: - case VK_FORMAT_R8G8B8_SINT: - case VK_FORMAT_B8G8R8_UINT: - case VK_FORMAT_B8G8R8_SINT: - case VK_FORMAT_R8G8B8A8_UINT: - case VK_FORMAT_R8G8B8A8_SINT: - case VK_FORMAT_B8G8R8A8_UINT: - case VK_FORMAT_B8G8R8A8_SINT: - case VK_FORMAT_A2R10G10B10_UINT_PACK32: - case VK_FORMAT_A2B10G10R10_UINT_PACK32: - - // 16-bit integer formats - case VK_FORMAT_R16_UINT: - case VK_FORMAT_R16_SINT: - case VK_FORMAT_R16G16_UINT: - case VK_FORMAT_R16G16_SINT: - case VK_FORMAT_R16G16B16_UINT: - case VK_FORMAT_R16G16B16_SINT: - case VK_FORMAT_R16G16B16A16_UINT: - case VK_FORMAT_R16G16B16A16_SINT: - - // 32-bit integer formats - case VK_FORMAT_R32_UINT: - case VK_FORMAT_R32_SINT: - case VK_FORMAT_R32G32_UINT: - case VK_FORMAT_R32G32_SINT: - case VK_FORMAT_R32G32B32_UINT: - case VK_FORMAT_R32G32B32_SINT: - case VK_FORMAT_R32G32B32A32_UINT: - case VK_FORMAT_R32G32B32A32_SINT: - - // 64-bit integer formats - case VK_FORMAT_R64_UINT: - case VK_FORMAT_R64_SINT: - case VK_FORMAT_R64G64_UINT: - case VK_FORMAT_R64G64_SINT: - case VK_FORMAT_R64G64B64_UINT: - case VK_FORMAT_R64G64B64_SINT: - case VK_FORMAT_R64G64B64A64_UINT: - case VK_FORMAT_R64G64B64A64_SINT: - return true; - - // Non-integer formats - default: - return false; - } }; - bool msaa_enabled = (attach_ptr->m_msaa_img != nullptr); - - // This decides if MSAA is enabled on a per-texture basis + // TODO: Support MSAA again! return wrapper::make_info({ - .imageView = msaa_enabled ? attach_ptr->m_msaa_img->m_img_view : attach_ptr->m_img->m_img_view, - // The image layout is the same for m_img and m_msaa_img - .imageLayout = img_layout, - // The resolve mode must be chosen on whether it's an integer format - .resolveMode = msaa_enabled ? (!is_integer_format(attach_ptr->m_format) ? VK_RESOLVE_MODE_AVERAGE_BIT - : VK_RESOLVE_MODE_MIN_BIT) - : VK_RESOLVE_MODE_NONE, // No resolve if MSAA is disabled - // This will be filled during rendering! - .resolveImageView = m_swapchain.m_current_img_view, - // TODO: Is this correct layout? - .resolveImageLayout = msaa_enabled ? img_layout : VK_IMAGE_LAYOUT_UNDEFINED, - // Does this pass require clearing this attachment? - .loadOp = attachment.second ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD, - // The result will always be stored + .imageView = attachment->m_img->m_img_view, + .imageLayout = get_image_layout(), + .resolveMode = VK_RESOLVE_MODE_NONE, + .resolveImageView = nullptr, + .loadOp = clear_value ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_DONT_CARE, .storeOp = VK_ATTACHMENT_STORE_OP_STORE, - // If clearing is enabled, specify the clear value - .clearValue = attachment.second ? attachment.second.value() : VkClearValue{}, + .clearValue = clear_value ? clear_value.value() : VkClearValue{}, }); }; - // Reserve memory for the color attachments - pass.m_color_attachment_infos.reserve(pass.m_write_color_attachments.size()); - - // Fill the color attachments - const bool has_any_color_attachment = pass.m_write_color_attachments.size() > 0; - for (const auto &color_attachment : pass.m_write_color_attachments) { - pass.m_color_attachment_infos.push_back(fill_rendering_info(color_attachment)); - } - // Fill the color attachment (if specified) - const bool has_depth_attachment = !pass.m_write_depth_attachment.first.expired(); - if (has_depth_attachment) { - pass.m_depth_attachment_info = fill_rendering_info(pass.m_write_depth_attachment); - pass.m_depth_attachment_info.resolveMode = VK_RESOLVE_MODE_NONE; + // Step 1: Process all write attachments (color, depth, stencil) of the graphics pass into VkRenderingInfo + for (const auto &write_attachment : pass.m_write_attachments) { + // What type of attachment is this? + const auto &attachment = write_attachment.first; + const auto &clear_value = write_attachment.second; + + switch (attachment.lock()->m_usage) { + case TextureUsage::COLOR_ATTACHMENT: + case TextureUsage::NORMAL: { + pass.m_color_attachments.push_back(fill_rendering_info_for_attachment(attachment, clear_value)); + break; + } + case TextureUsage::DEPTH_ATTACHMENT: { + pass.m_depth_attachment = fill_rendering_info_for_attachment(attachment, clear_value); + pass.m_has_depth_attachment = true; + break; + } + case TextureUsage::STENCIL_ATTACHMENT: { + pass.m_stencil_attachment = fill_rendering_info_for_attachment(attachment, clear_value); + pass.m_has_stencil_attachment = true; + break; + } + default: + continue; + } } - // Fill the stencil attachment (if specified) - const bool has_stencil_attachment = !pass.m_write_stencil_attachment.first.expired(); - if (has_stencil_attachment) { - pass.m_stencil_attachment_info = fill_rendering_info(pass.m_write_stencil_attachment); + + /// + /// + /// + auto fill_write_info_for_swapchain = [&](const std::weak_ptr &write_swapchain, + const std::optional &clear_value) { + // TODO: Support MSAA again! + return wrapper::make_info({ + .imageView = write_swapchain.lock()->m_current_img_view, + .imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + .resolveMode = VK_RESOLVE_MODE_NONE, + .resolveImageView = nullptr, + .loadOp = clear_value ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_DONT_CARE, + .storeOp = VK_ATTACHMENT_STORE_OP_STORE, + .clearValue = clear_value ? clear_value.value() : VkClearValue{}, + }); + }; + + // TODO: Step 2: Process all swapchain writes of the graphics pass into VkRenderingInfo + for (const auto &write_swapchain : pass.m_write_swapchains) { + const auto &swapchain = write_swapchain.first.lock(); + const auto &clear_value = write_swapchain.second; + pass.m_color_attachments.push_back(fill_write_info_for_swapchain(swapchain, clear_value)); } // TODO: If a pass has multiple color attachments those are multiple swapchains, does that mean we must group // rendering by swapchains because there is no guarantee that they all have the same swapchain extent? - // Fill the rendering info of the pass + // Step 3: Fill the rendering info pass.m_rendering_info = wrapper::make_info({ .renderArea = { - // TODO: Fix me! - //.extent = m_swapchain.extent(), + .extent = pass.m_extent, }, .layerCount = 1, - .colorAttachmentCount = static_cast(pass.m_color_attachment_infos.size()), - .pColorAttachments = has_any_color_attachment ? pass.m_color_attachment_infos.data() : nullptr, - .pDepthAttachment = has_depth_attachment ? &pass.m_depth_attachment_info : nullptr, - .pStencilAttachment = has_stencil_attachment ? &pass.m_stencil_attachment_info : nullptr, + .colorAttachmentCount = static_cast(pass.m_color_attachments.size()), + .pColorAttachments = (pass.m_color_attachments.size() > 0) ? pass.m_color_attachments.data() : nullptr, + .pDepthAttachment = pass.m_has_depth_attachment ? &pass.m_depth_attachment : nullptr, + .pStencilAttachment = pass.m_has_stencil_attachment ? &pass.m_stencil_attachment : nullptr, }); -#endif } void RenderGraph::record_command_buffer_for_pass(const CommandBuffer &cmd_buf, GraphicsPass &pass) { @@ -304,16 +268,34 @@ void RenderGraph::record_command_buffer_for_pass(const CommandBuffer &cmd_buf, G // Fill the VKRenderingInfo of the graphics pass fill_graphics_pass_rendering_info(pass); + // If there are writes to swapchains, the image layout of the swapchain must be changed because it comes back in + // undefined layout after presenting + for (const auto &swapchain : pass.m_write_swapchains) { + cmd_buf.insert_debug_label("Changing Swapchain image layout", get_debug_label_color(DebugLabelColor::GREEN)); + cmd_buf.change_image_layout(swapchain.first.lock()->m_current_img, VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + } + + // NOTE: Pipeline barriers must not be placed inside of dynamic rendering instances, which means we must change the + // image layout of all swapchains we write to before we call begin_rendering and then again after we call + // end_rendering. + // ---------------------------------------------------------------------------------------------------------------- // Start dynamic rendering with the compiled rendering info cmd_buf.begin_rendering(pass.m_rendering_info); - // Call the command buffer recording function of this graphics pass. In this function, the actual rendering takes // place: the programmer binds pipelines, descriptor sets, buffers, and calls Vulkan commands. Note that rendergraph // does not bind any pipelines, descriptor sets, or buffers automatically! std::invoke(pass.m_on_record_cmd_buffer, cmd_buf); - // End dynamic rendering cmd_buf.end_rendering(); + // ---------------------------------------------------------------------------------------------------------------- + + // Change the swapchain image layouts to prepare the swapchains for presenting + for (const auto &swapchain : pass.m_write_swapchains) { + cmd_buf.insert_debug_label("Changing Swapchain image layout", get_debug_label_color(DebugLabelColor::GREEN)); + cmd_buf.change_image_layout(swapchain.first.lock()->m_current_img, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); + } // End the debug label for this graphics pass cmd_buf.end_debug_label_region(); } @@ -321,42 +303,20 @@ void RenderGraph::record_command_buffer_for_pass(const CommandBuffer &cmd_buf, G void RenderGraph::render() { update_buffers(); update_textures(); - update_descriptor_sets(); - // NOTE: We don't need to update the write descriptor sets per frame! - - // TODO: Acquire next image for each swapchain used by rendergraph - // m_swapchain.acquire_next_image_index(); - - m_device.execute("[RenderGraph::render]", [&](const CommandBuffer &cmd_buf) { - // Call the command buffer recording function of every graphics pass - for (auto &pass : m_graphics_passes) { - record_command_buffer_for_pass(cmd_buf, *pass); - } - // So if we are writing to multiple swapchains in this pass, we must wait for every swapchains semaphore! - - // TODO: Accumulate all image available semaphores of swapchains that were used in this graphics pass! - // TODO: Change image layouts for swapchain write attachments - - // TODO: We are calling submit manually here, why though? - // TODO: Expose submit parameters to execute() - - //cmd_buf.submit_and_wait(); - }); - -#if 0 - // TODO: Further abstract this away? - const std::array stage_mask{VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; - cmd_buf.submit_and_wait(wrapper::make_info({ - .waitSemaphoreCount = 1, - .pWaitSemaphores = m_swapchain.image_available_semaphore(), - .pWaitDstStageMask = stage_mask.data(), - .commandBufferCount = 1, - .pCommandBuffers = &cmd_buf.m_cmd_buf, - })); -#endif - - // TODO: Call present for each swapchain! - // m_swapchain.present(); + update_write_descriptor_sets(); + // NOTE: We only need to call update_write_descriptor_sets() once in rendergraph compilation, not every frame! + + // So if we are writing to multiple swapchains in this pass, we must wait for every swapchains + // semaphore! + m_device.execute( + "[RenderGraph::render]", + [&](const CommandBuffer &cmd_buf) { + // Call the command buffer recording function of every graphics pass + for (auto &pass : m_graphics_passes) { + record_command_buffer_for_pass(cmd_buf, *pass); + } + }, + m_swapchains_imgs_available); } void RenderGraph::reset() { @@ -377,22 +337,6 @@ void RenderGraph::update_buffers() { }); } -void RenderGraph::update_descriptor_sets() { - // TODO: We should only need to call DescriptorSetUpdateBuilder once really during rendergraph compilation! - // TODO: Reserve? When to clear? - m_write_descriptor_sets.clear(); - for (const auto &descriptor : m_resource_descriptors) { - // Call descriptor set builder function for each descriptor - auto write_descriptor_sets = std::invoke(std::get<2>(descriptor), m_write_descriptor_set_builder); - // Store the results of the function that was called - std::move(write_descriptor_sets.begin(), write_descriptor_sets.end(), - std::back_inserter(m_write_descriptor_sets)); - } - // NOTE: We batch all descriptor set updates into one function call for performance - vkUpdateDescriptorSets(m_device.device(), static_cast(m_write_descriptor_sets.size()), - m_write_descriptor_sets.data(), 0, nullptr); -} - void RenderGraph::update_textures() { m_device.execute("[RenderGraph::update_textures]", [&](const CommandBuffer &cmd_buf) { for (const auto &texture : m_textures) { @@ -411,6 +355,22 @@ void RenderGraph::update_textures() { }); } +void RenderGraph::update_write_descriptor_sets() { + m_write_descriptor_sets.clear(); + // NOTE: We don't reserve memory for the vector because we don't know how many write descriptor sets will exist in + // total. Because we call update_descriptor_sets() only once during rendergraph compilation, this is not a problem. + for (const auto &descriptor : m_resource_descriptors) { + // Call descriptor set builder function for each descriptor + auto write_descriptor_sets = std::invoke(std::get<2>(descriptor), m_write_descriptor_set_builder); + // Store the results of the function that was called + std::move(write_descriptor_sets.begin(), write_descriptor_sets.end(), + std::back_inserter(m_write_descriptor_sets)); + } + // NOTE: We batch all descriptor set updates into one function call for optimal performance + vkUpdateDescriptorSets(m_device.device(), static_cast(m_write_descriptor_sets.size()), + m_write_descriptor_sets.data(), 0, nullptr); +} + void RenderGraph::validate_render_graph() { if (m_graphics_pass_create_functions.empty()) { throw std::runtime_error("[RenderGraph::validate_render_graph] Error: No graphics passes in rendergraph!"); diff --git a/src/vulkan-renderer/render-graph/texture.cpp b/src/vulkan-renderer/render-graph/texture.cpp index cafdc3c30..e086e6985 100644 --- a/src/vulkan-renderer/render-graph/texture.cpp +++ b/src/vulkan-renderer/render-graph/texture.cpp @@ -61,7 +61,7 @@ void Texture::create() { return VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; } - case TextureUsage::BACK_BUFFER: { + case TextureUsage::COLOR_ATTACHMENT: { return VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; } default: { @@ -82,7 +82,7 @@ void Texture::create() { { .aspectMask = [&]() -> VkImageAspectFlags { switch (m_usage) { - case TextureUsage::DEPTH_STENCIL_BUFFER: { + case TextureUsage::DEPTH_ATTACHMENT: { return VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; } default: { diff --git a/src/vulkan-renderer/renderers/imgui.cpp b/src/vulkan-renderer/renderers/imgui.cpp index 29153f3e2..7a294569c 100644 --- a/src/vulkan-renderer/renderers/imgui.cpp +++ b/src/vulkan-renderer/renderers/imgui.cpp @@ -124,12 +124,12 @@ ImGuiRenderer::ImGuiRenderer(const Device &device, }, }) .add_default_color_blend_attachment() - .add_color_attachment(swapchain.lock()->image_format()) + .add_color_attachment(m_swapchain.lock()->image_format()) .set_depth_attachment_format(VK_FORMAT_D32_SFLOAT_S8_UINT) - .set_viewport(swapchain.lock()->extent()) - .set_scissor(swapchain.lock()->extent()) - .uses_shader(m_vertex_shader) - .uses_shader(m_fragment_shader) + .set_viewport(m_swapchain.lock()->extent()) + .set_scissor(m_swapchain.lock()->extent()) + .add_shader(m_vertex_shader) + .add_shader(m_fragment_shader) .set_descriptor_set_layout(m_descriptor_set_layout) .add_push_constant_range(VK_SHADER_STAGE_VERTEX_BIT, sizeof(m_push_const_block)) .build("ImGui"); @@ -140,7 +140,7 @@ ImGuiRenderer::ImGuiRenderer(const Device &device, // NOTE: ImGui does not write to depth buffer and it reads from octree pass (previous pass) // NOTE: We directly return the ImGui graphics pass and do not store it in here because it's the last pass (for // now) and there is no reads_from function which would need it. - return builder.writes_to(swapchain) + return builder.writes_to(m_swapchain) .conditionally_reads_from(m_previous_pass, !m_previous_pass.expired()) .set_on_record([&](const wrapper::commands::CommandBuffer &cmd_buf) { ImDrawData *draw_data = ImGui::GetDrawData(); diff --git a/src/vulkan-renderer/wrapper/commands/command_buffer.cpp b/src/vulkan-renderer/wrapper/commands/command_buffer.cpp index b03f4bd2e..e6389315e 100644 --- a/src/vulkan-renderer/wrapper/commands/command_buffer.cpp +++ b/src/vulkan-renderer/wrapper/commands/command_buffer.cpp @@ -62,8 +62,8 @@ const CommandBuffer &CommandBuffer::begin_rendering(const VkRenderingInfo &rende const CommandBuffer &CommandBuffer::bind_descriptor_set(const VkDescriptorSet descriptor_set, const std::weak_ptr pipeline) const { assert(descriptor_set); - vkCmdBindDescriptorSets(m_cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.lock()->m_pipeline_layout, 0, 1, - &descriptor_set, 0, nullptr); + vkCmdBindDescriptorSets(m_cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, + pipeline.lock()->m_pipeline_layout->m_pipeline_layout, 0, 1, &descriptor_set, 0, nullptr); return *this; } @@ -431,13 +431,22 @@ const CommandBuffer &CommandBuffer::set_suboperation_debug_name(std::string name return *this; } -void CommandBuffer::submit_and_wait() const { +void CommandBuffer::submit_and_wait(const std::span wait_semaphores, + const std::span signal_semaphores) const { end_command_buffer(); + const VkPipelineStageFlags wait_dst_stage_mask[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; + const auto submit_info = make_info({ + .waitSemaphoreCount = static_cast(wait_semaphores.size()), + .pWaitSemaphores = wait_semaphores.data(), + .pWaitDstStageMask = wait_dst_stage_mask, .commandBufferCount = 1, .pCommandBuffers = &m_cmd_buf, + .signalSemaphoreCount = static_cast(signal_semaphores.size()), + .pSignalSemaphores = signal_semaphores.data(), }); + if (const auto result = vkQueueSubmit(m_device.graphics_queue(), 1, &submit_info, m_cmd_buf_execution_completed->m_fence)) { throw VulkanException("[CommandBuffer::submit] Error: vkQueueSubmit failed!", result); diff --git a/src/vulkan-renderer/wrapper/device.cpp b/src/vulkan-renderer/wrapper/device.cpp index e68025b6c..54172bcfa 100644 --- a/src/vulkan-renderer/wrapper/device.cpp +++ b/src/vulkan-renderer/wrapper/device.cpp @@ -464,11 +464,13 @@ bool Device::surface_supports_usage(const VkSurfaceKHR surface, const VkImageUsa } void Device::execute(const std::string &name, - const std::function &cmd_lambda) const { + const std::function &cmd_buf_recording_func, + const std::span wait_semaphores, + const std::span signal_semaphores) const { // TODO: Support other queues, not just graphics queue const auto &cmd_buf = thread_graphics_pool().request_command_buffer(name); - cmd_lambda(cmd_buf); - cmd_buf.submit_and_wait(); + std::invoke(cmd_buf_recording_func, cmd_buf); + cmd_buf.submit_and_wait(wait_semaphores, signal_semaphores); } std::optional Device::find_queue_family_index_if( diff --git a/src/vulkan-renderer/wrapper/pipelines/pipeline.cpp b/src/vulkan-renderer/wrapper/pipelines/pipeline.cpp index 012ea9d18..99391a5c7 100644 --- a/src/vulkan-renderer/wrapper/pipelines/pipeline.cpp +++ b/src/vulkan-renderer/wrapper/pipelines/pipeline.cpp @@ -13,25 +13,14 @@ GraphicsPipeline::GraphicsPipeline(const Device &device, std::vector push_constant_ranges, VkGraphicsPipelineCreateInfo pipeline_ci, std::string name) - : m_device(device), m_descriptor_set_layouts(std::move(descriptor_set_layouts)), - m_push_constant_ranges(std::move(push_constant_ranges)), m_name(std::move(name)) { - - const auto pipeline_layout_ci = wrapper::make_info({ - .setLayoutCount = static_cast(m_descriptor_set_layouts.size()), - .pSetLayouts = m_descriptor_set_layouts.data(), - .pushConstantRangeCount = static_cast(m_push_constant_ranges.size()), - .pPushConstantRanges = m_push_constant_ranges.data(), - }); - - // First create the graphics pipeline layout - if (const auto result = vkCreatePipelineLayout(m_device.device(), &pipeline_layout_ci, nullptr, &m_pipeline_layout); - result != VK_SUCCESS) { - throw VulkanException("Error: vkCreatePipelineLayout failed for pipeline layout " + m_name + "!", result); - } - m_device.set_debug_name(m_pipeline_layout, m_name); + : m_device(device), m_name(std::move(name)) { + + // Create the graphics pipeline layout + m_pipeline_layout = std::make_unique(m_device, m_name, std::move(descriptor_set_layouts), + std::move(push_constant_ranges)); // Set the pipeline layout - pipeline_ci.layout = m_pipeline_layout; + pipeline_ci.layout = m_pipeline_layout->m_pipeline_layout; // Then create the graphics pipeline if (const auto result = @@ -44,14 +33,11 @@ GraphicsPipeline::GraphicsPipeline(const Device &device, GraphicsPipeline::GraphicsPipeline(GraphicsPipeline &&other) noexcept : m_device(other.m_device) { m_pipeline = std::exchange(other.m_pipeline, VK_NULL_HANDLE); - m_pipeline_layout = std::exchange(other.m_pipeline_layout, VK_NULL_HANDLE); + m_pipeline_layout = std::exchange(other.m_pipeline_layout, nullptr); m_name = std::move(other.m_name); - m_descriptor_set_layouts = std::move(other.m_descriptor_set_layouts); - m_push_constant_ranges = std::move(m_push_constant_ranges); } GraphicsPipeline::~GraphicsPipeline() { - vkDestroyPipelineLayout(m_device.device(), m_pipeline_layout, nullptr); vkDestroyPipeline(m_device.device(), m_pipeline, nullptr); } diff --git a/src/vulkan-renderer/wrapper/pipelines/pipeline_builder.cpp b/src/vulkan-renderer/wrapper/pipelines/pipeline_builder.cpp index 1020baa28..e56f39077 100644 --- a/src/vulkan-renderer/wrapper/pipelines/pipeline_builder.cpp +++ b/src/vulkan-renderer/wrapper/pipelines/pipeline_builder.cpp @@ -11,26 +11,26 @@ GraphicsPipelineBuilder::GraphicsPipelineBuilder(const Device &device) : m_devic } GraphicsPipelineBuilder::GraphicsPipelineBuilder(GraphicsPipelineBuilder &&other) noexcept : m_device(other.m_device) { + m_pipeline_rendering_ci = std::move(other.m_pipeline_rendering_ci); + m_color_attachments = std::move(other.m_color_attachments); m_depth_attachment_format = other.m_depth_attachment_format; m_stencil_attachment_format = other.m_stencil_attachment_format; - m_color_attachments = std::move(other.m_color_attachments); - m_pipeline_rendering_ci = std::move(other.m_pipeline_rendering_ci); + m_shader_stages = std::move(other.m_shader_stages); + m_vertex_input_binding_descriptions = std::move(other.m_vertex_input_binding_descriptions); + m_vertex_input_attribute_descriptions = std::move(other.m_vertex_input_attribute_descriptions); m_vertex_input_sci = std::move(other.m_vertex_input_sci); m_input_assembly_sci = std::move(other.m_input_assembly_sci); m_tesselation_sci = std::move(other.m_tesselation_sci); m_viewport_sci = std::move(other.m_viewport_sci); + m_viewports = std::move(other.m_viewports); + m_scissors = std::move(other.m_scissors); m_rasterization_sci = std::move(m_rasterization_sci); m_multisample_sci = std::move(other.m_multisample_sci); m_depth_stencil_sci = std::move(other.m_depth_stencil_sci); m_color_blend_sci = std::move(other.m_color_blend_sci); + m_dynamic_states = std::move(other.m_dynamic_states); m_dynamic_states_sci = std::move(other.m_dynamic_states_sci); m_pipeline_layout = std::exchange(other.m_pipeline_layout, VK_NULL_HANDLE); - m_dynamic_states = std::move(other.m_dynamic_states); - m_viewports = std::move(other.m_viewports); - m_scissors = std::move(other.m_scissors); - m_shader_stages = std::move(other.m_shader_stages); - m_vertex_input_binding_descriptions = std::move(other.m_vertex_input_binding_descriptions); - m_vertex_input_attribute_descriptions = std::move(other.m_vertex_input_attribute_descriptions); m_color_blend_attachment_states = std::move(other.m_color_blend_attachment_states); } @@ -42,6 +42,14 @@ std::shared_ptr GraphicsPipelineBuilder::build(std::string nam // build the graphics pipeline. This is because validation of this data is job of the validation layers, and not the // job of GraphicsPipelineBuilder. We should not mimic the behavious of validation layers here. + m_pipeline_rendering_ci = make_info({ + // TODO: Support multiview rendering and expose viewMask parameter + .colorAttachmentCount = static_cast(m_color_attachments.size()), + .pColorAttachmentFormats = m_color_attachments.data(), + .depthAttachmentFormat = m_depth_attachment_format, + .stencilAttachmentFormat = m_stencil_attachment_format, + }); + m_vertex_input_sci = make_info({ .vertexBindingDescriptionCount = static_cast(m_vertex_input_binding_descriptions.size()), .pVertexBindingDescriptions = m_vertex_input_binding_descriptions.data(), @@ -57,23 +65,16 @@ std::shared_ptr GraphicsPipelineBuilder::build(std::string nam .pScissors = m_scissors.data(), }); - m_dynamic_states_sci = make_info({ - .dynamicStateCount = static_cast(m_dynamic_states.size()), - .pDynamicStates = m_dynamic_states.data(), - }); - - m_pipeline_rendering_ci = make_info({ - .colorAttachmentCount = static_cast(m_color_attachments.size()), - .pColorAttachmentFormats = m_color_attachments.data(), - .depthAttachmentFormat = m_depth_attachment_format, - .stencilAttachmentFormat = m_stencil_attachment_format, - }); - m_color_blend_sci = wrapper::make_info({ .attachmentCount = static_cast(m_color_blend_attachment_states.size()), .pAttachments = m_color_blend_attachment_states.data(), }); + m_dynamic_states_sci = make_info({ + .dynamicStateCount = static_cast(m_dynamic_states.size()), + .pDynamicStates = m_dynamic_states.data(), + }); + auto graphics_pipeline = std::make_shared( m_device, std::vector{m_descriptor_set_layout}, m_push_constant_ranges, make_info({ @@ -104,65 +105,239 @@ std::shared_ptr GraphicsPipelineBuilder::build(std::string nam } void GraphicsPipelineBuilder::reset() { + m_pipeline_rendering_ci = make_info(); m_color_attachments.clear(); m_depth_attachment_format = VK_FORMAT_UNDEFINED; m_stencil_attachment_format = VK_FORMAT_UNDEFINED; - m_pipeline_layout = VK_NULL_HANDLE; - m_color_blend_attachment_states.clear(); + m_shader_stages.clear(); m_vertex_input_binding_descriptions.clear(); m_vertex_input_attribute_descriptions.clear(); - m_vertex_input_sci = { - make_info(), - }; - - m_input_assembly_sci = { - make_info({ - .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, - .primitiveRestartEnable = VK_FALSE, - }), - }; + m_vertex_input_sci = make_info(); + + m_input_assembly_sci = make_info({ + .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, + .primitiveRestartEnable = VK_FALSE, + }); - m_tesselation_sci = { - make_info(), - }; + m_tesselation_sci = make_info(); m_viewports.clear(); m_scissors.clear(); - m_viewport_sci = { - make_info(), - }; - - m_rasterization_sci = { - make_info({ - .polygonMode = VK_POLYGON_MODE_FILL, - .cullMode = VK_CULL_MODE_BACK_BIT, - .frontFace = VK_FRONT_FACE_CLOCKWISE, - .lineWidth = 1.0f, - }), - }; + m_viewport_sci = make_info(); - m_multisample_sci = { - make_info({ - .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT, - .sampleShadingEnable = VK_FALSE, - .minSampleShading = 1.0f, - }), - }; + m_rasterization_sci = make_info({ + .polygonMode = VK_POLYGON_MODE_FILL, + .cullMode = VK_CULL_MODE_BACK_BIT, + .frontFace = VK_FRONT_FACE_CLOCKWISE, + .lineWidth = 1.0f, + }); - m_depth_stencil_sci = { - make_info(), - }; + m_multisample_sci = make_info({ + .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT, + .sampleShadingEnable = VK_FALSE, + .minSampleShading = 1.0f, + }); - m_color_blend_sci = { - make_info(), - }; + m_depth_stencil_sci = make_info(); + m_color_blend_sci = make_info(); m_dynamic_states.clear(); - m_dynamic_states_sci = { - make_info(), - }; + m_dynamic_states_sci = make_info(); + + m_pipeline_layout = VK_NULL_HANDLE; + m_color_blend_attachment_states.clear(); +} + +GraphicsPipelineBuilder &GraphicsPipelineBuilder::add_color_attachment(const VkFormat format) { + m_color_attachments.push_back(format); + return *this; +} + +GraphicsPipelineBuilder & +GraphicsPipelineBuilder::add_color_blend_attachment(const VkPipelineColorBlendAttachmentState &attachment) { + m_color_blend_attachment_states.push_back(attachment); + return *this; +} + +/// Add the default color blend attachment +/// @return A reference to the dereferenced this pointer (allows method calls to be chained) +[[nodiscard]] GraphicsPipelineBuilder &GraphicsPipelineBuilder::add_default_color_blend_attachment() { + return add_color_blend_attachment({ + .blendEnable = VK_TRUE, + .srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA, + .dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, + .colorBlendOp = VK_BLEND_OP_ADD, + .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE, + .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO, + .alphaBlendOp = VK_BLEND_OP_ADD, + .colorWriteMask = + VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT, + }); +} + +GraphicsPipelineBuilder &GraphicsPipelineBuilder::add_push_constant_range(const VkShaderStageFlags shader_stage, + const std::uint32_t size, + const std::uint32_t offset) { + m_push_constant_ranges.emplace_back(VkPushConstantRange{ + .stageFlags = shader_stage, + .offset = offset, + .size = size, + }); + return *this; +} + +GraphicsPipelineBuilder &GraphicsPipelineBuilder::add_shader(const std::weak_ptr shader) { + m_shader_stages.push_back(make_info({ + .stage = shader.lock()->m_shader_stage, + .module = shader.lock()->m_shader_module, + .pName = "main", + + })); + return *this; +} + +GraphicsPipelineBuilder & +GraphicsPipelineBuilder::set_color_blend(const VkPipelineColorBlendStateCreateInfo &color_blend) { + m_color_blend_sci = color_blend; + return *this; +} + +GraphicsPipelineBuilder &GraphicsPipelineBuilder::set_color_blend_attachments( + const std::vector &attachments) { + m_color_blend_attachment_states = attachments; + return *this; +} + +GraphicsPipelineBuilder &GraphicsPipelineBuilder::set_culling_mode(const VkBool32 culling_enabled) { + if (culling_enabled == VK_FALSE) { + spdlog::warn("Culling is disabled, which could have negative effects on the performance!"); + } + m_rasterization_sci.cullMode = culling_enabled == VK_TRUE ? VK_CULL_MODE_BACK_BIT : VK_CULL_MODE_NONE; + return *this; +} + +GraphicsPipelineBuilder &GraphicsPipelineBuilder::set_depth_attachment_format(const VkFormat format) { + m_depth_attachment_format = format; + return *this; +} + +GraphicsPipelineBuilder & +GraphicsPipelineBuilder::set_depth_stencil(const VkPipelineDepthStencilStateCreateInfo &depth_stencil) { + m_depth_stencil_sci = depth_stencil; + return *this; +} + +GraphicsPipelineBuilder &GraphicsPipelineBuilder::set_stencil_attachment_format(const VkFormat format) { + m_stencil_attachment_format = format; + return *this; +} + +GraphicsPipelineBuilder & +GraphicsPipelineBuilder::set_descriptor_set_layout(const VkDescriptorSetLayout descriptor_set_layout) { + assert(descriptor_set_layout); + m_descriptor_set_layout = descriptor_set_layout; + return *this; +} + +GraphicsPipelineBuilder & +GraphicsPipelineBuilder::set_dynamic_states(const std::vector &dynamic_states) { + assert(!dynamic_states.empty()); + m_dynamic_states = dynamic_states; + return *this; +} + +GraphicsPipelineBuilder & +GraphicsPipelineBuilder::set_input_assembly(const VkPipelineInputAssemblyStateCreateInfo &input_assembly) { + m_input_assembly_sci = input_assembly; + return *this; +} + +GraphicsPipelineBuilder &GraphicsPipelineBuilder::set_line_width(const float width) { + m_rasterization_sci.lineWidth = width; + return *this; +} + +GraphicsPipelineBuilder &GraphicsPipelineBuilder::set_multisampling(const VkSampleCountFlagBits sample_count, + const std::optional min_sample_shading) { + m_multisample_sci.rasterizationSamples = sample_count; + if (min_sample_shading) { + m_multisample_sci.minSampleShading = min_sample_shading.value(); + } + return *this; +} + +GraphicsPipelineBuilder &GraphicsPipelineBuilder::set_pipeline_layout(const VkPipelineLayout layout) { + assert(layout); + m_pipeline_layout = layout; + return *this; +} + +GraphicsPipelineBuilder &GraphicsPipelineBuilder::set_primitive_topology(const VkPrimitiveTopology topology) { + m_input_assembly_sci.topology = topology; + return *this; +} + +GraphicsPipelineBuilder & +GraphicsPipelineBuilder::set_rasterization(const VkPipelineRasterizationStateCreateInfo &rasterization) { + m_rasterization_sci = rasterization; + return *this; +} + +GraphicsPipelineBuilder &GraphicsPipelineBuilder::set_scissor(const VkRect2D &scissor) { + m_scissors = {scissor}; + m_viewport_sci.scissorCount = 1; + m_viewport_sci.pScissors = m_scissors.data(); + return *this; +} + +GraphicsPipelineBuilder &GraphicsPipelineBuilder::set_scissor(const VkExtent2D &extent) { + return set_scissor({ + // Convert VkExtent2D to VkRect2D + .extent = extent, + }); +} + +GraphicsPipelineBuilder & +GraphicsPipelineBuilder::set_tesselation_control_point_count(const std::uint32_t control_point_count) { + m_tesselation_sci.patchControlPoints = control_point_count; + return *this; +} + +GraphicsPipelineBuilder &GraphicsPipelineBuilder::set_vertex_input_attributes( + const std::vector &descriptions) { + assert(!descriptions.empty()); + m_vertex_input_attribute_descriptions = descriptions; + return *this; +} + +GraphicsPipelineBuilder & +GraphicsPipelineBuilder::set_vertex_input_bindings(const std::vector &descriptions) { + assert(!descriptions.empty()); + m_vertex_input_binding_descriptions = descriptions; + return *this; +} + +GraphicsPipelineBuilder &GraphicsPipelineBuilder::set_viewport(const VkViewport &viewport) { + m_viewports = {viewport}; + m_viewport_sci.viewportCount = 1; + m_viewport_sci.pViewports = m_viewports.data(); + return *this; +} + +GraphicsPipelineBuilder &GraphicsPipelineBuilder::set_viewport(const VkExtent2D &extent) { + return set_viewport({ + // Convert VkExtent2D to VkViewport + .width = static_cast(extent.width), + .height = static_cast(extent.height), + .maxDepth = 1.0f, + }); +} + +GraphicsPipelineBuilder &GraphicsPipelineBuilder::set_wireframe(const VkBool32 wireframe) { + m_rasterization_sci.polygonMode = (wireframe == VK_TRUE) ? VK_POLYGON_MODE_LINE : VK_POLYGON_MODE_FILL; + return *this; } } // namespace inexor::vulkan_renderer::wrapper::pipelines diff --git a/src/vulkan-renderer/wrapper/pipelines/pipeline_layout.cpp b/src/vulkan-renderer/wrapper/pipelines/pipeline_layout.cpp index 2f5295dae..1b1f76670 100644 --- a/src/vulkan-renderer/wrapper/pipelines/pipeline_layout.cpp +++ b/src/vulkan-renderer/wrapper/pipelines/pipeline_layout.cpp @@ -9,15 +9,27 @@ namespace inexor::vulkan_renderer::wrapper::pipelines { PipelineLayout::PipelineLayout(const Device &device, - const std::span desc_set_layouts, - const std::span push_constant_ranges, - std::string name) + std::string name, + const std::span descriptor_set_layouts, + const std::span push_constant_ranges) : m_device(device), m_name(std::move(name)) { if (m_name.empty()) { throw std::invalid_argument("[PipelineLayout::PipelineLayout] Error: Parameter 'name' is emtpy!"); } + const auto pipeline_layout_ci = wrapper::make_info({ + .setLayoutCount = static_cast(descriptor_set_layouts.size()), + .pSetLayouts = descriptor_set_layouts.data(), + .pushConstantRangeCount = static_cast(push_constant_ranges.size()), + .pPushConstantRanges = push_constant_ranges.data(), + }); + if (const auto result = vkCreatePipelineLayout(m_device.device(), &pipeline_layout_ci, nullptr, &m_pipeline_layout); + result != VK_SUCCESS) { + throw VulkanException("Error: vkCreatePipelineLayout failed for pipeline layout " + m_name + "!", result); + } + + m_device.set_debug_name(m_pipeline_layout, m_name); } PipelineLayout::PipelineLayout(PipelineLayout &&other) noexcept : m_device(other.m_device) { @@ -26,6 +38,7 @@ PipelineLayout::PipelineLayout(PipelineLayout &&other) noexcept : m_device(other } PipelineLayout::~PipelineLayout() { + vkDestroyPipelineLayout(m_device.device(), m_pipeline_layout, nullptr); } } // namespace inexor::vulkan_renderer::wrapper::pipelines diff --git a/src/vulkan-renderer/wrapper/swapchain.cpp b/src/vulkan-renderer/wrapper/swapchain.cpp index d9d592ea2..ea97eb4da 100644 --- a/src/vulkan-renderer/wrapper/swapchain.cpp +++ b/src/vulkan-renderer/wrapper/swapchain.cpp @@ -17,16 +17,19 @@ namespace inexor::vulkan_renderer::wrapper { Swapchain::Swapchain(Device &device, + std::string name, const VkSurfaceKHR surface, const std::uint32_t width, const std::uint32_t height, const bool vsync_enabled) : m_device(device), m_surface(surface), m_vsync_enabled(vsync_enabled) { + m_name = std::move(name); m_img_available = std::make_unique(m_device, "m_img_available"); setup(width, height, vsync_enabled); } Swapchain::Swapchain(Swapchain &&other) noexcept : m_device(other.m_device) { + // TODO: Check me! m_swapchain = std::exchange(other.m_swapchain, VK_NULL_HANDLE); m_surface = std::exchange(other.m_surface, VK_NULL_HANDLE); m_surface_format = other.m_surface_format; @@ -36,11 +39,12 @@ Swapchain::Swapchain(Swapchain &&other) noexcept : m_device(other.m_device) { m_img_available = std::exchange(other.m_img_available, nullptr); m_vsync_enabled = other.m_vsync_enabled; m_img_index = other.m_img_index; + m_name = std::move(other.m_name); } void Swapchain::acquire_next_image_index(const std::uint64_t timeout) { if (const auto result = vkAcquireNextImageKHR(m_device.device(), m_swapchain, timeout, - *m_img_available->semaphore(), VK_NULL_HANDLE, &m_img_index); + m_img_available->m_semaphore, VK_NULL_HANDLE, &m_img_index); result != VK_SUCCESS) { if (result == VK_SUBOPTIMAL_KHR) { // We need to recreate the swapchain