From ae3dbe1c36f26985407be9cc2a00a3e994b92782 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sat, 21 Nov 2020 19:18:33 +0100 Subject: [PATCH] Vk: Shader wrapper. This was easy ... except for the fun with taking ownership of the binary. And it led to me inventing CORRADE_INTERNAL_ASSERT_EXPRESSION(). --- doc/snippets/MagnumVk.cpp | 16 ++ doc/vulkan-mapping.dox | 12 +- src/Magnum/Vk/CMakeLists.txt | 2 + src/Magnum/Vk/Shader.cpp | 117 ++++++++++ src/Magnum/Vk/Shader.h | 286 ++++++++++++++++++++++++ src/Magnum/Vk/Test/.gitattributes | 10 + src/Magnum/Vk/Test/CMakeLists.txt | 12 + src/Magnum/Vk/Test/ShaderTest.cpp | 187 ++++++++++++++++ src/Magnum/Vk/Test/ShaderVkTest.cpp | 115 ++++++++++ src/Magnum/Vk/Test/configure.h.cmake | 26 +++ src/Magnum/Vk/Test/triangle-shaders.spv | Bin 0 -> 536 bytes 11 files changed, 781 insertions(+), 2 deletions(-) create mode 100644 src/Magnum/Vk/Shader.cpp create mode 100644 src/Magnum/Vk/Shader.h create mode 100644 src/Magnum/Vk/Test/.gitattributes create mode 100644 src/Magnum/Vk/Test/ShaderTest.cpp create mode 100644 src/Magnum/Vk/Test/ShaderVkTest.cpp create mode 100644 src/Magnum/Vk/Test/configure.h.cmake create mode 100644 src/Magnum/Vk/Test/triangle-shaders.spv diff --git a/doc/snippets/MagnumVk.cpp b/doc/snippets/MagnumVk.cpp index d243a0cd6..0af294012 100644 --- a/doc/snippets/MagnumVk.cpp +++ b/doc/snippets/MagnumVk.cpp @@ -23,7 +23,9 @@ DEALINGS IN THE SOFTWARE. */ +#include #include +#include #include "Magnum/Magnum.h" #include "Magnum/Math/Color.h" @@ -37,6 +39,7 @@ #include "Magnum/Vk/Integration.h" #include "Magnum/Vk/LayerProperties.h" #include "Magnum/Vk/Queue.h" +#include "Magnum/Vk/Shader.h" #include "MagnumExternal/Vulkan/flextVkGlobal.h" using namespace Magnum; @@ -256,6 +259,19 @@ if(instance.isExtensionEnabled()) { /* [Instance-isExtensionEnabled] */ } +{ +Vk::Device device{DOXYGEN_IGNORE(NoCreate)}; +/* [Shader-usage] */ +Vk::ShaderCreateInfo info{ + CORRADE_INTERNAL_ASSERT_EXPRESSION(Utility::Directory::read("shader.spv")) +}; + +DOXYGEN_IGNORE() + +Vk::Shader shader{device, info}; +/* [Shader-usage] */ +} + { /* [Integration] */ VkOffset2D a{64, 32}; diff --git a/doc/vulkan-mapping.dox b/doc/vulkan-mapping.dox index c9dcebe31..edc2e709d 100644 --- a/doc/vulkan-mapping.dox +++ b/doc/vulkan-mapping.dox @@ -62,7 +62,7 @@ Vulkan function | Matching API Vulkan function | Matching API --------------------------------------- | ------------ @fn_vk{BeginCommandBuffer}, \n \fn_vk{EndCommandBuffer} | | -@fn_vk{BindBufferMemory}, \n @fn_vk{BindBufferMemory2} @m_class{m-label m-flat m-success} **KHR, 1.1** | | +@fn_vk{BindBufferMemory}, \n @fn_vk{BindBufferMemory2} @m_class{m-label m-flat m-success} **KHR, 1.1** | @ref Buffer::bindMemory() @fn_vk{BindImageMemory}, \n @fn_vk{BindImageMemory2} @m_class{m-label m-flat m-success} **KHR, 1.1** | @ref Image::bindMemory() @subsection vulkan-mapping-functions-c C @@ -145,7 +145,7 @@ Vulkan function | Matching API @fn_vk{CreateSampler}, \n @fn_vk{DestroySampler} | | @fn_vk{CreateSamplerYcbcrConversion} @m_class{m-label m-flat m-success} **KHR, 1.1** , \n @fn_vk{DestroySamplerYcbcrConversion} @m_class{m-label m-flat m-success} **KHR, 1.1** | | @fn_vk{CreateSemaphore}, \n @fn_vk{DestroySemaphore} | | -@fn_vk{CreateShaderModule}, \n @fn_vk{DestroyShaderModule} | | +@fn_vk{CreateShaderModule}, \n @fn_vk{DestroyShaderModule} | @ref Shader constructor and destructor @subsection vulkan-mapping-functions-d D @@ -349,6 +349,14 @@ Vulkan structure | Matching API --------------------------------------- | ------------ @type_vk{MemoryAllocateInfo} | @ref MemoryAllocateInfo +@subsection vulkan-mapping-structures-s S + +@m_class{m-fullwidth} + +Vulkan structure | Matching API +--------------------------------------- | ------------ +@type_vk{ShaderCreateInfo} | @ref ShaderCreateInfo + */ }} diff --git a/src/Magnum/Vk/CMakeLists.txt b/src/Magnum/Vk/CMakeLists.txt index 5c10f74fb..930cc768e 100644 --- a/src/Magnum/Vk/CMakeLists.txt +++ b/src/Magnum/Vk/CMakeLists.txt @@ -34,6 +34,7 @@ set(MagnumVk_SRCS Instance.cpp Memory.cpp Result.cpp + Shader.cpp Version.cpp Implementation/Arguments.cpp @@ -67,6 +68,7 @@ set(MagnumVk_HEADERS Memory.h Queue.h Result.h + Shader.h TypeTraits.h Version.h Vk.h diff --git a/src/Magnum/Vk/Shader.cpp b/src/Magnum/Vk/Shader.cpp new file mode 100644 index 000000000..ad7995c9a --- /dev/null +++ b/src/Magnum/Vk/Shader.cpp @@ -0,0 +1,117 @@ +/* + 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 "Shader.h" + +#include + +#include "Magnum/Vk/Assert.h" +#include "Magnum/Vk/Device.h" +#include "Magnum/Vk/Handle.h" + +namespace Magnum { namespace Vk { + +ShaderCreateInfo::ShaderCreateInfo(Containers::ArrayView code, Flags flags): _info{} { + _info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + _info.flags = VkShaderModuleCreateFlags(flags); + _info.pCode = reinterpret_cast(code.data()); + /* Yes, the size is in bytes, while the code pointer is an int. Have fun + explaining this in every damn Vulkan tutorial. */ + _info.codeSize = code.size(); +} + +ShaderCreateInfo::ShaderCreateInfo(NoInitT) noexcept {} + +ShaderCreateInfo::ShaderCreateInfo(const VkShaderModuleCreateInfo& info): + /* Can't use {} with GCC 4.8 here because it tries to initialize the first + member instead of doing a copy */ + _info(info) {} + +ShaderCreateInfo::ShaderCreateInfo(ShaderCreateInfo&& 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), + _originalDeleter{other._originalDeleter}, _deleter{other._deleter} +{ + /* If we have a deleter and thus own the array, clear the original data + pointer as well as it inevitably becomes dangling */ + if(_deleter) { + other._info.pCode = {}; + other._info.codeSize = {}; + } + other._originalDeleter = {}; + other._deleter = {}; +} + +ShaderCreateInfo::~ShaderCreateInfo() { + if(_deleter) _deleter(_originalDeleter, _info.pCode, _info.codeSize); +} + +ShaderCreateInfo& ShaderCreateInfo::operator=(ShaderCreateInfo&& other) noexcept { + using std::swap; + swap(other._info, _info); + swap(other._originalDeleter, _originalDeleter); + swap(other._deleter, _deleter); + return *this; +} + +Shader Shader::wrap(Device& device, const VkShaderModule handle, const HandleFlags flags) { + Shader out{NoCreate}; + out._device = &device; + out._handle = handle; + out._flags = flags; + return out; +} + +Shader::Shader(Device& device, const ShaderCreateInfo& info): _device{&device}, _flags{HandleFlag::DestroyOnDestruction} { + MAGNUM_VK_INTERNAL_ASSERT_SUCCESS(device->CreateShaderModule(device, info, nullptr, &_handle)); +} + +Shader::Shader(NoCreateT): _device{}, _handle{} {} + +Shader::Shader(Shader&& other) noexcept: _device{other._device}, _handle{other._handle}, _flags{other._flags} { + other._handle = {}; +} + +Shader::~Shader() { + if(_handle && (_flags & HandleFlag::DestroyOnDestruction)) + (**_device).DestroyShaderModule(*_device, _handle, nullptr); +} + +Shader& Shader::operator=(Shader&& other) noexcept { + using std::swap; + swap(other._device, _device); + swap(other._handle, _handle); + swap(other._flags, _flags); + return *this; +} + +VkShaderModule Shader::release() { + const VkShaderModule handle = _handle; + _handle = {}; + return handle; +} + +}} diff --git a/src/Magnum/Vk/Shader.h b/src/Magnum/Vk/Shader.h new file mode 100644 index 000000000..8bf9230cf --- /dev/null +++ b/src/Magnum/Vk/Shader.h @@ -0,0 +1,286 @@ +#ifndef Magnum_Vk_Shader_h +#define Magnum_Vk_Shader_h +/* + 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. +*/ + +/** @file + * @brief Class @ref Magnum::Vk::ShaderCreateInfo, @ref Magnum::Vk::Shader + * @m_since_latest + */ + +#include + +#include "Magnum/Magnum.h" +#include "Magnum/Tags.h" +#include "Magnum/Vk/Vk.h" +#include "Magnum/Vk/Vulkan.h" +#include "Magnum/Vk/visibility.h" + +namespace Magnum { namespace Vk { + +/** +@brief Shader creation info +@m_since_latest + +Wraps a @type_vk_keyword{ShaderModuleCreateInfo}. See @ref Shader for usage +information. +*/ +class MAGNUM_VK_EXPORT ShaderCreateInfo { + public: + /** + * @brief Shader creation flag + * + * Wraps @type_vk_keyword{ShaderModuleCreateFlagBits}. + * @see @ref Flags, @ref ShaderCreateInfo() + * @m_enum_values_as_keywords + */ + enum class Flag: UnsignedInt {}; + + /** + * @brief Shader creation flags + * + * Type-safe wrapper for @type_vk_keyword{ShaderModuleCreateFlags}. + * @see @ref ShaderCreateInfo() + */ + typedef Containers::EnumSet Flags; + + /** + * @brief Constructor + * @param code Shader code + * @param flags Shader creation flags + * + * The following @type_vk{ShaderModuleCreateInfo} fields are pre-filled + * in addition to `sType`, everything else is zero-filled: + * + * - `flags` + * - `pCode` and `codeSize` to @p code + * + * @attention The class doesn't make any copy of @p code, so you either + * have to ensure it stays in scope until @ref Shader is + * constructed, or instantiate a temporary @ref ShaderCreateInfo + * directly in @ref Shader constructor call expression, as shown + * in its usage docs. If you have the data in + * @ref Corrade::Containers::Array, there's also + * @ref ShaderCreateInfo(Containers::Array&&, Flags). + */ + explicit ShaderCreateInfo(Containers::ArrayView code, Flags flags = {}); + + /** + * @brief Construct taking ownership of a r-value array instance + * + * Behaves like @ref ShaderCreateInfo(Containers::ArrayView, Flags) + * but in addition ensures @p code stays in scope until @ref Shader is + * created, deleting it on destruction. The cleanup relies on the + * pointer and size stored in @type_vk{ShaderModuleCreateInfo}, + * changing the `pCode` and `codeSize` members afterwards may result in + * memory corruption. + */ + template explicit ShaderCreateInfo(Containers::Array&& code, Flags flags = {}); + + /** + * @overload + * + * Sorry, custom @ref Corrade::Containers::Array deleter types can't be + * taken over. + */ + template explicit ShaderCreateInfo(Containers::Array&& code, Flags flags = {}) = delete; + + /** + * @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 ShaderCreateInfo(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 ShaderCreateInfo(const VkShaderModuleCreateInfo& info); + + /** @brief Copying is not allowed */ + ShaderCreateInfo(const ShaderCreateInfo&) = delete; + + /** @brief Move constructor */ + ShaderCreateInfo(ShaderCreateInfo&& other) noexcept; + + /** + * @brief Destructor + * + * If the @ref ShaderCreateInfo(Containers::Array&&, Flags) + * constructor was used, calls deleter on the stored array. + */ + ~ShaderCreateInfo(); + + /** @brief Copying is not allowed */ + ShaderCreateInfo& operator=(const ShaderCreateInfo&) = delete; + + /** @brief Move assignment */ + ShaderCreateInfo& operator=(ShaderCreateInfo&& other) noexcept; + + /** @brief Underlying @type_vk{ShaderModuleCreateInfo} structure */ + VkShaderModuleCreateInfo& operator*() { return _info; } + /** @overload */ + const VkShaderModuleCreateInfo& operator*() const { return _info; } + /** @overload */ + VkShaderModuleCreateInfo* operator->() { return &_info; } + /** @overload */ + const VkShaderModuleCreateInfo* operator->() const { return &_info; } + /** @overload */ + operator const VkShaderModuleCreateInfo*() const { return &_info; } + + private: + VkShaderModuleCreateInfo _info; + + /* Used by the Array&& constructor. Instead of wrapping an Array of an + arbitrary type we just take its deleter, the pointer + size pair is + stored in _info already. */ + void(*_originalDeleter)(){}; + void(*_deleter)(void(*)(), const void*, std::size_t){}; +}; + +CORRADE_ENUMSET_OPERATORS(ShaderCreateInfo::Flags) + +/** +@brief Shader +@m_since_latest + +Wraps a @type_vk_keyword{ShaderModule}. + +@section Vk-Shader-usage Usage + +The @ref ShaderCreateInfo structure takes a single required parameter, which is +the SPIR-V binary. Besides accepting a @ref Corrade::Containers::ArrayView, +to which any container is convertible, it can also take ownership of a +@ref Corrade::Containers::Array, which means you don't need to worry about +keeping a loaded file in scope until it's consumed by the @ref Shader +constructor: + +@snippet MagnumVk.cpp Shader-usage +*/ +class MAGNUM_VK_EXPORT Shader { + public: + /** + * @brief Wrap existing Vulkan handle + * @param device Vulkan device the shader is created on + * @param handle The @type_vk{ShaderModule} handle + * @param flags Handle flags + * + * The @p handle is expected to be originating from @p device. Unlike + * a shader created using a constructor, the Vulkan shader is by + * default not deleted on destruction, use @p flags for different + * behavior. + * @see @ref release() + */ + static Shader wrap(Device& device, VkShaderModule handle, HandleFlags flags = {}); + + /** + * @brief Constructor + * @param device Vulkan device to create the shader on + * @param info Shader creation info + * + * @see @fn_vk_keyword{CreateShaderModule} + */ + explicit Shader(Device& device, const ShaderCreateInfo& info); + + /** + * @brief Construct without creating the shader + * + * The constructed instance is equivalent to moved-from state. Useful + * in cases where you will overwrite the instance later anyway. Move + * another object over it to make it useful. + */ + explicit Shader(NoCreateT); + + /** @brief Copying is not allowed */ + Shader(const Shader&) = delete; + + /** @brief Move constructor */ + Shader(Shader&& other) noexcept; + + /** + * @brief Destructor + * + * Destroys associated @type_vk{ShaderModule} handle, unless the + * instance was created using @ref wrap() without + * @ref HandleFlag::DestroyOnDestruction specified. + * @see @fn_vk_keyword{DestroyShaderModule}, @ref release() + */ + ~Shader(); + + /** @brief Copying is not allowed */ + Shader& operator=(const Shader&) = delete; + + /** @brief Move assignment */ + Shader& operator=(Shader&& other) noexcept; + + /** @brief Underlying @type_vk{Buffer} handle */ + VkShaderModule handle() { return _handle; } + /** @overload */ + operator VkShaderModule() { return _handle; } + + /** @brief Handle flags */ + HandleFlags handleFlags() const { return _flags; } + + /** + * @brief Release the underlying Vulkan shader + * + * Releases ownership of the Vulkan shader and returns its handle so + * @fn_vk{DestroyShaderModule} is not called on destruction. The + * internal state is then equivalent to moved-from state. + * @see @ref wrap() + */ + VkShaderModule release(); + + private: + /* Can't be a reference because of the NoCreate constructor */ + Device* _device; + + VkShaderModule _handle; + HandleFlags _flags; +}; + +template /*implicit*/ ShaderCreateInfo::ShaderCreateInfo(Containers::Array&& code, Flags flags): ShaderCreateInfo{code, flags} { + /* Remember the deleter. The pointer and size is stored in + VkShaderModuleCreateInfo and we assume it won't get stomped on + afterwards */ + _originalDeleter = reinterpret_cast(code.deleter() ? code.deleter() : static_cast([](T* data, std::size_t) { delete[] data; })); + _deleter = [](void(*originalDeleter)(), const void* data, std::size_t size) { + /* VkShaderModuleCreateInfo stores the size in bytes, convert it back + to the element count for the deleter */ + reinterpret_cast(originalDeleter)(reinterpret_cast(const_cast(data)), size/sizeof(T)); + }; + + /* Release the original array so the deleter isn't called too early */ + code.release(); +} + +}} + +#endif diff --git a/src/Magnum/Vk/Test/.gitattributes b/src/Magnum/Vk/Test/.gitattributes new file mode 100644 index 000000000..d715cd5b7 --- /dev/null +++ b/src/Magnum/Vk/Test/.gitattributes @@ -0,0 +1,10 @@ +# You have to add the following to your .git/config or global +# ~/.gitconfig to make the binary diffs work (without the comment +# character, of course). Assumes spirv-dis exists: +# +# [diff "spirv"] +# textconv = spirv-dis +# binary = true +# + +*.spv binary diff=spirv diff --git a/src/Magnum/Vk/Test/CMakeLists.txt b/src/Magnum/Vk/Test/CMakeLists.txt index 7b92ec028..06a8efb5e 100644 --- a/src/Magnum/Vk/Test/CMakeLists.txt +++ b/src/Magnum/Vk/Test/CMakeLists.txt @@ -39,6 +39,7 @@ corrade_add_test(VkIntegrationTest IntegrationTest.cpp LIBRARIES MagnumVk) corrade_add_test(VkLayerPropertiesTest LayerPropertiesTest.cpp LIBRARIES MagnumVk) corrade_add_test(VkMemoryTest MemoryTest.cpp LIBRARIES MagnumVk) corrade_add_test(VkResultTest ResultTest.cpp LIBRARIES MagnumVk) +corrade_add_test(VkShaderTest ShaderTest.cpp LIBRARIES MagnumVk) add_library(VkAssertTestObjects OBJECT AssertTest.cpp) target_include_directories(VkAssertTestObjects PRIVATE $) @@ -95,6 +96,15 @@ target_compile_definitions(VkAssertStandardDisabledTest PRIVATE corrade_add_test(VkVersionTest VersionTest.cpp LIBRARIES MagnumVk) if(BUILD_VK_TESTS) + if(CORRADE_TARGET_ANDROID) + set(VK_TEST_DIR ".") + else() + set(VK_TEST_DIR ${CMAKE_CURRENT_SOURCE_DIR}) + endif() + + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/configure.h.cmake + ${CMAKE_CURRENT_BINARY_DIR}/configure.h) + corrade_add_test(VkBufferVkTest BufferVkTest.cpp LIBRARIES MagnumVulkanTester) corrade_add_test(VkCommandBufferVkTest CommandBufferVkTest.cpp LIBRARIES MagnumVulkanTester) corrade_add_test(VkCommandPoolVkTest CommandPoolVkTest.cpp LIBRARIES MagnumVulkanTester) @@ -105,5 +115,7 @@ if(BUILD_VK_TESTS) corrade_add_test(VkImageVkTest ImageVkTest.cpp LIBRARIES MagnumVk MagnumVulkanTester) corrade_add_test(VkInstanceVkTest InstanceVkTest.cpp LIBRARIES MagnumVk) corrade_add_test(VkMemoryVkTest MemoryVkTest.cpp LIBRARIES MagnumVk MagnumVulkanTester) + corrade_add_test(VkShaderVkTest ShaderVkTest.cpp LIBRARIES MagnumVk MagnumVulkanTester) + target_include_directories(VkShaderVkTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) corrade_add_test(VkVersionVkTest VersionVkTest.cpp LIBRARIES MagnumVk) endif() diff --git a/src/Magnum/Vk/Test/ShaderTest.cpp b/src/Magnum/Vk/Test/ShaderTest.cpp new file mode 100644 index 000000000..a32bae3a9 --- /dev/null +++ b/src/Magnum/Vk/Test/ShaderTest.cpp @@ -0,0 +1,187 @@ +/* + 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 +#include + +#include "Magnum/Vk/Shader.h" + +namespace Magnum { namespace Vk { namespace Test { namespace { + +struct ShaderTest: TestSuite::Tester { + explicit ShaderTest(); + + void createInfoConstruct(); + void createInfoConstructTransferOwnership(); + void createInfoConstructNoInit(); + void createInfoConstructFromVk(); + void createInfoConstructCopy(); + void createInfoConstructMove(); + + void constructNoCreate(); + void constructCopy(); +}; + +ShaderTest::ShaderTest() { + addTests({&ShaderTest::createInfoConstruct, + &ShaderTest::createInfoConstructTransferOwnership, + &ShaderTest::createInfoConstructNoInit, + &ShaderTest::createInfoConstructFromVk, + &ShaderTest::createInfoConstructCopy, + &ShaderTest::createInfoConstructMove, + + &ShaderTest::constructNoCreate, + &ShaderTest::constructCopy}); +} + +void ShaderTest::createInfoConstruct() { + const UnsignedInt data[] { 0xdead, 0xbee5, 0xbaba }; + + ShaderCreateInfo info{data}; + CORRADE_COMPARE(info->flags, 0); + CORRADE_COMPARE(info->pCode, data); + CORRADE_COMPARE(info->codeSize, 3*4); +} + +Int destructorCallCount = 0; +std::size_t destructedSize = 0; +void* destructedData = nullptr; + +void ShaderTest::createInfoConstructTransferOwnership() { + destructorCallCount = 0; + destructedData = nullptr; + destructedSize = 0; + UnsignedInt data[] { 0xdead, 0xbee5, 0xbaba }; + + { + ShaderCreateInfo info{Containers::Array{data, 3, [](UnsignedInt* data, std::size_t size) { + /* This is called in a destructor and exceptions aren't allowed in + a destructor, which means we can't use CORRADE_COMPARE() */ + destructedData = data; + destructedSize = size; + ++destructorCallCount; + }}}; + + CORRADE_COMPARE(info->pCode, data); + CORRADE_COMPARE(info->codeSize, 3*4); + CORRADE_COMPARE(destructorCallCount, 0); + } + + CORRADE_COMPARE(destructorCallCount, 1); + CORRADE_COMPARE(destructedData, static_cast(data)); + CORRADE_COMPARE(destructedSize, 3); +} + +void ShaderTest::createInfoConstructNoInit() { + { + ShaderCreateInfo info{NoInit}; + info->sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2; + new(&info) ShaderCreateInfo{NoInit}; + CORRADE_COMPARE(info->sType, VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2); + } + + /* The deleter should be zero-init'd and thus no function called on + destruction */ + + CORRADE_VERIFY((std::is_nothrow_constructible::value)); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!(std::is_convertible::value)); +} + +void ShaderTest::createInfoConstructFromVk() { + VkShaderModuleCreateInfo vkInfo; + vkInfo.sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2; + + ShaderCreateInfo info{vkInfo}; + CORRADE_COMPARE(info->sType, VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2); +} + +void ShaderTest::createInfoConstructCopy() { + CORRADE_VERIFY(!(std::is_constructible{})); + CORRADE_VERIFY(!(std::is_assignable{})); +} + +void ShaderTest::createInfoConstructMove() { + destructorCallCount = 0; + destructedData = nullptr; + destructedSize = 0; + UnsignedInt data[] { 0xdead, 0xbee5, 0xbaba }; + + { + ShaderCreateInfo a{Containers::Array{data, 3, [](UnsignedInt* data, std::size_t size) { + /* This is called in a destructor and exceptions aren't allowed in + a destructor, which means we can't use CORRADE_COMPARE() */ + destructedData = data; + destructedSize = size; + ++destructorCallCount; + }}}; + CORRADE_COMPARE(destructorCallCount, 0); + + /* Besides the deleter, the original code pointer and size should also + be cleared because it will inevitably become dangling */ + ShaderCreateInfo b = std::move(a); + CORRADE_VERIFY(!a->pCode); + CORRADE_COMPARE(a->codeSize, 0); + CORRADE_COMPARE(b->pCode, data); + CORRADE_COMPARE(b->codeSize, 3*4); + CORRADE_COMPARE(destructorCallCount, 0); + + ShaderCreateInfo c{NoInit}; + c->pCode = &data[1]; + c->codeSize = 2; + c = std::move(b); + /* It just swaps, so the moved-from instance doesn't have the code + pointer cleared */ + CORRADE_COMPARE(b->pCode, &data[1]); + CORRADE_COMPARE(b->codeSize, 2); + CORRADE_COMPARE(c->pCode, data); + CORRADE_COMPARE(c->codeSize, 3*4); + CORRADE_COMPARE(destructorCallCount, 0); + } + + CORRADE_COMPARE(destructorCallCount, 1); + CORRADE_COMPARE(destructedData, static_cast(data)); + CORRADE_COMPARE(destructedSize, 3); +} + +void ShaderTest::constructNoCreate() { + { + Shader shader{NoCreate}; + CORRADE_VERIFY(!shader.handle()); + } + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!(std::is_convertible::value)); +} + +void ShaderTest::constructCopy() { + CORRADE_VERIFY(!(std::is_constructible{})); + CORRADE_VERIFY(!(std::is_assignable{})); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::Vk::Test::ShaderTest) diff --git a/src/Magnum/Vk/Test/ShaderVkTest.cpp b/src/Magnum/Vk/Test/ShaderVkTest.cpp new file mode 100644 index 000000000..a44ca5d6c --- /dev/null +++ b/src/Magnum/Vk/Test/ShaderVkTest.cpp @@ -0,0 +1,115 @@ +/* + 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 +#include +#include +#include + +#include "Magnum/Vk/Shader.h" +#include "Magnum/Vk/DeviceProperties.h" +#include "Magnum/Vk/Handle.h" +#include "Magnum/Vk/Memory.h" +#include "Magnum/Vk/Result.h" +#include "Magnum/Vk/VulkanTester.h" + +#include "configure.h" + +namespace Magnum { namespace Vk { namespace Test { namespace { + +struct ShaderVkTest: VulkanTester { + explicit ShaderVkTest(); + + void construct(); + void constructMove(); + + void wrap(); +}; + +ShaderVkTest::ShaderVkTest() { + addTests({&ShaderVkTest::construct, + &ShaderVkTest::constructMove, + + &ShaderVkTest::wrap}); +} + +void ShaderVkTest::construct() { + Containers::Array data = Utility::Directory::read(Utility::Directory::join(VK_TEST_DIR, "triangle-shaders.spv")); + CORRADE_VERIFY(data); + + { + Shader shader{device(), ShaderCreateInfo{data}}; + CORRADE_VERIFY(shader.handle()); + CORRADE_COMPARE(shader.handleFlags(), HandleFlag::DestroyOnDestruction); + } + + /* Shouldn't crash or anything */ + CORRADE_VERIFY(true); +} + +void ShaderVkTest::constructMove() { + Containers::Array data = Utility::Directory::read(Utility::Directory::join(VK_TEST_DIR, "triangle-shaders.spv")); + CORRADE_VERIFY(data); + + Shader a{device(), ShaderCreateInfo{data}}; + VkShaderModule handle = a.handle(); + + Shader b = std::move(a); + CORRADE_VERIFY(!a.handle()); + CORRADE_COMPARE(b.handle(), handle); + CORRADE_COMPARE(b.handleFlags(), HandleFlag::DestroyOnDestruction); + + Shader c{NoCreate}; + c = std::move(b); + CORRADE_VERIFY(!b.handle()); + CORRADE_COMPARE(b.handleFlags(), HandleFlags{}); + CORRADE_COMPARE(c.handle(), handle); + CORRADE_COMPARE(c.handleFlags(), HandleFlag::DestroyOnDestruction); + + CORRADE_VERIFY(std::is_nothrow_move_constructible::value); + CORRADE_VERIFY(std::is_nothrow_move_assignable::value); +} + +void ShaderVkTest::wrap() { + Containers::Array data = Utility::Directory::read(Utility::Directory::join(VK_TEST_DIR, "triangle-shaders.spv")); + CORRADE_VERIFY(data); + + VkShaderModule shader{}; + CORRADE_COMPARE(Result(device()->CreateShaderModule(device(), + ShaderCreateInfo{data}, + nullptr, &shader)), Result::Success); + + auto wrapped = Shader::wrap(device(), shader, HandleFlag::DestroyOnDestruction); + CORRADE_COMPARE(wrapped.handle(), shader); + + /* Release the handle again, destroy by hand */ + CORRADE_COMPARE(wrapped.release(), shader); + CORRADE_VERIFY(!wrapped.handle()); + device()->DestroyShaderModule(device(), shader, nullptr); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::Vk::Test::ShaderVkTest) diff --git a/src/Magnum/Vk/Test/configure.h.cmake b/src/Magnum/Vk/Test/configure.h.cmake new file mode 100644 index 000000000..a03b1b5bf --- /dev/null +++ b/src/Magnum/Vk/Test/configure.h.cmake @@ -0,0 +1,26 @@ +/* + 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. +*/ + +#define VK_TEST_DIR "${VK_TEST_DIR}" diff --git a/src/Magnum/Vk/Test/triangle-shaders.spv b/src/Magnum/Vk/Test/triangle-shaders.spv new file mode 100644 index 0000000000000000000000000000000000000000..a8be29a099ae92116ac6b218f525e8c5d7e4a2a8 GIT binary patch literal 536 zcmYk2NeTi{3`FBR+fL|C6z?E{=+<+%&;z*e>}~|#`xr5hpHzlQbvNp}PDCvtI