From bbb066664bb5b29253f5f740eeff4ae51a0ede30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 6 Jan 2021 18:00:29 +0100 Subject: [PATCH] Vk: add a wrapper for mesh layout setup. Next up is *the unthinkable*, a Vk::Mesh. After that I'll finally have enough APIs exposed to document everything including command buffer recording and submission. --- doc/changelog.dox | 5 + doc/snippets/MagnumVk.cpp | 24 ++ doc/vulkan-mapping.dox | 16 +- doc/vulkan-support.dox | 2 +- src/Magnum/Vk/CMakeLists.txt | 2 + src/Magnum/Vk/Enums.cpp | 33 +-- src/Magnum/Vk/Enums.h | 34 +-- src/Magnum/Vk/MeshLayout.cpp | 210 +++++++++++++++ src/Magnum/Vk/MeshLayout.h | 373 ++++++++++++++++++++++++++ src/Magnum/Vk/Test/CMakeLists.txt | 2 + src/Magnum/Vk/Test/EnumsTest.cpp | 96 +------ src/Magnum/Vk/Test/MeshLayoutTest.cpp | 352 ++++++++++++++++++++++++ src/Magnum/Vk/Vk.h | 2 + 13 files changed, 995 insertions(+), 156 deletions(-) create mode 100644 src/Magnum/Vk/MeshLayout.cpp create mode 100644 src/Magnum/Vk/MeshLayout.h create mode 100644 src/Magnum/Vk/Test/MeshLayoutTest.cpp diff --git a/doc/changelog.dox b/doc/changelog.dox index 647bbe12d..cd5ea5a03 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -373,6 +373,11 @@ See also: @ref Vk::pixelFormat(Magnum::CompressedPixelFormat) that return the new @ref Vk::PixelFormat enum that contains only values suitable for a pixel format +- @cpp Vk::hasVkPrimitiveTopology() @ce and + @cpp Vk::vkPrimitiveTopology() @ce returning a raw + @type_vk{PrimitiveToplogy} are deprecated in favor of + @ref Vk::hasMeshPrimitive() and @ref Vk::meshPrimitive() that return the + new @ref Vk::MeshPrimitive enum @subsection changelog-latest-compatibility Potential compatibility breakages, removed APIs diff --git a/doc/snippets/MagnumVk.cpp b/doc/snippets/MagnumVk.cpp index 3055be959..9f4e64fb1 100644 --- a/doc/snippets/MagnumVk.cpp +++ b/doc/snippets/MagnumVk.cpp @@ -29,7 +29,9 @@ #include #include "Magnum/Magnum.h" +#include "Magnum/Mesh.h" #include "Magnum/PixelFormat.h" +#include "Magnum/VertexFormat.h" #include "Magnum/Math/Color.h" #include "Magnum/Vk/Assert.h" #include "Magnum/Vk/BufferCreateInfo.h" @@ -48,6 +50,7 @@ #include "Magnum/Vk/ImageViewCreateInfo.h" #include "Magnum/Vk/LayerProperties.h" #include "Magnum/Vk/MemoryAllocateInfo.h" +#include "Magnum/Vk/MeshLayout.h" #include "Magnum/Vk/Pipeline.h" #include "Magnum/Vk/PixelFormat.h" #include "Magnum/Vk/Queue.h" @@ -765,6 +768,27 @@ indices.bindMemory(memory, indicesOffset); /* [Memory-mapping] */ } +{ +/* [MeshLayout-usage] */ +constexpr UnsignedInt BufferBinding = 0; + +constexpr UnsignedInt PositionLocation = 0; +constexpr UnsignedInt TextureCoordinateLocation = 1; +constexpr UnsignedInt NormalLocation = 5; + +Vk::MeshLayout meshLayout{MeshPrimitive::Triangles}; +meshLayout + .addBinding(BufferBinding, + sizeof(Vector3) + sizeof(Vector2) + sizeof(Vector3)) + .addAttribute(PositionLocation, BufferBinding, VertexFormat::Vector3, + 0) + .addAttribute(TextureCoordinateLocation, BufferBinding, VertexFormat::Vector2, + sizeof(Vector3)) + .addAttribute(NormalLocation, BufferBinding, VertexFormat::Vector3, + sizeof(Vector3) + sizeof(Vector2)); +/* [MeshLayout-usage] */ +} + { Vk::Device device{NoCreate}; /* The include should be a no-op here since it was already included above */ diff --git a/doc/vulkan-mapping.dox b/doc/vulkan-mapping.dox index c0b438b6e..ebfc60047 100644 --- a/doc/vulkan-mapping.dox +++ b/doc/vulkan-mapping.dox @@ -647,7 +647,7 @@ Vulkan structure | Matching API @type_vk{PipelineColorBlendStateCreateInfo} | | @type_vk{PipelineDepthStencilStateCreateInfo} | | @type_vk{PipelineDynamicStateCreateInfo} | | -@type_vk{PipelineInputAssemblyStateCreateInfo} | | +@type_vk{PipelineInputAssemblyStateCreateInfo} | @ref MeshLayout @type_vk{PipelineLayoutCreateInfo} | | @type_vk{PipelineLibraryCreateInfoKHR} @m_class{m-label m-flat m-warning} **KHR** | | @type_vk{PipelineMultisampleStateCreateInfo} | | @@ -655,8 +655,8 @@ Vulkan structure | Matching API @type_vk{PipelineShaderStageCreateInfo} | | @type_vk{PipelineTessellationStateCreateInfo} | | @type_vk{PipelineTessellationDomainOriginStateCreateInfo} @m_class{m-label m-flat m-success} **KHR, 1.1** | | -@type_vk{PipelineVertexInputDivisorStateCreateInfoEXT} @m_class{m-label m-flat m-warning} **EXT** | | -@type_vk{PipelineVertexInputStateCreateInfo} | | +@type_vk{PipelineVertexInputDivisorStateCreateInfoEXT} @m_class{m-label m-flat m-warning} **EXT** | @ref MeshLayout +@type_vk{PipelineVertexInputStateCreateInfo} | @ref MeshLayout @type_vk{PipelineViewportStateCreateInfo} | | @type_vk{ProtectedSubmitInfo} | | @type_vk{PushConstantRange} | | @@ -739,9 +739,9 @@ Vulkan structure | Matching API Vulkan structure | Matching API --------------------------------------- | ------------ @type_vk{ValidationFeaturesEXT} @m_class{m-label m-flat m-warning} **EXT** | | -@type_vk{VertexInputBindingDescription} | | -@type_vk{VertexInputBindingDivisorDescriptionEXT} @m_class{m-label m-flat m-warning} **EXT** | | -@type_vk{VertexInputAttributeDescription} | | +@type_vk{VertexInputBindingDescription} | @ref MeshLayout +@type_vk{VertexInputBindingDivisorDescriptionEXT} @m_class{m-label m-flat m-warning} **EXT** | @ref MeshLayout +@type_vk{VertexInputAttributeDescription} | @ref MeshLayout @type_vk{Viewport} | convertible from/to @ref Range3D using @ref Magnum/Vk/Integration.h @subsection vulkan-mapping-structures-w W @@ -914,7 +914,7 @@ Vulkan enum | Matching API @type_vk{PipelineStageFlagBits}, \n @type_vk{PipelineStageFlags} | @ref PipelineStage, \n @ref PipelineStages @type_vk{PointClippingBehavior} @m_class{m-label m-flat m-success} **KHR, 1.1** | | @type_vk{PolygonMode} | | -@type_vk{PrimitveTopology} | only @ref vkPrimitiveTopology() +@type_vk{PrimitveTopology} | @ref MeshPrimitive @subsection vulkan-mapping-enums-q Q @@ -985,7 +985,7 @@ Vulkan enum | Matching API @type_vk{ValidationFeaturesDisableEXT} @m_class{m-label m-flat m-warning} **EXT** | | @type_vk{ValidationFeaturesEnableEXT} @m_class{m-label m-flat m-warning} **EXT** | | @type_vk{VendorId} | | -@type_vk{VertexInputRate} | | +@type_vk{VertexInputRate} | internal to @ref MeshLayout */ diff --git a/doc/vulkan-support.dox b/doc/vulkan-support.dox index 62dcee1c7..45cffd24d 100644 --- a/doc/vulkan-support.dox +++ b/doc/vulkan-support.dox @@ -120,7 +120,7 @@ Extension | Status @vk_extension{EXT,texture_compression_astc_hdr} | done @vk_extension{EXT,debug_utils} @m_class{m-label m-info} **instance** | | @vk_extension{EXT,validation_features} @m_class{m-label m-info} **instance** | | -@vk_extension{EXT,vertex_attribute_divisor} | | +@vk_extension{EXT,vertex_attribute_divisor} | done @vk_extension{EXT,index_type_uint8} | @ref Vk::vkIndexType() only @vk_extension{KHR,acceleration_structure} | | @vk_extension{KHR,portability_subset} | done except properties diff --git a/src/Magnum/Vk/CMakeLists.txt b/src/Magnum/Vk/CMakeLists.txt index 579b4cf2a..2445e3d81 100644 --- a/src/Magnum/Vk/CMakeLists.txt +++ b/src/Magnum/Vk/CMakeLists.txt @@ -55,6 +55,7 @@ set(MagnumVk_GracefulAssert_SRCS ImageView.cpp Instance.cpp LayerProperties.cpp + MeshLayout.cpp Memory.cpp PixelFormat.cpp RenderPass.cpp @@ -89,6 +90,7 @@ set(MagnumVk_HEADERS LayerProperties.h Memory.h MemoryAllocateInfo.h + MeshLayout.h Pipeline.h PixelFormat.h Queue.h diff --git a/src/Magnum/Vk/Enums.cpp b/src/Magnum/Vk/Enums.cpp index 49d076ac7..805c20469 100644 --- a/src/Magnum/Vk/Enums.cpp +++ b/src/Magnum/Vk/Enums.cpp @@ -31,6 +31,7 @@ #include "Magnum/Sampler.h" #ifdef MAGNUM_BUILD_DEPRECATED +#include "Magnum/Vk/MeshLayout.h" #include "Magnum/Vk/PixelFormat.h" #include "Magnum/Vk/VertexFormat.h" #endif @@ -39,19 +40,6 @@ namespace Magnum { namespace Vk { namespace { -constexpr VkPrimitiveTopology PrimitiveTopologyMapping[]{ - VK_PRIMITIVE_TOPOLOGY_POINT_LIST, - VK_PRIMITIVE_TOPOLOGY_LINE_LIST, - VkPrimitiveTopology(~UnsignedInt{}), - VK_PRIMITIVE_TOPOLOGY_LINE_STRIP, - VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, - VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, - VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN, - VkPrimitiveTopology(~UnsignedInt{}), /* Instances */ - VkPrimitiveTopology(~UnsignedInt{}), /* Faces */ - VkPrimitiveTopology(~UnsignedInt{}) /* Edges */ -}; - constexpr VkIndexType IndexTypeMapping[]{ VK_INDEX_TYPE_UINT8_EXT, VK_INDEX_TYPE_UINT16, @@ -79,26 +67,15 @@ constexpr VkSamplerAddressMode SamplerAddressModeMapping[]{ } +#ifdef MAGNUM_BUILD_DEPRECATED bool hasVkPrimitiveTopology(const Magnum::MeshPrimitive primitive) { - if(isMeshPrimitiveImplementationSpecific(primitive)) - return true; - - CORRADE_ASSERT(UnsignedInt(primitive) - 1 < Containers::arraySize(PrimitiveTopologyMapping), - "Vk::hasVkPrimitiveTopology(): invalid primitive" << primitive, {}); - return UnsignedInt(PrimitiveTopologyMapping[UnsignedInt(primitive) - 1]) != ~UnsignedInt{}; + return hasMeshPrimitive(primitive); } VkPrimitiveTopology vkPrimitiveTopology(const Magnum::MeshPrimitive primitive) { - if(isMeshPrimitiveImplementationSpecific(primitive)) - return meshPrimitiveUnwrap(primitive); - - CORRADE_ASSERT(UnsignedInt(primitive) - 1 < Containers::arraySize(PrimitiveTopologyMapping), - "Vk::vkPrimitiveTopology(): invalid primitive" << primitive, {}); - const VkPrimitiveTopology out = PrimitiveTopologyMapping[UnsignedInt(primitive) - 1]; - CORRADE_ASSERT(out != VkPrimitiveTopology(~UnsignedInt{}), - "Vk::vkPrimitiveTopology(): unsupported primitive" << primitive, {}); - return out; + return VkPrimitiveTopology(meshPrimitive(primitive)); } +#endif bool hasVkIndexType(const Magnum::MeshIndexType type) { CORRADE_ASSERT(UnsignedInt(type) - 1 < Containers::arraySize(IndexTypeMapping), diff --git a/src/Magnum/Vk/Enums.h b/src/Magnum/Vk/Enums.h index 2a45f570d..f976dfcca 100644 --- a/src/Magnum/Vk/Enums.h +++ b/src/Magnum/Vk/Enums.h @@ -39,33 +39,19 @@ namespace Magnum { namespace Vk { +#ifdef MAGNUM_BUILD_DEPRECATED /** -@brief Check availability of a generic mesh primitive - -In particular, Vulkan doesn't support the @ref MeshPrimitive::LineLoop -primitive. Returns @cpp false @ce if Vulkan doesn't support such primitive, -@cpp true @ce otherwise. Moreover, returns @cpp true @ce also for all types -that are @ref isMeshPrimitiveImplementationSpecific(). The @p primitive value -is expected to be valid. -@see @ref vkPrimitiveTopology() -*/ -MAGNUM_VK_EXPORT bool hasVkPrimitiveTopology(Magnum::MeshPrimitive primitive); + * @brief @copybrief hasMeshPrimitive() + * @m_deprecated_since_latest Use @ref hasMeshPrimitive() instead. + */ +CORRADE_DEPRECATED("use hasMeshPrimitive() instead") MAGNUM_VK_EXPORT bool hasVkPrimitiveTopology(Magnum::MeshPrimitive primitive); /** -@brief Convert generic mesh primitive to Vulkan primitive topology - -In case @ref isMeshPrimitiveImplementationSpecific() returns @cpp false @ce for -@p primitive, maps it to a corresponding Vulkan primitive topology. In case -@ref isMeshPrimitiveImplementationSpecific() returns @cpp true @ce, assumes -@p primitive stores a Vulkan-specific primitive topology and returns -@ref meshPrimitiveUnwrap() cast to @type_vk{PrimitiveTopology}. - -Not all generic mesh primitives have a Vulkan equivalent and this function -expects that given primitive is available. Use @ref hasVkPrimitiveTopology() to -query availability of given primitive. -@see @ref vkIndexType() -*/ -MAGNUM_VK_EXPORT VkPrimitiveTopology vkPrimitiveTopology(Magnum::MeshPrimitive primitive); + * @brief @copybrief meshPrimitive() + * @m_deprecated_since_latest Use @ref meshPrimitive() instead. + */ +CORRADE_DEPRECATED("use meshPrimitive() instead") MAGNUM_VK_EXPORT VkPrimitiveTopology vkPrimitiveTopology(Magnum::MeshPrimitive primitive); +#endif /** @brief Check availability of a generic index type diff --git a/src/Magnum/Vk/MeshLayout.cpp b/src/Magnum/Vk/MeshLayout.cpp new file mode 100644 index 000000000..d24ba5c55 --- /dev/null +++ b/src/Magnum/Vk/MeshLayout.cpp @@ -0,0 +1,210 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021 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 "MeshLayout.h" + +#include + +#include "Magnum/Mesh.h" +#include "Magnum/Vk/VertexFormat.h" +#include "Magnum/Vk/Implementation/structureHelpers.h" + +namespace Magnum { namespace Vk { + +namespace { + +constexpr MeshPrimitive MeshPrimitiveMapping[]{ + MeshPrimitive::Points, + MeshPrimitive::Lines, + MeshPrimitive(~Int{}), /* LineLoop */ + MeshPrimitive::LineStrip, + MeshPrimitive::Triangles, + MeshPrimitive::TriangleStrip, + MeshPrimitive::TriangleFan, + MeshPrimitive(~Int{}), /* Instances */ + MeshPrimitive(~Int{}), /* Faces */ + MeshPrimitive(~Int{}) /* Edges */ +}; + +} + +bool hasMeshPrimitive(const Magnum::MeshPrimitive primitive) { + if(isMeshPrimitiveImplementationSpecific(primitive)) + return true; + + CORRADE_ASSERT(UnsignedInt(primitive) - 1 < Containers::arraySize(MeshPrimitiveMapping), + "Vk::hasMeshPrimitive(): invalid primitive" << primitive, {}); + return UnsignedInt(MeshPrimitiveMapping[UnsignedInt(primitive) - 1]) != ~UnsignedInt{}; +} + +MeshPrimitive meshPrimitive(const Magnum::MeshPrimitive primitive) { + if(isMeshPrimitiveImplementationSpecific(primitive)) + return meshPrimitiveUnwrap(primitive); + + CORRADE_ASSERT(UnsignedInt(primitive) - 1 < Containers::arraySize(MeshPrimitiveMapping), + "Vk::meshPrimitive(): invalid primitive" << primitive, {}); + const MeshPrimitive out = MeshPrimitiveMapping[UnsignedInt(primitive) - 1]; + CORRADE_ASSERT(out != MeshPrimitive(~UnsignedInt{}), + "Vk::meshPrimitive(): unsupported primitive" << primitive, {}); + return out; +} + +struct MeshLayout::State { + Containers::Array bindings; + Containers::Array bindingDivisors; + Containers::Array attributes; + VkPipelineVertexInputDivisorStateCreateInfoEXT vertexDivisorInfo{}; +}; + +MeshLayout::MeshLayout(const MeshPrimitive primitive): _vertexInfo{}, _assemblyInfo{} { + _vertexInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + + _assemblyInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + _assemblyInfo.topology = VkPrimitiveTopology(primitive); +} + +MeshLayout::MeshLayout(const Magnum::MeshPrimitive primitive): MeshLayout{meshPrimitive(primitive)} {} + +MeshLayout::MeshLayout(NoInitT) noexcept {} + +MeshLayout::MeshLayout(const VkPipelineVertexInputStateCreateInfo& vertexInfo, const VkPipelineInputAssemblyStateCreateInfo& assemblyInfo): + /* Can't use {} with GCC 4.8 here because it tries to initialize the first + member instead of doing a copy */ + _vertexInfo(vertexInfo), _assemblyInfo(assemblyInfo) {} + +MeshLayout::MeshLayout(MeshLayout&& other) noexcept: + /* Can't use {} with GCC 4.8 here because it tries to initialize the first + member instead of doing a copy */ + _vertexInfo(other._vertexInfo), + _assemblyInfo(other._assemblyInfo), + _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._vertexInfo.pNext = nullptr; + other._vertexInfo.vertexBindingDescriptionCount = 0; + other._vertexInfo.pVertexBindingDescriptions = nullptr; + other._vertexInfo.vertexAttributeDescriptionCount = 0; + other._vertexInfo.pVertexAttributeDescriptions = nullptr; + other._assemblyInfo.pNext = nullptr; +} + +MeshLayout::~MeshLayout() = default; + +MeshLayout& MeshLayout::operator=(MeshLayout&& other) noexcept { + using std::swap; + swap(other._vertexInfo, _vertexInfo); + swap(other._assemblyInfo, _assemblyInfo); + swap(other._state, _state); + return *this; +} + +MeshLayout& MeshLayout::addBinding(const UnsignedInt binding, const UnsignedInt stride) { + if(!_state) _state.emplace(); + + VkVertexInputBindingDescription description{}; + description.binding = binding; + description.stride = stride; + description.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + arrayAppend(_state->bindings, description); + _vertexInfo.vertexBindingDescriptionCount = _state->bindings.size(); + _vertexInfo.pVertexBindingDescriptions = _state->bindings; + + return *this; +} + +MeshLayout& MeshLayout::addInstancedBinding(const UnsignedInt binding, const UnsignedInt stride, const UnsignedInt divisor) { + if(!_state) _state.emplace(); + + VkVertexInputBindingDescription description{}; + description.binding = binding; + description.stride = stride; + description.inputRate = VK_VERTEX_INPUT_RATE_INSTANCE; + arrayAppend(_state->bindings, description); + _vertexInfo.vertexBindingDescriptionCount = _state->bindings.size(); + _vertexInfo.pVertexBindingDescriptions = _state->bindings; + + if(divisor != 1) { + VkVertexInputBindingDivisorDescriptionEXT divisorDescription{}; + divisorDescription.binding = binding; + divisorDescription.divisor = divisor; + arrayAppend(_state->bindingDivisors, divisorDescription); + _state->vertexDivisorInfo.vertexBindingDivisorCount = _state->bindingDivisors.size(); + _state->vertexDivisorInfo.pVertexBindingDivisors = _state->bindingDivisors; + + /* Attach the structure if not already */ + if(!_state->vertexDivisorInfo.sType) + Implementation::structureConnectOne(_vertexInfo.pNext, _state->vertexDivisorInfo, VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_DIVISOR_STATE_CREATE_INFO_EXT); + } + + return *this; +} + +MeshLayout& MeshLayout::addAttribute(const UnsignedInt location, const UnsignedInt binding, const VertexFormat format, const UnsignedInt offset) { + if(!_state) _state.emplace(); + + VkVertexInputAttributeDescription description{}; + description.location = location; + description.binding = binding; + description.format = VkFormat(format); + description.offset = offset; + arrayAppend(_state->attributes, description); + _vertexInfo.vertexAttributeDescriptionCount = _state->attributes.size(); + _vertexInfo.pVertexAttributeDescriptions = _state->attributes; + + return *this; +} + +MeshLayout& MeshLayout::addAttribute(const UnsignedInt location, const UnsignedInt binding, const Magnum::VertexFormat format, const UnsignedInt offset) { + return addAttribute(location, binding, vertexFormat(format), offset); +} + +Debug& operator<<(Debug& debug, const MeshPrimitive value) { + debug << "Vk::MeshPrimitive" << Debug::nospace; + + switch(value) { + /* LCOV_EXCL_START */ + #define _c(value) case Vk::MeshPrimitive::value: return debug << "::" << Debug::nospace << #value; + _c(Points) + _c(Lines) + _c(LineStrip) + _c(Triangles) + _c(TriangleStrip) + _c(TriangleFan) + _c(LinesAdjacency) + _c(LineStripAdjacency) + _c(TrianglesAdjacency) + _c(TriangleStripAdjacency) + _c(Patches) + #undef _c + /* LCOV_EXCL_STOP */ + } + + /* Vulkan docs have the values in decimal, so not converting to hex */ + return debug << "(" << Debug::nospace << Int(value) << Debug::nospace << ")"; +} + +}} diff --git a/src/Magnum/Vk/MeshLayout.h b/src/Magnum/Vk/MeshLayout.h new file mode 100644 index 000000000..c69fc4f17 --- /dev/null +++ b/src/Magnum/Vk/MeshLayout.h @@ -0,0 +1,373 @@ +#ifndef Magnum_Vk_MeshLayout_h +#define Magnum_Vk_MeshLayout_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021 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::MeshLayout, enum @ref Magnum::Vk::MeshPrimitive, function @ref Magnum::Vk::hasMeshPrimitive(), @ref Magnum::Vk::meshPrimitive() + * @m_since_latest + */ + +#include + +#include "Magnum/Tags.h" +#include "Magnum/Vk/Vk.h" +#include "Magnum/Vk/Vulkan.h" +#include "Magnum/Vk/visibility.h" + +namespace Magnum { namespace Vk { + +/* About naming -- I wonder why Vulkan tries *so hard* to avoid naming anything + a "mesh". It would so nicely group things togehter BUT NO, there's primitive + topology, and vertex input state, and input assembly and ugh. */ + +/** +@brief Mesh primitive +@m_since_latest + +Wraps a @type_vk_keyword{PrimitiveTopology}. + +@m_enum_values_as_keywords + +@see @ref Magnum::MeshPrimitive, @ref hasMeshPrimitive(), @ref meshPrimitive(), + @ref MeshLayout +*/ +enum class MeshPrimitive: Int { + /* The _LIST seems too verbose and looks like Vulkan naming got inspired by + D3D here. I'm omitting those since it's unnecessary verbosity, Metal + doesn't have those either. GL had the naming right. */ + + /** Single points. */ + Points = VK_PRIMITIVE_TOPOLOGY_POINT_LIST, + + /** + * Each pair of vertices defines a single line, lines aren't + * connected together. + */ + Lines = VK_PRIMITIVE_TOPOLOGY_LINE_LIST, + + /** + * First two vertices define first line segment, each following + * vertex defines another segment. + */ + LineStrip = VK_PRIMITIVE_TOPOLOGY_LINE_STRIP, + + /** Each three vertices define one triangle. */ + Triangles = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, + + /** + * First three vertices define first triangle, each following + * vertex defines another triangle. + */ + TriangleStrip = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, + + /** + * First vertex is center, each following vertex is connected to + * previous and center vertex. + * @requires_vk_feature @ref DeviceFeature::TriangleFans if the + * @vk_extension{KHR,portability_subset} extension is present + */ + TriangleFan = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN, + + /** + * Lines with adjacency information. + * @requires_vk_feature @ref DeviceFeature::GeometryShader + */ + LinesAdjacency = VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY, + + /** + * Line strip with adjacency information. + * @requires_vk_feature @ref DeviceFeature::GeometryShader + */ + LineStripAdjacency = VK_PRIMITIVE_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY, + + /** + * Triangles with adjacency information. + * @requires_vk_feature @ref DeviceFeature::GeometryShader + */ + TrianglesAdjacency = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY, + + /** + * Triangle strip with adjacency information. + * @requires_vk_feature @ref DeviceFeature::GeometryShader + */ + TriangleStripAdjacency = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP_WITH_ADJACENCY, + + /** + * Patches. + * @requires_vk_feature @ref DeviceFeature::TessellationShader + */ + Patches = VK_PRIMITIVE_TOPOLOGY_PATCH_LIST +}; + +/** +@debugoperatorenum{MeshPrimitive} +@m_since_latest +*/ +MAGNUM_VK_EXPORT Debug& operator<<(Debug& debug, MeshPrimitive value); + +/** +@brief Check availability of a generic mesh primitive +@m_since_latest + +In particular, Vulkan doesn't support the @ref MeshPrimitive::LineLoop +primitive. Returns @cpp false @ce if Vulkan doesn't support such primitive, +@cpp true @ce otherwise. Moreover, returns @cpp true @ce also for all types +that are @ref isMeshPrimitiveImplementationSpecific(). The @p primitive value +is expected to be valid. + +@note Support of some types depends on presence of a particular Vulkan + extension. Such check is outside of the scope of this function and you are + expected to verify extension availability before using such type. + +@see @ref meshPrimitive() +*/ +MAGNUM_VK_EXPORT bool hasMeshPrimitive(Magnum::MeshPrimitive primitive); + +/** +@brief Convert generic mesh primitive to Vulkan mesh primitive +@m_since_latest + +In case @ref isMeshPrimitiveImplementationSpecific() returns @cpp false @ce for +@p primitive, maps it to a corresponding Vulkan primitive topology. In case +@ref isMeshPrimitiveImplementationSpecific() returns @cpp true @ce, assumes +@p primitive stores a Vulkan-specific primitive topology and returns +@ref meshPrimitiveUnwrap() cast to @type_vk{PrimitiveTopology}. + +Not all generic mesh primitives have a Vulkan equivalent and this function +expects that given primitive is available. Use @ref hasMeshPrimitive() to query +availability of given primitive. +@see @ref vkIndexType() +*/ +MAGNUM_VK_EXPORT MeshPrimitive meshPrimitive(Magnum::MeshPrimitive primitive); + +/** +@brief Mesh layout +@m_since_latest + +Wraps a + +- @type_vk_keyword{VertexInputBindingDescription}, +- @type_vk_keyword{VertexInputAttributeDescription}, +- @type_vk_keyword{PipelineVertexInputStateCreateInfo}, +- @type_vk_keyword{PipelineInputAssemblyStateCreateInfo}, +- @type_vk_keyword{VertexInputBindingDivisorDescriptionEXT} and +- @type_vk_keyword{PipelineVertexInputDivisorStateCreateInfoEXT}, + +@m_class{m-noindent} + +describing how vertex attributes are organized in buffers and what's the layout +of each attribute. + +@section Vk-MeshLayout-usage Usage + +As an example let's assume a shader expects positions, texture coordinates and +normals in locations @cpp 0 @ce, @cpp 1 @ce and @cpp 5 @ce, respectively. If +we'd have them stored interleaved in a single buffer, the layout description +could look like this: + +@snippet MagnumVk.cpp MeshLayout-usage + +The `BufferBinding` is then subsequently used as a binding index for a concrete +vertex buffer when drawing. +*/ +class MAGNUM_VK_EXPORT MeshLayout { + public: + /** + * @brief Constructor + * @param primitive Mesh primitive + * + * The following @type_vk{PipelineVertexInputStateCreateInfo} fields + * are pre-filled in addition to `sType`, everything else is + * zero-filled: + * + * - (none) + * + * The following @type_vk{PipelineInputAssemblyStateCreateInfo} fields + * are pre-filled in addition to `sType`, everything else is + * zero-filled: + * + * - `topology` to @p primitive + * + * @see @ref vkPipelineVertexInputStateCreateInfo(), + * @ref vkPipelineInputAssemblyStateCreateInfo() + */ + explicit MeshLayout(MeshPrimitive primitive); + /** @overload */ + explicit MeshLayout(Magnum::MeshPrimitive primitive); + + /** + * @brief Construct without initializing the contents + * + * Note that not even the `sType` fields are set --- the structures + * have to be fully initialized afterwards in order to be usable. + */ + explicit MeshLayout(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 MeshLayout(const VkPipelineVertexInputStateCreateInfo& vertexInfo, const VkPipelineInputAssemblyStateCreateInfo& assemblyInfo); + + /** @brief Copying is not allowed */ + MeshLayout(const MeshLayout&) = delete; + + /** @brief Move constructor */ + MeshLayout(MeshLayout&& other) noexcept; + + ~MeshLayout(); + + /** @brief Copying is not allowed */ + MeshLayout& operator=(const MeshLayout&) = delete; + + /** @brief Move assignment */ + MeshLayout& operator=(MeshLayout&& other) noexcept; + + /** + * @brief Add a buffer binding + * @param binding Binding index, to which a buffer subrange will be + * bound when drawing the mesh. Has to be unique among all + * @ref addBinding() and @ref addInstancedBinding() calls. + * @param stride Binding stride, in bytes + * @return Reference to self (for method chaining) + * + * Adds a new @type_vk{VertexInputBindingDescription} structure to + * @ref vkPipelineVertexInputStateCreateInfo() with the following + * fields set: + * + * - `binding` + * - `stride` + * - `inputRate` to @val_vk{VERTEX_INPUT_RATE_VERTEX,VertexInputRate} + * + * @see @ref addInstancedBinding() + */ + MeshLayout& addBinding(UnsignedInt binding, UnsignedInt stride); + + /** + * @brief Add an instanced buffer binding + * @param binding Binding index, to which a buffer subrange will be + * bound when drawing the mesh. Has to be unique among all + * @ref addBinding() and @ref addInstancedBinding() calls. + * @param stride Binding stride, in bytes + * @param divisor Attribute divisor. @cpp 1 @ce means the attribute + * will be advanced for each instance, larger values will mean + * `n` instances will be drawn using the same attribute, + * @cpp 0 @ce will make all instances use a single attribute + * (which is effectively the same as setting @p divisor to + * instance count). + * @return Reference to self (for method chaining) + * + * Compared to @ref addBinding(), sets `inputRate` to + * @val_vk{VERTEX_INPUT_RATE_INSTANCE,VertexInputRate}. If @p divisor + * is not @cpp 1 @ce, a new + * @type_vk{VertexInputBindingDivisorDescriptionEXT} structure is added + * to @type_vk{PipelineVertexInputDivisorStateCreateInfoEXT} which is + * then referenced from the `pNext` chain of + * @ref vkPipelineVertexInputStateCreateInfo(), with the following + * fields set: + * + * - `binding` + * - `divisor` + * + * + * + * @requires_vk_feature @ref DeviceFeature::VertexAttributeInstanceRateDivisor + * if @p divisor isn't `1` + * @requires_vk_feature @ref DeviceFeature::VertexAttributeInstanceRateZeroDivisor + * if @p divisor is `0` + */ + MeshLayout& addInstancedBinding(UnsignedInt binding, UnsignedInt stride, UnsignedInt divisor = 1); + + /** + * @brief Add an attribute + * @param location Attribute location, matching a shader input + * @param binding Binding index, corresponding to the @p binding + * parameter of one of the @ref addBinding() / + * @ref addInstancedBinding() calls. + * @param format Vertex format + * @param offset Attribute offset in bytes inside @p stride of + * given @p binding + * @return Reference to self (for method chaining) + * + * Adds a new @type_vk{VertexInputAttributeDescription} structure to + * @ref vkPipelineVertexInputStateCreateInfo() with the following + * fields set: + * + * - `location` + * - `binding` + * - `format` + * - `offset` + */ + MeshLayout& addAttribute(UnsignedInt location, UnsignedInt binding, VertexFormat format, UnsignedInt offset); + /** @overload */ + MeshLayout& addAttribute(UnsignedInt location, UnsignedInt binding, Magnum::VertexFormat format, UnsignedInt offset); + + /** @brief Underlying @type_vk{PipelineVertexInputStateCreateInfo} structure */ + VkPipelineVertexInputStateCreateInfo& vkPipelineVertexInputStateCreateInfo() { + return _vertexInfo; + } + /** @overload */ + const VkPipelineVertexInputStateCreateInfo& vkPipelineVertexInputStateCreateInfo() const { + return _vertexInfo; + } + /** @overload */ + operator const VkPipelineVertexInputStateCreateInfo*() const { + return &_vertexInfo; + } + + /** + * @brief Underlying @type_vk{PipelineInputAssemblyStateCreateInfo} structure + * + * If @ref addInstancedBinding() was called with @p divisor different + * than @cpp 0 @ce, the `pNext` chain contains the + * @type_vk{PipelineVertexInputDivisorStateCreateInfoEXT} structure. + */ + VkPipelineInputAssemblyStateCreateInfo& vkPipelineInputAssemblyStateCreateInfo() { + return _assemblyInfo; + } + /** @overload */ + const VkPipelineInputAssemblyStateCreateInfo& vkPipelineInputAssemblyStateCreateInfo() const { + return _assemblyInfo; + } + /** @overload */ + operator const VkPipelineInputAssemblyStateCreateInfo*() const { + return &_assemblyInfo; + } + + private: + VkPipelineVertexInputStateCreateInfo _vertexInfo; + VkPipelineInputAssemblyStateCreateInfo _assemblyInfo; + + struct State; + Containers::Pointer _state; +}; + +}} + +#endif diff --git a/src/Magnum/Vk/Test/CMakeLists.txt b/src/Magnum/Vk/Test/CMakeLists.txt index f3135d5c2..0e158a378 100644 --- a/src/Magnum/Vk/Test/CMakeLists.txt +++ b/src/Magnum/Vk/Test/CMakeLists.txt @@ -42,6 +42,7 @@ corrade_add_test(VkInstanceTest InstanceTest.cpp LIBRARIES MagnumVk) corrade_add_test(VkIntegrationTest IntegrationTest.cpp LIBRARIES MagnumVk) corrade_add_test(VkLayerPropertiesTest LayerPropertiesTest.cpp LIBRARIES MagnumVk) corrade_add_test(VkMemoryTest MemoryTest.cpp LIBRARIES MagnumVkTestLib) +corrade_add_test(VkMeshLayoutTest MeshLayoutTest.cpp LIBRARIES MagnumVkTestLib) corrade_add_test(VkPipelineTest PipelineTest.cpp LIBRARIES MagnumVk) corrade_add_test(VkPixelFormatTest PixelFormatTest.cpp LIBRARIES MagnumVkTestLib) corrade_add_test(VkQueueTest QueueTest.cpp LIBRARIES MagnumVk) @@ -137,6 +138,7 @@ set_target_properties( VkIntegrationTest VkLayerPropertiesTest VkMemoryTest + VkMeshLayoutTest VkPipelineTest VkPixelFormatTest VkQueueTest diff --git a/src/Magnum/Vk/Test/EnumsTest.cpp b/src/Magnum/Vk/Test/EnumsTest.cpp index 9b96fe889..39406e0bb 100644 --- a/src/Magnum/Vk/Test/EnumsTest.cpp +++ b/src/Magnum/Vk/Test/EnumsTest.cpp @@ -38,11 +38,6 @@ namespace Magnum { namespace Vk { namespace Test { namespace { struct EnumsTest: TestSuite::Tester { explicit EnumsTest(); - void mapVkPrimitiveTopology(); - void mapVkPrimitiveTopologyImplementationSpecific(); - void mapVkPrimitiveTopologyUnsupported(); - void mapVkPrimitiveTopologyInvalid(); - void mapVkIndexType(); void mapVkIndexTypeUnsupported(); void mapVkIndexTypeInvalid(); @@ -60,12 +55,7 @@ struct EnumsTest: TestSuite::Tester { }; EnumsTest::EnumsTest() { - addTests({&EnumsTest::mapVkPrimitiveTopology, - &EnumsTest::mapVkPrimitiveTopologyImplementationSpecific, - &EnumsTest::mapVkPrimitiveTopologyUnsupported, - &EnumsTest::mapVkPrimitiveTopologyInvalid, - - &EnumsTest::mapVkIndexType, + addTests({&EnumsTest::mapVkIndexType, &EnumsTest::mapVkIndexTypeUnsupported, &EnumsTest::mapVkIndexTypeInvalid, @@ -81,90 +71,6 @@ EnumsTest::EnumsTest() { &EnumsTest::mapVkSamplerAddressModeInvalid}); } -void EnumsTest::mapVkPrimitiveTopology() { - CORRADE_VERIFY(hasVkPrimitiveTopology(Magnum::MeshPrimitive::Points)); - CORRADE_COMPARE(vkPrimitiveTopology(Magnum::MeshPrimitive::Points), VK_PRIMITIVE_TOPOLOGY_POINT_LIST); - - CORRADE_VERIFY(hasVkPrimitiveTopology(Magnum::MeshPrimitive::Lines)); - CORRADE_COMPARE(vkPrimitiveTopology(Magnum::MeshPrimitive::Lines), VK_PRIMITIVE_TOPOLOGY_LINE_LIST); - - CORRADE_VERIFY(hasVkPrimitiveTopology(Magnum::MeshPrimitive::LineStrip)); - CORRADE_COMPARE(vkPrimitiveTopology(Magnum::MeshPrimitive::LineStrip), VK_PRIMITIVE_TOPOLOGY_LINE_STRIP); - - CORRADE_VERIFY(hasVkPrimitiveTopology(Magnum::MeshPrimitive::Triangles)); - CORRADE_COMPARE(vkPrimitiveTopology(Magnum::MeshPrimitive::Triangles), VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); - - CORRADE_VERIFY(hasVkPrimitiveTopology(Magnum::MeshPrimitive::TriangleStrip)); - CORRADE_COMPARE(vkPrimitiveTopology(Magnum::MeshPrimitive::TriangleStrip), VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP); - - CORRADE_VERIFY(hasVkPrimitiveTopology(Magnum::MeshPrimitive::TriangleFan)); - CORRADE_COMPARE(vkPrimitiveTopology(Magnum::MeshPrimitive::TriangleFan), VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN); - - /* Ensure all generic primitives are handled. This goes through the first - 16 bits, which should be enough. Going through 32 bits takes 8 seconds, - too much. */ - for(UnsignedInt i = 1; i <= 0xffff; ++i) { - const auto primitive = Magnum::MeshPrimitive(i); - #ifdef __GNUC__ - #pragma GCC diagnostic push - #pragma GCC diagnostic error "-Wswitch" - #endif - switch(primitive) { - #define _c(primitive) \ - case Magnum::MeshPrimitive::primitive: \ - if(hasVkPrimitiveTopology(Magnum::MeshPrimitive::primitive)) \ - CORRADE_VERIFY(UnsignedInt(vkPrimitiveTopology(Magnum::MeshPrimitive::primitive)) >= 0); \ - break; - #include "Magnum/Implementation/meshPrimitiveMapping.hpp" - #undef _c - } - #ifdef __GNUC__ - #pragma GCC diagnostic pop - #endif - } -} - -void EnumsTest::mapVkPrimitiveTopologyImplementationSpecific() { - CORRADE_VERIFY(hasVkPrimitiveTopology(meshPrimitiveWrap(VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY))); - CORRADE_COMPARE(vkPrimitiveTopology(meshPrimitiveWrap(VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY)), - VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY); -} - -void EnumsTest::mapVkPrimitiveTopologyUnsupported() { - #ifdef CORRADE_NO_ASSERT - CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); - #endif - - CORRADE_VERIFY(!hasVkPrimitiveTopology(Magnum::MeshPrimitive::LineLoop)); - - std::ostringstream out; - { - Error redirectError{&out}; - vkPrimitiveTopology(Magnum::MeshPrimitive::LineLoop); - } - CORRADE_COMPARE(out.str(), - "Vk::vkPrimitiveTopology(): unsupported primitive MeshPrimitive::LineLoop\n"); -} - -void EnumsTest::mapVkPrimitiveTopologyInvalid() { - #ifdef CORRADE_NO_ASSERT - CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); - #endif - - std::ostringstream out; - Error redirectError{&out}; - - hasVkPrimitiveTopology(Magnum::MeshPrimitive{}); - hasVkPrimitiveTopology(Magnum::MeshPrimitive(0x12)); - vkPrimitiveTopology(Magnum::MeshPrimitive{}); - vkPrimitiveTopology(Magnum::MeshPrimitive(0x12)); - CORRADE_COMPARE(out.str(), - "Vk::hasVkPrimitiveTopology(): invalid primitive MeshPrimitive(0x0)\n" - "Vk::hasVkPrimitiveTopology(): invalid primitive MeshPrimitive(0x12)\n" - "Vk::vkPrimitiveTopology(): invalid primitive MeshPrimitive(0x0)\n" - "Vk::vkPrimitiveTopology(): invalid primitive MeshPrimitive(0x12)\n"); -} - void EnumsTest::mapVkIndexType() { CORRADE_VERIFY(hasVkIndexType(Magnum::MeshIndexType::UnsignedShort)); CORRADE_COMPARE(vkIndexType(Magnum::MeshIndexType::UnsignedShort), VK_INDEX_TYPE_UINT16); diff --git a/src/Magnum/Vk/Test/MeshLayoutTest.cpp b/src/Magnum/Vk/Test/MeshLayoutTest.cpp new file mode 100644 index 000000000..3a3cc9acc --- /dev/null +++ b/src/Magnum/Vk/Test/MeshLayoutTest.cpp @@ -0,0 +1,352 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021 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 + +#include "Magnum/Mesh.h" +#include "Magnum/VertexFormat.h" +#include "Magnum/Vk/MeshLayout.h" +#include "Magnum/Vk/VertexFormat.h" + +namespace Magnum { namespace Vk { namespace Test { namespace { + +struct MeshLayoutTest: TestSuite::Tester { + explicit MeshLayoutTest(); + + void mapMeshPrimitive(); + void mapMeshPrimitiveImplementationSpecific(); + void mapMeshPrimitiveUnsupported(); + void mapMeshPrimitiveInvalid(); + + template void construct(); + void constructNoInit(); + void constructFromVk(); + void constructCopy(); + void constructMove(); + + void addBinding(); + void addInstancedBinding(); + void addInstancedBindingDivisor(); + template void addAttribute(); + + void debugMeshPrimitive(); +}; + +MeshLayoutTest::MeshLayoutTest() { + addTests({&MeshLayoutTest::mapMeshPrimitive, + &MeshLayoutTest::mapMeshPrimitiveImplementationSpecific, + &MeshLayoutTest::mapMeshPrimitiveUnsupported, + &MeshLayoutTest::mapMeshPrimitiveInvalid, + + &MeshLayoutTest::construct, + &MeshLayoutTest::construct, + &MeshLayoutTest::constructNoInit, + &MeshLayoutTest::constructFromVk, + &MeshLayoutTest::constructCopy, + &MeshLayoutTest::constructMove, + + &MeshLayoutTest::addBinding, + &MeshLayoutTest::addInstancedBinding, + &MeshLayoutTest::addInstancedBindingDivisor, + &MeshLayoutTest::addAttribute, + &MeshLayoutTest::addAttribute, + + &MeshLayoutTest::debugMeshPrimitive}); +} + +template struct VertexFormatTraits; +template<> struct VertexFormatTraits { + static const char* name() { return "VertexFormat"; } +}; +template<> struct VertexFormatTraits { + static const char* name() { return "Magnum::VertexFormat"; } +}; + +void MeshLayoutTest::mapMeshPrimitive() { + CORRADE_VERIFY(hasMeshPrimitive(Magnum::MeshPrimitive::Points)); + CORRADE_COMPARE(meshPrimitive(Magnum::MeshPrimitive::Points), MeshPrimitive::Points); + + CORRADE_VERIFY(hasMeshPrimitive(Magnum::MeshPrimitive::Lines)); + CORRADE_COMPARE(meshPrimitive(Magnum::MeshPrimitive::Lines), MeshPrimitive::Lines); + + CORRADE_VERIFY(hasMeshPrimitive(Magnum::MeshPrimitive::LineStrip)); + CORRADE_COMPARE(meshPrimitive(Magnum::MeshPrimitive::LineStrip), MeshPrimitive::LineStrip); + + CORRADE_VERIFY(hasMeshPrimitive(Magnum::MeshPrimitive::Triangles)); + CORRADE_COMPARE(meshPrimitive(Magnum::MeshPrimitive::Triangles), MeshPrimitive::Triangles); + + CORRADE_VERIFY(hasMeshPrimitive(Magnum::MeshPrimitive::TriangleStrip)); + CORRADE_COMPARE(meshPrimitive(Magnum::MeshPrimitive::TriangleStrip), MeshPrimitive::TriangleStrip); + + CORRADE_VERIFY(hasMeshPrimitive(Magnum::MeshPrimitive::TriangleFan)); + CORRADE_COMPARE(meshPrimitive(Magnum::MeshPrimitive::TriangleFan), MeshPrimitive::TriangleFan); + + /* Ensure all generic primitives are handled. This goes through the first + 16 bits, which should be enough. Going through 32 bits takes 8 seconds, + too much. */ + for(UnsignedInt i = 1; i <= 0xffff; ++i) { + const auto primitive = Magnum::MeshPrimitive(i); + #ifdef __GNUC__ + #pragma GCC diagnostic push + #pragma GCC diagnostic error "-Wswitch" + #endif + switch(primitive) { + #define _c(primitive) \ + case Magnum::MeshPrimitive::primitive: \ + if(hasMeshPrimitive(Magnum::MeshPrimitive::primitive)) \ + CORRADE_VERIFY(Int(meshPrimitive(Magnum::MeshPrimitive::primitive)) >= 0); \ + break; + #include "Magnum/Implementation/meshPrimitiveMapping.hpp" + #undef _c + } + #ifdef __GNUC__ + #pragma GCC diagnostic pop + #endif + } +} + +void MeshLayoutTest::mapMeshPrimitiveImplementationSpecific() { + CORRADE_VERIFY(hasMeshPrimitive(meshPrimitiveWrap(VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY))); + CORRADE_COMPARE(meshPrimitive(meshPrimitiveWrap(VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY)), + MeshPrimitive(VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY)); +} + +void MeshLayoutTest::mapMeshPrimitiveUnsupported() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + CORRADE_VERIFY(!hasMeshPrimitive(Magnum::MeshPrimitive::LineLoop)); + + std::ostringstream out; + { + Error redirectError{&out}; + meshPrimitive(Magnum::MeshPrimitive::LineLoop); + } + CORRADE_COMPARE(out.str(), + "Vk::meshPrimitive(): unsupported primitive MeshPrimitive::LineLoop\n"); +} + +void MeshLayoutTest::mapMeshPrimitiveInvalid() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + hasMeshPrimitive(Magnum::MeshPrimitive{}); + hasMeshPrimitive(Magnum::MeshPrimitive(0x12)); + meshPrimitive(Magnum::MeshPrimitive{}); + meshPrimitive(Magnum::MeshPrimitive(0x12)); + CORRADE_COMPARE(out.str(), + "Vk::hasMeshPrimitive(): invalid primitive MeshPrimitive(0x0)\n" + "Vk::hasMeshPrimitive(): invalid primitive MeshPrimitive(0x12)\n" + "Vk::meshPrimitive(): invalid primitive MeshPrimitive(0x0)\n" + "Vk::meshPrimitive(): invalid primitive MeshPrimitive(0x12)\n"); +} + +template void MeshLayoutTest::construct() { + setTestCaseTemplateName(std::is_same::value ? "MeshPrimitive" : "Magnum::MeshPrimitive"); + + MeshLayout layout{T::TriangleFan}; + CORRADE_COMPARE(layout.vkPipelineInputAssemblyStateCreateInfo().topology, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN); + CORRADE_COMPARE(layout.vkPipelineVertexInputStateCreateInfo().vertexBindingDescriptionCount, 0); + CORRADE_COMPARE(layout.vkPipelineVertexInputStateCreateInfo().vertexAttributeDescriptionCount, 0); + CORRADE_VERIFY(!layout.vkPipelineVertexInputStateCreateInfo().pVertexBindingDescriptions); + CORRADE_VERIFY(!layout.vkPipelineVertexInputStateCreateInfo().pVertexAttributeDescriptions); +} + +void MeshLayoutTest::constructNoInit() { + MeshLayout layout{NoInit}; + layout.vkPipelineVertexInputStateCreateInfo().sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2; + layout.vkPipelineInputAssemblyStateCreateInfo().sType = VK_STRUCTURE_TYPE_EXTERNAL_FENCE_PROPERTIES; + new(&layout) MeshLayout{NoInit}; + CORRADE_COMPARE(layout.vkPipelineVertexInputStateCreateInfo().sType, VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2); + CORRADE_COMPARE(layout.vkPipelineInputAssemblyStateCreateInfo().sType, VK_STRUCTURE_TYPE_EXTERNAL_FENCE_PROPERTIES); + + CORRADE_VERIFY((std::is_nothrow_constructible::value)); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!(std::is_convertible::value)); +} + +void MeshLayoutTest::constructFromVk() { + VkPipelineVertexInputStateCreateInfo vertexInfo; + vertexInfo.sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2; + VkPipelineInputAssemblyStateCreateInfo assemblyInfo; + assemblyInfo.sType = VK_STRUCTURE_TYPE_EXTERNAL_FENCE_PROPERTIES; + + MeshLayout layout{vertexInfo, assemblyInfo}; + CORRADE_COMPARE(layout.vkPipelineVertexInputStateCreateInfo().sType, VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2); + CORRADE_COMPARE(layout.vkPipelineInputAssemblyStateCreateInfo().sType, VK_STRUCTURE_TYPE_EXTERNAL_FENCE_PROPERTIES); +} + +void MeshLayoutTest::constructCopy() { + CORRADE_VERIFY(!std::is_copy_constructible{}); + CORRADE_VERIFY(!std::is_copy_assignable{}); +} + +void MeshLayoutTest::constructMove() { + MeshLayout a{MeshPrimitive::Patches}; + a.addInstancedBinding(3, 5, 555) + .addAttribute(15, 23, VertexFormat::UnsignedShort, 11); + + MeshLayout b = std::move(a); + CORRADE_VERIFY(!a.vkPipelineVertexInputStateCreateInfo().pNext); + CORRADE_COMPARE(a.vkPipelineVertexInputStateCreateInfo().vertexBindingDescriptionCount, 0); + CORRADE_VERIFY(!a.vkPipelineVertexInputStateCreateInfo().pVertexBindingDescriptions); + CORRADE_COMPARE(a.vkPipelineVertexInputStateCreateInfo().vertexAttributeDescriptionCount, 0); + CORRADE_VERIFY(!a.vkPipelineVertexInputStateCreateInfo().pVertexAttributeDescriptions); + CORRADE_VERIFY(b.vkPipelineVertexInputStateCreateInfo().pNext); + CORRADE_COMPARE(static_cast(b.vkPipelineVertexInputStateCreateInfo().pNext)->sType, VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_DIVISOR_STATE_CREATE_INFO_EXT); + CORRADE_COMPARE(b.vkPipelineVertexInputStateCreateInfo().vertexBindingDescriptionCount, 1); + CORRADE_VERIFY(b.vkPipelineVertexInputStateCreateInfo().pVertexBindingDescriptions); + CORRADE_COMPARE(b.vkPipelineVertexInputStateCreateInfo().pVertexBindingDescriptions[0].stride, 5); + CORRADE_COMPARE(b.vkPipelineVertexInputStateCreateInfo().vertexAttributeDescriptionCount, 1); + CORRADE_VERIFY(b.vkPipelineVertexInputStateCreateInfo().pVertexAttributeDescriptions); + CORRADE_COMPARE(b.vkPipelineVertexInputStateCreateInfo().pVertexAttributeDescriptions[0].format, VK_FORMAT_R16_UINT); + + MeshLayout c{{}, {}}; + c = std::move(b); + CORRADE_VERIFY(!b.vkPipelineVertexInputStateCreateInfo().pNext); + CORRADE_COMPARE(b.vkPipelineVertexInputStateCreateInfo().vertexBindingDescriptionCount, 0); + CORRADE_VERIFY(!b.vkPipelineVertexInputStateCreateInfo().pVertexBindingDescriptions); + CORRADE_COMPARE(b.vkPipelineVertexInputStateCreateInfo().vertexAttributeDescriptionCount, 0); + CORRADE_VERIFY(!b.vkPipelineVertexInputStateCreateInfo().pVertexAttributeDescriptions); + CORRADE_VERIFY(c.vkPipelineVertexInputStateCreateInfo().pNext); + CORRADE_COMPARE(static_cast(c.vkPipelineVertexInputStateCreateInfo().pNext)->sType, VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_DIVISOR_STATE_CREATE_INFO_EXT); + CORRADE_COMPARE(c.vkPipelineVertexInputStateCreateInfo().vertexBindingDescriptionCount, 1); + CORRADE_VERIFY(c.vkPipelineVertexInputStateCreateInfo().pVertexBindingDescriptions); + CORRADE_COMPARE(c.vkPipelineVertexInputStateCreateInfo().pVertexBindingDescriptions[0].stride, 5); + CORRADE_COMPARE(c.vkPipelineVertexInputStateCreateInfo().vertexAttributeDescriptionCount, 1); + CORRADE_VERIFY(c.vkPipelineVertexInputStateCreateInfo().pVertexAttributeDescriptions); + CORRADE_COMPARE(c.vkPipelineVertexInputStateCreateInfo().pVertexAttributeDescriptions[0].format, VK_FORMAT_R16_UINT); + + CORRADE_VERIFY(std::is_nothrow_move_constructible::value); + CORRADE_VERIFY(std::is_nothrow_move_assignable::value); +} + +void MeshLayoutTest::addBinding() { + MeshLayout layout{MeshPrimitive::Triangles}; + layout.addBinding(35, 2) + .addBinding(36, 17); + + CORRADE_COMPARE(layout.vkPipelineVertexInputStateCreateInfo().vertexBindingDescriptionCount, 2); + CORRADE_VERIFY(layout.vkPipelineVertexInputStateCreateInfo().pVertexBindingDescriptions); + CORRADE_COMPARE(layout.vkPipelineVertexInputStateCreateInfo().pVertexBindingDescriptions[0].binding, 35); + CORRADE_COMPARE(layout.vkPipelineVertexInputStateCreateInfo().pVertexBindingDescriptions[0].stride, 2); + CORRADE_COMPARE(layout.vkPipelineVertexInputStateCreateInfo().pVertexBindingDescriptions[0].inputRate, VK_VERTEX_INPUT_RATE_VERTEX); + CORRADE_COMPARE(layout.vkPipelineVertexInputStateCreateInfo().pVertexBindingDescriptions[1].binding, 36); + CORRADE_COMPARE(layout.vkPipelineVertexInputStateCreateInfo().pVertexBindingDescriptions[1].stride, 17); + CORRADE_COMPARE(layout.vkPipelineVertexInputStateCreateInfo().pVertexBindingDescriptions[1].inputRate, VK_VERTEX_INPUT_RATE_VERTEX); +} + +void MeshLayoutTest::addInstancedBinding() { + MeshLayout layout{MeshPrimitive::Triangles}; + layout.addInstancedBinding(35, 17) + .addBinding(36, 2); + + CORRADE_COMPARE(layout.vkPipelineVertexInputStateCreateInfo().vertexBindingDescriptionCount, 2); + CORRADE_VERIFY(layout.vkPipelineVertexInputStateCreateInfo().pVertexBindingDescriptions); + CORRADE_COMPARE(layout.vkPipelineVertexInputStateCreateInfo().pVertexBindingDescriptions[0].binding, 35); + CORRADE_COMPARE(layout.vkPipelineVertexInputStateCreateInfo().pVertexBindingDescriptions[0].stride, 17); + CORRADE_COMPARE(layout.vkPipelineVertexInputStateCreateInfo().pVertexBindingDescriptions[0].inputRate, VK_VERTEX_INPUT_RATE_INSTANCE); + CORRADE_COMPARE(layout.vkPipelineVertexInputStateCreateInfo().pVertexBindingDescriptions[1].binding, 36); + CORRADE_COMPARE(layout.vkPipelineVertexInputStateCreateInfo().pVertexBindingDescriptions[1].stride, 2); + CORRADE_COMPARE(layout.vkPipelineVertexInputStateCreateInfo().pVertexBindingDescriptions[1].inputRate, VK_VERTEX_INPUT_RATE_VERTEX); +} + +void MeshLayoutTest::addInstancedBindingDivisor() { + MeshLayout layout{MeshPrimitive::Triangles}; + + /* Set the pNext pointer to something to verify it's preserved */ + VkPhysicalDeviceVariablePointersFeatures variableFeatures{}; + layout.vkPipelineVertexInputStateCreateInfo().pNext = &variableFeatures; + + layout.addBinding(35, 2) + .addInstancedBinding(36, 17, 555) + .addInstancedBinding(37, 22, 0); + + CORRADE_COMPARE(layout.vkPipelineVertexInputStateCreateInfo().vertexBindingDescriptionCount, 3); + CORRADE_VERIFY(layout.vkPipelineVertexInputStateCreateInfo().pVertexBindingDescriptions); + CORRADE_COMPARE(layout.vkPipelineVertexInputStateCreateInfo().pVertexBindingDescriptions[0].binding, 35); + CORRADE_COMPARE(layout.vkPipelineVertexInputStateCreateInfo().pVertexBindingDescriptions[0].stride, 2); + CORRADE_COMPARE(layout.vkPipelineVertexInputStateCreateInfo().pVertexBindingDescriptions[0].inputRate, VK_VERTEX_INPUT_RATE_VERTEX); + CORRADE_COMPARE(layout.vkPipelineVertexInputStateCreateInfo().pVertexBindingDescriptions[1].binding, 36); + CORRADE_COMPARE(layout.vkPipelineVertexInputStateCreateInfo().pVertexBindingDescriptions[1].stride, 17); + CORRADE_COMPARE(layout.vkPipelineVertexInputStateCreateInfo().pVertexBindingDescriptions[1].inputRate, VK_VERTEX_INPUT_RATE_INSTANCE); + CORRADE_COMPARE(layout.vkPipelineVertexInputStateCreateInfo().pVertexBindingDescriptions[2].binding, 37); + CORRADE_COMPARE(layout.vkPipelineVertexInputStateCreateInfo().pVertexBindingDescriptions[2].stride, 22); + CORRADE_COMPARE(layout.vkPipelineVertexInputStateCreateInfo().pVertexBindingDescriptions[2].inputRate, VK_VERTEX_INPUT_RATE_INSTANCE); + + /* Verify the extra structure is connected properly */ + CORRADE_VERIFY(layout.vkPipelineVertexInputStateCreateInfo().pNext); + auto& vertexDivisorInfo = *static_cast(layout.vkPipelineVertexInputStateCreateInfo().pNext); + CORRADE_VERIFY(&vertexDivisorInfo != static_cast(&variableFeatures)); + CORRADE_COMPARE(vertexDivisorInfo.sType, VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_DIVISOR_STATE_CREATE_INFO_EXT); + /* The original chain should be preserved */ + CORRADE_COMPARE(vertexDivisorInfo.pNext, &variableFeatures); + + CORRADE_COMPARE(vertexDivisorInfo.vertexBindingDivisorCount, 2); + CORRADE_VERIFY(vertexDivisorInfo.pVertexBindingDivisors); + CORRADE_COMPARE(vertexDivisorInfo.pVertexBindingDivisors[0].binding, 36); + CORRADE_COMPARE(vertexDivisorInfo.pVertexBindingDivisors[0].divisor, 555); + CORRADE_COMPARE(vertexDivisorInfo.pVertexBindingDivisors[1].binding, 37); + CORRADE_COMPARE(vertexDivisorInfo.pVertexBindingDivisors[1].divisor, 0); +} + +template void MeshLayoutTest::addAttribute() { + setTestCaseTemplateName(VertexFormatTraits::name()); + + MeshLayout layout{MeshPrimitive::Triangles}; + layout.addAttribute(1, 35, T::Vector2ui, 17) + .addAttribute(2, 36, T::Double, 22); + + CORRADE_COMPARE(layout.vkPipelineVertexInputStateCreateInfo().vertexAttributeDescriptionCount, 2); + CORRADE_VERIFY(layout.vkPipelineVertexInputStateCreateInfo().pVertexAttributeDescriptions); + CORRADE_COMPARE(layout.vkPipelineVertexInputStateCreateInfo().pVertexAttributeDescriptions[0].location, 1); + CORRADE_COMPARE(layout.vkPipelineVertexInputStateCreateInfo().pVertexAttributeDescriptions[0].binding, 35); + CORRADE_COMPARE(layout.vkPipelineVertexInputStateCreateInfo().pVertexAttributeDescriptions[0].format, VK_FORMAT_R32G32_UINT); + CORRADE_COMPARE(layout.vkPipelineVertexInputStateCreateInfo().pVertexAttributeDescriptions[0].offset, 17); + CORRADE_COMPARE(layout.vkPipelineVertexInputStateCreateInfo().pVertexAttributeDescriptions[1].location, 2); + CORRADE_COMPARE(layout.vkPipelineVertexInputStateCreateInfo().pVertexAttributeDescriptions[1].binding, 36); + CORRADE_COMPARE(layout.vkPipelineVertexInputStateCreateInfo().pVertexAttributeDescriptions[1].format, VK_FORMAT_R64_SFLOAT); + CORRADE_COMPARE(layout.vkPipelineVertexInputStateCreateInfo().pVertexAttributeDescriptions[1].offset, 22); +} + +void MeshLayoutTest::debugMeshPrimitive() { + std::ostringstream out; + Debug{&out} << MeshPrimitive::TriangleFan << MeshPrimitive(-10007655); + CORRADE_COMPARE(out.str(), "Vk::MeshPrimitive::TriangleFan Vk::MeshPrimitive(-10007655)\n"); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::Vk::Test::MeshLayoutTest) diff --git a/src/Magnum/Vk/Vk.h b/src/Magnum/Vk/Vk.h index 7e0a9a481..5657b3b34 100644 --- a/src/Magnum/Vk/Vk.h +++ b/src/Magnum/Vk/Vk.h @@ -94,6 +94,8 @@ enum class MemoryFlag: UnsignedInt; typedef Containers::EnumSet MemoryFlags; enum class MemoryHeapFlag: UnsignedInt; typedef Containers::EnumSet MemoryHeapFlags; +class MeshLayout; +enum class MeshPrimitive: Int; enum class PipelineStage: UnsignedInt; typedef Containers::EnumSet PipelineStages; enum class PixelFormat: Int;