From 2e437e0c36f0a5322635a6567cb12768aa4769a0 Mon Sep 17 00:00:00 2001 From: Johannes Unterguggenberger Date: Fri, 12 Apr 2024 13:54:45 +0200 Subject: [PATCH] Dynamic rendering (#92) * Modified graphics pipeline so that it can now be created with dynamic rendering * Use KHR types to fix build issues with Vulkan 1.2 * Added begin and end rendering commands * Fix bugs in begin_dynamic_rendering() * Apply suggestions from code review Changing local variables naming to lowerCamelCase Co-authored-by: Johannes Unterguggenberger * Add fixes to issues raised in pr review: - removed dynamic_rendering_attachment - added declare_dynamic(_for) functions to avk::attachment which create dynamic rendering attachments - changed graphics_pipeline_t::renderpass_pointer() back to renderpass_reference() which now returns a reference wrapper * PR proposed fixes (mostly cosmetic) + inital draft of MSAA support (needs to be discussed further) * Modified dynamic rendering attachments to take in subpass_usages and modified the dynamic functionality to conform to these usages * Added View mask to begin dynamic rendering parameters * Trying to fix GitHub workflows * Trying to fix GitHub workflows * Using Invoke-WebRequest instead of curl * Reverted the last change * I have no idea what I'm doing * Moving two vulkan-hpp includes a bit down, namely: #include #include * Testing ["latest", "1.3.216.0", "1.2.198.1"] also in windows-latest * Using vk::ImageLayout::eDepthStencilAttachmentOptimal instead of vk::ImageLayout::eAttachmentOptimal * Disable VMA=ON on Linux with 1.2 SDK --------- Co-authored-by: MatejSakmary --- .github/workflows/build.yml | 49 +-- CMakeLists.txt | 5 +- README.md | 2 +- include/avk/attachment.hpp | 32 ++ include/avk/avk.hpp | 47 ++- include/avk/commands.hpp | 26 ++ include/avk/graphics_pipeline.hpp | 26 +- include/avk/graphics_pipeline_config.hpp | 19 +- src/avk.cpp | 391 +++++++++++++++++++++-- 9 files changed, 523 insertions(+), 74 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 24f4244..fd1c4b7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,19 +28,19 @@ jobs: #} - { name: "linux: gcc", - cc: "gcc-10", - cxx: "g++-10" + cc: "gcc-13", + cxx: "g++-13" } # note: if a specific vulkan version (e.g. 1.1.x, or < 1.2.135) needs testing, you can add it here: # (not that ubuntu-latest (20.04) only supports >= v1.2.148 via apt) - vulkan-sdk: ["latest", "1.3.204", "1.3.216"] + vulkan-sdk: ["latest", "1.3.216", "1.2.198"] vma: ["ON", "OFF"] exclude: # exclude combinations that are known to fail - - vulkan-sdk: "1.3.204" + - vulkan-sdk: "1.2.198" vma: "ON" steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Create Build Environment shell: bash @@ -55,13 +55,18 @@ jobs: sudo wget -qO /etc/apt/sources.list.d/lunarg-vulkan-${{ matrix.vulkan-sdk }}-focal.list https://packages.lunarg.com/vulkan/${{ matrix.vulkan-sdk }}/lunarg-vulkan-${{ matrix.vulkan-sdk }}-focal.list fi + # For GCC-13 + sudo add-apt-repository -y ppa:ubuntu-toolchain-r/ppa + # Update package lists sudo apt-get update -qq # Install dependencies sudo apt-get install -y \ - vulkan-sdk - + vulkan-sdk \ + g++-13 \ + gcc-13 + # clang does not yet (<= 12) support "Down with typename" (P0634R3), which is used in libstdc++ >= 11 in the ranges-header if [ "${{ matrix.config.cc }}" = "clang" ]; then sudo apt remove libgcc-11-dev gcc-11 @@ -86,7 +91,7 @@ jobs: shell: bash working-directory: ${{ runner.workspace }}/build # Execute the build. You can specify a specific target with "--target " - run: cmake --build . --config $BUILD_TYPE + run: cmake --build . windows: name: ${{ matrix.config.name }}, windows-2019, VulkanSDK ${{ matrix.vulkan-sdk }}, VMA=${{ matrix.vma }} @@ -103,18 +108,18 @@ jobs: cxx: "cl" } # note: if a specific vulkan version needs testing, you can add it here: - vulkan-sdk: ["latest", "1.3.204.1", "1.3.216.0"] + vulkan-sdk: ["latest", "1.3.216.0", "1.2.198.1"] vma: ["ON", "OFF"] exclude: # exclude combinations that are known to fail - vulkan-sdk: "1.2.198" vma: "ON" steps: - # apparently checkout@v2 pulls to Auto-Vk/Auto-Vk on windows - - uses: actions/checkout@v2 + # apparently checkout@v3 pulls to Auto-Vk/Auto-Vk on windows + - uses: actions/checkout@v3 - name: Create Build Environment - # apparently checkout@v2 pulls to Auto-Vk/Auto-Vk on windows + # apparently checkout@v3 pulls to Auto-Vk/Auto-Vk on windows working-directory: ${{ runner.workspace }}/${{ github.event.repository.name }} shell: pwsh # Some projects don't allow in-source building, so create a separate build directory @@ -155,10 +160,10 @@ jobs: - name: Build shell: bash - # apparently checkout@v2 pulls to Auto-Vk/Auto-Vk on windows + # apparently checkout@v3 pulls to Auto-Vk/Auto-Vk on windows working-directory: ${{ runner.workspace }}/${{ github.event.repository.name }}/build # Execute the build. You can specify a specific target with "--target " - run: cmake --build . --config $BUILD_TYPE + run: VULKAN_SDK=${{ env.vulkan_sdk }} cmake --build . --config $BUILD_TYPE windows-latest: name: ${{ matrix.config.name }}, windows-latest, VulkanSDK ${{ matrix.vulkan-sdk }}, VMA=${{ matrix.vma }} @@ -175,18 +180,18 @@ jobs: cxx: "cl" } # note: if a specific vulkan version needs testing, you can add it here: - vulkan-sdk: ["latest", "1.3.204.1", "1.3.216.0"] + vulkan-sdk: ["latest", "1.3.216.0", "1.2.198.1"] vma: ["ON", "OFF"] exclude: # exclude combinations that are known to fail - vulkan-sdk: "1.2.198" vma: "ON" steps: - # apparently checkout@v2 pulls to Auto-Vk/Auto-Vk on windows - - uses: actions/checkout@v2 + # apparently checkout@v3 pulls to Auto-Vk/Auto-Vk on windows + - uses: actions/checkout@v3 - name: Create Build Environment - # apparently checkout@v2 pulls to Auto-Vk/Auto-Vk on windows + # apparently checkout@v3 pulls to Auto-Vk/Auto-Vk on windows working-directory: ${{ runner.workspace }}/${{ github.event.repository.name }} shell: pwsh # Some projects don't allow in-source building, so create a separate build directory @@ -202,7 +207,7 @@ jobs: mkdir "${{ env.vulkan_sdk }}Include/vma/" curl -LS -o "${{ env.vulkan_sdk }}Include/vma/vk_mem_alloc.h" https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator/blob/master/include/vk_mem_alloc.h?raw=true - # apparently checkout@v2 pulls to Auto-Vk/Auto-Vk on windows + # apparently checkout@v3 pulls to Auto-Vk/Auto-Vk on windows cmake -E make_directory ${{ runner.workspace }}/${{ github.event.repository.name }}/build - name: Configure CMake @@ -217,17 +222,15 @@ jobs: run: | $env:CC="${{ matrix.config.cc }}" $env:CXX="${{ matrix.config.cxx }}" - $env:Path += ";${{ env.vulkan_sdk }}\;${{ env.vulkan_sdk }}\Bin\" $env:VULKAN_SDK="${{ env.vulkan_sdk }}" $env:Vulkan_LIBRARY="${{ env.vulkan_sdk }}/Bin" $env:Vulkan_INCLUDE_DIR="${{ env.vulkan_sdk }}/Include" - - # apparently checkout@v2 pulls to Auto-Vk/Auto-Vk on windows + $env:Path += ";${{ env.vulkan_sdk }}\;${{ env.vulkan_sdk }}\Bin\" cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -Davk_LibraryType=STATIC ${{ runner.workspace }}/${{ github.event.repository.name }} -Davk_UseVMA=${{ matrix.vma }} -G "Visual Studio 17 2022" -A x64 - name: Build shell: bash - # apparently checkout@v2 pulls to Auto-Vk/Auto-Vk on windows + # apparently checkout@v3 pulls to Auto-Vk/Auto-Vk on windows working-directory: ${{ runner.workspace }}/${{ github.event.repository.name }}/build # Execute the build. You can specify a specific target with "--target " run: cmake --build . --config $BUILD_TYPE diff --git a/CMakeLists.txt b/CMakeLists.txt index 47e22ef..486e151 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,9 @@ -cmake_minimum_required(VERSION 3.13) +cmake_minimum_required(VERSION 3.16) project(avk) if (MSVC) # support requires /std:c++latest on MSVC - set(CMAKE_CXX_STANDARD 23) - add_compile_options(/bigobj) + set(CMAKE_CXX_STANDARD 20) else (MSVC) set(CMAKE_CXX_STANDARD 20) endif (MSVC) diff --git a/README.md b/README.md index 01c9141..3168498 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ avk::framebuffer framebuffer = myRoot.create_framebuffer(...); avk::semaphore imageAvailableSemaphore = ...; mRoot.record({ - avk::command::render_pass(graphicsPipeline->renderpass_reference(), framebuffer.as_reference(), { + avk::command::render_pass(graphicsPipeline->renderpass_reference().value(), framebuffer.as_reference(), { avk::command::bind_pipeline(graphicsPipeline.as_reference()), avk::command::draw(3u, 1u, 0u, 0u) }) diff --git a/include/avk/attachment.hpp b/include/avk/attachment.hpp index c3565b9..6bf62a1 100644 --- a/include/avk/attachment.hpp +++ b/include/avk/attachment.hpp @@ -60,6 +60,35 @@ namespace avk */ static attachment declare_for(const image_view_t& aImageView, attachment_load_config aLoadOp, subpass_usages aUsageInSubpasses, attachment_store_config aStoreOp); + /** Declare multisampled format of an attachment for a dynamic rendering pipeline. This attachment can only be used with pipeline that has dynamic rendering enabled. + * It has fewer parameters than regular attachment since some of its values (load/store ops etc...) are set when starting the dynamic render pass + * as opposed to being declared beforehand.. + * @param aFormatAndSamples Multisampled format definition: A tuple with the format of the attachment in its first element, and with the number of samples in its second element. + * @param aUsage How is this attachment being used in the renderpass? In contrast to non-dynamic attachments, this usage can only contain a single subpass as such + * Possible values in namespace avk::usage:: + * Usages for different subpasses can be defined by concatenating them using operator>>. + * Example 1: avk::usage::color(0) // Indicates that this attachment is used as color attachment at location=0 in the renderpass + * Example 2: avk::usage::color(2) + usage::resolve_to(3) // Indicates that this attachment is used as color attachment at location=2 in the renderpass + * // Additionally, at the end of renderpass, its contents are resolved into the attachment at index 3. + * Example 3: usage::unused // Indicates that this attachment is unused in the renderpass (it will only be used as a resolve target for example) + */ + static attachment declare_dynamic(std::tuple aFormatAndSamples, subpass_usages aUsage); + + /** Declare multisampled format of an attachment for a dynamic rendering pipeline. This attachment can only be used with pipeline that has dynamic rendering enabled. + * It has fewer parameters than regular attachment since some of its values (load/store ops etc...) are set when starting the dynamic render pass + * as opposed to being declared beforehand.. + * @param aFormat The format of the attachment + */ + static attachment declare_dynamic(vk::Format aFormat, subpass_usages aUsage); + + /** Declare multisampled format of an attachment for a dynamic rendering pipeline. This attachment can only be used with pipeline that has dynamic rendering enabled. + * It has fewer parameters than regular attachment since some of its values (load/store ops etc...) are set when starting the dynamic render pass + * as opposed to being declared beforehand.. + * @param aImageView The format of the attachment is copied from the given image view. + */ + static attachment declare_dynamic_for(const image_view_t& aImageView, subpass_usages aUsage); + + attachment& set_clear_color(std::array aColor) { mColorClearValue = aColor; return *this; } attachment& set_depth_clear_value(float aDepthClear) { mDepthClearValue = aDepthClear; return *this; } attachment& set_stencil_clear_value(uint32_t aStencilClear) { mStencilClearValue = aStencilClear; return *this; } @@ -88,6 +117,8 @@ namespace avk auto sample_count() const { return mSampleCount; } /** True if a multisample resolve pass shall be set up. */ auto is_to_be_resolved() const { return mSubpassUsages.contains_resolve(); } + /** True if this attachment is declared for dynamic rendering pipelines ie. using one of the dynamic declare functions*/ + bool is_for_dynamic_rendering() const { return mDynamicRenderingAttachment; } /** Returns the stencil load operation */ auto get_stencil_load_op() const { return mStencilLoadOperation.value_or(mLoadOperation); } @@ -108,5 +139,6 @@ namespace avk std::array mColorClearValue; float mDepthClearValue; uint32_t mStencilClearValue; + bool mDynamicRenderingAttachment; }; } diff --git a/include/avk/avk.hpp b/include/avk/avk.hpp index 72f3915..f334344 100644 --- a/include/avk/avk.hpp +++ b/include/avk/avk.hpp @@ -711,11 +711,12 @@ namespace avk * @param aAlterConfigBeforeCreation Optional custom callback function which can be used to alter the new pipeline's config right before it is being created on the device. * @return A new graphics pipeline instance. */ - graphics_pipeline create_graphics_pipeline_from_template(const graphics_pipeline_t& aTemplate, renderpass aNewRenderpass, std::optional aSubpassIndex = {}, std::function aAlterConfigBeforeCreation = {}); + graphics_pipeline create_graphics_pipeline_from_template(const graphics_pipeline_t& aTemplate, std::optional aNewRenderpass, std::optional aSubpassIndex = {}, std::function aAlterConfigBeforeCreation = {}); - /** Creates a graphics pipeline based on another graphics pipeline, which serves as a template, - * which either uses the same renderpass (if it has shared ownership enabled) or creates a new - * renderpass internally using create_renderpass_from_template with the template's renderpass. + /** Creates a graphics pipeline based on another graphics pipeline, which serves as a template, which either: + * - uses the same renderpass (if it has shared ownership enabled) + * - creates a new renderpass internally using create_renderpass_from_template with the template's renderpass + * - uses dynamic rendering and thus does not need to create new renderpass * @param aTemplate Another, already existing graphics pipeline, which serves as a template for the newly created graphics pipeline. * @param aAlterConfigBeforeCreation Optional custom callback function which can be used to alter the new pipeline's config right before it is being created on the device. * @return A new graphics pipeline instance. @@ -728,6 +729,7 @@ namespace avk * - cfg::pipeline_settings (flags) * - renderpass * - avk::attachment (use either attachments or renderpass!) + * - avk::dynamic_rendering_attachment (only use if dynamic_rendering::enabled) * - input_binding_location_data (vertex input) * - cfg::primitive_topology * - shader_info @@ -736,6 +738,7 @@ namespace avk * - cfg::depth_write * - cfg::viewport_depth_scissors_config * - cfg::culling_mode + * - cfg::dynamic_rendering * - cfg::front_face * - cfg::polygon_drawing * - cfg::rasterizer_geometry_mode @@ -759,17 +762,39 @@ namespace avk std::function alterConfigFunction; graphics_pipeline_config config; add_config(config, renderPassAttachments, alterConfigFunction, std::move(args)...); - - // Check if render pass attachments are in renderPassAttachments XOR config => only in that case, it is clear how to proceed, fail in other cases - if (renderPassAttachments.size() > 0 == (config.mRenderPassSubpass.has_value() && static_cast(std::get(*config.mRenderPassSubpass)->handle()))) { - if (renderPassAttachments.size() == 0) { - throw avk::runtime_error("No renderpass config provided! Please provide a renderpass or attachments!"); + bool hasRenderPassAttachments = false; + bool hasDynamicRenderingAttachments = false; + for(const auto & attachment : renderPassAttachments) + { + if(attachment.is_for_dynamic_rendering()) + { + hasDynamicRenderingAttachments = true; + } else { + hasRenderPassAttachments = true; } - throw avk::runtime_error("Ambiguous renderpass config! Either set a renderpass XOR provide attachments!"); } + + const bool hasValidRenderPass = config.mRenderPassSubpass.has_value() && static_cast(std::get(*config.mRenderPassSubpass)->handle()); + const bool isDynamicRenderingSet = config.mDynamicRendering == avk::cfg::dynamic_rendering::enabled; + // Check all invalid configurations when dynamic rendering is set + if (isDynamicRenderingSet ) + { + if(hasValidRenderPass) { throw avk::runtime_error("Dynamic rendering does not accept renderpasses! They are set dynamically during rendering!"); } + if(hasRenderPassAttachments) { throw avk::runtime_error("Only avk::attachments created by declare_dynamic(_for) functions are allowed when dynamic rendering is enabled!"); } + if(!hasDynamicRenderingAttachments) { throw avk::runtime_error("Dynamic rendering enabled but no avk::attachmenst created by declare_dynamic(_for) functions provided! Please provide at least one attachment!"); } + } + // Check all invalid configurations when normal rendering (with renderpasses) is used + else + { + if(hasValidRenderPass && hasRenderPassAttachments) { throw avk::runtime_error("Ambiguous renderpass config! Either set a renderpass OR provide attachments but not both at the same time!"); } + if(!(hasValidRenderPass || hasRenderPassAttachments)) { throw avk::runtime_error("No renderpass config provided! Please provide a renderpass or attachments!"); } + } + // ^ that was the sanity check. See if we have to build the renderpass from the attachments: - if (renderPassAttachments.size() > 0) { + if (hasRenderPassAttachments) { add_config(config, renderPassAttachments, alterConfigFunction, create_renderpass(std::move(renderPassAttachments))); + } else { + config.mDynamicRenderingAttachments = std::move(renderPassAttachments); } // 2. CREATE PIPELINE according to the config diff --git a/include/avk/commands.hpp b/include/avk/commands.hpp index 4ffa2b9..571fef2 100644 --- a/include/avk/commands.hpp +++ b/include/avk/commands.hpp @@ -3,6 +3,7 @@ #include "buffer.hpp" #include "image.hpp" +#include namespace avk { @@ -506,6 +507,19 @@ namespace avk }; } + /** Begins dynamic rendering scope + * @param aAttachemnts attachments used in during this dynamic rendering pass + * @param aImageViews image views bound for each attachment + * @param aRenderAreaOffset Render area offset (default is (0,0), i.e., no offset) + * @param aRenderAreaExtent Render area extent (default is full extent inferred from images passed in aImageViews) + * @param aLayerCount number of layers that will be used for rendering (default is 1) + */ + extern action_type_command begin_dynamic_rendering(std::vector aAttachments, std::vector aImageViews, vk::Offset2D aRenderAreaOffset = {0, 0}, std::optional aRenderAreaExtent = {}, uint32_t aLayerCount = 1, uint32_t aViewMask = 0); + + /** Ends dynamic rendering scope + */ + extern action_type_command end_dynamic_rendering(); + /** Begins a render pass for a given framebuffer * @param aRenderpass Renderpass which shall begin (auto lifetime handling not supported by this command) * @param aFramebuffer Framebuffer to use with the renderpass (auto lifetime handling not supported by this command) @@ -515,6 +529,7 @@ namespace avk */ extern action_type_command begin_render_pass_for_framebuffer(const renderpass_t& aRenderpass, const framebuffer_t& aFramebuffer, vk::Offset2D aRenderAreaOffset = { 0, 0 }, std::optional aRenderAreaExtent = {}, bool aSubpassesInline = true); + /** Ends a render pass */ extern action_type_command end_render_pass(); @@ -536,6 +551,17 @@ namespace avk bool aSubpassesInline = true ); + /** Begins dynamic rendering and supports nested commands in between + * @param aNestedCommands Nested commands to be recorded between begin and end + * @param aRenderAreaOffset Render area offset (default is (0,0), i.e., no offset) + * @param aRenderAreaExtent Render area extent (default is full extent) + */ + // extern action_type_command dynamic_renderpass( + // std::vector aNestedCommands = {}, + // vk::Offset2D aRenderAreaOffset = {0, 0}, + // std::optional aRenderAreaExtent = {} + // ); + /** Advances to the next subpass within a render pass. */ extern action_type_command next_subpass(bool aSubpassesInline = true); diff --git a/include/avk/graphics_pipeline.hpp b/include/avk/graphics_pipeline.hpp index e0a10e8..7d4a817 100644 --- a/include/avk/graphics_pipeline.hpp +++ b/include/avk/graphics_pipeline.hpp @@ -1,5 +1,6 @@ #pragma once #include "avk/avk.hpp" +#include namespace avk { @@ -16,10 +17,22 @@ namespace avk graphics_pipeline_t& operator=(const graphics_pipeline_t&) = delete; ~graphics_pipeline_t() = default; - [[nodiscard]] avk::renderpass renderpass() const { return mRenderPass; } - [[nodiscard]] const avk::renderpass_t& renderpass_reference() const { return mRenderPass.get(); } - auto renderpass_handle() const { return mRenderPass->handle(); } - auto subpass_id() const { return mSubpassIndex; } + [[nodiscard]] auto renderpass() const { return mRenderPass; } + [[nodiscard]] auto renderpass_reference() const -> std::optional> + { + if(mRenderPass.has_value()) { return std::cref(mRenderPass.value().get()); } + else { return std::nullopt; } + } + auto renderpass_handle() const -> std::optional + { + if(mRenderPass.has_value()) {return mRenderPass.value()->handle();} + else {return std::nullopt;} + } + auto subpass_id() const -> std::optional + { + if(mRenderPass.has_value()) {return mSubpassIndex;} + else {return std::nullopt;} + }; auto& vertex_input_binding_descriptions() { return mOrderedVertexInputBindingDescriptions; } auto& vertex_input_attribute_descriptions() { return mVertexInputAttributeDescriptions; } auto& vertex_input_state_create_info() { return mPipelineVertexInputStateCreateInfo; } @@ -69,7 +82,7 @@ namespace avk const auto& handle() const { return mPipeline.get(); } private: - avk::renderpass mRenderPass; + std::optional mRenderPass; uint32_t mSubpassIndex; // The vertex input data: std::vector mOrderedVertexInputBindingDescriptions; @@ -85,6 +98,9 @@ namespace avk std::vector mViewports; std::vector mScissors; vk::PipelineViewportStateCreateInfo mViewportStateCreateInfo; + // Dynamic rendering state + std::vector mDynamicRenderingColorFormats; + std::optional mRenderingCreateInfo; // Rasterization state: vk::PipelineRasterizationStateCreateInfo mRasterizationStateCreateInfo; // Depth stencil config: diff --git a/include/avk/graphics_pipeline_config.hpp b/include/avk/graphics_pipeline_config.hpp index eaea507..6e90519 100644 --- a/include/avk/graphics_pipeline_config.hpp +++ b/include/avk/graphics_pipeline_config.hpp @@ -164,6 +164,13 @@ namespace avk bool mDynamicScissorEnabled; }; + /** Dyanmic rendering*/ + enum struct dynamic_rendering + { + disabled, + enabled + }; + /** Pipeline configuration data: Culling Mode */ enum struct culling_mode { @@ -609,6 +616,7 @@ namespace avk cfg::pipeline_settings mPipelineSettings; // TODO: Handle settings! std::optional> mRenderPassSubpass; + std::optional> mDynamicRenderingAttachments; std::vector mInputBindingLocations; cfg::primitive_topology mPrimitiveTopology; std::vector mShaderInfos; @@ -616,6 +624,7 @@ namespace avk cfg::rasterizer_geometry_mode mRasterizerGeometryMode; cfg::polygon_drawing mPolygonDrawingModeAndConfig; cfg::culling_mode mCullingMode; + cfg::dynamic_rendering mDynamicRendering; cfg::front_face mFrontFaceWindingOrder; cfg::depth_clamp_bias mDepthClampBiasConfig; cfg::depth_test mDepthTestConfig; @@ -672,7 +681,7 @@ namespace avk add_config(aConfig, aAttachments, aFunc, std::move(args)...); } - // Add a renderpass attachment to the (temporary) attachments vector and build renderpass afterwards + // Add a renderpass attachment to the (temporary) attachments vector and later build renderpass from them template void add_config(graphics_pipeline_config& aConfig, std::vector& aAttachments, std::function& aFunc, avk::attachment aAttachment, Ts... args) { @@ -752,6 +761,14 @@ namespace avk add_config(aConfig, aAttachments, aFunc, std::move(args)...); } + // Set dynamic rendering + template + void add_config(graphics_pipeline_config& aConfig, std::vector& aAttachments, std::function& aFunc, cfg::dynamic_rendering aDynamicRendering, Ts... args) + { + aConfig.mDynamicRendering = aDynamicRendering; + add_config(aConfig, aAttachments, aFunc, std::move(args)...); + } + // Set the definition of front faces in the pipeline config template void add_config(graphics_pipeline_config& aConfig, std::vector& aAttachments, std::function& aFunc, cfg::front_face aFrontFace, Ts... args) diff --git a/src/avk.cpp b/src/avk.cpp index db05209..59c35f4 100644 --- a/src/avk.cpp +++ b/src/avk.cpp @@ -11,6 +11,9 @@ #endif #endif +#include +#include + namespace avk { #pragma region root definitions @@ -579,6 +582,18 @@ namespace avk return it != depthFormats.end(); } + bool is_stencil_format(const vk::Format& aImageFormat) + { + static std::set stencilFormats = { + vk::Format::eD16UnormS8Uint, + vk::Format::eD24UnormS8Uint, + vk::Format::eD32SfloatS8Uint, + vk::Format::eS8Uint, + }; + auto it = std::find(std::begin(stencilFormats), std::end(stencilFormats), aImageFormat); + return it != stencilFormats.end(); + } + bool is_1component_format(const vk::Format& aImageFormat) { static std::set singleCompFormats = { @@ -1403,6 +1418,9 @@ namespace avk #pragma endregion +#pragma region dynamic rendering attachment definitions +#pragma endregion + #pragma region attachment definitions attachment attachment::declare(std::tuple aFormatAndSamples, attachment_load_config aLoadOp, subpass_usages aUsageInSubpasses, attachment_store_config aStoreOp) { @@ -1413,7 +1431,8 @@ namespace avk {}, {}, std::move(aUsageInSubpasses), { 0.0, 0.0, 0.0, 0.0 }, - 1.0f, 0u + 1.0f, 0u, + false }; } @@ -1429,6 +1448,33 @@ namespace avk auto result = declare({format, imageConfig.samples}, aLoadOp, std::move(aUsageInSubpasses), aStoreOp); return result; } + + attachment attachment::declare_dynamic(std::tuple aFormatAndSamples, subpass_usages aUsage) + { + if(aUsage.num_subpasses() != 1) + { + throw avk::runtime_error("Dynamic rendering does not support multiple subpasses, please only provide usage with a single subpass"); + } + return attachment{ + .mFormat = std::get(aFormatAndSamples), + .mSampleCount = std::get(aFormatAndSamples), + .mSubpassUsages = subpass_usages(aUsage), + .mDynamicRenderingAttachment = true + }; + } + + attachment attachment::declare_dynamic(vk::Format aFormat, subpass_usages aUsage) + { + return declare_dynamic({aFormat, vk::SampleCountFlagBits::e1}, aUsage); + } + + attachment attachment::declare_dynamic_for(const image_view_t& aImageView, subpass_usages aUsage) + { + const auto& imageConfig = aImageView.get_image().create_info(); + const auto format = imageConfig.format; + const auto samples = imageConfig.samples; + return declare_dynamic({format, samples}, aUsage); + } #pragma endregion #pragma region acceleration structure definitions @@ -4269,11 +4315,13 @@ namespace avk // Set sensible defaults: graphics_pipeline_config::graphics_pipeline_config() : mPipelineSettings{ cfg::pipeline_settings::nothing } + , mDynamicRenderingAttachments {} , mRenderPassSubpass {} // not set by default , mPrimitiveTopology{ cfg::primitive_topology::triangles } // triangles after one another , mRasterizerGeometryMode{ cfg::rasterizer_geometry_mode::rasterize_geometry } // don't discard, but rasterize! , mPolygonDrawingModeAndConfig{ cfg::polygon_drawing::config_for_filling() } // Fill triangles , mCullingMode{ cfg::culling_mode::cull_back_faces } // Cull back faces + , mDynamicRendering {cfg::dynamic_rendering::disabled } , mFrontFaceWindingOrder{ cfg::front_face::define_front_faces_to_be_counter_clockwise() } // CCW == front face , mDepthClampBiasConfig{ cfg::depth_clamp_bias::config_nothing_special() } // no clamp, no bias, no factors , mDepthTestConfig{ cfg::depth_test::enabled() } // enable depth testing @@ -4335,9 +4383,17 @@ namespace avk .setAttachmentCount(static_cast(aPreparedPipeline.mBlendingConfigsForColorAttachments.size())) .setPAttachments(aPreparedPipeline.mBlendingConfigsForColorAttachments.data()); - aPreparedPipeline.mMultisampleStateCreateInfo - .setRasterizationSamples(aPreparedPipeline.renderpass_reference().num_samples_for_subpass(aPreparedPipeline.subpass_id())) - .setPSampleMask(nullptr); + + // NOTE(msakmary) Not really sure why we set the samples again when they were previously set in root::create_graphics_pipeline + // (ask for clarification) - but if dynamic rendering is enabled there is no renderpass... + const bool dynamicRenderingEnabled = aPreparedPipeline.mRenderingCreateInfo.has_value(); + if(!dynamicRenderingEnabled) + { + aPreparedPipeline.mMultisampleStateCreateInfo + .setRasterizationSamples( + aPreparedPipeline.renderpass_reference().value().get().num_samples_for_subpass(aPreparedPipeline.subpass_id().value())) + .setPSampleMask(nullptr); + } aPreparedPipeline.mDynamicStateCreateInfo .setDynamicStateCount(static_cast(aPreparedPipeline.mDynamicStateEntries.size())) @@ -4349,10 +4405,17 @@ namespace avk aPreparedPipeline.mPipelineLayout = device().createPipelineLayoutUnique(aPreparedPipeline.mPipelineLayoutCreateInfo, nullptr, dispatch_loader_core()); assert(static_cast(aPreparedPipeline.layout_handle())); + const void * pNext = dynamicRenderingEnabled ? &(aPreparedPipeline.mRenderingCreateInfo.value()) : nullptr; + VkRenderPass render_pass = VK_NULL_HANDLE; + if(!dynamicRenderingEnabled) + { + render_pass = (aPreparedPipeline.mRenderPass.value())->handle(); + } // Create the PIPELINE, a.k.a. putting it all together: auto pipelineInfo = vk::GraphicsPipelineCreateInfo{} // 0. Render Pass - .setRenderPass((*aPreparedPipeline.mRenderPass).handle()) + .setPNext(pNext) + .setRenderPass(render_pass) .setSubpass(aPreparedPipeline.mSubpassIndex) // 1., 2., and 3. .setPVertexInputState(&aPreparedPipeline.mPipelineVertexInputStateCreateInfo) @@ -4434,12 +4497,17 @@ namespace avk graphics_pipeline_t result; - // 0. Own the renderpass + // 0. Own the renderpass - if one is required + const bool dynamicRenderingEnabled = aConfig.mDynamicRendering == avk::cfg::dynamic_rendering::enabled; + { - assert(aConfig.mRenderPassSubpass.has_value()); - auto [rp, sp] = std::move(aConfig.mRenderPassSubpass.value()); - result.mRenderPass = std::move(rp); - result.mSubpassIndex = sp; + if(!dynamicRenderingEnabled) + { + assert(aConfig.mRenderPassSubpass.has_value()); + auto [rp, sp] = std::move(aConfig.mRenderPassSubpass.value()); + result.mRenderPass = std::move(rp); + result.mSubpassIndex = sp; + } } // 1. Compile the array of vertex input binding descriptions @@ -4622,18 +4690,37 @@ namespace avk "config (which is not attached to a specific color target) or assign them to specific color target attachment ids."); } - // Iterate over all color target attachments and set a color blending config - if (result.subpass_id() >= result.mRenderPass->attachment_descriptions().size()) { - throw avk::runtime_error( - "There are fewer subpasses in the renderpass (" - + std::to_string(result.mRenderPass->attachment_descriptions().size()) + - ") than the subpass index (" - + std::to_string(result.subpass_id()) + - ") indicates. I.e. the subpass index is out of bounds."); + // Iterate over all color target attachments and set a color blending config + size_t blendingConfigNum; + if (!dynamicRenderingEnabled) + { + const auto & renderPassVal = result.mRenderPass.value(); + if (result.subpass_id() >= renderPassVal->attachment_descriptions().size()) { + throw avk::runtime_error( + "There are fewer subpasses in the renderpass (" + + std::to_string(renderPassVal->attachment_descriptions().size()) + + ") than the subpass index (" + + std::to_string(result.subpass_id().value()) + + ") indicates. I.e. the subpass index is out of bounds."); + } + blendingConfigNum = renderPassVal->color_attachments_for_subpass(result.subpass_id().value()).size(); /////////////////// TODO: (doublecheck or) FIX this section (after renderpass refactoring) + } + // Renderpasses and Subpasses are not supported when dynamic rendering is enabled + // Instead we read size of the dynamic_rendering_attachments provided + else + { + blendingConfigNum = 0; + + for(const auto & dynRenderingAttachment : aConfig.mDynamicRenderingAttachments.value()) + { + if(dynRenderingAttachment.mSubpassUsages.contains_color()) + { + blendingConfigNum++; + } + } } - const auto n = result.mRenderPass->color_attachments_for_subpass(result.subpass_id()).size(); /////////////////// TODO: (doublecheck or) FIX this section (after renderpass refactoring) - result.mBlendingConfigsForColorAttachments.reserve(n); // Important! Otherwise the vector might realloc and .data() will become invalid! - for (size_t i = 0; i < n; ++i) { + result.mBlendingConfigsForColorAttachments.reserve(blendingConfigNum); // Important! Otherwise the vector might realloc and .data() will become invalid! + for (size_t i = 0; i < blendingConfigNum; ++i) { // Do we have a specific blending config for color attachment i? #if defined(_MSC_VER) && _MSC_VER < 1930 auto configForI = aConfig.mColorBlendingPerAttachment @@ -4680,7 +4767,24 @@ namespace avk // 10. Multisample state // TODO: Can the settings be inferred from the renderpass' color attachments (as they are right now)? If they can't, how to handle this situation? { /////////////////// TODO: FIX this section (after renderpass refactoring) - vk::SampleCountFlagBits numSamples = (*result.mRenderPass).num_samples_for_subpass(result.subpass_id()); + vk::SampleCountFlagBits numSamples = vk::SampleCountFlagBits::e1; + if(!dynamicRenderingEnabled) + { + numSamples = result.mRenderPass.value()->num_samples_for_subpass(result.subpass_id().value()); + } else { + for(const auto & attachment : aConfig.mDynamicRenderingAttachments.value()) + { + if(attachment.is_multisampled()) + { + if(numSamples != vk::SampleCountFlagBits::e1 && numSamples != attachment.sample_count()) + { + //NOTE(msakmary) This may be possible with some extension I'm not 100% sure... + throw avk::runtime_error("Cannot have different sample counts for attachments in the same renderpass"); + } + numSamples = attachment.sample_count(); + } + } + } // Evaluate and set the PER SAMPLE shading configuration: auto perSample = aConfig.mPerSampleShading.value_or(per_sample_shading_config{ false, 1.0f }); @@ -4773,19 +4877,48 @@ namespace avk .setPushConstantRangeCount(static_cast(result.mPushConstantRanges.size())) .setPPushConstantRanges(result.mPushConstantRanges.data()); - // 15. Maybe alter the config?! + // 15. Set Rendering info if dynamic rendering is enabled + if(dynamicRenderingEnabled) + { + std::vector depth_attachments; + std::vector stencil_attachments; + for(const auto & dynamicRenderingAttachment : aConfig.mDynamicRenderingAttachments.value()) + { + if(is_depth_format(dynamicRenderingAttachment.format())) { + depth_attachments.push_back(dynamicRenderingAttachment.format()); + } else if (is_stencil_format(dynamicRenderingAttachment.format())) { + stencil_attachments.push_back(dynamicRenderingAttachment.format()); + } else if (!dynamicRenderingAttachment.mSubpassUsages.get_subpass_usage(0).as_color()) { + result.mDynamicRenderingColorFormats.push_back(dynamicRenderingAttachment.format()); + } + } + if(depth_attachments.size() > 1) { throw avk::runtime_error("Provided multiple depth attachments! Only one is supported!"); } + if(stencil_attachments.size() > 1) { throw avk::runtime_error("Provided multiple stencil attachments! Only one is supported!"); } + + result.mRenderingCreateInfo = vk::PipelineRenderingCreateInfoKHR{} + .setColorAttachmentCount(static_cast(result.mDynamicRenderingColorFormats.size())) + .setPColorAttachmentFormats(result.mDynamicRenderingColorFormats.data()) + .setDepthAttachmentFormat(depth_attachments.size() == 1 ? depth_attachments.at(0) : vk::Format{}) + .setStencilAttachmentFormat(stencil_attachments.size() == 1 ? stencil_attachments.at(0) : vk::Format{}); + } + + // 16. Maybe alter the config?! if (aAlterConfigBeforeCreation) { aAlterConfigBeforeCreation(result); } - assert (aConfig.mRenderPassSubpass.has_value()); + assert (aConfig.mRenderPassSubpass.has_value() || dynamicRenderingEnabled); rewire_config_and_create_graphics_pipeline(result); return result; } - graphics_pipeline root::create_graphics_pipeline_from_template(const graphics_pipeline_t& aTemplate, renderpass aNewRenderpass, std::optional aSubpassIndex, std::function aAlterConfigBeforeCreation) + graphics_pipeline root::create_graphics_pipeline_from_template(const graphics_pipeline_t& aTemplate, std::optional aNewRenderpass, std::optional aSubpassIndex, std::function aAlterConfigBeforeCreation) { graphics_pipeline_t result; + if(aTemplate.mRenderingCreateInfo.has_value() && aNewRenderpass.has_value()) + { + throw avk::runtime_error("Attempting to create pipeline using renderpass from a template pipeline using dynamic rendering (which has no renderpasses) is not valid!"); + } result.mRenderPass = std::move(aNewRenderpass); result.mSubpassIndex = aSubpassIndex.value_or(cfg::subpass_index{ aTemplate.mSubpassIndex }).mSubpassIndex; @@ -4835,26 +4968,36 @@ namespace avk graphics_pipeline root::create_graphics_pipeline_from_template(const graphics_pipeline_t& aTemplate, std::function aAlterConfigBeforeCreation) { - renderpass renderpassForPipeline; - if (aTemplate.mRenderPass.is_shared_ownership_enabled()) { + // If dynamic rendering is enabled we don't want to create new renderpass + if(aTemplate.mRenderingCreateInfo.has_value()) + { + return create_graphics_pipeline_from_template(aTemplate, std::nullopt, std::nullopt, std::move(aAlterConfigBeforeCreation)); + } + std::optional renderpassForPipeline; + if (aTemplate.mRenderPass.value().is_shared_ownership_enabled()) { renderpassForPipeline = aTemplate.mRenderPass; } else { - renderpassForPipeline = create_renderpass_from_template(*aTemplate.mRenderPass, {}); + renderpassForPipeline = create_renderpass_from_template(*(aTemplate.mRenderPass.value()), {}); } return create_graphics_pipeline_from_template(aTemplate, std::move(renderpassForPipeline), std::nullopt, std::move(aAlterConfigBeforeCreation)); } renderpass root::replace_render_pass_for_pipeline(graphics_pipeline& aPipeline, renderpass aNewRenderPass) { - if (aPipeline->mRenderPass.is_shared_ownership_enabled()) { + if(aPipeline.get().mRenderingCreateInfo.has_value()) + { + throw avk::runtime_error("Attempting to replace renderpass of pipeline using dynamic rendering (which has no renderpasses) is not valid!"); + } + + if (aPipeline->mRenderPass.value().is_shared_ownership_enabled()) { aNewRenderPass.enable_shared_ownership(); } auto oldRenderPass = std::move(aPipeline->mRenderPass); aPipeline->mRenderPass = std::move(aNewRenderPass); - return oldRenderPass; + return oldRenderPass.value(); } #pragma endregion @@ -8238,6 +8381,194 @@ namespace avk namespace command { + + action_type_command begin_dynamic_rendering( + std::vector aAttachments, + std::vector aImageViews, + vk::Offset2D aRenderAreaOffset, + std::optional aRenderAreaExtent, + uint32_t aLayerCount, + uint32_t aViewMask) + { +#ifdef _DEBUG + if (aAttachments.size() != aImageViews.size()) { + throw avk::runtime_error("Incomplete config for begin dynamic rendering: number of attachments (" + std::to_string(aAttachments.size()) + ") does not equal the number of image views (" + std::to_string(aImageViews.size()) + ")"); + } + auto n = aAttachments.size(); + for (size_t i = 0; i < n; ++i) { + auto& a = aAttachments[i]; + auto& v = aImageViews[i]; + if ((is_depth_format(v->get_image().format()) || has_stencil_component(v->get_image().format())) && !a.is_used_as_depth_stencil_attachment()) { + AVK_LOG_WARNING("Possibly misconfigured framebuffer: image[" + std::to_string(i) + "] is a depth/stencil format, but it is never indicated to be used as such in the attachment-description[" + std::to_string(i) + "]."); + } + if(!a.is_for_dynamic_rendering()) + { + AVK_LOG_WARNING("Provided attachment which was not created compatible with dynamic rendering. Please provide an attachment created with one of the declare_dynamic_* functions"); + } + } +#endif //_DEBUG + const bool detectExtent = !aRenderAreaExtent.has_value(); + std::vector> unsortedColorAttachments = {}; + std::optional depthAttachment = {}; + std::optional stencilAttachment = {}; + // First parse all the attachments into vulkan structs + for(uint32_t attachmentIndex = 0; attachmentIndex < aAttachments.size(); attachmentIndex++) + { + const auto & currAttachment = aAttachments.at(attachmentIndex); + // Unused attachments should not contribute to any rendering + if(currAttachment.mSubpassUsages.contains_unused()) { continue; } + + const auto & currImageView = aImageViews.at(attachmentIndex); + if(detectExtent && !aRenderAreaExtent.has_value()) + { + const auto imageExtent = currImageView->get_image().create_info().extent; + aRenderAreaExtent = vk::Extent2D{ + imageExtent.width - static_cast(aRenderAreaOffset.x), + imageExtent.height - static_cast(aRenderAreaOffset.y) + }; + } +#ifdef _DEBUG + else if(detectExtent) + { + const auto imageExtent = currImageView->get_image().create_info().extent; + const auto currAreaExtent = vk::Extent2D{ + imageExtent.width - static_cast(aRenderAreaOffset.x), + imageExtent.height - static_cast(aRenderAreaOffset.y) + }; + if(currAreaExtent != aRenderAreaExtent.value()) + { + throw avk::runtime_error("Autodetect extent failed because the images passed in image views have differing extents"); + } + } +#endif //_DEBUG + if(currAttachment.is_used_as_color_attachment()) + { + const auto usage = currAttachment.mSubpassUsages.get_subpass_usage(0); + const bool shouldResolve = currAttachment.is_to_be_resolved(); + unsortedColorAttachments.push_back({ + vk::RenderingAttachmentInfoKHR{} + .setImageView(currImageView->handle()) + .setImageLayout(vk::ImageLayout::eColorAttachmentOptimal) + .setResolveMode(shouldResolve ? vk::ResolveModeFlagBits::eAverage : vk::ResolveModeFlagBits::eNone) + .setResolveImageView(shouldResolve ? aImageViews.at(usage.mResolveAttachmentIndex)->handle() : VK_NULL_HANDLE) + .setResolveImageLayout(vk::ImageLayout::eColorAttachmentOptimal) + .setLoadOp(to_vk_load_op(currAttachment.mLoadOperation.mLoadBehavior)) + .setStoreOp(to_vk_store_op(currAttachment.mStoreOperation.mStoreBehavior)) + .setClearValue(vk::ClearColorValue(currAttachment.clear_color())), + usage.color_location() + } + ); + } + else // currAttachment is either used as depth or as stencil + { + // NOTE(msakmary): This will brake if we want depth image and stencil both D24S8 but separate images (so two D24S8 images + // one used as depth one as stencil) probably should have this info in a custom attachment type. + // I think something like begin_rendering_attachment should be added which would have an explicit field + // which would denote how to use the attachment - use this attachment as stencil, depth or color + if(is_depth_format(currAttachment.format())) + { + if(depthAttachment.has_value()) + { + throw avk::runtime_error("Multiple depth attachments provided! Please provide only a single depth attachment"); + } + const auto usage = currAttachment.mSubpassUsages.get_subpass_usage(0); + const bool shouldResolve = currAttachment.is_to_be_resolved(); + depthAttachment = vk::RenderingAttachmentInfoKHR{} + .setImageView(currImageView->handle()) + .setImageLayout(vk::ImageLayout::eDepthStencilAttachmentOptimal) + .setResolveMode(shouldResolve ? static_cast(static_cast(usage.mResolveModeDepth)) : vk::ResolveModeFlagBits::eNone) + .setResolveImageView(shouldResolve ? aImageViews.at(usage.mResolveAttachmentIndex)->handle() : VK_NULL_HANDLE) + .setResolveImageLayout(vk::ImageLayout::eDepthStencilAttachmentOptimal) + .setLoadOp(to_vk_load_op(currAttachment.mLoadOperation.mLoadBehavior)) + .setStoreOp(to_vk_store_op(currAttachment.mStoreOperation.mStoreBehavior)) + .setClearValue(vk::ClearDepthStencilValue( + currAttachment.depth_clear_value(), + currAttachment.stencil_clear_value())); + } + if(is_stencil_format(currAttachment.format())) + { + if(stencilAttachment.has_value()) + { + throw avk::runtime_error("Multiple stencil attachments provided! Please provide only a single stencil attachment"); + } + const auto usage = currAttachment.mSubpassUsages.get_subpass_usage(0); + const bool shouldResolve = currAttachment.is_to_be_resolved(); + stencilAttachment = vk::RenderingAttachmentInfoKHR{} + .setImageView(currImageView->handle()) + .setImageLayout(vk::ImageLayout::eDepthStencilAttachmentOptimal) + .setResolveMode(shouldResolve ? static_cast(static_cast(usage.mResolveModeStencil)) : vk::ResolveModeFlagBits::eNone) + .setResolveImageView(shouldResolve ? aImageViews.at(usage.mResolveAttachmentIndex)->handle() : VK_NULL_HANDLE) + .setResolveImageLayout(vk::ImageLayout::eDepthStencilAttachmentOptimal) + .setLoadOp(to_vk_load_op(currAttachment.mLoadOperation.mLoadBehavior)) + .setStoreOp(to_vk_store_op(currAttachment.mStoreOperation.mStoreBehavior)) + .setClearValue(vk::ClearDepthStencilValue( + currAttachment.depth_clear_value(), + currAttachment.stencil_clear_value())); + } + } + } + std::sort(unsortedColorAttachments.begin(), unsortedColorAttachments.end(), + [](const auto & a, const auto & b) -> bool { return a.second < b.second; } + ); + std::vector colorAttachments = {}; + colorAttachments.reserve(unsortedColorAttachments.size()); + for(const auto & attachmentPair : unsortedColorAttachments) { colorAttachments.push_back(attachmentPair.first); } + + return action_type_command{ + avk::sync::sync_hint { + {{ // What previous commands must synchronize with: + vk::PipelineStageFlagBits2KHR::eAllGraphics, + vk::AccessFlagBits2KHR::eColorAttachmentRead | vk::AccessFlagBits2KHR::eColorAttachmentWrite | vk::AccessFlagBits2KHR::eDepthStencilAttachmentRead | vk::AccessFlagBits2KHR::eDepthStencilAttachmentWrite + }}, + {{ // What subsequent commands must synchronize with: + vk::PipelineStageFlagBits2KHR::eAllGraphics, + vk::AccessFlagBits2KHR::eColorAttachmentWrite | vk::AccessFlagBits2KHR::eDepthStencilAttachmentWrite + }} + }, + {}, + [ + colorAttachments, + depthAttachment, + stencilAttachment, + aLayerCount, + aViewMask, + aRenderAreaOffset, + aRenderAreaExtent + ](avk::command_buffer_t& cb) { + auto const renderingInfo = vk::RenderingInfoKHR{} + .setRenderArea(vk::Rect2D(aRenderAreaOffset, aRenderAreaExtent.value())) + .setLayerCount(aLayerCount) + .setViewMask(aViewMask) + .setColorAttachmentCount(static_cast(colorAttachments.size())) + .setPColorAttachments(colorAttachments.data()) + .setPDepthAttachment(depthAttachment.has_value() ? &depthAttachment.value() : nullptr) + .setPStencilAttachment(stencilAttachment.has_value() ? &stencilAttachment.value() : nullptr); + cb.handle().beginRenderingKHR(renderingInfo, cb.root_ptr()->dispatch_loader_ext()); + } + }; + } + + action_type_command end_dynamic_rendering() + { + return action_type_command + { + avk::sync::sync_hint { + {{ // What previous commands must synchronize with: + vk::PipelineStageFlagBits2KHR::eAllCommands, // eAllGraphics does not include new stages or ext-stages. Therefore, eAllCommands! + vk::AccessFlagBits2KHR::eColorAttachmentRead | vk::AccessFlagBits2KHR::eColorAttachmentWrite | vk::AccessFlagBits2KHR::eDepthStencilAttachmentRead | vk::AccessFlagBits2KHR::eDepthStencilAttachmentWrite + }}, + {{ // What subsequent commands must synchronize with: + vk::PipelineStageFlagBits2KHR::eAllCommands, // Same comment as above regarding eAllCommands vs. eAllGraphics + vk::AccessFlagBits2KHR::eColorAttachmentWrite | vk::AccessFlagBits2KHR::eDepthStencilAttachmentWrite + }} + }, + {}, + [](avk::command_buffer_t& cb){ + cb.handle().endRenderingKHR(cb.root_ptr()->dispatch_loader_ext()); + } + }; + } + action_type_command begin_render_pass_for_framebuffer(const renderpass_t& aRenderpass, const framebuffer_t& aFramebuffer, vk::Offset2D aRenderAreaOffset, std::optional aRenderAreaExtent, bool aSubpassesInline) { return action_type_command{