diff --git a/include/inexor/vulkan-renderer/render-graph/graphics_pass.hpp b/include/inexor/vulkan-renderer/render-graph/graphics_pass.hpp index 2b1c48025..d2ee5a887 100644 --- a/include/inexor/vulkan-renderer/render-graph/graphics_pass.hpp +++ b/include/inexor/vulkan-renderer/render-graph/graphics_pass.hpp @@ -21,14 +21,6 @@ namespace inexor::vulkan_renderer::render_graph { // Forward declaration class RenderGraph; -// These using instructions make our life easier -// TODO: The second part of the pair is std::optional because not all buffers are read in some specific shader stage(?) -using BufferRead = std::pair, std::optional>; -using BufferReads = std::vector; -using TextureRead = std::pair, std::optional>; -using TextureReads = std::vector; -using TextureWrites = std::vector>; - using wrapper::descriptors::DescriptorSetLayout; /// A wrapper for graphics passes inside of rendergraph @@ -43,19 +35,18 @@ class GraphicsPass { /// Add members which describe data related to graphics passes here std::function m_on_record{[](auto &) {}}; - /// The buffers the graphics passes reads from - /// If the buffer's ``BufferType`` is ``UNIFORM_BUFFER``, a value for the shader stage flag must be specified, - /// because uniform buffers can be read from vertex or fragment stage bit. - BufferReads m_buffer_reads; - /// The textures the graphics passes reads from - TextureReads m_texture_reads; - /// The textures the graphics passes writes to - TextureWrites m_texture_writes; + /// Enable MSAA for this pass + bool m_enable_msaa{false}; + /// Clear the color attachment + bool m_clear_color_attachment{false}; + /// Clear the stencil attachment + bool m_clear_stencil_attachment{false}; - /// The vertex buffers (will be set by the rendergraph) - std::vector m_vertex_buffers; - /// The index buffer (will be set by the rendergraph) - VkBuffer m_index_buffer{VK_NULL_HANDLE}; + std::weak_ptr m_color_attachment; + std::weak_ptr m_depth_attachment; + std::weak_ptr m_stencil_attachment; + std::weak_ptr m_msaa_color_attachment; + std::weak_ptr m_msaa_depth_attachment; /// The descriptor set layout of the pass (will be created by rendergraph) std::unique_ptr m_descriptor_set_layout; @@ -63,29 +54,21 @@ class GraphicsPass { /// The descriptor set of the pass (will be created by rendergraph) VkDescriptorSet m_descriptor_set{VK_NULL_HANDLE}; - [[nodiscard]] bool has_index_buffer() const noexcept { - return m_index_buffer != VK_NULL_HANDLE; - } - public: - /// Default constructor - /// @param name The name of the graphics pass - /// @param buffer_reads The buffers (vertex-, index-, or uniform buffers) the graphics passes reads from - /// @param texture_reads The textures the graphics passes reads from - /// @param texture_writes The textures the graphics passes writes to - /// @param on_record The function which is called when the command buffer of the passes is being recorded - /// @param clear_screen If specified, ``VkAttachmentLoadOp`` in ``VkRenderingAttachmentInfo`` will be set to - /// ``VK_ATTACHMENT_LOAD_OP_CLEAR``, and the clear values specified here are used (``std::nullopt`` by default, in - /// which case ``VK_ATTACHMENT_LOAD_OP_LOAD`` is used) - /// @exception std::runtime_error More than one index buffer is specified GraphicsPass(std::string name, - BufferReads buffer_reads, - TextureReads texture_reads, - TextureWrites texture_writes, std::function on_record, + std::weak_ptr m_color_attachment, + std::weak_ptr m_depth_attachment, + std::weak_ptr m_stencil_attachment, + std::weak_ptr m_msaa_color_attachment, + std::weak_ptr m_msaa_depth_attachment, + bool enable_msaa, + bool clear_color_attachment, + bool clear_stencil_attachment, std::optional clear_values); GraphicsPass(const GraphicsPass &) = delete; + // TODO: Fix me! GraphicsPass(GraphicsPass &&other) noexcept; ~GraphicsPass() = default; 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 ab8dfbabb..62dbabc50 100644 --- a/include/inexor/vulkan-renderer/render-graph/graphics_pass_builder.hpp +++ b/include/inexor/vulkan-renderer/render-graph/graphics_pass_builder.hpp @@ -20,25 +20,25 @@ namespace inexor::vulkan_renderer::render_graph { /// @warning Make sure that the order or add calls for buffers and textures matches the binding order! class GraphicsPassBuilder { private: - /// Indicates if the screen is cleared at the beginning of this pass - std::optional m_clear_value; /// Add members which describe data related to graphics passes here std::function m_on_record; /// Depth testing - bool m_depth_test; - - /// The buffers which are read by the graphics pass - /// If the buffer's ``BufferType`` is ``UNIFORM_BUFFER``, a value for the shader stage flag must be specified, - /// because uniform buffers can be read from vertex or fragment stage bit. - BufferReads m_buffer_reads; - /// The textures the graphics pass reads from - TextureReads m_texture_reads; - /// The textures the graphics pass writes to - TextureWrites m_texture_writes; - - // TODO: Merge push constant ranges into one block and put it as member here? - // TODO: Copy all data into one piece of memory and call vkCmdPushConstants only once? - void compile_push_constants(); + bool m_enable_depth_test{false}; + /// Multisample anti-aliasing (MSAA) + bool m_enable_msaa{false}; + /// Clear the color attachment + bool m_clear_color{false}; + /// Clear the stencil attachment + bool m_clear_stencil{false}; + /// Indicates if the screen is cleared at the beginning of the pass + VkClearValue m_clear_value{}; + + // TODO: Multiple color attachments! + std::weak_ptr m_color_attachment; + std::weak_ptr m_depth_attachment; + std::weak_ptr m_stencil_attachment; + std::weak_ptr m_msaa_color_attachment; + std::weak_ptr m_msaa_depth_attachment; /// Reset all data of the graphics pass builder void reset(); @@ -53,69 +53,68 @@ class GraphicsPassBuilder { GraphicsPassBuilder &operator=(const GraphicsPassBuilder &) = delete; GraphicsPassBuilder &operator=(GraphicsPassBuilder &&) noexcept; + /// Add a color attachment to the pass + /// @param color_attachment The color attachment + /// @param clear_color The clear color for the color attachment + /// @return A const reference to the this pointer (allowing method calls to be chained) + [[nodiscard]] auto &add_color_attachment(std::weak_ptr color_attachment, + std::optional clear_color = std::nullopt) { + if (color_attachment.expired()) { + throw std::invalid_argument( + "[GraphicsPassBuilder::add_color_attachment] Error: 'color_attachment' is nullptr!"); + } + m_color_attachment = color_attachment; + if (clear_color) { + m_clear_color = true; + m_clear_value.color = clear_color.value(); + } + return *this; + } + /// Build the graphics pass /// @param name The name of the graphics pass /// @return The graphics pass that was just created [[nodiscard]] auto build(std::string name) { - auto graphics_pass = std::make_shared(std::move(name), std::move(m_buffer_reads), - std::move(m_texture_reads), std::move(m_texture_writes), - std::move(m_on_record), std::move(m_clear_value)); - // Don't forget to reset the builder automatically before returning the graphics pass that was just created + auto graphics_pass = std::make_shared( + std::move(name), std::move(m_on_record), std::move(m_color_attachment), std::move(m_depth_attachment), + std::move(m_stencil_attachment), std::move(m_msaa_color_attachment), std::move(m_msaa_depth_attachment), + m_enable_msaa, m_clear_color, m_clear_stencil, std::move(m_clear_value)); + + // Reset the builder so the builder can be re-used reset(); // Return the graphics pass that was created return graphics_pass; } - // TODO: We must specify buffer reads for vertex and index buffers, but bind manually... is that good? - // TODO: std::optional or better default VkShaderStageFlagBits to VK_SHADER_STAGE_VERTEX_BIT? - - /// Specify that the pass reads from a buffer - /// @param buffer The buffer the pass reads from - /// @param shader_stage The shader stage the buffer is read from + /// Enable depth testing for the pass + /// @param depth_buffer /// @return A const reference to the this pointer (allowing method calls to be chained) - [[nodiscard]] auto &reads_from_buffer(std::weak_ptr buffer, - std::optional shader_stage = std::nullopt) { - if (buffer.expired()) { - throw std::invalid_argument("[GraphicsPassBuilder::reads_from_buffer] Error: buffer is nullptr!"); + [[nodiscard]] auto &enable_depth_test(std::weak_ptr depth_attachment) { + if (depth_attachment.expired()) { + throw std::invalid_argument("[GraphicsPassBuilder::enable_depth_test] Error: 'depth_buffer' is nullptr!"); } - m_buffer_reads.emplace_back(std::move(buffer), shader_stage); + m_enable_depth_test = true; + m_depth_attachment = depth_attachment; return *this; } - /// Specify that the pass reads from a texture - /// @param texture The texture the pass reads from - /// @param shader_stage The shader stage the texture is read from + /// Enable multisample anti-aliasing (MSAA) for the pass + /// @param sample_count The MSAA sample count + /// @param msaa_back_attachment The MSAA attachment + /// @param msaa_depth_attachment The MSAA depth attachment /// @return A const reference to the this pointer (allowing method calls to be chained) - [[nodiscard]] auto &reads_from_texture(std::weak_ptr texture, - std::optional shader_stage = std::nullopt) { - if (texture.expired()) { - throw std::invalid_argument("[GraphicsPassBuilder::reads_from_texture] Error: texture is nullptr!"); + [[nodiscard]] auto &enable_msaa(VkSampleCountFlagBits sample_count, + std::weak_ptr msaa_back_attachment, + std::weak_ptr msaa_depth_attachment) { + if (msaa_back_attachment.expired()) { + throw std::invalid_argument("[GraphicsPassBuilder::enable_msaa] Error: 'msaa_back_buffer' is nullptr!"); } - m_texture_reads.emplace_back(std::move(texture), shader_stage); - return *this; - } - - /// Specify that the pass writes to a texture - /// @param texture The texture the pass writes to - /// @return A const reference to the this pointer (allowing method calls to be chained) - [[nodiscard]] auto &writes_to_texture(std::weak_ptr texture) { - m_texture_writes.emplace_back(texture); - return *this; - } - - /// Set the clear status for the pass - /// @param clear_value The clear value for color and depth - /// @return A const reference to the this pointer (allowing method calls to be chained) - [[nodiscard]] auto &set_clear_value(VkClearValue clear_value) { - m_clear_value = clear_value; - return *this; - } - - /// Enable or disable depth testing - /// @param depth_test ``true`` if depth testing is enabled for this pass - /// @return A const reference to the this pointer (allowing method calls to be chained) - [[nodiscard]] auto &set_depth_test(bool depth_test) { - m_depth_test = depth_test; + if (msaa_depth_attachment.expired()) { + throw std::invalid_argument("[GraphicsPassBuilder::enable_msaa] Error: 'msaa_depth_buffer' is nullptr!"); + } + m_enable_msaa = true; + m_msaa_color_attachment = msaa_back_attachment; + m_msaa_depth_attachment = msaa_depth_attachment; return *this; } diff --git a/include/inexor/vulkan-renderer/render-graph/render_graph.hpp b/include/inexor/vulkan-renderer/render-graph/render_graph.hpp index ae53e74c6..efb0bbb41 100644 --- a/include/inexor/vulkan-renderer/render-graph/render_graph.hpp +++ b/include/inexor/vulkan-renderer/render-graph/render_graph.hpp @@ -132,6 +132,9 @@ class RenderGraph { // TODO: Add @exception to documentation of other methods/code parts! + /// Allocate the descriptor sets + void allocate_descriptor_sets(); + /// The rendergraph must not have any cycles in it! /// @exception std::logic_error The rendergraph is not acyclic! void check_for_cycles(); @@ -140,6 +143,7 @@ class RenderGraph { /// @param cmd_buf The command buffer to record into void create_buffers(); + /// Create the descriptor set layouts void create_descriptor_set_layouts(); /// Create the graphics passes @@ -175,12 +179,12 @@ class RenderGraph { /// @note If a uniform buffer has been updated, an update of the associated descriptor set will be performed void update_buffers(); - /// Update dynamic textures - void update_textures(); - /// Update the descriptor sets void update_descriptor_sets(); + /// Update dynamic textures + void update_textures(); + /// Make sure all required resources are specified so rendergraph is ready to be compiled void validate_render_graph(); @@ -219,9 +223,6 @@ class RenderGraph { [[nodiscard]] std::shared_ptr add_buffer(std::string buffer_name, BufferType buffer_type, std::function on_update); - /// - void allocate_descriptor_sets(); - /// Add a descriptor to rendergraph /// @param on_create_descriptor_set_layout /// @param on_allocate_descriptor_set diff --git a/include/inexor/vulkan-renderer/renderers/imgui.hpp b/include/inexor/vulkan-renderer/renderers/imgui.hpp index 5615e0f24..7c5d81664 100644 --- a/include/inexor/vulkan-renderer/renderers/imgui.hpp +++ b/include/inexor/vulkan-renderer/renderers/imgui.hpp @@ -60,6 +60,8 @@ class ImGuiRenderer { /// It will be called at the beginning of set_on_update std::function m_on_update_user_data{[]() {}}; + std::weak_ptr m_color_attachment; + void load_font_data_from_file(); /// Customize ImGui style like text color for example @@ -76,8 +78,7 @@ class ImGuiRenderer { ImGuiRenderer(const Device &device, const Swapchain &swapchain, render_graph::RenderGraph &render_graph, - std::weak_ptr back_buffer, - std::weak_ptr depth_buffer, + std::weak_ptr color_attachment, std::function on_update_user_data); ImGuiRenderer(const ImGuiRenderer &) = delete; diff --git a/include/inexor/vulkan-renderer/wrapper/commands/command_buffer.hpp b/include/inexor/vulkan-renderer/wrapper/commands/command_buffer.hpp index b7f71d1f9..5fc83abfd 100644 --- a/include/inexor/vulkan-renderer/wrapper/commands/command_buffer.hpp +++ b/include/inexor/vulkan-renderer/wrapper/commands/command_buffer.hpp @@ -85,7 +85,7 @@ class CommandBuffer { /// @note ``begin_render_pass`` has been deprecated because of dynamic rendering (``VK_KHR_dynamic_rendering``) /// @param rendering_info The info for dynamic rendering /// @return A const reference to the this pointer (allowing method calls to be chained) - const CommandBuffer &begin_rendering(const VkRenderingInfo *rendering_info) const; + const CommandBuffer &begin_rendering(const VkRenderingInfo &rendering_info) const; /// Call vkCmdBindDescriptorSets /// @param desc_sets The descriptor set to bind diff --git a/include/inexor/vulkan-renderer/wrapper/pipelines/pipeline.hpp b/include/inexor/vulkan-renderer/wrapper/pipelines/pipeline.hpp index 0f956b02f..ebf9ad0af 100644 --- a/include/inexor/vulkan-renderer/wrapper/pipelines/pipeline.hpp +++ b/include/inexor/vulkan-renderer/wrapper/pipelines/pipeline.hpp @@ -58,18 +58,8 @@ class GraphicsPipeline { GraphicsPipeline &operator=(const GraphicsPipeline &) = delete; GraphicsPipeline &operator=(GraphicsPipeline &&) = delete; - // TODO: Make private and use friend declaration only? - - [[nodiscard]] auto &descriptor_set_layouts() const { - return m_descriptor_set_layouts; - } - - [[nodiscard]] auto &name() const { - return m_name; - } - - [[nodiscard]] auto &push_constant_ranges() const { - return m_push_constant_ranges; + [[nodiscard]] auto pipeline_layout() const { + return m_pipeline_layout; } }; diff --git a/src/vulkan-renderer/application.cpp b/src/vulkan-renderer/application.cpp index da161811a..323ae2074 100644 --- a/src/vulkan-renderer/application.cpp +++ b/src/vulkan-renderer/application.cpp @@ -503,6 +503,7 @@ void Application::setup_render_graph() { .inputRate = VK_VERTEX_INPUT_RATE_VERTEX, }, }) + .set_multisampling(m_device->get_max_usable_sample_count(), 1.0f) .add_default_color_blend_attachment() .add_color_attachment(m_swapchain->image_format()) .set_depth_attachment_format(VK_FORMAT_D32_SFLOAT_S8_UINT) @@ -533,16 +534,16 @@ void Application::setup_render_graph() { using wrapper::commands::CommandBuffer; m_render_graph->add_graphics_pass([&](GraphicsPassBuilder &graphics_pass_builder) { m_octree_pass = - graphics_pass_builder - .set_clear_value({ - // TODO: Define default color values for these, like COLOR::RED, COLOR::BLUE... - .color = {1.0f, 0.0f, 0.0f}, - }) - .set_depth_test(true) + graphics_pass_builder.add_color_attachment(m_back_buffer, VkClearColorValue{1.0f, 0.0f, 0.0f, 1.0f}) + .enable_depth_test(m_depth_buffer) + .enable_msaa(m_device->get_max_usable_sample_count(), m_msaa_color, m_msaa_depth) .set_on_record([&](const CommandBuffer &cmd_buf) { - // Record the command buffer for rendering the octree - cmd_buf - .bind_pipeline(m_octree_pipeline) + // NOTE: It's the responsibility of the programmer to bind pipelines, descriptor sets, + // and buffers manually inside of this on_record lambda! It's also the responsibility of + // the programmer to make sure that every variable captured by reference inside of this + // lambda is still in a valid state at the time of execution of the lambda! + cmd_buf.bind_pipeline(m_octree_pipeline) + .bind_descriptor_set(m_descriptor_set, m_octree_pipeline->pipeline_layout()) // TODO: Binding vertex buffer must automatically respect current swapchain img index! .bind_vertex_buffer(m_vertex_buffer) // TODO: Binding index buffer must automatically respect current swapchain img index! @@ -550,22 +551,13 @@ void Application::setup_render_graph() { // TODO: Automatically respect current swapchain img index! .draw_indexed(static_cast(m_octree_indices.size())); }) - // TOOD: Even simpler API like .reads_from() and .writes_to() without templates (just overloading)? - // TODO: Since we don't bind vertex or index buffers, do we even need these calls to reads_from_buffer? - // TODO: We could imagine some API where we have vertex_buffer.bind_me() in on_record and then check - // which ones were not bound - .reads_from_buffer(m_index_buffer) - .reads_from_buffer(m_vertex_buffer) - .reads_from_buffer(m_uniform_buffer, VK_SHADER_STAGE_VERTEX_BIT) - .writes_to_texture(m_back_buffer) - .writes_to_texture(m_depth_buffer) .build("Octree"); return m_octree_pass; }); // TODO: We don't need to recreate the imgui overlay when swapchain is recreated, use a .recreate() method instead? - m_imgui_overlay = std::make_unique( - *m_device, *m_swapchain, *m_render_graph.get(), m_back_buffer, m_msaa_color, [&]() { update_imgui_overlay(); }); + // m_imgui_overlay = std::make_unique(*m_device, *m_swapchain, *m_render_graph.get(), + // m_back_buffer, [&]() { update_imgui_overlay(); }); m_render_graph->compile(); } diff --git a/src/vulkan-renderer/render-graph/graphics_pass.cpp b/src/vulkan-renderer/render-graph/graphics_pass.cpp index 2b1ecb6c2..e4066b76b 100644 --- a/src/vulkan-renderer/render-graph/graphics_pass.cpp +++ b/src/vulkan-renderer/render-graph/graphics_pass.cpp @@ -6,40 +6,29 @@ namespace inexor::vulkan_renderer::render_graph { GraphicsPass::GraphicsPass(std::string name, - BufferReads buffer_reads, - TextureReads texture_reads, - TextureWrites texture_writes, std::function on_record, + std::weak_ptr color_attachment, + std::weak_ptr depth_attachment, + std::weak_ptr stencil_attachment, + std::weak_ptr msaa_color_attachment, + std::weak_ptr msaa_depth_attachment, + bool enable_msaa, + bool clear_color_attachment, + bool clear_stencil_attachment, std::optional clear_values) - : m_name(std::move(name)), m_buffer_reads(std::move(buffer_reads)), m_texture_reads(std::move(texture_reads)), - m_texture_writes(std::move(texture_writes)), m_on_record(std::move(on_record)), - m_clear_values(std::move(clear_values)) { - // Make sure there is no more than one index buffer (or none) - bool index_buffer_present = false; - for (const auto buffer : m_buffer_reads) { - // Is this buffer resource an index buffer? - if (buffer.first.lock()->m_buffer_type == BufferType::INDEX_BUFFER) { - // Is an index buffer already specified? - if (index_buffer_present) { - throw std::runtime_error("Error: More than one index buffer in graphics pass " + m_name + "!"); - } - // This was the first index buffer we found - index_buffer_present = true; - } - } -} + : m_name(std::move(name)), m_on_record(std::move(on_record)), m_color_attachment(std::move(color_attachment)), + m_depth_attachment(std::move(depth_attachment)), m_stencil_attachment(std::move(stencil_attachment)), + m_msaa_color_attachment(std::move(msaa_color_attachment)), m_enable_msaa(enable_msaa), + m_msaa_depth_attachment(msaa_depth_attachment), m_clear_color_attachment(clear_color_attachment), + m_clear_stencil_attachment(clear_stencil_attachment), m_clear_values(std::move(clear_values)) {} GraphicsPass::GraphicsPass(GraphicsPass &&other) noexcept { m_name = std::move(other.m_name); m_clear_values = other.m_clear_values; m_on_record = std::move(other.m_on_record); - m_buffer_reads = std::move(other.m_buffer_reads); - m_texture_reads = std::move(other.m_texture_reads); - m_texture_writes = std::move(other.m_texture_writes); - m_index_buffer = std::exchange(other.m_index_buffer, VK_NULL_HANDLE); - m_vertex_buffers = std::move(other.m_vertex_buffers); m_descriptor_set_layout = std::exchange(other.m_descriptor_set_layout, nullptr); m_descriptor_set = std::exchange(other.m_descriptor_set, VK_NULL_HANDLE); + m_enable_msaa = other.m_enable_msaa; } } // 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 23edf90c9..1ab533c68 100644 --- a/src/vulkan-renderer/render-graph/graphics_pass_builder.cpp +++ b/src/vulkan-renderer/render-graph/graphics_pass_builder.cpp @@ -7,12 +7,12 @@ GraphicsPassBuilder::GraphicsPassBuilder() { } void GraphicsPassBuilder::reset() { - m_clear_value = std::nullopt; + m_clear_value = {}; m_on_record = [](auto &) {}; - m_depth_test = false; - m_buffer_reads.clear(); - m_texture_reads.clear(); - m_texture_writes.clear(); + m_enable_depth_test = false; + m_enable_msaa = false; + m_clear_color = false; + m_clear_stencil = false; } // TODO: Move stuff to .cpp file again. Header files should contain declarations, cpp files should contain definitions! diff --git a/src/vulkan-renderer/render-graph/render_graph.cpp b/src/vulkan-renderer/render-graph/render_graph.cpp index 2c897b627..d89d40696 100644 --- a/src/vulkan-renderer/render-graph/render_graph.cpp +++ b/src/vulkan-renderer/render-graph/render_graph.cpp @@ -112,60 +112,85 @@ void RenderGraph::create_graphics_pipelines() { } void RenderGraph::create_textures() { + /// The following code should not be part of texture wrapper because its only purpose is to fill VkImageCreateInfo + /// and VkImageViewCreateInfo in the code below to make it shorter. + auto fill_image_ci = [&](const VkFormat format, const VkImageUsageFlags image_usage, + const VkSampleCountFlagBits sample_count = VK_SAMPLE_COUNT_1_BIT) { + return wrapper::make_info({ + .imageType = VK_IMAGE_TYPE_2D, + .format = format, + .extent = + { + .width = static_cast(m_swapchain.extent().width), + .height = static_cast(m_swapchain.extent().height), + .depth = 1, + }, + .mipLevels = 1, + .arrayLayers = 1, + .samples = sample_count, + .tiling = VK_IMAGE_TILING_OPTIMAL, + .usage = image_usage, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + }); + }; + + auto fill_image_view_ci = [&](const VkFormat format, const VkImageAspectFlags aspect_flags) { + return wrapper::make_info({ + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = format, + .subresourceRange = + { + .aspectMask = aspect_flags, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }); + }; + m_device.execute("[RenderGraph::create_textures]", [&](const CommandBuffer &cmd_buf) { for (const auto &texture : m_textures) { switch (texture->m_usage) { case TextureUsage::NORMAL: { if (texture->m_on_init) { std::invoke(texture->m_on_init.value()); + // TODO: How to unify ->create()? texture->create(); texture->update(cmd_buf); } break; } - case TextureUsage::DEPTH_STENCIL_BUFFER: + case TextureUsage::DEPTH_STENCIL_BUFFER: { + texture->create( + fill_image_ci(texture->m_format, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT), + fill_image_view_ci(texture->m_format, VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)); + break; + } case TextureUsage::BACK_BUFFER: { + texture->create(fill_image_ci(texture->m_format, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT), + fill_image_view_ci(texture->m_format, VK_IMAGE_ASPECT_COLOR_BIT)); + break; + } + case TextureUsage::MSAA_BACK_BUFFER: { + texture->create(fill_image_ci(texture->m_format, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + // TODO: Expose this as a parameter + // NOTE: We use the highest available sample count for MSAA + m_device.get_max_usable_sample_count()), + fill_image_view_ci(texture->m_format, VK_IMAGE_ASPECT_COLOR_BIT)); + break; + } + case TextureUsage::MSAA_DEPTH_STENCIL_BUFFER: { texture->create( - wrapper::make_info({ - .imageType = VK_IMAGE_TYPE_2D, - .format = texture->m_format, - .extent = - { - .width = static_cast(m_swapchain.extent().width), - .height = static_cast(m_swapchain.extent().height), - .depth = 1, - }, - .mipLevels = 1, - .arrayLayers = 1, - .samples = VK_SAMPLE_COUNT_1_BIT, - .tiling = VK_IMAGE_TILING_OPTIMAL, - .usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, - .sharingMode = VK_SHARING_MODE_EXCLUSIVE, - .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, - }), - wrapper::make_info({ - .viewType = VK_IMAGE_VIEW_TYPE_2D, - .format = texture->m_format, - .subresourceRange = - { - // NOTE: This is the only difference between DEPTH_STENCIL_BUFFER and BACK_BUFFER - .aspectMask = (texture->m_usage == TextureUsage::DEPTH_STENCIL_BUFFER) - ? static_cast(VK_IMAGE_ASPECT_DEPTH_BIT | - VK_IMAGE_ASPECT_STENCIL_BIT) - : VK_IMAGE_ASPECT_COLOR_BIT, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1, - }, - })); + fill_image_ci(texture->m_format, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, + // TODO: Expose this as a parameter + // NOTE: We use the highest available sample count for MSAA + m_device.get_max_usable_sample_count()), + fill_image_view_ci(texture->m_format, VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)); break; } default: { - // TODO: - // MSAA_BACK_BUFFER - // MSAA_DEPTH_STENCIL_BUFFER - m_log->warn("Unhandled switch case for creating texture {}", texture->m_name); break; } } @@ -188,106 +213,74 @@ void RenderGraph::record_command_buffer_for_pass(const CommandBuffer &cmd_buf, // TODO: Or do we need the image index for buffers? (We want to automatically double or triple buffer them) // Start a new debug label for this graphics pass (visible in graphics debuggers like RenderDoc) - // TODO: Generate color gradient depending on the number of passes? (Interpolate e.g. in 12 steps for 12 passes) + // TODO: Generate color gradient? cmd_buf.begin_debug_label_region(pass.m_name, {1.0f, 0.0f, 0.0f, 1.0f}); - // If this is the first graphics pass, we need to transform the swapchain image, which comes back in undefined - // layout after presenting + // If this is the first graphics pass, change the image layout of the swapchain image which comes back in undefined + // image layout after presenting if (is_first_pass) { cmd_buf.change_image_layout(m_swapchain.image(img_index), VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); } - // TODO: We need a really clever way to make MSAA easy here - - // TODO: FIX ME! - VkImageView resolve_color{VK_NULL_HANDLE}; - const auto color_attachment = wrapper::make_info({ - .imageView = resolve_color, + .imageView = + (pass.m_enable_msaa) ? pass.m_msaa_color_attachment.lock()->m_img_view : m_swapchain.image_view(img_index), .imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, .resolveMode = VK_RESOLVE_MODE_AVERAGE_BIT, - // TODO: Remove img_index and implement swapchain.get_current_image() .resolveImageView = m_swapchain.image_view(img_index), .resolveImageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, - .loadOp = pass.m_clear_values ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD, + .loadOp = (pass.m_clear_color_attachment) ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD, .storeOp = VK_ATTACHMENT_STORE_OP_STORE, - .clearValue = pass.m_clear_values.value().color, + .clearValue = pass.m_clear_values.value(), }); - // TODO: FIX ME! - VkImageView resolve_depth{VK_NULL_HANDLE}; - VkImageView depth_buffer{depth_buffer}; - -#if 0 const auto depth_attachment = wrapper::make_info({ - .imageView = resolve_depth, + .imageView = + (pass.m_enable_msaa) ? pass.m_msaa_depth_attachment.lock()->m_img_view : m_swapchain.image_view(img_index), .imageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, .resolveMode = VK_RESOLVE_MODE_MIN_BIT, - .resolveImageView = depth_buffer, + .resolveImageView = pass.m_depth_attachment.lock()->m_img_view, .resolveImageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, - .loadOp = pass.m_clear_values ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD, + .loadOp = (pass.m_clear_stencil_attachment) ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD, .storeOp = VK_ATTACHMENT_STORE_OP_STORE, - .clearValue = pass.m_clear_values.value().depthStencil, + .clearValue = pass.m_clear_values.value(), }); + // TODO: Implement stencil attachment + const auto rendering_info = wrapper::make_info({ - .renderingArea = + .renderArea = { - m_swapchain.extent(), + .extent = m_swapchain.extent(), }, .layerCount = 1, - .colorAttachmentCount = 1, + .colorAttachmentCount = 1, // TODO: Implement multiple color attachments .pColorAttachments = &color_attachment, - // TODO: depth and stencil attachment + .pDepthAttachment = &depth_attachment, + // TODO: Implement stencil attachment }); - // Start dynamic rendering (we are no longer using renderpasses) - cmd_buf.begin_rendering(&rendering_info); - - // Bind the vertex buffers of the pass - // Note that vertex buffers are optional, meaning the user can either give vertex buffers - if (!pass.m_vertex_buffers.empty()) { - cmd_buf.bind_vertex_buffers(pass.m_vertex_buffers); - } + // Start dynamic rendering + cmd_buf.begin_rendering(rendering_info); - // Bind an index buffer if any is present - if (pass.has_index_buffer()) { - // Note that in Vulkan you can have a variable number of vertex buffers, but only one index buffer bound - cmd_buf.bind_index_buffer(pass.m_index_buffer); - } - -#endif - - // Call the custom command buffer recording function of the graphics pass + // Call the command buffer recording function of the graphics pass std::invoke(pass.m_on_record, cmd_buf); // End dynamic rendering cmd_buf.end_rendering(); - // If this is the last graphics pass, change the image layout of the back buffer for presenting + // If this is the last graphics pass, change the image layout of the swapchain image for presenting if (is_last_pass) { cmd_buf.change_image_layout(m_swapchain.image(img_index), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); } - - // End the debug label for this graphics pass (visible in graphics debuggers like RenderDoc) + // End the debug label for this graphics pass cmd_buf.end_debug_label_region(); } void RenderGraph::record_command_buffers(const CommandBuffer &cmd_buf, const std::uint32_t img_index) { - // TODO: Find the balance between recording all passes into one big command buffer vs. recording one command - // buffer per pass. Recording one command buffer per pass would allow us to do this in parallel using - // taskflow. Assuming the programmer takes responsibility for synchronization of all on_record functions he - // provides, there should be a performance benefit from recording command buffers in parallel. On the other - // hand, each command buffer introduces new overhead (maybe even when all command buffers are submitted in - // batch). Another solution would be to let the user request a command buffer manually and to let him - // specify which command buffer to use in which pass. Combined with the user-defined order of passes, this - // will give more flexibility. - - // Loop through all graphics passes and record their command buffer for (std::size_t pass_index = 0; pass_index < m_graphics_passes.size(); pass_index++) { - // This is important to know because of image layout transitions for back buffer for example const bool is_first_pass = (pass_index == 0); const bool is_last_pass = (pass_index == (m_graphics_passes.size() - 1)); record_command_buffer_for_pass(cmd_buf, *m_graphics_passes[pass_index], is_first_pass, is_last_pass, img_index); diff --git a/src/vulkan-renderer/renderers/imgui.cpp b/src/vulkan-renderer/renderers/imgui.cpp index 43cfbcc72..a2a3a568d 100644 --- a/src/vulkan-renderer/renderers/imgui.cpp +++ b/src/vulkan-renderer/renderers/imgui.cpp @@ -15,10 +15,10 @@ namespace inexor::vulkan_renderer::renderers { ImGuiRenderer::ImGuiRenderer(const Device &device, const Swapchain &swapchain, render_graph::RenderGraph &render_graph, - const std::weak_ptr back_buffer, - const std::weak_ptr depth_buffer, + std::weak_ptr color_attachment, std::function on_update_user_data) - : m_device(device), m_on_update_user_data(std::move(on_update_user_data)) { + : m_device(device), m_on_update_user_data(std::move(on_update_user_data)), + m_color_attachment(std::move(color_attachment)) { spdlog::trace("Creating ImGui context"); ImGui::CreateContext(); @@ -160,23 +160,21 @@ ImGuiRenderer::ImGuiRenderer(const Device &device, using render_graph::GraphicsPassBuilder; using wrapper::commands::CommandBuffer; render_graph.add_graphics_pass([&](GraphicsPassBuilder &graphics_pass_builder) { - m_imgui_pass = graphics_pass_builder.reads_from_buffer(m_index_buffer) - .reads_from_buffer(m_vertex_buffer) - .reads_from_texture(m_imgui_texture, VK_SHADER_STAGE_FRAGMENT_BIT) - .writes_to_texture(back_buffer) - .writes_to_texture(depth_buffer) - .writes_to_texture(back_buffer) - .writes_to_texture(depth_buffer) + m_imgui_pass = graphics_pass_builder.add_color_attachment(m_color_attachment) .set_on_record([&](const CommandBuffer &cmd_buf) { - // Record the command buffer for rendering ImGui + // NOTE: It's the responsibility of the programmer to bind pipelines, descriptor sets, + // and buffers manually inside of this on_record lambda! It's also the responsibility of + // the programmer to make sure that every variable captured by reference inside of this + // lambda is still in a valid state at the time of execution of the lambda! const ImGuiIO &io = ImGui::GetIO(); m_push_const_block.scale = glm::vec2(2.0f / io.DisplaySize.x, 2.0f / io.DisplaySize.y); - cmd_buf.bind_pipeline(m_imgui_pipeline); - // TODO: Bind descriptor set! - // cmd_buf.bind_descriptor_set(); - // TODO: Push constant! - // cmd_buf.push_constant(); + cmd_buf.bind_pipeline(m_imgui_pipeline) + .bind_vertex_buffer(m_vertex_buffer) + .bind_index_buffer(m_index_buffer) + .bind_descriptor_set(m_descriptor_set, m_imgui_pipeline->pipeline_layout()) + .push_constant(m_imgui_pipeline->pipeline_layout(), m_push_const_block, + VK_SHADER_STAGE_VERTEX_BIT); ImDrawData *draw_data = ImGui::GetDrawData(); if (draw_data == nullptr) { diff --git a/src/vulkan-renderer/wrapper/commands/command_buffer.cpp b/src/vulkan-renderer/wrapper/commands/command_buffer.cpp index 2e7ce44db..fb2bee000 100644 --- a/src/vulkan-renderer/wrapper/commands/command_buffer.cpp +++ b/src/vulkan-renderer/wrapper/commands/command_buffer.cpp @@ -59,9 +59,8 @@ const CommandBuffer &CommandBuffer::begin_debug_label_region(std::string name, s return *this; } -const CommandBuffer &CommandBuffer::begin_rendering(const VkRenderingInfo *rendering_info) const { - assert(rendering_info); - vkCmdBeginRendering(m_cmd_buf, rendering_info); +const CommandBuffer &CommandBuffer::begin_rendering(const VkRenderingInfo &rendering_info) const { + vkCmdBeginRendering(m_cmd_buf, &rendering_info); return *this; }; @@ -107,7 +106,7 @@ const CommandBuffer &CommandBuffer::bind_vertex_buffer(const std::shared_ptrm_name + " is not a vertex buffer!"); } - vkCmdBindVertexBuffers(m_cmd_buf, 0, 1, &buffer->m_buffer, 0); + vkCmdBindVertexBuffers(m_cmd_buf, 0, 1, &buffer->m_buffer, std::vector(1, 0).data()); return *this; }