diff --git a/doc/vulkan-mapping.dox b/doc/vulkan-mapping.dox index ded3fca07..5af086671 100644 --- a/doc/vulkan-mapping.dox +++ b/doc/vulkan-mapping.dox @@ -268,7 +268,7 @@ Vulkan function | Matching API @fn_vk{QueueBeginDebugUtilsLabelEXT} @m_class{m-label m-flat m-warning} **EXT**, \n @fn_vk{QueueEndDebugUtilsLabelEXT} @m_class{m-label m-flat m-warning} **EXT** | | @fn_vk{QueueBindSparse} | | @fn_vk{QueueInsertDebugUtilsLabelEXT} @m_class{m-label m-flat m-warning} **EXT** | | -@fn_vk{QueueSubmit} | | +@fn_vk{QueueSubmit} | @ref Queue::submit() @fn_vk{QueueWaitIdle} | | @subsection vulkan-mapping-functions-r R @@ -667,7 +667,7 @@ Vulkan structure | Matching API @type_vk{SpecializationMapEntry} | | @type_vk{StencilOpState} | | @type_vk{StridedDeviceAddressRegionKHR} @m_class{m-label m-flat m-warning} **KHR** | | -@type_vk{SubmitInfo} | | +@type_vk{SubmitInfo} | @ref SubmitInfo @type_vk{SubpassBeginInfo} @m_class{m-label m-flat m-success} **KHR, 1.2** | @ref SubpassBeginInfo @type_vk{SubpassEndInfo} @m_class{m-label m-flat m-success} **KHR, 1.2** | @ref SubpassEndInfo @type_vk{SubpassDependency}, \n @type_vk{SubpassDependency2} @m_class{m-label m-flat m-success} **KHR, 1.2** | @ref SubpassDependency diff --git a/src/Magnum/Vk/Queue.cpp b/src/Magnum/Vk/Queue.cpp index 5073463d9..89f8b1512 100644 --- a/src/Magnum/Vk/Queue.cpp +++ b/src/Magnum/Vk/Queue.cpp @@ -26,8 +26,12 @@ #include "Queue.h" #include +#include #include +#include "Magnum/Vk/Assert.h" +#include "Magnum/Vk/Device.h" + namespace Magnum { namespace Vk { Queue Queue::wrap(Device& device, VkQueue handle) { @@ -52,4 +56,88 @@ Queue& Queue::operator=(Queue&& other) noexcept { return *this; } +void Queue::submit(const Containers::ArrayView> infos, const VkFence fence) { + /** @todo use DynamicArray here. I also thought about taking an ArrayView + of VkSubmitInfo structures directly and thus avoiding this whole hell, + but that feels kinda inconsistent in the public API (as everywhere else + we take only the structure wrappers, as opposed to handles which *are* + taken raw); plus once the SubmitInfo gets more complex it may not be + so easy to verify we don't use unsupported functionality / backport on + the raw structure (like done in RenderPassCreateInfo, e.g.) */ + + /* If we have just one item, we don't need to allocate. This will become + obsolete once DynamicArray can handle both cases efficiently */ + if(infos.size() == 1) { + MAGNUM_VK_INTERNAL_ASSERT_SUCCESS((**_device).QueueSubmit(_handle, 1, *infos[0], fence)); + return; + } + + Containers::Array vkInfos{Containers::NoInit, infos.size()}; + for(std::size_t i = 0; i != infos.size(); ++i) + vkInfos[i] = *infos[i]; + + MAGNUM_VK_INTERNAL_ASSERT_SUCCESS((**_device).QueueSubmit(_handle, vkInfos.size(), vkInfos, fence)); +} + +void Queue::submit(std::initializer_list> infos, VkFence fence) { + return submit(Containers::arrayView(infos), fence); +} + +struct SubmitInfo::State { + Containers::Array commandBuffers; +}; + +SubmitInfo::SubmitInfo(): _info{} { + _info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; +} + +SubmitInfo::SubmitInfo(NoInitT) noexcept {} + +SubmitInfo::SubmitInfo(const VkSubmitInfo& info): + /* Can't use {} with GCC 4.8 here because it tries to initialize the first + member instead of doing a copy */ + _info(info) {} + +SubmitInfo::SubmitInfo(SubmitInfo&& other) noexcept: + /* Can't use {} with GCC 4.8 here because it tries to initialize the first + member instead of doing a copy */ + _info(other._info), + _state{std::move(other._state)} +{ + /* Ensure the previous instance doesn't reference state that's now ours */ + /** @todo this is now more like a destructible move, do it more selectively + and clear only what's really ours and not external? */ + other._info.pNext = nullptr; + other._info.waitSemaphoreCount = 0; + other._info.pWaitSemaphores = nullptr; + other._info.pWaitDstStageMask = nullptr; + other._info.commandBufferCount = 0; + other._info.pCommandBuffers = nullptr; + other._info.signalSemaphoreCount = 0; + other._info.pSignalSemaphores = nullptr; +} + +SubmitInfo::~SubmitInfo() = default; + +SubmitInfo& SubmitInfo::operator=(SubmitInfo&& other) noexcept { + using std::swap; + swap(other._info, _info); + swap(other._state, _state); + return *this; +} + +SubmitInfo& SubmitInfo::setCommandBuffers(Containers::ArrayView buffers) { + if(!_state) _state.emplace(); + + _state->commandBuffers = Containers::Array{Containers::NoInit, buffers.size()}; + Utility::copy(buffers, _state->commandBuffers); + _info.commandBufferCount = _state->commandBuffers.size(); + _info.pCommandBuffers = _state->commandBuffers; + return *this; +} + +SubmitInfo& SubmitInfo::setCommandBuffers(std::initializer_list buffers) { + return setCommandBuffers(Containers::arrayView(buffers)); +} + }} diff --git a/src/Magnum/Vk/Queue.h b/src/Magnum/Vk/Queue.h index d933c6651..39663dd4f 100644 --- a/src/Magnum/Vk/Queue.h +++ b/src/Magnum/Vk/Queue.h @@ -30,6 +30,8 @@ * @m_since_latest */ +#include + #include "Magnum/Tags.h" #include "Magnum/Vk/Vk.h" #include "Magnum/Vk/Vulkan.h" @@ -43,7 +45,7 @@ namespace Magnum { namespace Vk { Wraps a @type_vk_keyword{Queue}. See @ref Device class docs for an introduction on how to create a queue. -@see @ref DeviceCreateInfo::addQueues() +@see @ref DeviceCreateInfo::addQueues(), @ref submit() */ class MAGNUM_VK_EXPORT Queue { public: @@ -98,6 +100,22 @@ class MAGNUM_VK_EXPORT Queue { /** @overload */ operator VkQueue() { return _handle; } + /** + * @brief Submit a sequence of semaphores or command buffers to a queue + * @param infos Submit info structures, each specifying a command + * buffer submission batch + * @param fence A @ref Fence or a raw Vulkan fence handle to be + * signaled once all submitted command buffers have completed + * execution. Pass a @cpp {} @ce or a + * @ref Fence::Fence(NoCreateT) "NoCreate"'d @ref Fence to not + * signal anything. + * + * @see @fn_vk_keyword{QueueSubmit} + */ + void submit(Containers::ArrayView> infos, VkFence fence); + /** @overload */ + void submit(std::initializer_list> infos, VkFence fence); + private: /* Can't be a reference because of the NoCreate constructor */ Device* _device; @@ -105,6 +123,92 @@ class MAGNUM_VK_EXPORT Queue { VkQueue _handle; }; +/** +@brief Queue submit info +@m_since_latest + +Wraps a @type_vk_keyword{SubmitInfo}. +*/ +class MAGNUM_VK_EXPORT SubmitInfo { + public: + /** + * @brief Constructor + * + * The following @type_vk{SubmitInfo} fields are pre-filled in + * addition to `sType`, everything else is zero-filled: + * + * - *(none)* + * + * @see @ref setCommandBuffers() + */ + explicit SubmitInfo(); + + /** + * @brief Construct without initializing the contents + * + * Note that not even the `sType` field is set --- the structure has to + * be fully initialized afterwards in order to be usable. + */ + explicit SubmitInfo(NoInitT) noexcept; + + /** + * @brief Construct from existing data + * + * Copies the existing values verbatim, pointers are kept unchanged + * without taking over the ownership. Modifying the newly created + * instance will not modify the original data nor the pointed-to data. + */ + explicit SubmitInfo(const VkSubmitInfo& info); + + /** @brief Copying is not allowed */ + SubmitInfo(const SubmitInfo&) = delete; + + /** @brief Move constructor */ + SubmitInfo(SubmitInfo&& other) noexcept; + + ~SubmitInfo(); + + /** @brief Copying is not allowed */ + SubmitInfo& operator=(const SubmitInfo&) = delete; + + /** @brief Move assignment */ + SubmitInfo& operator=(SubmitInfo&& other) noexcept; + + /** + * @brief Set command buffers to execute in the batch + * @return Reference to self (for method chaining) + */ + SubmitInfo& setCommandBuffers(Containers::ArrayView buffers); + /** @overload */ + SubmitInfo& setCommandBuffers(std::initializer_list buffers); + + /** @brief Underlying @type_vk{SubmitInfo} structure */ + VkSubmitInfo& operator*() { return _info; } + /** @overload */ + const VkSubmitInfo& operator*() const { return _info; } + /** @overload */ + VkSubmitInfo* operator->() { return &_info; } + /** @overload */ + const VkSubmitInfo* operator->() const { return &_info; } + /** @overload */ + operator const VkSubmitInfo*() const { return &_info; } + + /** + * @overload + * + * The class is implicitly convertible to a reference in addition to + * a pointer because the type is commonly used in arrays as well, which + * would be annoying to do with a pointer conversion. + */ + operator const VkSubmitInfo&() const { return _info; } + + private: + VkSubmitInfo _info; + + struct State; + Containers::Pointer _state; +}; + }} #endif diff --git a/src/Magnum/Vk/Test/CMakeLists.txt b/src/Magnum/Vk/Test/CMakeLists.txt index 77c525dc3..69c6958c6 100644 --- a/src/Magnum/Vk/Test/CMakeLists.txt +++ b/src/Magnum/Vk/Test/CMakeLists.txt @@ -165,6 +165,7 @@ if(BUILD_VK_TESTS) corrade_add_test(VkImageViewVkTest ImageViewVkTest.cpp LIBRARIES MagnumVk MagnumVulkanTester) corrade_add_test(VkInstanceVkTest InstanceVkTest.cpp LIBRARIES MagnumVkTestLib) corrade_add_test(VkMemoryVkTest MemoryVkTest.cpp LIBRARIES MagnumVk MagnumVulkanTester) + corrade_add_test(VkQueueVkTest QueueVkTest.cpp LIBRARIES MagnumVk MagnumVulkanTester) corrade_add_test(VkRenderPassVkTest RenderPassVkTest.cpp LIBRARIES MagnumVkTestLib MagnumVulkanTester) corrade_add_test(VkShaderVkTest ShaderVkTest.cpp LIBRARIES MagnumVk MagnumVulkanTester) target_include_directories(VkShaderVkTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) @@ -184,6 +185,7 @@ if(BUILD_VK_TESTS) VkImageViewVkTest VkInstanceVkTest VkMemoryVkTest + VkQueueVkTest VkRenderPassVkTest VkShaderVkTest VkVersionVkTest diff --git a/src/Magnum/Vk/Test/QueueTest.cpp b/src/Magnum/Vk/Test/QueueTest.cpp index c372f417b..a7ac904ac 100644 --- a/src/Magnum/Vk/Test/QueueTest.cpp +++ b/src/Magnum/Vk/Test/QueueTest.cpp @@ -23,6 +23,7 @@ DEALINGS IN THE SOFTWARE. */ +#include #include #include "Magnum/Vk/Device.h" @@ -38,6 +39,13 @@ struct QueueTest: TestSuite::Tester { void constructMove(); void wrap(); + + void submitInfoConstruct(); + void submitInfoConstructNoInit(); + void submitInfoConstructCommandBuffers(); + void submitInfoConstructFromVk(); + void submitInfoConstructCopy(); + void submitInfoConstructMove(); }; QueueTest::QueueTest() { @@ -45,7 +53,14 @@ QueueTest::QueueTest() { &QueueTest::constructCopy, &QueueTest::constructMove, - &QueueTest::wrap}); + &QueueTest::wrap, + + &QueueTest::submitInfoConstruct, + &QueueTest::submitInfoConstructNoInit, + &QueueTest::submitInfoConstructCommandBuffers, + &QueueTest::submitInfoConstructFromVk, + &QueueTest::submitInfoConstructCopy, + &QueueTest::submitInfoConstructMove}); } void QueueTest::constructNoCreate() { @@ -93,6 +108,70 @@ void QueueTest::wrap() { CORRADE_COMPARE(queue.handle(), vkQueue); } +void QueueTest::submitInfoConstruct() { + SubmitInfo info; + CORRADE_COMPARE(info->commandBufferCount, 0); + CORRADE_VERIFY(!info->pCommandBuffers); +} + +void QueueTest::submitInfoConstructNoInit() { + SubmitInfo info{NoInit}; + info->sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2; + new(&info) SubmitInfo{NoInit}; + CORRADE_COMPARE(info->sType, VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2); + + CORRADE_VERIFY((std::is_nothrow_constructible::value)); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!(std::is_convertible::value)); +} + +void QueueTest::submitInfoConstructCommandBuffers() { + SubmitInfo info; + info.setCommandBuffers({ + reinterpret_cast(0xbadbeef), + reinterpret_cast(0xcafecafe) + }); + + CORRADE_COMPARE(info->commandBufferCount, 2); + CORRADE_VERIFY(info->pCommandBuffers); + CORRADE_COMPARE(info->pCommandBuffers[0], reinterpret_cast(0xbadbeef)); + CORRADE_COMPARE(info->pCommandBuffers[1], reinterpret_cast(0xcafecafe)); +} + +void QueueTest::submitInfoConstructFromVk() { + VkSubmitInfo vkInfo; + vkInfo.sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2; + + SubmitInfo info{vkInfo}; + CORRADE_COMPARE(info->sType, VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2); +} + +void QueueTest::submitInfoConstructCopy() { + CORRADE_VERIFY(!(std::is_copy_constructible{})); + CORRADE_VERIFY(!(std::is_copy_assignable{})); +} + +void QueueTest::submitInfoConstructMove() { + SubmitInfo a; + a.setCommandBuffers({{}, reinterpret_cast(0xcafecafe)}); + + SubmitInfo b = std::move(a); + CORRADE_COMPARE(a->commandBufferCount, 0); + CORRADE_VERIFY(!a->pCommandBuffers); + CORRADE_COMPARE(b->commandBufferCount, 2); + CORRADE_VERIFY(b->pCommandBuffers); + CORRADE_COMPARE(b->pCommandBuffers[1], reinterpret_cast(0xcafecafe)); + + SubmitInfo c{VkSubmitInfo{}}; + c = std::move(b); + CORRADE_COMPARE(b->commandBufferCount, 0); + CORRADE_VERIFY(!b->pCommandBuffers); + CORRADE_COMPARE(c->commandBufferCount, 2); + CORRADE_VERIFY(c->pCommandBuffers); + CORRADE_COMPARE(c->pCommandBuffers[1], reinterpret_cast(0xcafecafe)); +} + }}}} CORRADE_TEST_MAIN(Magnum::Vk::Test::QueueTest) diff --git a/src/Magnum/Vk/Test/QueueVkTest.cpp b/src/Magnum/Vk/Test/QueueVkTest.cpp new file mode 100644 index 000000000..63ca75613 --- /dev/null +++ b/src/Magnum/Vk/Test/QueueVkTest.cpp @@ -0,0 +1,102 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include "Magnum/Vk/CommandBuffer.h" +#include "Magnum/Vk/CommandPoolCreateInfo.h" +#include "Magnum/Vk/DeviceProperties.h" +#include "Magnum/Vk/Fence.h" +#include "Magnum/Vk/VulkanTester.h" + +namespace Magnum { namespace Vk { namespace Test { namespace { + +struct QueueVkTest: VulkanTester { + explicit QueueVkTest(); + + /* construction tested in DeviceVkTest as it's done implicitly on Device + creation */ + /* move and wrap tested in QueueTest as there's no vkDestroyQueue() and + thus we don't need a Vulkan-enabled test for that */ + + void submit(); + void submitOne(); +}; + +QueueVkTest::QueueVkTest() { + addTests({&QueueVkTest::submit, + &QueueVkTest::submitOne}); +} + +void QueueVkTest::submit() { + CommandPool pool{device(), CommandPoolCreateInfo{ + device().properties().pickQueueFamily(QueueFlag::Graphics)}}; + + CommandBuffer a = pool.allocate(); + a.begin() + .end(); + + CommandBuffer b = pool.allocate(); + b.begin() + .end(); + + CommandBuffer c = pool.allocate(); + c.begin() + .end(); + + Fence fence{device()}; + CORRADE_VERIFY(!fence.status()); + + queue().submit({ + SubmitInfo{}.setCommandBuffers({a, b}), + SubmitInfo{}.setCommandBuffers({c}) + }, fence); + + CORRADE_VERIFY(fence.wait(std::chrono::milliseconds{1000})); +} + +void QueueVkTest::submitOne() { + /* Until DynamicArray is a thing, submit() has a special case for a single + item that doesn't allocate but instead just point to it */ + /** @todo drop once DynamicArray handles that */ + + CommandPool pool{device(), CommandPoolCreateInfo{ + device().properties().pickQueueFamily(QueueFlag::Graphics)}}; + + CommandBuffer a = pool.allocate(); + a.begin() + .end(); + + Fence fence{device()}; + CORRADE_VERIFY(!fence.status()); + + queue().submit({ + SubmitInfo{}.setCommandBuffers({a}) + }, fence); + + CORRADE_VERIFY(fence.wait(std::chrono::milliseconds{1000})); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::Vk::Test::QueueVkTest) diff --git a/src/Magnum/Vk/Vk.h b/src/Magnum/Vk/Vk.h index 961c23a66..10b92709e 100644 --- a/src/Magnum/Vk/Vk.h +++ b/src/Magnum/Vk/Vk.h @@ -88,6 +88,7 @@ class RenderPassCreateInfo; enum class Result: Int; class Shader; class ShaderCreateInfo; +class SubmitInfo; class SubpassBeginInfo; class SubpassEndInfo; enum class Version: UnsignedInt;