From 5423b675c967b6dfb77c2acee2ba00506d55fd62 Mon Sep 17 00:00:00 2001 From: Johannes Unterguggenberger Date: Wed, 24 May 2023 11:27:20 +0200 Subject: [PATCH 01/16] Workflow for testing VMA (#88) * Added Vulkan SDK version 1.3.216.0, which is the first that included VMA. * VMA is now directly fetched from GitHub (not a very great solution, but don't know how else to do it) * mkdir before curling vma --- .github/workflows/build.yml | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 12fb962..dc35856 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -34,7 +34,10 @@ jobs: # 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.2.176"] - vma: ["OFF"] + vma: ["ON", "OFF"] + exclude: # exclude combinations that are known to fail + - vulkan-sdk: "1.2.176" + vma: "ON" steps: - uses: actions/checkout@v2 @@ -100,8 +103,11 @@ jobs: cxx: "cl" } # note: if a specific vulkan version (e.g. 1.1.x, or < 1.2.135) needs testing, you can add it here: - vulkan-sdk: ["latest", "1.2.176.1"] - vma: ["OFF"] + vulkan-sdk: ["latest", "1.3.216.0", "1.2.176.1"] + vma: ["ON", "OFF"] + exclude: # exclude combinations that are known to fail + - vulkan-sdk: "1.2.176" + vma: "ON" steps: # apparently checkout@v2 pulls to Auto-Vk/Auto-Vk on windows @@ -121,6 +127,8 @@ jobs: curl -LS -o vulkan-sdk.exe https://sdk.lunarg.com/sdk/download/${{ matrix.vulkan-sdk }}/windows/VulkanSDK-${{ matrix.vulkan-sdk }}-Installer.exe } 7z x vulkan-sdk.exe -o"${{ env.vulkan_sdk }}" + 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 cmake -E make_directory ${{ runner.workspace }}/${{ github.event.repository.name }}/build @@ -164,8 +172,11 @@ jobs: cxx: "cl" } # note: if a specific vulkan version (e.g. 1.1.x, or < 1.2.135) needs testing, you can add it here: - vulkan-sdk: ["latest", "1.2.176.1"] - vma: ["OFF"] + vulkan-sdk: ["latest", "1.3.216.0", "1.2.176.1"] + vma: ["ON", "OFF"] + exclude: # exclude combinations that are known to fail + - vulkan-sdk: "1.2.176" + vma: "ON" steps: # apparently checkout@v2 pulls to Auto-Vk/Auto-Vk on windows @@ -185,6 +196,8 @@ jobs: curl -LS -o vulkan-sdk.exe https://sdk.lunarg.com/sdk/download/${{ matrix.vulkan-sdk }}/windows/VulkanSDK-${{ matrix.vulkan-sdk }}-Installer.exe } 7z x vulkan-sdk.exe -o"${{ env.vulkan_sdk }}" + 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 cmake -E make_directory ${{ runner.workspace }}/${{ github.event.repository.name }}/build From a79bf351b17bb4f577336e354706d8786ac07fd8 Mon Sep 17 00:00:00 2001 From: Johannes Unterguggenberger Date: Sat, 16 Sep 2023 17:16:42 +0200 Subject: [PATCH 02/16] Added indirect draw mesh tasks commands * Added commands for indirect drawMeshTasks * Linked implementation to correct function name: draw_mesh_tasks_indirect_count_ext * Trying the workflows with SDK version 1.2.198.1 --- .github/workflows/build.yml | 8 +++--- include/avk/commands.hpp | 57 ++++++++++++++++++++++++++----------- src/avk.cpp | 55 +++++++++++++++++++++++++++++++++-- 3 files changed, 97 insertions(+), 23 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dc35856..23a690f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -103,10 +103,10 @@ jobs: cxx: "cl" } # note: if a specific vulkan version (e.g. 1.1.x, or < 1.2.135) needs testing, you can add it here: - vulkan-sdk: ["latest", "1.3.216.0", "1.2.176.1"] + 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.176" + - vulkan-sdk: "1.2.198" vma: "ON" steps: @@ -172,10 +172,10 @@ jobs: cxx: "cl" } # note: if a specific vulkan version (e.g. 1.1.x, or < 1.2.135) needs testing, you can add it here: - vulkan-sdk: ["latest", "1.3.216.0", "1.2.176.1"] + 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.176" + - vulkan-sdk: "1.2.198" vma: "ON" steps: diff --git a/include/avk/commands.hpp b/include/avk/commands.hpp index 5a85339..0987fec 100644 --- a/include/avk/commands.hpp +++ b/include/avk/commands.hpp @@ -930,31 +930,54 @@ namespace avk } /** - * \brief Perform a dispatch call, i.e., invoke a compute shader - * \param aGroupCountX The number of local workgroups to dispatch in the X dimension. - * \param aGroupCountY The number of local workgroups to dispatch in the Y dimension. - * \param aGroupCountZ The number of local workgroups to dispatch in the Z dimension. - * \return An action_type_command instance which you must submit to a queue. + * @brief Perform a dispatch call, i.e., invoke a compute shader + * @param aGroupCountX The number of local workgroups to dispatch in the X dimension. + * @param aGroupCountY The number of local workgroups to dispatch in the Y dimension. + * @param aGroupCountZ The number of local workgroups to dispatch in the Z dimension. + * @return An action_type_command instance which you must submit to a queue. */ extern action_type_command dispatch(uint32_t aGroupCountX, uint32_t aGroupCountY, uint32_t aGroupCountZ); /** - * \brief Invoke a graphics mesh pipeline-type draw call using an API function provided by VK_NV_mesh_shader. - * \param aTaskCount The number of local workgroups to dispatch in the X dimension. Y and Z dimension are implicitly set to one. - * \param aFirstTask The X component of the first workgroup ID - * \return An action_type_command instance which you must submit to a queue. + * @brief Invoke a graphics mesh pipeline-type draw call using an API function provided by VK_NV_mesh_shader. + * @param aTaskCount The number of local workgroups to dispatch in the X dimension. Y and Z dimension are implicitly set to one. + * @param aFirstTask The X component of the first workgroup ID + * @return An action_type_command instance which you must submit to a queue. */ extern action_type_command draw_mesh_tasks_nv(uint32_t aTaskCount, uint32_t aFirstTask); #if VK_HEADER_VERSION >= 239 - /** - * \brief Invoke a graphics mesh pipeline-type draw call using an API function provided by VK_EXT_mesh_shader. - * \param aGroupCountX The number of local workgroups to dispatch in the X dimension. - * \param aGroupCountY The number of local workgroups to dispatch in the Y dimension. - * \param aGroupCountZ The number of local workgroups to dispatch in the Z dimension. - * \return An action_type_command instance which you must submit to a queue. - */ - extern action_type_command draw_mesh_tasks_ext(uint32_t aGroupCountX, uint32_t aGroupCountY, uint32_t aGroupCountZ); + /** + * @brief Invoke a graphics mesh pipeline-type draw call using an API function provided by VK_EXT_mesh_shader. + * @param aGroupCountX The number of local workgroups to dispatch in the X dimension. + * @param aGroupCountY The number of local workgroups to dispatch in the Y dimension. + * @param aGroupCountZ The number of local workgroups to dispatch in the Z dimension. + * @return An action_type_command instance which you must submit to a queue. + */ + + extern action_type_command draw_mesh_tasks_ext(uint32_t aGroupCountX, uint32_t aGroupCountY, uint32_t aGroupCountZ); + + /** + * @brief Invoke a graphics mesh pipeline-type draw call using an API function provided by VK_EXT_mesh_shader. + * @param aDrawBuffer buffer containing draw parameters. + * @param aDrawBufferOffset byte offset into buffer where parameters begin. + * @param aDrawCount number of draws to execute, and can be zero. + * @param aStride byte stride between successive sets of draw parameters. + * @return An action_type_command instance which you must submit to a queue. + */ + extern action_type_command draw_mesh_tasks_indirect_ext(avk::buffer aDrawBuffer, vk::DeviceSize aDrawBufferOffset, uint32_t aDrawCount, uint32_t aStride); + + /** + * @brief Invoke a graphics mesh pipeline-type draw call using an API function provided by VK_EXT_mesh_shader. + * @param aDrawBuffer buffer containing draw parameters. + * @param aDrawBufferOffset byte offset into buffer where parameters begin. + * @param aCountBuffer buffer containing the draw count. + * @param aCountBufferOffset byte offset into countBuffer where the draw count begins. + * @param aMaxDrawCount number of draws to execute, and can be zero. + * @param aStride byte stride between successive sets of draw parameters. + * @return An action_type_command instance which you must submit to a queue. + */ + extern action_type_command draw_mesh_tasks_indirect_count_ext(avk::buffer aDrawBuffer, vk::DeviceSize aDrawBufferOffset, avk::buffer aCountBuffer, uint32_t aCountBufferOffset, uint32_t aMaxDrawCount, uint32_t aStride); #endif #if VK_HEADER_VERSION >= 135 diff --git a/src/avk.cpp b/src/avk.cpp index 7879560..a891e7e 100644 --- a/src/avk.cpp +++ b/src/avk.cpp @@ -2887,8 +2887,6 @@ namespace avk command_buffer_t& command_buffer_t::handle_lifetime_of(any_owning_resource_t aResource) { - - mLifetimeHandledResources.push_back(std::move(aResource)); return *this; } @@ -8539,6 +8537,59 @@ namespace avk } }; } + + action_type_command draw_mesh_tasks_indirect_ext(avk::buffer aDrawBuffer, vk::DeviceSize aDrawBufferOffset, uint32_t aDrawCount, uint32_t aStride) + { + auto actionTypeCmd = action_type_command{ + avk::sync::sync_hint { + {{ // What previous commands must synchronize with: + vk::PipelineStageFlagBits2KHR::eAllGraphics, + vk::AccessFlagBits2KHR::eInputAttachmentRead + | vk::AccessFlagBits2KHR::eColorAttachmentRead + | vk::AccessFlagBits2KHR::eColorAttachmentWrite + | vk::AccessFlagBits2KHR::eDepthStencilAttachmentRead + | vk::AccessFlagBits2KHR::eDepthStencilAttachmentWrite + | vk::AccessFlagBits2KHR::eShaderStorageRead // Because we must expect to read data from buffers + }}, + {{ // What subsequent commands must synchronize with: + vk::PipelineStageFlagBits2KHR::eAllGraphics, + vk::AccessFlagBits2KHR::eColorAttachmentWrite | vk::AccessFlagBits2KHR::eDepthStencilAttachmentWrite + }} + }, + {}, [lBufferHandle = aDrawBuffer->handle(), aDrawBufferOffset, aDrawCount, aStride](avk::command_buffer_t& cb) { + cb.handle().drawMeshTasksIndirectEXT(lBufferHandle, aDrawBufferOffset, aDrawCount, aStride, cb.root_ptr()->dispatch_loader_ext()); + } + }; + actionTypeCmd.mLifetimeHandledResources.push_back(std::move(aDrawBuffer)); + return actionTypeCmd; + } + + action_type_command draw_mesh_tasks_indirect_count_ext(avk::buffer aDrawBuffer, vk::DeviceSize aDrawBufferOffset, avk::buffer aCountBuffer, uint32_t aCountBufferOffset, uint32_t aMaxDrawCount, uint32_t aStride) + { + auto actionTypeCmd = action_type_command{ + avk::sync::sync_hint { + {{ // What previous commands must synchronize with: + vk::PipelineStageFlagBits2KHR::eAllGraphics, + vk::AccessFlagBits2KHR::eInputAttachmentRead + | vk::AccessFlagBits2KHR::eColorAttachmentRead + | vk::AccessFlagBits2KHR::eColorAttachmentWrite + | vk::AccessFlagBits2KHR::eDepthStencilAttachmentRead + | vk::AccessFlagBits2KHR::eDepthStencilAttachmentWrite + | vk::AccessFlagBits2KHR::eShaderStorageRead // Because we must expect to read data from buffers + }}, + {{ // What subsequent commands must synchronize with: + vk::PipelineStageFlagBits2KHR::eAllGraphics, + vk::AccessFlagBits2KHR::eColorAttachmentWrite | vk::AccessFlagBits2KHR::eDepthStencilAttachmentWrite + }} + }, + {}, [lDrawBufferHandle = aDrawBuffer->handle(), aDrawBufferOffset, lCountBufferHandle = aCountBuffer->handle(), aCountBufferOffset, aMaxDrawCount, aStride](avk::command_buffer_t& cb) { + cb.handle().drawMeshTasksIndirectCountEXT(lDrawBufferHandle, aDrawBufferOffset, lCountBufferHandle, aCountBufferOffset, aMaxDrawCount, aStride, cb.root_ptr()->dispatch_loader_ext()); + } + }; + actionTypeCmd.mLifetimeHandledResources.push_back(std::move(aDrawBuffer)); + actionTypeCmd.mLifetimeHandledResources.push_back(std::move(aCountBuffer)); + return actionTypeCmd; + } #endif From c904add6f07da0bf2fbef6e52986cdd0bb8ffe00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C4=9Bj=20Sakmary?= Date: Mon, 23 Oct 2023 09:04:29 +0200 Subject: [PATCH 03/16] Change barrier access bit to fix validation errors (#91) --- src/avk.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/avk.cpp b/src/avk.cpp index a891e7e..a25e7ec 100644 --- a/src/avk.cpp +++ b/src/avk.cpp @@ -1661,8 +1661,8 @@ namespace avk // Create sync hint for each one of the buffers // As the specification has it: // Accesses to other input buffers [...] must be synchronized with the VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR pipeline stageand an access type of VK_ACCESS_SHADER_READ_BIT: - resSpecificSyncHints.push_back(std::make_tuple(vertexBuffer->handle(), avk::sync::sync_hint{ stage::acceleration_structure_build + access::shader_read, stage::acceleration_structure_build + access::none })); - resSpecificSyncHints.push_back(std::make_tuple(indexBuffer->handle(), avk::sync::sync_hint{ stage::acceleration_structure_build + access::shader_read, stage::acceleration_structure_build + access::none })); + resSpecificSyncHints.push_back(std::make_tuple(vertexBuffer->handle(), avk::sync::sync_hint{ stage::acceleration_structure_build + access::acceleration_structure_read, stage::acceleration_structure_build + access::none })); + resSpecificSyncHints.push_back(std::make_tuple(indexBuffer->handle(), avk::sync::sync_hint{ stage::acceleration_structure_build + access::acceleration_structure_read, stage::acceleration_structure_build + access::none })); // See if we must handle the lifetime of the two buffers: if (vertexBuffer.is_ownership()) { @@ -1786,7 +1786,7 @@ namespace avk // Let's additionally also fill the dependencies for the geometries buffer: // As the specification has it: // Accesses to other input buffers [...] must be synchronized with the VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR pipeline stage and an access type of VK_ACCESS_SHADER_READ_BIT: - resSpecificSyncHints.push_back(std::make_tuple(aGeometriesBuffer->handle(), avk::sync::sync_hint{ stage::acceleration_structure_build + access::shader_read, stage::acceleration_structure_build + access::none })); + resSpecificSyncHints.push_back(std::make_tuple(aGeometriesBuffer->handle(), avk::sync::sync_hint{ stage::acceleration_structure_build + access::acceleration_structure_read, stage::acceleration_structure_build + access::none })); const auto& aabbMeta = aGeometriesBuffer->meta(); auto startAddress = aGeometriesBuffer->device_address(); @@ -2048,7 +2048,7 @@ namespace avk // Let's additionally also fill the dependencies for the geometries buffer: // As the specification has it: // Accesses to other input buffers [...] must be synchronized with the VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR pipeline stage and an access type of VK_ACCESS_SHADER_READ_BIT: - resSpecificSyncHints.push_back(std::make_tuple(aGeometryInstancesBuffer->handle(), avk::sync::sync_hint{ stage::acceleration_structure_build + access::shader_read, stage::acceleration_structure_build + access::none })); + resSpecificSyncHints.push_back(std::make_tuple(aGeometryInstancesBuffer->handle(), avk::sync::sync_hint{ stage::acceleration_structure_build + access::acceleration_structure_read, stage::acceleration_structure_build + access::none })); const auto& metaData = aGeometryInstancesBuffer->meta(); auto startAddress = aGeometryInstancesBuffer->device_address(); From ef130f13aa0a41f8a8e55475db6b58fd597e37fe Mon Sep 17 00:00:00 2001 From: Johannes Unterguggenberger Date: Tue, 19 Dec 2023 15:21:07 +0100 Subject: [PATCH 04/16] Added dispatch_indirect command --- include/avk/commands.hpp | 15 ++++++++++++--- src/avk.cpp | 22 ++++++++++++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/include/avk/commands.hpp b/include/avk/commands.hpp index 0987fec..a73d48a 100644 --- a/include/avk/commands.hpp +++ b/include/avk/commands.hpp @@ -938,6 +938,14 @@ namespace avk */ extern action_type_command dispatch(uint32_t aGroupCountX, uint32_t aGroupCountY, uint32_t aGroupCountZ); + /** + * @brief Perform an indirect dispatch call, i.e., invoke a compute shader with dispatch parameters read from a buffer + * @param aCountBuffer Buffer containing the draw count in form of a VkDispatchIndirectCommand record. See Vulkan specification: https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkDispatchIndirectCommand.html + * @param aCountBufferOffset Byte offset into countBuffer to the beginning of such a VkDispatchIndirectCommand record. + * @return An action_type_command instance which you must submit to a queue. + */ + extern action_type_command dispatch_indirect(avk::buffer aCountBuffer, uint32_t aCountBufferOffset); + /** * @brief Invoke a graphics mesh pipeline-type draw call using an API function provided by VK_NV_mesh_shader. * @param aTaskCount The number of local workgroups to dispatch in the X dimension. Y and Z dimension are implicitly set to one. @@ -954,7 +962,6 @@ namespace avk * @param aGroupCountZ The number of local workgroups to dispatch in the Z dimension. * @return An action_type_command instance which you must submit to a queue. */ - extern action_type_command draw_mesh_tasks_ext(uint32_t aGroupCountX, uint32_t aGroupCountY, uint32_t aGroupCountZ); /** @@ -1318,8 +1325,10 @@ namespace avk inline static void add_commands(std::vector& aGatheredCommands, std::vector& aManyMoreCommands, Ts&... aRest) { // Use std::insert with std::make_move_iterator as suggested here: https://stackoverflow.com/questions/15004517/moving-elements-from-stdvector-to-another-one - aGatheredCommands.insert(std::end(aGatheredCommands), std::make_move_iterator(std::begin(aManyMoreCommands)), - std::make_move_iterator(std::end(aManyMoreCommands))); + aGatheredCommands.insert( + std::end(aGatheredCommands), + std::make_move_iterator(std::begin(aManyMoreCommands)), std::make_move_iterator(std::end(aManyMoreCommands)) + ); add_commands(aGatheredCommands, aRest...); } diff --git a/src/avk.cpp b/src/avk.cpp index a25e7ec..3e6e7cb 100644 --- a/src/avk.cpp +++ b/src/avk.cpp @@ -8487,6 +8487,28 @@ namespace avk }; } + action_type_command dispatch_indirect(avk::buffer aCountBuffer, uint32_t aCountBufferOffset) + { + auto actionTypeCmd = action_type_command{ + avk::sync::sync_hint { + {{ // What previous commands must synchronize with: + vk::PipelineStageFlagBits2KHR::eComputeShader, + vk::AccessFlagBits2KHR::eShaderSampledRead | vk::AccessFlagBits2KHR::eShaderStorageRead | vk::AccessFlagBits2KHR::eShaderStorageWrite + }}, + {{ // What subsequent commands must synchronize with: + vk::PipelineStageFlagBits2KHR::eComputeShader, + vk::AccessFlagBits2KHR::eShaderStorageWrite + }} + }, + {}, + [lCountBufferHandle = aCountBuffer->handle(), aCountBufferOffset](avk::command_buffer_t& cb) { + cb.handle().dispatchIndirect(lCountBufferHandle, aCountBufferOffset, cb.root_ptr()->dispatch_loader_core()); + } + }; + actionTypeCmd.mLifetimeHandledResources.push_back(std::move(aCountBuffer)); + return actionTypeCmd; + } + action_type_command draw_mesh_tasks_nv(uint32_t aTaskCount, uint32_t aFirstTask) { return action_type_command{ From 9e60e927e818ba714526d5463a477b908c97b86a Mon Sep 17 00:00:00 2001 From: Johannes Unterguggenberger Date: Thu, 21 Dec 2023 12:34:29 +0100 Subject: [PATCH 05/16] Added offset support to draw_indexed, via const std::tuple& aIndexBufferAndOffset --- include/avk/commands.hpp | 149 ++++++++++++++++++++++++++++++--------- 1 file changed, 114 insertions(+), 35 deletions(-) diff --git a/include/avk/commands.hpp b/include/avk/commands.hpp index a73d48a..ed4d7a0 100644 --- a/include/avk/commands.hpp +++ b/include/avk/commands.hpp @@ -585,6 +585,9 @@ namespace avk // stop this reCURSEion! } + template + void bind_vertex_buffer(vk::Buffer* aHandlePtr, vk::DeviceSize* aOffsetPtr, const std::tuple& aVertexBufferAndOffset, const Rest&... aRest); + template void bind_vertex_buffer(vk::Buffer* aHandlePtr, vk::DeviceSize* aOffsetPtr, const buffer_t& aVertexBuffer, const Rest&... aRest) { @@ -610,8 +613,12 @@ namespace avk * @param aNumberOfInstances Number of instances to draw * @param aFirstVertex Offset to the first vertex * @param aFirstInstance The ID of the first instance - * @param aFurtherBuffers And optionally, there can be further references to vertex buffers, i.e. you MUST manually convert - * to avk::resource_reference, either via avk::referenced or via avk::const_referenced + * @param aFurtherBuffers Multiple const-references to buffers, or tuples of const-references to buffers + offsets. + * First case: Pass const buffer_t& types! + * Second case: Pass tuples of type std::tuple! + * Hint: std::forward_as_tuple might be useful to get that reference into a std::tuple. + * Example: avk::buffer myVertexBuffer; + * auto myTuple = std::forward_as_tuple(myVertexBuffer.get(), size_t{0}); */ template action_type_command draw_vertices(uint32_t aNumberOfVertices, uint32_t aNumberOfInstances, uint32_t aFirstVertex, uint32_t aFirstInstance, const buffer_t& aVertexBuffer, const Bfrs&... aFurtherBuffers) @@ -658,9 +665,11 @@ namespace avk * @param aFirstVertex Offset to the first vertex * @param aFirstInstance The ID of the first instance * @param aFurtherBuffers Multiple const-references to buffers, or tuples of const-references to buffers + offsets. - * First case: Pass resource_reference types! - * Second case: Pass tuples of type std::tuple, size_t>! - * Note: You MUST manually convert to avk::resource_reference via avk::const_referenced! + * First case: Pass const buffer_t& types! + * Second case: Pass tuples of type std::tuple! + * Hint: std::forward_as_tuple might be useful to get that reference into a std::tuple. + * Example: avk::buffer myVertexBuffer; + * auto myTuple = std::forward_as_tuple(myVertexBuffer.get(), size_t{0}); */ template action_type_command draw_vertices(uint32_t aNumberOfInstances, uint32_t aFirstVertex, uint32_t aFirstInstance, const buffer_t& aVertexBuffer, const Bfrs&... aFurtherBuffers) @@ -679,9 +688,11 @@ namespace avk * @param aVertexBuffer There must be at least one vertex buffer, the meta data of which will be used * to get the number of vertices to draw. * @param aFurtherBuffers Multiple const-references to buffers, or tuples of const-references to buffers + offsets. - * First case: Pass resource_reference types! - * Second case: Pass tuples of type std::tuple, size_t>! - * Note: You MUST manually convert to avk::resource_reference via avk::const_referenced! + * First case: Pass const buffer_t& types! + * Second case: Pass tuples of type std::tuple! + * Hint: std::forward_as_tuple might be useful to get that reference into a std::tuple. + * Example: avk::buffer myVertexBuffer; + * auto myTuple = std::forward_as_tuple(myVertexBuffer.get(), size_t{0}); */ template action_type_command draw_vertices(const buffer_t& aVertexBuffer, const Bfrs&... aFurtherBuffers) @@ -692,25 +703,32 @@ namespace avk /** Perform an indexed draw call with vertex buffer bindings starting at BUFFER-BINDING #0 top to the number of total vertex buffers passed -1. * "BUFFER-BINDING" means that it corresponds to the binding specified in `input_binding_location_data::from_buffer_at_binding`. * There can be no gaps between buffer bindings. - * @param aIndexBuffer Reference to an index buffer - * @param aNumberOfInstances Number of instances to draw - * @param aFirstIndex Offset to the first index - * @param aVertexOffset Offset to the first vertex - * @param aFirstInstance The ID of the first instance - * @param aVertexBuffers Multiple const-references to buffers, or tuples of const-references to buffers + offsets. - * First case: Pass resource_reference types! - * Second case: Pass tuples of type std::tuple, size_t>! - * Note: You MUST manually convert to avk::resource_reference via avk::const_referenced! + * @param aIndexBufferAndOffset One const-reference to an index buffer, or a tuple of a const-reference and an offset. + * First case: Pass a const buffer_t& + * Second case: Pass a tuple of type std::tuple! + * Hint: std::forward_as_tuple might be useful to get that reference into a std::tuple. + * Example: avk::buffer myIndexBuffer; + * auto myTuple = std::forward_as_tuple(myIndexBuffer.get(), size_t{0}); + * @param aNumberOfInstances Number of instances to draw + * @param aFirstIndex Offset to the first index + * @param aVertexOffset Offset to the first vertex + * @param aFirstInstance The ID of the first instance + * @param aVertexBuffers Multiple const-references to buffers, or tuples of const-references to buffers + offsets. + * First case: Pass const buffer_t& types! + * Second case: Pass tuples of type std::tuple! + * Hint: std::forward_as_tuple might be useful to get that reference into a std::tuple. + * Example: avk::buffer myVertexBuffer; + * auto myTuple = std::forward_as_tuple(myVertexBuffer.get(), size_t{0}); */ template - action_type_command draw_indexed(const buffer_t& aIndexBuffer, uint32_t aNumberOfInstances, uint32_t aFirstIndex, uint32_t aVertexOffset, uint32_t aFirstInstance, const Bfrs&... aVertexBuffers) + action_type_command draw_indexed(const std::tuple& aIndexBufferAndOffset, uint32_t aNumberOfInstances, uint32_t aFirstIndex, uint32_t aVertexOffset, uint32_t aFirstInstance, const Bfrs&... aVertexBuffers) { constexpr size_t N = sizeof...(aVertexBuffers); std::array handles; std::array offsets; bind_vertex_buffer(&handles[0], &offsets[0], aVertexBuffers...); - const auto& indexMeta = aIndexBuffer.template meta(); + const auto& indexMeta = std::get(aIndexBufferAndOffset).template meta(); vk::IndexType indexType; switch (indexMeta.sizeof_one_element()) { case sizeof(uint16_t) : indexType = vk::IndexType::eUint16; break; @@ -731,22 +749,71 @@ namespace avk }, {}, // no resource-specific sync hints [ + indexBufferOffset = std::get(aIndexBufferAndOffset), lBindingCount = static_cast(N), handles, offsets, indexType, lNumElemments = static_cast(indexMeta.num_elements()), - lIndexBufferHandle = aIndexBuffer.handle(), + lIndexBufferHandle = std::get(aIndexBufferAndOffset).handle(), aNumberOfInstances, aFirstIndex, aVertexOffset, aFirstInstance ](avk::command_buffer_t& cb) { cb.handle().bindVertexBuffers( 0u, // TODO: Should the first binding really always be 0? lBindingCount, handles.data(), offsets.data() ); - cb.handle().bindIndexBuffer(lIndexBufferHandle, 0u, indexType); + cb.handle().bindIndexBuffer(lIndexBufferHandle, indexBufferOffset, indexType); cb.handle().drawIndexed(lNumElemments, aNumberOfInstances, aFirstIndex, aVertexOffset, aFirstInstance); } }; } + /** Perform an indexed draw call with vertex buffer bindings starting at BUFFER-BINDING #0 top to the number of total vertex buffers passed -1. + * "BUFFER-BINDING" means that it corresponds to the binding specified in `input_binding_location_data::from_buffer_at_binding`. + * Offset into the index buffer is 0. + * There can be no gaps between buffer bindings. + * @param aIndexBuffer Reference to an index buffer + * @param aNumberOfInstances Number of instances to draw + * @param aFirstIndex Offset to the first index + * @param aVertexOffset Offset to the first vertex + * @param aFirstInstance The ID of the first instance + * @param aVertexBuffers Multiple const-references to buffers, or tuples of const-references to buffers + offsets. + * First case: Pass const buffer_t& types! + * Second case: Pass tuples of type std::tuple! + * Hint: std::forward_as_tuple might be useful to get that reference into a std::tuple. + * Example: avk::buffer myVertexBuffer; + * auto myTuple = std::forward_as_tuple(myVertexBuffer.get(), size_t{0}); + */ + template + action_type_command draw_indexed(const buffer_t& aIndexBuffer, uint32_t aNumberOfInstances, uint32_t aFirstIndex, uint32_t aVertexOffset, uint32_t aFirstInstance, const Bfrs&... aVertexBuffers) + { + return draw_indexed(std::forward_as_tuple(aIndexBuffer, size_t{0}), aNumberOfInstances, aFirstIndex, aVertexOffset, aFirstInstance, aVertexBuffers...); + } + + /** Perform an indexed draw call with vertex buffer bindings starting at BUFFER-BINDING #0 top to the number of total vertex buffers passed -1. + * "BUFFER-BINDING" means that it corresponds to the binding specified in `input_binding_location_data::from_buffer_at_binding`. + * There can be no gaps between buffer bindings. + * Number of instances is set to 1. + * The first index is set to 0. + * The vertex offset is set to 0. + * The ID of the first instance is set to 0. + * @param aIndexBufferAndOffset One const-reference to an index buffer, or a tuple of a const-reference and an offset. + * First case: Pass a const buffer_t& + * Second case: Pass a tuple of type std::tuple! + * Hint: std::forward_as_tuple might be useful to get that reference into a std::tuple. + * Example: avk::buffer myIndexBuffer; + * auto myTuple = std::forward_as_tuple(myIndexBuffer.get(), size_t{0}); + * @param aVertexBuffers Multiple const-references to buffers, or tuples of const-references to buffers + offsets. + * First case: Pass const buffer_t& types! + * Second case: Pass tuples of type std::tuple! + * Hint: std::forward_as_tuple might be useful to get that reference into a std::tuple. + * Example: avk::buffer myVertexBuffer; + * auto myTuple = std::forward_as_tuple(myVertexBuffer.get(), size_t{0}); + */ + template + action_type_command draw_indexed(const std::tuple& aIndexBufferAndOffset, const Bfrs&... aVertexBuffers) + { + return draw_indexed(aIndexBufferAndOffset, 1u, 0u, 0u, 0u, aVertexBuffers...); + } + /** Perform an indexed draw call with vertex buffer bindings starting at BUFFER-BINDING #0 top to the number of total vertex buffers passed -1. * "BUFFER-BINDING" means that it corresponds to the binding specified in `input_binding_location_data::from_buffer_at_binding`. * There can be no gaps between buffer bindings. @@ -755,8 +822,12 @@ namespace avk * The vertex offset is set to 0. * The ID of the first instance is set to 0. * @param aIndexBuffer Reference to an index buffer - * @param aVertexBuffers References to one or multiple vertex buffers, i.e. you MUST manually convert - * to avk::resource_reference, either via avk::referenced or via avk::const_referenced + * @param aVertexBuffers Multiple const-references to buffers, or tuples of const-references to buffers + offsets. + * First case: Pass const buffer_t& types! + * Second case: Pass tuples of type std::tuple! + * Hint: std::forward_as_tuple might be useful to get that reference into a std::tuple. + * Example: avk::buffer myVertexBuffer; + * auto myTuple = std::forward_as_tuple(myVertexBuffer.get(), size_t{0}); */ template action_type_command draw_indexed(const buffer_t& aIndexBuffer, const Bfrs&... aVertexBuffers) @@ -773,9 +844,11 @@ namespace avk * @param aParametersOffset Byte offset into the parameters buffer where the actual draw parameters begin * @param aParametersStride Byte stride between successive sets of draw parameters in the parameters buffer * @param aVertexBuffers Multiple const-references to buffers, or tuples of const-references to buffers + offsets. - * First case: Pass resource_reference types! - * Second case: Pass tuples of type std::tuple, size_t>! - * Note: You MUST manually convert to avk::resource_reference via avk::const_referenced! + * First case: Pass const buffer_t& types! + * Second case: Pass tuples of type std::tuple! + * Hint: std::forward_as_tuple might be useful to get that reference into a std::tuple. + * Example: avk::buffer myVertexBuffer; + * auto myTuple = std::forward_as_tuple(myVertexBuffer.get(), size_t{0}); * * NOTE: Make sure the _exact_ types are used for aParametersOffset (vk::DeviceSize) and aParametersStride (uint32_t) to avoid compile errors. */ @@ -833,9 +906,11 @@ namespace avk * @param aIndexBuffer Reference to an index buffer * @param aNumberOfDraws Number of draws to execute * @param aVertexBuffers Multiple const-references to buffers, or tuples of const-references to buffers + offsets. - * First case: Pass resource_reference types! - * Second case: Pass tuples of type std::tuple, size_t>! - * Note: You MUST manually convert to avk::resource_reference via avk::const_referenced! + * First case: Pass const buffer_t& types! + * Second case: Pass tuples of type std::tuple! + * Hint: std::forward_as_tuple might be useful to get that reference into a std::tuple. + * Example: avk::buffer myVertexBuffer; + * auto myTuple = std::forward_as_tuple(myVertexBuffer.get(), size_t{0}); */ template action_type_command draw_indexed_indirect(const buffer_t& aParametersBuffer, const buffer_t& aIndexBuffer, uint32_t aNumberOfDraws, const Bfrs&... aVertexBuffers) @@ -854,9 +929,11 @@ namespace avk * @param aDrawCountBuffer Reference to a draw count buffer, containing the number of draws to execute in one uint32_t * @param aDrawCountOffset Byte offset into the draw count buffer where the actual draw count is located * @param aVertexBuffers Multiple const-references to buffers, or tuples of const-references to buffers + offsets. - * First case: Pass resource_reference types! - * Second case: Pass tuples of type std::tuple, size_t>! - * Note: You MUST manually convert to avk::resource_reference via avk::const_referenced! + * First case: Pass const buffer_t& types! + * Second case: Pass tuples of type std::tuple! + * Hint: std::forward_as_tuple might be useful to get that reference into a std::tuple. + * Example: avk::buffer myVertexBuffer; + * auto myTuple = std::forward_as_tuple(myVertexBuffer.get(), size_t{0}); * * See vkCmdDrawIndexedIndirectCount in the Vulkan specification for more details. */ @@ -917,9 +994,11 @@ namespace avk * @param aMaxNumberOfDraws Maximum number of draws to execute (the actual number of draws is the minimum of aMaxNumberOfDraws and the value stored in the draw count buffer) * @param aDrawCountBuffer Reference to a draw count buffer, containing the number of draws to execute in one uint32_t * @param aVertexBuffers Multiple const-references to buffers, or tuples of const-references to buffers + offsets. - * First case: Pass resource_reference types! - * Second case: Pass tuples of type std::tuple, size_t>! - * Note: You MUST manually convert to avk::resource_reference via avk::const_referenced! + * First case: Pass const buffer_t& types! + * Second case: Pass tuples of type std::tuple! + * Hint: std::forward_as_tuple might be useful to get that reference into a std::tuple. + * Example: avk::buffer myVertexBuffer; + * auto myTuple = std::forward_as_tuple(myVertexBuffer.get(), size_t{0}); * * See vkCmdDrawIndexedIndirectCount in the Vulkan specification for more details. */ From a0be5e08b034e91c9a545ee2626ce9382cd4ff35 Mon Sep 17 00:00:00 2001 From: Johannes Unterguggenberger Date: Thu, 21 Dec 2023 16:24:50 +0100 Subject: [PATCH 06/16] Number of indices to draw is now configurable as well in draw_indexed --- include/avk/commands.hpp | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/include/avk/commands.hpp b/include/avk/commands.hpp index ed4d7a0..78c8c29 100644 --- a/include/avk/commands.hpp +++ b/include/avk/commands.hpp @@ -703,12 +703,11 @@ namespace avk /** Perform an indexed draw call with vertex buffer bindings starting at BUFFER-BINDING #0 top to the number of total vertex buffers passed -1. * "BUFFER-BINDING" means that it corresponds to the binding specified in `input_binding_location_data::from_buffer_at_binding`. * There can be no gaps between buffer bindings. - * @param aIndexBufferAndOffset One const-reference to an index buffer, or a tuple of a const-reference and an offset. - * First case: Pass a const buffer_t& - * Second case: Pass a tuple of type std::tuple! + * @param aIndexBufferAndOffsetAndNumElements Tuple of a const-reference and an offset and a number of elements to draw + * Pass a tuple of type std::tuple! * Hint: std::forward_as_tuple might be useful to get that reference into a std::tuple. * Example: avk::buffer myIndexBuffer; - * auto myTuple = std::forward_as_tuple(myIndexBuffer.get(), size_t{0}); + * auto myTuple = std::forward_as_tuple(myIndexBuffer.get(), size_t{0}, uint32_t{1}); * @param aNumberOfInstances Number of instances to draw * @param aFirstIndex Offset to the first index * @param aVertexOffset Offset to the first vertex @@ -721,14 +720,14 @@ namespace avk * auto myTuple = std::forward_as_tuple(myVertexBuffer.get(), size_t{0}); */ template - action_type_command draw_indexed(const std::tuple& aIndexBufferAndOffset, uint32_t aNumberOfInstances, uint32_t aFirstIndex, uint32_t aVertexOffset, uint32_t aFirstInstance, const Bfrs&... aVertexBuffers) + action_type_command draw_indexed(const std::tuple& aIndexBufferAndOffsetAndNumElements, uint32_t aNumberOfInstances, uint32_t aFirstIndex, uint32_t aVertexOffset, uint32_t aFirstInstance, const Bfrs&... aVertexBuffers) { constexpr size_t N = sizeof...(aVertexBuffers); std::array handles; std::array offsets; bind_vertex_buffer(&handles[0], &offsets[0], aVertexBuffers...); - const auto& indexMeta = std::get(aIndexBufferAndOffset).template meta(); + const auto& indexMeta = std::get(aIndexBufferAndOffsetAndNumElements).template meta(); vk::IndexType indexType; switch (indexMeta.sizeof_one_element()) { case sizeof(uint16_t) : indexType = vk::IndexType::eUint16; break; @@ -749,11 +748,11 @@ namespace avk }, {}, // no resource-specific sync hints [ - indexBufferOffset = std::get(aIndexBufferAndOffset), + indexBufferOffset = std::get(aIndexBufferAndOffsetAndNumElements), lBindingCount = static_cast(N), handles, offsets, indexType, - lNumElemments = static_cast(indexMeta.num_elements()), - lIndexBufferHandle = std::get(aIndexBufferAndOffset).handle(), + lNumElemments = std::get(aIndexBufferAndOffsetAndNumElements), + lIndexBufferHandle = std::get(aIndexBufferAndOffsetAndNumElements).handle(), aNumberOfInstances, aFirstIndex, aVertexOffset, aFirstInstance ](avk::command_buffer_t& cb) { cb.handle().bindVertexBuffers( @@ -785,7 +784,8 @@ namespace avk template action_type_command draw_indexed(const buffer_t& aIndexBuffer, uint32_t aNumberOfInstances, uint32_t aFirstIndex, uint32_t aVertexOffset, uint32_t aFirstInstance, const Bfrs&... aVertexBuffers) { - return draw_indexed(std::forward_as_tuple(aIndexBuffer, size_t{0}), aNumberOfInstances, aFirstIndex, aVertexOffset, aFirstInstance, aVertexBuffers...); + const auto& indexMeta = aIndexBuffer.meta(); + return draw_indexed(std::forward_as_tuple(aIndexBuffer, size_t{0}, indexMeta.num_elements()), aNumberOfInstances, aFirstIndex, aVertexOffset, aFirstInstance, aVertexBuffers...); } /** Perform an indexed draw call with vertex buffer bindings starting at BUFFER-BINDING #0 top to the number of total vertex buffers passed -1. @@ -795,12 +795,11 @@ namespace avk * The first index is set to 0. * The vertex offset is set to 0. * The ID of the first instance is set to 0. - * @param aIndexBufferAndOffset One const-reference to an index buffer, or a tuple of a const-reference and an offset. - * First case: Pass a const buffer_t& - * Second case: Pass a tuple of type std::tuple! + * @param aIndexBufferAndOffsetAndNumElements Tuple of a const-reference and an offset and a number of elements to draw + * Pass a tuple of type std::tuple! * Hint: std::forward_as_tuple might be useful to get that reference into a std::tuple. * Example: avk::buffer myIndexBuffer; - * auto myTuple = std::forward_as_tuple(myIndexBuffer.get(), size_t{0}); + * auto myTuple = std::forward_as_tuple(myIndexBuffer.get(), size_t{0}, uint32_t{1}); * @param aVertexBuffers Multiple const-references to buffers, or tuples of const-references to buffers + offsets. * First case: Pass const buffer_t& types! * Second case: Pass tuples of type std::tuple! @@ -809,9 +808,9 @@ namespace avk * auto myTuple = std::forward_as_tuple(myVertexBuffer.get(), size_t{0}); */ template - action_type_command draw_indexed(const std::tuple& aIndexBufferAndOffset, const Bfrs&... aVertexBuffers) + action_type_command draw_indexed(const std::tuple& aIndexBufferAndOffsetAndNumElements, const Bfrs&... aVertexBuffers) { - return draw_indexed(aIndexBufferAndOffset, 1u, 0u, 0u, 0u, aVertexBuffers...); + return draw_indexed(aIndexBufferAndOffsetAndNumElements, 1u, 0u, 0u, 0u, aVertexBuffers...); } /** Perform an indexed draw call with vertex buffer bindings starting at BUFFER-BINDING #0 top to the number of total vertex buffers passed -1. From dc2f354083828183c3bdd0556fc95c1b80c078f0 Mon Sep 17 00:00:00 2001 From: Johannes Unterguggenberger Date: Thu, 21 Dec 2023 16:25:44 +0100 Subject: [PATCH 07/16] Bugfix: Properly taking aOffsetInBytes (DESTINATION buffer offset) into account in buffer_t::fill --- src/avk.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/avk.cpp b/src/avk.cpp index 3e6e7cb..d15e219 100644 --- a/src/avk.cpp +++ b/src/avk.cpp @@ -2626,7 +2626,7 @@ namespace avk //cb.handle().copyBuffer2KHR(©BufferInfo); // TODO: No idea why copyBuffer2KHR fails with an access violation - const auto copyRegion = vk::BufferCopy{ 0u, 0u, dataSize }; + const auto copyRegion = vk::BufferCopy{ 0u, dstOffset, dataSize }; cb.handle().copyBuffer(lOwnedStagingBuffer->handle(), lDstBufferHandle, 1u, ©Region, lRoot->dispatch_loader_core()); // Take care of the lifetime handling of the stagingBuffer, it might still be in use when this method returns: From e9b98e4ceb0c866aeda2ef27f97ff67a005cf825 Mon Sep 17 00:00:00 2001 From: Johannes Unterguggenberger Date: Wed, 27 Dec 2023 12:52:36 +0100 Subject: [PATCH 08/16] Reworked command::draw_vertices a bit s.t. it can be used without vertex buffers as well. Thanks to constexpr there should be no downside to this change whatsoever. --- include/avk/commands.hpp | 86 +++++++++++++++++++++++++--------------- 1 file changed, 53 insertions(+), 33 deletions(-) diff --git a/include/avk/commands.hpp b/include/avk/commands.hpp index 78c8c29..fb4a784 100644 --- a/include/avk/commands.hpp +++ b/include/avk/commands.hpp @@ -608,8 +608,6 @@ namespace avk * "BUFFER-BINDING" means that it corresponds to the binding specified in `input_binding_location_data::from_buffer_at_binding`. * There can be no gaps between buffer bindings. * @param aNumberOfVertices Number of vertices to draw - * @param aVertexBuffer There must be at least one vertex buffer, the meta data of which will be used - * to get the number of vertices to draw. * @param aNumberOfInstances Number of instances to draw * @param aFirstVertex Offset to the first vertex * @param aFirstInstance The ID of the first instance @@ -621,38 +619,60 @@ namespace avk * auto myTuple = std::forward_as_tuple(myVertexBuffer.get(), size_t{0}); */ template - action_type_command draw_vertices(uint32_t aNumberOfVertices, uint32_t aNumberOfInstances, uint32_t aFirstVertex, uint32_t aFirstInstance, const buffer_t& aVertexBuffer, const Bfrs&... aFurtherBuffers) + action_type_command draw_vertices(uint32_t aNumberOfVertices, uint32_t aNumberOfInstances, uint32_t aFirstVertex, uint32_t aFirstInstance, const Bfrs&... aFurtherBuffers) { - - constexpr size_t N = 1 + sizeof...(aFurtherBuffers); - std::array handles; - std::array offsets; - bind_vertex_buffer(&handles[0], &offsets[0], aVertexBuffer, aFurtherBuffers...); - - return action_type_command{ - avk::sync::sync_hint { - {{ // DESTINATION dependencies for previous commands: - vk::PipelineStageFlagBits2KHR::eAllGraphics, - vk::AccessFlagBits2KHR::eInputAttachmentRead | vk::AccessFlagBits2KHR::eColorAttachmentRead | vk::AccessFlagBits2KHR::eColorAttachmentWrite | vk::AccessFlagBits2KHR::eDepthStencilAttachmentRead | vk::AccessFlagBits2KHR::eDepthStencilAttachmentWrite - }}, - {{ // SOURCE dependencies for subsequent commands: - vk::PipelineStageFlagBits2KHR::eAllGraphics, - vk::AccessFlagBits2KHR::eColorAttachmentWrite | vk::AccessFlagBits2KHR::eDepthStencilAttachmentWrite - }} - }, - {}, // no resource-specific sync hints - [ - lBindingCount = static_cast(N), - handles, offsets, - aNumberOfVertices, aNumberOfInstances, aFirstVertex, aFirstInstance - ](avk::command_buffer_t& cb) { - cb.handle().bindVertexBuffers( - 0u, // TODO: Should the first binding really always be 0? - static_cast(N), handles.data(), offsets.data() - ); - cb.handle().draw(aNumberOfVertices, aNumberOfInstances, aFirstVertex, aFirstInstance); - } - }; + constexpr size_t N = sizeof...(aFurtherBuffers); + + if constexpr (N == 0) { + return action_type_command{ + avk::sync::sync_hint { + {{ // DESTINATION dependencies for previous commands: + vk::PipelineStageFlagBits2KHR::eAllGraphics, + vk::AccessFlagBits2KHR::eInputAttachmentRead | vk::AccessFlagBits2KHR::eColorAttachmentRead | vk::AccessFlagBits2KHR::eColorAttachmentWrite | vk::AccessFlagBits2KHR::eDepthStencilAttachmentRead | vk::AccessFlagBits2KHR::eDepthStencilAttachmentWrite + }}, + {{ // SOURCE dependencies for subsequent commands: + vk::PipelineStageFlagBits2KHR::eAllGraphics, + vk::AccessFlagBits2KHR::eColorAttachmentWrite | vk::AccessFlagBits2KHR::eDepthStencilAttachmentWrite + }} + }, + {}, // no resource-specific sync hints + [ + aNumberOfVertices, aNumberOfInstances, aFirstVertex, aFirstInstance + ](avk::command_buffer_t& cb) { + cb.handle().draw(aNumberOfVertices, aNumberOfInstances, aFirstVertex, aFirstInstance); + } + }; + } + else { + std::array handles; + std::array offsets; + bind_vertex_buffer(&handles[0], &offsets[0], aFurtherBuffers...); + + return action_type_command{ + avk::sync::sync_hint { + {{ // DESTINATION dependencies for previous commands: + vk::PipelineStageFlagBits2KHR::eAllGraphics, + vk::AccessFlagBits2KHR::eInputAttachmentRead | vk::AccessFlagBits2KHR::eColorAttachmentRead | vk::AccessFlagBits2KHR::eColorAttachmentWrite | vk::AccessFlagBits2KHR::eDepthStencilAttachmentRead | vk::AccessFlagBits2KHR::eDepthStencilAttachmentWrite + }}, + {{ // SOURCE dependencies for subsequent commands: + vk::PipelineStageFlagBits2KHR::eAllGraphics, + vk::AccessFlagBits2KHR::eColorAttachmentWrite | vk::AccessFlagBits2KHR::eDepthStencilAttachmentWrite + }} + }, + {}, // no resource-specific sync hints + [ + lBindingCount = static_cast(N), + handles, offsets, + aNumberOfVertices, aNumberOfInstances, aFirstVertex, aFirstInstance + ](avk::command_buffer_t& cb) { + cb.handle().bindVertexBuffers( + 0u, // TODO: Should the first binding really always be 0? + static_cast(N), handles.data(), offsets.data() + ); + cb.handle().draw(aNumberOfVertices, aNumberOfInstances, aFirstVertex, aFirstInstance); + } + }; + } } /** Draw vertices with vertex buffer bindings starting at BUFFER-BINDING #0 top to the number of total buffers passed -1. From a40991563e7fdc98306d637dea7e8d8e877e5207 Mon Sep 17 00:00:00 2001 From: Johannes Unterguggenberger Date: Wed, 27 Dec 2023 16:10:55 +0100 Subject: [PATCH 09/16] Added convenience function avk::command::one_n_times and avk::command::many_n_times --- include/avk/commands.hpp | 51 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/include/avk/commands.hpp b/include/avk/commands.hpp index fb4a784..b5c723c 100644 --- a/include/avk/commands.hpp +++ b/include/avk/commands.hpp @@ -1445,7 +1445,13 @@ namespace avk return result; } - // TODO: Comment + /** Convenience function for gathering recorded commands---one avk::command for each entry of a given collection. + * @aCollection Collection to iterate over + * @aGenerator Callback function which must return exactly one command (i.e. must be assignable to avk::recorded_commands_t). + * @example std::vector numbers = {1, 2, 3}; + * avk::command::one_for_each(numbers, [](const int& aNumber) { return ... }; + * @return A vector of recorded commands + */ template inline static std::vector one_for_each(const T& aCollection, F aGenerator) { @@ -1456,7 +1462,13 @@ namespace avk return result; } - // TODO: Comment + /** Convenience function for gathering recorded commands---a collection of commands for each entry of a given collection. + * @aCollection Collection to iterate over + * @aGenerator Callback function which must return a vector of commands (i.e. must be assignable to std::vector). + * @example std::vector numbers = {1, 2, 3}; + * avk::command::one_for_each(numbers, [](const int& aNumber) { return avk::command::gather( ... ); }; + * @return A vector of recorded commands + */ template inline static std::vector many_for_each(const T& aCollection, F aGenerator) { @@ -1470,6 +1482,41 @@ namespace avk return result; } + /** Convenience function for gathering recorded commands, namely a total number of aN. + * @aN Collection to iterate over + * @aGenerator Callback function which must return exactly one command (i.e. must be assignable to avk::recorded_commands_t). + * @example avk::command::one_n_times(5, [](int aIndex) { return ... }; + * @return A vector of recorded commands + */ + template + inline static std::vector one_n_times(I aN, F aGenerator) + { + std::vector result; + for (I i = 0; i < aN; ++i) { + result.push_back(aGenerator(i)); + } + return result; + } + + /** Convenience function for gathering recorded commands, namely a total number of aN. + * @aN Collection to iterate over + * @aGenerator Callback function which must return exactly one command (i.e. must be assignable to avk::recorded_commands_t). + * @example avk::command::many_n_times(5, [](int aIndex) { return avk::command::gather( ... ); }; + * @return A vector of recorded commands + */ + template + inline static std::vector many_n_times(I aN, F aGenerator) + { + std::vector result; + for (I i = 0; i < aN; ++i) { + auto commands = aGenerator(i); + for (auto& command : commands) { + result.push_back(std::move(command)); + } + } + return result; + } + // TODO: Comment template inline static avk::recorded_commands_t conditional(C aCondition, F1 aPositive) From 630dc0cee2891703b8571f33e2a32c79d46c3348 Mon Sep 17 00:00:00 2001 From: Johannes Unterguggenberger Date: Thu, 14 Mar 2024 12:09:18 +0100 Subject: [PATCH 10/16] Further draw indirect commands added * Added further draw_indirect commands * Bugfix: Post execution handler was not invoked in command_buffer_t::prepare_for_reuse() * Attempt to fix build.yml * trying to fix windows-latest builds --- .github/workflows/build.yml | 28 +++++--- include/avk/commands.hpp | 136 ++++++++++++++++++++++++++++++++++++ src/avk.cpp | 7 +- 3 files changed, 158 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 23a690f..24f4244 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,10 +33,10 @@ jobs: } # 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.2.176"] + vulkan-sdk: ["latest", "1.3.204", "1.3.216"] vma: ["ON", "OFF"] exclude: # exclude combinations that are known to fail - - vulkan-sdk: "1.2.176" + - vulkan-sdk: "1.3.204" vma: "ON" steps: @@ -86,10 +86,10 @@ jobs: shell: bash working-directory: ${{ runner.workspace }}/build # Execute the build. You can specify a specific target with "--target " - run: cmake --build . + run: cmake --build . --config $BUILD_TYPE windows: - name: ${{ matrix.config.name }}, VulkanSDK ${{ matrix.vulkan-sdk }}, VMA=${{ matrix.vma }} + name: ${{ matrix.config.name }}, windows-2019, VulkanSDK ${{ matrix.vulkan-sdk }}, VMA=${{ matrix.vma }} runs-on: windows-2019 env: vulkan_sdk: "$GITHUB_WORKSPACE/vulkan_sdk/" @@ -102,8 +102,8 @@ jobs: cc: "cl", cxx: "cl" } - # note: if a specific vulkan version (e.g. 1.1.x, or < 1.2.135) needs testing, you can add it here: - vulkan-sdk: ["latest", "1.3.216.0", "1.2.198.1"] + # note: if a specific vulkan version needs testing, you can add it here: + vulkan-sdk: ["latest", "1.3.204.1", "1.3.216.0"] vma: ["ON", "OFF"] exclude: # exclude combinations that are known to fail - vulkan-sdk: "1.2.198" @@ -146,6 +146,9 @@ jobs: $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 cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -Davk_LibraryType=STATIC ${{ runner.workspace }}/${{ github.event.repository.name }} -Davk_UseVMA=${{ matrix.vma }} -G "Visual Studio 16 2019" -A x64 @@ -155,10 +158,10 @@ jobs: # apparently checkout@v2 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 . + run: cmake --build . --config $BUILD_TYPE windows-latest: - name: ${{ matrix.config.name }}, VulkanSDK ${{ matrix.vulkan-sdk }}, VMA=${{ matrix.vma }} + name: ${{ matrix.config.name }}, windows-latest, VulkanSDK ${{ matrix.vulkan-sdk }}, VMA=${{ matrix.vma }} runs-on: windows-latest env: vulkan_sdk: "$GITHUB_WORKSPACE/vulkan_sdk/" @@ -171,8 +174,8 @@ jobs: cc: "cl", cxx: "cl" } - # note: if a specific vulkan version (e.g. 1.1.x, or < 1.2.135) needs testing, you can add it here: - vulkan-sdk: ["latest", "1.3.216.0", "1.2.198.1"] + # note: if a specific vulkan version needs testing, you can add it here: + vulkan-sdk: ["latest", "1.3.204.1", "1.3.216.0"] vma: ["ON", "OFF"] exclude: # exclude combinations that are known to fail - vulkan-sdk: "1.2.198" @@ -215,6 +218,9 @@ jobs: $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 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 @@ -224,4 +230,4 @@ jobs: # apparently checkout@v2 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 . + run: cmake --build . --config $BUILD_TYPE diff --git a/include/avk/commands.hpp b/include/avk/commands.hpp index b5c723c..4ffa2b9 100644 --- a/include/avk/commands.hpp +++ b/include/avk/commands.hpp @@ -604,6 +604,142 @@ namespace avk bind_vertex_buffer(aHandlePtr + 1, aOffsetPtr + 1, aRest...); } + /** Draw vertices with vertex buffer bindings starting at BUFFER-BINDING #0 top to the number of total buffers passed -1. + * "BUFFER-BINDING" means that it corresponds to the binding specified in `input_binding_location_data::from_buffer_at_binding`. + * There can be no gaps between buffer bindings. + * @param aFurtherBuffers Multiple const-references to buffers, or tuples of const-references to buffers + offsets. + * First case: Pass const buffer_t& types! + * Second case: Pass tuples of type std::tuple! + * Hint: std::forward_as_tuple might be useful to get that reference into a std::tuple. + * Example: avk::buffer myVertexBuffer; + * auto myTuple = std::forward_as_tuple(myVertexBuffer.get(), size_t{0}); + */ + template + action_type_command draw_vertices_indirect(const buffer_t& aParametersBuffer, vk::DeviceSize aParametersOffset, uint32_t aParametersStride, uint32_t aDrawCount, const Bfrs&... aFurtherBuffers) + { + constexpr size_t N = sizeof...(aFurtherBuffers); + + if constexpr (N == 0) { + return action_type_command{ + avk::sync::sync_hint { + {{ // DESTINATION dependencies for previous commands: + vk::PipelineStageFlagBits2KHR::eAllGraphics, + vk::AccessFlagBits2KHR::eInputAttachmentRead | vk::AccessFlagBits2KHR::eColorAttachmentRead | vk::AccessFlagBits2KHR::eColorAttachmentWrite | vk::AccessFlagBits2KHR::eDepthStencilAttachmentRead | vk::AccessFlagBits2KHR::eDepthStencilAttachmentWrite + }}, + {{ // SOURCE dependencies for subsequent commands: + vk::PipelineStageFlagBits2KHR::eAllGraphics, + vk::AccessFlagBits2KHR::eColorAttachmentWrite | vk::AccessFlagBits2KHR::eDepthStencilAttachmentWrite + }} + }, + {}, // no resource-specific sync hints + [ + lParametersBufferHandle = aParametersBuffer.handle(), aParametersOffset, aParametersStride, aDrawCount + ](avk::command_buffer_t& cb) { + cb.handle().drawIndirect(lParametersBufferHandle, aParametersOffset, aDrawCount, aParametersStride); + } + }; + } + else { + std::array handles; + std::array offsets; + bind_vertex_buffer(&handles[0], &offsets[0], aFurtherBuffers...); + + return action_type_command{ + avk::sync::sync_hint { + {{ // DESTINATION dependencies for previous commands: + vk::PipelineStageFlagBits2KHR::eAllGraphics, + vk::AccessFlagBits2KHR::eInputAttachmentRead | vk::AccessFlagBits2KHR::eColorAttachmentRead | vk::AccessFlagBits2KHR::eColorAttachmentWrite | vk::AccessFlagBits2KHR::eDepthStencilAttachmentRead | vk::AccessFlagBits2KHR::eDepthStencilAttachmentWrite + }}, + {{ // SOURCE dependencies for subsequent commands: + vk::PipelineStageFlagBits2KHR::eAllGraphics, + vk::AccessFlagBits2KHR::eColorAttachmentWrite | vk::AccessFlagBits2KHR::eDepthStencilAttachmentWrite + }} + }, + {}, // no resource-specific sync hints + [ + lBindingCount = static_cast(N), + lParametersBufferHandle = aParametersBuffer.handle(), aParametersOffset, aParametersStride, aDrawCount, + handles, offsets + ](avk::command_buffer_t& cb) { + cb.handle().bindVertexBuffers( + 0u, // TODO: Should the first binding really always be 0? + static_cast(N), handles.data(), offsets.data() + ); + cb.handle().drawIndirect(lParametersBufferHandle, aParametersOffset, aDrawCount, aParametersStride); + } + }; + } + } + + /** Draw vertices with vertex buffer bindings starting at BUFFER-BINDING #0 top to the number of total buffers passed -1. + * "BUFFER-BINDING" means that it corresponds to the binding specified in `input_binding_location_data::from_buffer_at_binding`. + * There can be no gaps between buffer bindings. + * @param aFurtherBuffers Multiple const-references to buffers, or tuples of const-references to buffers + offsets. + * First case: Pass const buffer_t& types! + * Second case: Pass tuples of type std::tuple! + * Hint: std::forward_as_tuple might be useful to get that reference into a std::tuple. + * Example: avk::buffer myVertexBuffer; + * auto myTuple = std::forward_as_tuple(myVertexBuffer.get(), size_t{0}); + */ + template + action_type_command draw_vertices_indirect_count(const buffer_t& aParametersBuffer, vk::DeviceSize aParametersOffset, uint32_t aParametersStride, const buffer_t& aDrawCountBuffer, vk::DeviceSize aDrawCountOffset, uint32_t aMaxNumberOfDraws, const Bfrs&... aFurtherBuffers) + { + constexpr size_t N = sizeof...(aFurtherBuffers); + + if constexpr (N == 0) { + return action_type_command{ + avk::sync::sync_hint { + {{ // DESTINATION dependencies for previous commands: + vk::PipelineStageFlagBits2KHR::eAllGraphics, + vk::AccessFlagBits2KHR::eInputAttachmentRead | vk::AccessFlagBits2KHR::eColorAttachmentRead | vk::AccessFlagBits2KHR::eColorAttachmentWrite | vk::AccessFlagBits2KHR::eDepthStencilAttachmentRead | vk::AccessFlagBits2KHR::eDepthStencilAttachmentWrite + }}, + {{ // SOURCE dependencies for subsequent commands: + vk::PipelineStageFlagBits2KHR::eAllGraphics, + vk::AccessFlagBits2KHR::eColorAttachmentWrite | vk::AccessFlagBits2KHR::eDepthStencilAttachmentWrite + }} + }, + {}, // no resource-specific sync hints + [ + lParametersBufferHandle = aParametersBuffer.handle(), aParametersOffset, aParametersStride, + lDrawCountBufferHandle = aDrawCountBuffer.handle(), aDrawCountOffset, aMaxNumberOfDraws + ](avk::command_buffer_t& cb) { + cb.handle().drawIndirectCount(lParametersBufferHandle, aParametersOffset, lDrawCountBufferHandle, aDrawCountOffset, aMaxNumberOfDraws, aParametersStride); + } + }; + } + else { + std::array handles; + std::array offsets; + bind_vertex_buffer(&handles[0], &offsets[0], aFurtherBuffers...); + + return action_type_command{ + avk::sync::sync_hint { + {{ // DESTINATION dependencies for previous commands: + vk::PipelineStageFlagBits2KHR::eAllGraphics, + vk::AccessFlagBits2KHR::eInputAttachmentRead | vk::AccessFlagBits2KHR::eColorAttachmentRead | vk::AccessFlagBits2KHR::eColorAttachmentWrite | vk::AccessFlagBits2KHR::eDepthStencilAttachmentRead | vk::AccessFlagBits2KHR::eDepthStencilAttachmentWrite + }}, + {{ // SOURCE dependencies for subsequent commands: + vk::PipelineStageFlagBits2KHR::eAllGraphics, + vk::AccessFlagBits2KHR::eColorAttachmentWrite | vk::AccessFlagBits2KHR::eDepthStencilAttachmentWrite + }} + }, + {}, // no resource-specific sync hints + [ + lBindingCount = static_cast(N), + lParametersBufferHandle = aParametersBuffer.handle(), aParametersOffset, aParametersStride, + lDrawCountBufferHandle = aDrawCountBuffer.handle(), aDrawCountOffset, aMaxNumberOfDraws, + handles, offsets + ](avk::command_buffer_t& cb) { + cb.handle().bindVertexBuffers( + 0u, // TODO: Should the first binding really always be 0? + static_cast(N), handles.data(), offsets.data() + ); + cb.handle().drawIndirectCount(lParametersBufferHandle, aParametersOffset, lDrawCountBufferHandle, aDrawCountOffset, aMaxNumberOfDraws, aParametersStride); + } + }; + } + } + /** Draw vertices with vertex buffer bindings starting at BUFFER-BINDING #0 top to the number of total buffers passed -1. * "BUFFER-BINDING" means that it corresponds to the binding specified in `input_binding_location_data::from_buffer_at_binding`. * There can be no gaps between buffer bindings. diff --git a/src/avk.cpp b/src/avk.cpp index d15e219..db05209 100644 --- a/src/avk.cpp +++ b/src/avk.cpp @@ -2856,12 +2856,15 @@ namespace avk void command_buffer_t::prepare_for_reuse() { if (mPostExecutionHandler.has_value()) { - // Clear post-execution handler + // If there is a post-execution handler => call it now: + invoke_post_execution_handler(); + // Clear post-execution handler: mPostExecutionHandler.reset(); } if (mCustomDeleter.has_value() && *mCustomDeleter) { - // If there is a custom deleter => call it now + // If there is a custom deleter => call it now: (*mCustomDeleter)(); + // Clear custom deleter: mCustomDeleter.reset(); } mLifetimeHandledResources.clear(); From eea56041034c1afdf1275370522a0a5c400f961e Mon Sep 17 00:00:00 2001 From: Johannes Unterguggenberger Date: Thu, 14 Mar 2024 12:16:22 +0100 Subject: [PATCH 11/16] Using std::filesystem for getting file size instead of unreliable tellg() (#94) * Using std::filesystem to load binary files --- include/avk/cpp_utils.hpp | 41 ++++++++++++++------------------------- 1 file changed, 15 insertions(+), 26 deletions(-) diff --git a/include/avk/cpp_utils.hpp b/include/avk/cpp_utils.hpp index f253c57..5020789 100644 --- a/include/avk/cpp_utils.hpp +++ b/include/avk/cpp_utils.hpp @@ -69,39 +69,28 @@ namespace avk } /** Load a binary file into memory. - * Adapted from: http://www.cplusplus.com/reference/istream/istream/read/ + * Adapted from: https://coniferproductions.com/posts/2022/10/25/reading-binary-files-cpp/ */ static std::vector load_binary_file(std::string path) { - std::vector buffer; - std::ifstream is(path.c_str(), std::ifstream::binary); - if (!is) { - throw avk::runtime_error("Couldn't load file '" + path + "'"); + try { + std::filesystem::path inputFilePath{path}; + auto length = std::filesystem::file_size(inputFilePath); + if (length == 0) { + throw avk::runtime_error("Couldn't load file '" + path + "'. It appears to be empty."); + } + std::vector buffer(((length + 3) / 4) * 4); + std::ifstream inputFile(path, std::ios_base::binary); + inputFile.read(reinterpret_cast(buffer.data()), length); + inputFile.close(); + return buffer; } - - // get length of file: - is.seekg(0, is.end); - size_t length = is.tellg(); - is.seekg(0, is.beg); - - buffer.resize(length); - - // read data as a block: - is.read(buffer.data(), length); - - if (!is) { - is.close(); - throw avk::runtime_error( - "Couldn't read file '" + path + "' into buffer. " + - "load_binary_file could only read " + std::to_string(is.gcount()) + " bytes instead of " + std::to_string(length) - ); + catch (std::filesystem::filesystem_error& e) + { + throw avk::runtime_error("Couldn't load file '" + path + "'. Reason: " + e.what()); } - - is.close(); - return buffer; } - static std::string trim_spaces(std::string_view s) { if (s.empty()) { From a260d0bf282402603bbff50de56d452af6a4ebb1 Mon Sep 17 00:00:00 2001 From: Johannes Unterguggenberger Date: Thu, 14 Mar 2024 12:17:43 +0100 Subject: [PATCH 12/16] Just settling on Vulkan 1.3 SDK as the baseline requirement --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 88d6acf..01c9141 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ I.e., the big, important concepts, which make Vulkan as performant as it can be- # Setup _Auto-Vk_ requires -* A Vulkan 1.2 SDK or a Vulkan 1.3 SDK +* A Vulkan 1.3 SDK * [Vulkan-Hpp](https://github.com/KhronosGroup/Vulkan-Hpp) * A C++20 compiler From 2e437e0c36f0a5322635a6567cb12768aa4769a0 Mon Sep 17 00:00:00 2001 From: Johannes Unterguggenberger Date: Fri, 12 Apr 2024 13:54:45 +0200 Subject: [PATCH 13/16] 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{ From 180578ca9a4b57e512d3092903228096c294fe02 Mon Sep 17 00:00:00 2001 From: Johannes Unterguggenberger Date: Fri, 12 Apr 2024 15:28:41 +0200 Subject: [PATCH 14/16] Hot fix: Added overload to command::render_pass in order to re-establish compatibility with the way of passing renderpass-es (before the merge of dynamic rendering) --- include/avk/commands.hpp | 20 ++++++++++++++++++++ src/avk.cpp | 19 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/include/avk/commands.hpp b/include/avk/commands.hpp index 571fef2..b2cf3a6 100644 --- a/include/avk/commands.hpp +++ b/include/avk/commands.hpp @@ -551,6 +551,26 @@ namespace avk bool aSubpassesInline = true ); + + /** Begins and ends a render pass for a given framebuffer, and supports some nested commands to be recorded in between + * @param aRenderpass Renderpass which shall begin (auto lifetime handling not supported by this command). + * Optional parameter serves as a convenience-overload for the changes introduced during dynamic rendering pull request, + * namely the change of the return type of graphics_pipeline_t::renderpass_reference(), which returns an optional now. + * @param aFramebuffer Framebuffer to use with the renderpass (auto lifetime handling not supported by this command) + * @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) + * @param aSubpassesInline Whether or not subpasses are inline (default is true) + */ + extern action_type_command render_pass( + std::optional> aRenderpass, + const framebuffer_t& aFramebuffer, + std::vector aNestedCommands = {}, + vk::Offset2D aRenderAreaOffset = { 0, 0 }, + std::optional aRenderAreaExtent = {}, + 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) diff --git a/src/avk.cpp b/src/avk.cpp index 59c35f4..96b067b 100644 --- a/src/avk.cpp +++ b/src/avk.cpp @@ -8674,6 +8674,25 @@ namespace avk }; } + action_type_command render_pass( + std::optional> aRenderpass, + const framebuffer_t& aFramebuffer, + std::vector aNestedCommands, + vk::Offset2D aRenderAreaOffset, + std::optional aRenderAreaExtent, + bool aSubpassesInline) + { + auto result = action_type_command{}; + + if (!aRenderpass.has_value()) { + AVK_LOG_ERROR("The renderpass passed to command::render_pass via the std::optional parameter does not conatin a value, i.e., no Vulkan render pass handle."); + } + + result = render_pass(aRenderpass.value().get(), aFramebuffer, std::move(aNestedCommands), aRenderAreaOffset, aRenderAreaExtent, aSubpassesInline); + + return result; + } + action_type_command next_subpass(bool aSubpassesInline) { return action_type_command{ From fc77af89d281f74cb08298697d4c850cb002512a Mon Sep 17 00:00:00 2001 From: Johannes Unterguggenberger Date: Tue, 23 Apr 2024 16:22:16 +0200 Subject: [PATCH 15/16] Hot-fix: Added an overload taking a std::optional> aRenderpass also for begin_render_pass_for_framebuffer --- include/avk/commands.hpp | 11 +++++++++++ src/avk.cpp | 13 +++++++++++++ 2 files changed, 24 insertions(+) diff --git a/include/avk/commands.hpp b/include/avk/commands.hpp index b2cf3a6..8d08c62 100644 --- a/include/avk/commands.hpp +++ b/include/avk/commands.hpp @@ -529,6 +529,17 @@ 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); + /** Begins a render pass for a given framebuffer + * @param aRenderpass Renderpass which shall begin (auto lifetime handling not supported by this command). + * Optional parameter serves as a convenience-overload for the changes introduced during dynamic rendering pull request, + * namely the change of the return type of graphics_pipeline_t::renderpass_reference(), which returns an optional now. + * @param aFramebuffer Framebuffer to use with the renderpass (auto lifetime handling not supported by this command) + * @param aRenderAreaOffset Render area offset (default is (0,0), i.e., no offset) + * @param aRenderAreaExtent Render area extent (default is full extent) + * @param aSubpassesInline Whether or not subpasses are inline (default is true) + */ + extern action_type_command begin_render_pass_for_framebuffer(std::optional> aRenderpass, const framebuffer_t& aFramebuffer, vk::Offset2D aRenderAreaOffset = { 0, 0 }, std::optional aRenderAreaExtent = {}, bool aSubpassesInline = true); + /** Ends a render pass */ diff --git a/src/avk.cpp b/src/avk.cpp index 96b067b..58b0757 100644 --- a/src/avk.cpp +++ b/src/avk.cpp @@ -8625,6 +8625,19 @@ namespace avk }; } + action_type_command begin_render_pass_for_framebuffer(std::optional> aRenderpass, const framebuffer_t& aFramebuffer, vk::Offset2D aRenderAreaOffset, std::optional aRenderAreaExtent, bool aSubpassesInline) + { + auto result = action_type_command{}; + + if (!aRenderpass.has_value()) { + AVK_LOG_ERROR("The renderpass passed to command::render_pass via the std::optional parameter does not conatin a value, i.e., no Vulkan render pass handle."); + } + + result = begin_render_pass_for_framebuffer(aRenderpass.value().get(), aFramebuffer, aRenderAreaOffset, aRenderAreaExtent, aSubpassesInline); + + return result; + } + action_type_command end_render_pass() { return action_type_command{ From 2e5597442fc4bc7ac156b36fea6677e1dccc79d4 Mon Sep 17 00:00:00 2001 From: Johannes Unterguggenberger Date: Fri, 31 May 2024 14:03:25 +0200 Subject: [PATCH 16/16] Added proper documentation to buffer_t::read_into and to buffer_t::read --- include/avk/buffer.hpp | 58 ++++++++++++++++++---------------- src/avk.cpp | 70 ++++++++++-------------------------------- 2 files changed, 48 insertions(+), 80 deletions(-) diff --git a/include/avk/buffer.hpp b/include/avk/buffer.hpp index c6f5a8b..ddb4722 100644 --- a/include/avk/buffer.hpp +++ b/include/avk/buffer.hpp @@ -226,19 +226,6 @@ namespace avk */ command::action_type_command fill(const void* aDataPtr, size_t aMetaDataIndex) const; - // TODO: Maybe the following overload could be re-enabled after command/commands refactoring?! - ///** Fill buffer with data according to the meta data of the given type Meta. - // * The buffer's size is determined from its metadata - // * @param aDataPtr Pointer to the data to copy to the buffer. MUST point to at least enough data to fill the buffer entirely. - // * @param aMetaDataSkip How often a meta data of type Meta shall be skipped. I.e. values != 0 only make sense if there ar multiple meta data entries of type Meta. - // */ - //template - //std::optional fill(const void* aDataPtr, size_t aMetaDataSkip, old_sync aSyncHandler) - //{ - // assert(has_meta(aMetaDataSkip)); - // return fill(aDataPtr, index_of_meta(aMetaDataSkip), std::move(aSyncHandler)); - //} - /** Fill buffer partially with data. * * @param aDataPtr Pointer to the data to copy to the buffer @@ -248,23 +235,42 @@ namespace avk */ command::action_type_command fill(const void* aDataPtr, size_t aMetaDataIndex, size_t aOffsetInBytes, size_t aDataSizeInBytes) const; - /** Read data from buffer back to the CPU-side, into some given memory. - * @param aDataPtr Target memory where to write read-back data into - * @param aMetaDataIndex Index of the meta data index which is used for the buffer's read-back data (size and stuff) - */ + /** Reads values from a buffer back into some host-side memory. + * @param aDataPtr Where to store the read-back memory into. + * @param aMetaDataIndex Which meta data index shall be used to determine the data size to be read back. + * @return An avk::command is returned which you, generally, must send to a queue to be executed. + * It could be that the returned command is empty. This will happen if the buffer's memory + * is stored in a host visible memory region. + * + * @example Read an uint64_t back to host memory from a buffer that is backed by device-local memory, + * and wait with a fence until the operation has completed: + * + * avk::buffer mMyBuffer = ...; + * uint32_t mMyReadBackData; + * context().record_and_submit_with_fence({ + * mMyBuffer->read_into(&mMyReadBackData, 0) + * }, *mQueue)->wait_until_signalled(); + */ avk::command::action_type_command read_into(void* aDataPtr, size_t aMetaDataIndex) const; /** - * Read back data from a buffer. - * - * This is a convenience overload to avk::read. - * - * Example usage: - * uint32_t readData = avk::read(mMySsbo, avk::old_sync::not_required()); - * // ^ given that mMySsbo is a host-coherent buffer. If it is not, sync is required. + * Read back data from a buffer that is backed by host-visible memory. + * This is a convenience overload to avk::read, and is mostly intended to be used for small amounts of data, + * because the data container is allocated on the stack and returned to the caller. + * Technically, every data type that can be copy-constructed is fine. + * Attention: This does not support read backs from buffers backed by device-local memory, since the + * result of avk::read_into is discarded. * - * @tparam Ret Specify the type of data that shall be read from the buffer (this is `uint32_t` in the example above). - * @returns A value of type `Ret` which is returned by value. + * @tparam Ret Specify the type of data that shall be read from the buffer + * @param aMetaDataIndex Which meta data index shall be used to determine the data size to be read back. + * @return The value that has been read back from the buffer's host visible memory. + * The value's size is of size `Ret` and is returned by value. + * + * @example Read back one uint32_t value: + * + * avk::buffer mMyBuffer = ...; + * uint32_t myData = mMyBuffer->read(0); + * // ^ given that mMyBuffer is a buffer backed by host visible memory and hence, does not require commands to be executed. */ template [[nodiscard]] Ret read(size_t aMetaDataIndex) { diff --git a/src/avk.cpp b/src/avk.cpp index 58b0757..32cdcd9 100644 --- a/src/avk.cpp +++ b/src/avk.cpp @@ -2683,60 +2683,22 @@ namespace avk } } - //std::optional buffer_t::fill(const void* aDataPtr, size_t aMetaDataIndex) - //{ - // return commands{ pipeline_stage::transfer, memory_access::transfer_read_access, [this](command_buffer_t& aCmdBfr) { - // auto metaData = meta_at_index(aMetaDataIndex); - // auto bufferSize = static_cast(metaData.total_size()); - // auto memProps = memory_properties(); - - // // #1: Is our memory accessible from the CPU-SIDE? - // if (avk::has_flag(memProps, vk::MemoryPropertyFlagBits::eHostVisible)) { - // auto mapped = scoped_mapping{mBuffer, mapping_access::write}; - // memcpy(mapped.get(), aDataPtr, bufferSize); - // return {}; - // } - - // // #2: Otherwise, it must be on the GPU-SIDE! - // else { - // assert(avk::has_flag(memProps, vk::MemoryPropertyFlagBits::eDeviceLocal)); - - // // We have to create a (somewhat temporary) staging buffer and transfer it to the GPU - // // "somewhat temporary" means that it can not be deleted in this function, but only - // // after the transfer operation has completed => handle via sync - // auto stagingBuffer = root::create_buffer( - // mPhysicalDevice, mDevice, mBuffer.allocator(), - // AVK_STAGING_BUFFER_MEMORY_USAGE, - // vk::BufferUsageFlagBits::eTransferSrc, - // generic_buffer_meta::create_from_size(bufferSize) - // ); - // stagingBuffer->fill(aDataPtr, 0, old_sync::wait_idle()); // Recurse into the other if-branch - - // auto& commandBuffer = aSyncHandler.get_or_create_command_buffer(); - // // Sync before: - // aSyncHandler.establish_barrier_before_the_operation(pipeline_stage::transfer, read_memory_access{memory_access::transfer_read_access}); - - // // Operation: - // auto copyRegion = vk::BufferCopy{} - // .setSrcOffset(0u) // TODO: Support different offsets or whatever?! - // .setDstOffset(0u) - // .setSize(bufferSize); - // commandBuffer.handle().copyBuffer(stagingBuffer->handle(), handle(), { copyRegion }); - - // // Sync after: - // aSyncHandler.establish_barrier_after_the_operation(pipeline_stage::transfer, write_memory_access{memory_access::transfer_write_access}); - - // // Take care of the lifetime handling of the stagingBuffer, it might still be in use: - // commandBuffer.set_custom_deleter([ - // lOwnedStagingBuffer{ std::move(stagingBuffer) } - // ]() { /* Nothing to do here, the buffers' destructors will do the cleanup, the lambda is just storing it. */ }); - // - // // Finish him: - // return aSyncHandler.submit_and_sync(); - // } - // }, pipeline_stage::transfer, memory_access::transfer_write_access); - //} - + /* Reads values from a buffer back into some host-side memory. + * @param aDataPtr Where to store the read-back memory into. + * @param aMetaDataIndex Which meta data index shall be used to determine the data size to be read back. + * @return An avk::command is returned which you, generally, must send to a queue to be executed. + * It could be that the returned command is empty. This will happen if the buffer's memory + * is stored in a host visible memory region. + * + * @example Read an uint64_t back to host memory from a buffer that is backed by device-local memory, + * and wait with a fence until the operation has completed: + * + * avk::buffer mMyBuffer = ...; + * uint32_t mMyReadBackData; + * context().record_and_submit_with_fence({ + * mMyBuffer->read_into(&mMyReadBackData, 0) + * }, *mQueue)->wait_until_signalled(); + */ avk::command::action_type_command buffer_t::read_into(void* aDataPtr, size_t aMetaDataIndex) const { auto metaData = meta_at_index(aMetaDataIndex);