From d4ab9f4cb09052b7bdc0bedd376666ad522a49a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 9 Feb 2021 00:08:03 +0100 Subject: [PATCH] Vk: implemented a Mesh wrapper. Together with: * CommandBuffer::draw() * Support for indexed and non-indexed meshes * Support for setting primitive and stride dynamically I took one shortcut and vkCmdBindVertexBuffers() is currently called once for each binding. The interface is ready for this, but I'm not yet 100% sure how to test that it actually does batch the buffers, so it's left at the lazy implementation for now. --- doc/changelog.dox | 5 + doc/snippets/MagnumVk.cpp | 150 ++- doc/vulkan-mapping.dox | 11 +- doc/vulkan-support.dox | 2 +- package/ci/unix-desktop-vulkan.sh | 2 +- src/Magnum/Mesh.h | 13 +- src/Magnum/Vk/CMakeLists.txt | 2 + src/Magnum/Vk/CommandBuffer.h | 13 + src/Magnum/Vk/DeviceFeatures.h | 2 +- src/Magnum/Vk/Enums.cpp | 24 +- src/Magnum/Vk/Enums.h | 32 +- src/Magnum/Vk/Implementation/DeviceState.cpp | 6 + src/Magnum/Vk/Implementation/DeviceState.h | 2 + src/Magnum/Vk/Mesh.cpp | 250 +++++ src/Magnum/Vk/Mesh.h | 406 ++++++++ src/Magnum/Vk/MeshLayout.h | 14 +- .../Vk/RasterizationPipelineCreateInfo.h | 9 +- src/Magnum/Vk/Test/CMakeLists.txt | 49 +- src/Magnum/Vk/Test/EnumsTest.cpp | 78 +- src/Magnum/Vk/Test/MeshTest.cpp | 278 ++++++ src/Magnum/Vk/Test/MeshTestFiles/convert.sh | 5 + src/Magnum/Vk/Test/MeshTestFiles/flat.spv | Bin 0 -> 444 bytes src/Magnum/Vk/Test/MeshTestFiles/flat.spvasm | 35 + src/Magnum/Vk/Test/MeshTestFiles/flat.tga | Bin 0 -> 338 bytes src/Magnum/Vk/Test/MeshTestFiles/noop.spv | Bin 0 -> 360 bytes src/Magnum/Vk/Test/MeshTestFiles/noop.spvasm | 30 + src/Magnum/Vk/Test/MeshTestFiles/noop.tga | Bin 0 -> 178 bytes .../Vk/Test/MeshTestFiles/nullcolor.tga | Bin 0 -> 338 bytes .../Vk/Test/MeshTestFiles/vertexcolor.spv | Bin 0 -> 536 bytes .../Vk/Test/MeshTestFiles/vertexcolor.spvasm | 40 + .../Vk/Test/MeshTestFiles/vertexcolor.tga | Bin 0 -> 1362 bytes src/Magnum/Vk/Test/MeshVkTest.cpp | 878 ++++++++++++++++++ src/Magnum/Vk/Test/configure.h.cmake | 2 + src/Magnum/Vk/VertexFormat.h | 1 + src/Magnum/Vk/Vk.h | 2 + 35 files changed, 2182 insertions(+), 159 deletions(-) create mode 100644 src/Magnum/Vk/Mesh.cpp create mode 100644 src/Magnum/Vk/Mesh.h create mode 100644 src/Magnum/Vk/Test/MeshTest.cpp create mode 100755 src/Magnum/Vk/Test/MeshTestFiles/convert.sh create mode 100644 src/Magnum/Vk/Test/MeshTestFiles/flat.spv create mode 100644 src/Magnum/Vk/Test/MeshTestFiles/flat.spvasm create mode 100644 src/Magnum/Vk/Test/MeshTestFiles/flat.tga create mode 100644 src/Magnum/Vk/Test/MeshTestFiles/noop.spv create mode 100644 src/Magnum/Vk/Test/MeshTestFiles/noop.spvasm create mode 100644 src/Magnum/Vk/Test/MeshTestFiles/noop.tga create mode 100644 src/Magnum/Vk/Test/MeshTestFiles/nullcolor.tga create mode 100644 src/Magnum/Vk/Test/MeshTestFiles/vertexcolor.spv create mode 100644 src/Magnum/Vk/Test/MeshTestFiles/vertexcolor.spvasm create mode 100644 src/Magnum/Vk/Test/MeshTestFiles/vertexcolor.tga create mode 100644 src/Magnum/Vk/Test/MeshVkTest.cpp diff --git a/doc/changelog.dox b/doc/changelog.dox index b6bc3b172..b5249d819 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -378,6 +378,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::hasVkIndexType() @ce and @cpp Vk::vkIndexType() @ce returning a + raw @type_vk{IndexType} are deprecated in favor of + @ref Vk::meshIndexType() that returns the new @ref Vk::MeshIndexType enum. + Since all generic index types are available in Vulkan now, there's no need + for a @cpp hasMeshIndexType() @ce anymore. - @cpp Vk::hasVkPrimitiveTopology() @ce and @cpp Vk::vkPrimitiveTopology() @ce returning a raw @type_vk{PrimitiveToplogy} are deprecated in favor of diff --git a/doc/snippets/MagnumVk.cpp b/doc/snippets/MagnumVk.cpp index c6a7e811b..414ad47f5 100644 --- a/doc/snippets/MagnumVk.cpp +++ b/doc/snippets/MagnumVk.cpp @@ -51,7 +51,7 @@ #include "Magnum/Vk/ImageViewCreateInfo.h" #include "Magnum/Vk/LayerProperties.h" #include "Magnum/Vk/MemoryAllocateInfo.h" -#include "Magnum/Vk/MeshLayout.h" +#include "Magnum/Vk/Mesh.h" #include "Magnum/Vk/Pipeline.h" #include "Magnum/Vk/PipelineLayout.h" #include "Magnum/Vk/PixelFormat.h" @@ -774,25 +774,153 @@ indices.bindMemory(memory, indicesOffset); { /* [MeshLayout-usage] */ -constexpr UnsignedInt BufferBinding = 0; +constexpr UnsignedInt Binding = 0; constexpr UnsignedInt PositionLocation = 0; -constexpr UnsignedInt TextureCoordinateLocation = 1; +constexpr UnsignedInt TextureLocation = 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)); + .addBinding(Binding, 8*sizeof(Float)) + .addAttribute(PositionLocation, Binding, VertexFormat::Vector3, 0) + .addAttribute(TextureLocation, Binding, VertexFormat::Vector2, 3*sizeof(Float)) + .addAttribute(NormalLocation, Binding, VertexFormat::Vector3, 5*sizeof(Float)); /* [MeshLayout-usage] */ } +{ +constexpr UnsignedInt Binding = 0; +constexpr UnsignedInt PositionLocation = 0; +constexpr UnsignedInt TextureLocation = 1; +constexpr UnsignedInt NormalLocation = 5; +UnsignedInt vertexCount = 35, indexCount = 48; +Vk::Device device{NoCreate}; +/* [Mesh-populating] */ +Vk::MeshLayout meshLayout{MeshPrimitive::Triangles}; +meshLayout + .addBinding(Binding, 8*sizeof(Float)) + .addAttribute(PositionLocation, Binding, VertexFormat::Vector3, 0) + .addAttribute(TextureLocation, Binding, VertexFormat::Vector2, 3*sizeof(Float)) + .addAttribute(NormalLocation, Binding, VertexFormat::Vector3, 5*sizeof(Float)); + +Vk::Buffer vertices{DOXYGEN_IGNORE(device), Vk::BufferCreateInfo{ + Vk::BufferUsage::VertexBuffer, vertexCount*8*sizeof(Float) +}, DOXYGEN_IGNORE(NoAllocate)}; + +DOXYGEN_IGNORE() + +Vk::Mesh mesh{meshLayout}; +mesh.addVertexBuffer(Binding, vertices, 0) + .setCount(vertexCount); +/* [Mesh-populating] */ + +/* [Mesh-populating-indexed] */ +Vk::Buffer indices{DOXYGEN_IGNORE(device), Vk::BufferCreateInfo{ + Vk::BufferUsage::IndexBuffer, indexCount*sizeof(UnsignedShort) +}, DOXYGEN_IGNORE(NoAllocate)}; + +DOXYGEN_IGNORE() + +mesh.setIndexBuffer(indices, 0, MeshIndexType::UnsignedShort) + .setCount(indexCount); +/* [Mesh-populating-indexed] */ +} + +{ +Vk::Device device{NoCreate}; +/* [Mesh-populating-owned] */ +Vk::Buffer buffer{DOXYGEN_IGNORE(device), Vk::BufferCreateInfo{ + Vk::BufferUsage::VertexBuffer|Vk::BufferUsage::IndexBuffer, DOXYGEN_IGNORE(0) +}, DOXYGEN_IGNORE(NoAllocate)}; + +DOXYGEN_IGNORE() + +Vk::Mesh mesh{Vk::MeshLayout{MeshPrimitive::Triangles} + .addBinding(DOXYGEN_IGNORE(0, 0)) + DOXYGEN_IGNORE() +}; +mesh.addVertexBuffer(DOXYGEN_IGNORE(0), buffer, DOXYGEN_IGNORE(0)) + .setIndexBuffer(std::move(buffer), DOXYGEN_IGNORE(0, MeshIndexType{})) + .setCount(DOXYGEN_IGNORE(0)); +/* [Mesh-populating-owned] */ +} + +{ +Vk::Device device{NoCreate}; +Vk::CommandBuffer cmd{NoCreate}; +Vk::ShaderSet shaderSet; +Vk::PipelineLayout pipelineLayout{NoCreate}; +Vk::RenderPass renderPass{NoCreate}; +/* [Mesh-drawing] */ +Vk::Mesh mesh{DOXYGEN_IGNORE(Vk::MeshLayout{MeshPrimitive{}})}; + +Vk::Pipeline pipeline{DOXYGEN_IGNORE(device), Vk::RasterizationPipelineCreateInfo{ + DOXYGEN_IGNORE(shaderSet), mesh.layout(), DOXYGEN_IGNORE(pipelineLayout, renderPass, 0, 1) + }DOXYGEN_IGNORE() +}; + +DOXYGEN_IGNORE() + +cmd.bindPipeline(pipeline) + .draw(mesh); +/* [Mesh-drawing] */ +} + +{ +Vk::Device device{NoCreate}; +Vk::CommandBuffer cmd{NoCreate}; +Vk::ShaderSet shaderSet; +Vk::PipelineLayout pipelineLayout{NoCreate}; +Vk::RenderPass renderPass{NoCreate}; +constexpr UnsignedInt PositionLocation = 0; +constexpr UnsignedInt TextureLocation = 1; +constexpr UnsignedInt NormalLocation = 5; +/* [Mesh-drawing-dynamic] */ +/* Use zero stride and zero offsets, as the stride gets specified dynamically + and offsets specified in concrete buffer bindings instead */ +Vk::MeshLayout dynamicMeshLayout{MeshPrimitive::Triangles}; +dynamicMeshLayout + .addBinding(0, 0) + .addBinding(1, 0) + .addBinding(2, 0) + .addAttribute(PositionLocation, 0, VertexFormat::Vector3, 0) + .addAttribute(TextureLocation, 1, VertexFormat::Vector2, 0) + .addAttribute(NormalLocation, 2, VertexFormat::Vector3, 0); + +Vk::Pipeline pipeline{DOXYGEN_IGNORE(device), Vk::RasterizationPipelineCreateInfo{ + DOXYGEN_IGNORE(shaderSet), dynamicMeshLayout, DOXYGEN_IGNORE(pipelineLayout, renderPass, 0, 1)} + /* Enable dynamic primitive and stride */ + .setDynamicStates(Vk::DynamicRasterizationState::MeshPrimitive| + Vk::DynamicRasterizationState::VertexInputBindingStride) + DOXYGEN_IGNORE() +}; + +Vk::Buffer vertices{DOXYGEN_IGNORE(NoCreate)}; + +Vk::Mesh mesh{Vk::MeshLayout{MeshPrimitive::Triangles} /* Or TriangleStrip etc */ + /* Concrete stride */ + .addBinding(0, 8*sizeof(Float)) + .addBinding(1, 8*sizeof(Float)) + .addBinding(2, 8*sizeof(Float)) + /* Rest the same as in the dynamicMeshLayout */ + .addAttribute(PositionLocation, 0, VertexFormat::Vector3, 0) + .addAttribute(TextureLocation, 1, VertexFormat::Vector2, 0) + .addAttribute(NormalLocation, 2, VertexFormat::Vector3, 0) +}; + +/* Bind the same buffer to three different bindings, with concrete offsets */ +mesh.addVertexBuffer(0, vertices, 0) + .addVertexBuffer(1, vertices, 3*sizeof(Float)) + .addVertexBuffer(2, vertices, 5*sizeof(Float)) + .setCount(DOXYGEN_IGNORE(0)); + +cmd.bindPipeline(pipeline) + /* Updates the dynamic primitive and stride as needed by the mesh */ + .draw(mesh); +/* [Mesh-drawing-dynamic] */ +} + { 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 d200e5177..43d5db142 100644 --- a/doc/vulkan-mapping.dox +++ b/doc/vulkan-mapping.dox @@ -113,9 +113,9 @@ Vulkan function | Matching API @fn_vk{CmdBeginDebugUtilsLabelEXT} @m_class{m-label m-flat m-warning} **EXT**, \n @fn_vk{CmdEndDebugUtilsLabelEXT} @m_class{m-label m-flat m-warning} **EXT** | | @fn_vk{CmdBeginRenderPass}, \n @fn_vk{CmdBeginRenderPass2} @m_class{m-label m-flat m-success} **KHR, 1.2**, \n @fn_vk{CmdNextSubpass}, \n @fn_vk{CmdNextSubpass2} @m_class{m-label m-flat m-success} **KHR, 1.2**, \n @fn_vk{CmdEndRenderpass}, \n @fn_vk{CmdEndRenderpass2} @m_class{m-label m-flat m-success} **KHR, 1.2** | @ref CommandBuffer::beginRenderPass(), \n @ref CommandBuffer::nextSubpass(), \n @ref CommandBuffer::endRenderPass() @fn_vk{CmdBindDescriptorSets} | | -@fn_vk{CmdBindIndexBuffer} | | +@fn_vk{CmdBindIndexBuffer} | internal to @ref CommandBuffer::draw() @fn_vk{CmdBindPipeline} | @ref CommandBuffer::bindPipeline() -@fn_vk{CmdBindVertexBuffers}, \n @fn_vk{CmdBindVertexBuffers2EXT} @m_class{m-label m-flat m-warning} **EXT** | | +@fn_vk{CmdBindVertexBuffers}, \n @fn_vk{CmdBindVertexBuffers2EXT} @m_class{m-label m-flat m-warning} **EXT** | internal to @ref CommandBuffer::draw() @fn_vk{CmdBlitImage}, \n @fn_vk{CmdBlitImage2KHR} @m_class{m-label m-flat m-warning} **KHR** | | @fn_vk{CmdBuildAccelerationStructuresIndirectKHR} @m_class{m-label m-flat m-warning} **KHR** | | @fn_vk{CmdBuildAccelerationStructuresKHR} @m_class{m-label m-flat m-warning} **KHR** | | @@ -135,8 +135,7 @@ Vulkan function | Matching API @fn_vk{CmdDispatch} | | @fn_vk{CmdDispatchBase} @m_class{m-label m-flat m-success} **KHR, 1.1** | | @fn_vk{CmdDispatchIndirect} | | -@fn_vk{CmdDraw} | | -@fn_vk{CmdDrawIndexed} | | +@fn_vk{CmdDraw}, \n @fn_vk{CmdDrawIndexed} | @ref CommandBuffer::draw() @fn_vk{CmdDrawIndexedIndirect} | | @fn_vk{CmdDrawIndexedIndirectCount} @m_class{m-label m-flat m-success} **KHR, 1.2** | | @fn_vk{CmdDrawIndirect} | | @@ -161,7 +160,7 @@ Vulkan function | Matching API @fn_vk{CmdSetEvent} | | @fn_vk{CmdSetFrontFaceEXT} @m_class{m-label m-flat m-warning} **EXT** | | @fn_vk{CmdSetLineWidth} | | -@fn_vk{CmdSetPrimitiveTopologyEXT} @m_class{m-label m-flat m-warning} **EXT** | | +@fn_vk{CmdSetPrimitiveTopologyEXT} @m_class{m-label m-flat m-warning} **EXT** | internal to @ref CommandBuffer::draw() @fn_vk{CmdSetRayTracingPipelineStackSizeKHR} @m_class{m-label m-flat m-warning} **KHR** | | @fn_vk{CmdSetScissor} | | @fn_vk{CmdSetScissorWithCountEXT} @m_class{m-label m-flat m-warning} **EXT** | | @@ -881,7 +880,7 @@ Vulkan enum | Matching API @type_vk{ImageType} | not exposed, internal to @ref ImageCreateInfo subclasses @type_vk{ImageUsageFlagBits}, \n @type_vk{ImageUsageFlags} | @ref ImageUsage, \n @ref ImageUsages @type_vk{ImageViewType} | not exposed, internal to @ref ImageViewCreateInfo subclasses -@type_vk{IndexType} | only @ref vkIndexType() +@type_vk{IndexType} | @ref MeshIndexType @type_vk{InternalAllocationType} | @ref vulkan-wrapping-host-allocation "not exposed" @subsection vulkan-mapping-enums-l L diff --git a/doc/vulkan-support.dox b/doc/vulkan-support.dox index 0f2defdf7..9791802f3 100644 --- a/doc/vulkan-support.dox +++ b/doc/vulkan-support.dox @@ -122,7 +122,7 @@ Extension | Status @vk_extension{EXT,validation_features} @m_class{m-label m-info} **instance** | | @vk_extension{EXT,vertex_attribute_divisor} | done @vk_extension{EXT,index_type_uint8} | @ref Vk::vkIndexType() only -@vk_extension{EXT,extended_dynamic_state} | | +@vk_extension{EXT,extended_dynamic_state} | only dynamic primitive and stride @vk_extension{EXT,robustness2} | done except properties @vk_extension{EXT,image_robustness} | done @vk_extension{KHR,acceleration_structure} | | diff --git a/package/ci/unix-desktop-vulkan.sh b/package/ci/unix-desktop-vulkan.sh index 8747b9dd3..c15d48445 100755 --- a/package/ci/unix-desktop-vulkan.sh +++ b/package/ci/unix-desktop-vulkan.sh @@ -32,7 +32,7 @@ cmake .. \ -DCMAKE_INSTALL_PREFIX=$HOME/deps \ -DCMAKE_BUILD_TYPE=Debug \ -DWITH_AUDIO=OFF \ - -DWITH_DEBUGTOOLS=OFF \ + -DWITH_DEBUGTOOLS=ON \ -DWITH_GL=OFF \ -DWITH_MESHTOOLS=OFF \ -DWITH_PRIMITIVES=OFF \ diff --git a/src/Magnum/Mesh.h b/src/Magnum/Mesh.h index 620f914fb..36b4b9351 100644 --- a/src/Magnum/Mesh.h +++ b/src/Magnum/Mesh.h @@ -240,10 +240,9 @@ In case of OpenGL, corresponds to @ref GL::MeshIndexType and is convertible to it using @ref GL::meshIndexType(). See documentation of each value for more information about the mapping. -In case of Vulkan, corresponds to @type_vk_keyword{IndexType} and is -convertible to it using @ref Vk::vkIndexType(). See documentation of each value -for more information about the mapping. Note that not every type is available -there, use @ref Vk::hasVkIndexType() to check for its presence. +In case of Vulkan, corresponds to @ref Vk::MeshIndexType and is convertible to +it using @ref Vk::meshIndexType(). See documentation of each value for more +information about the mapping. @see @ref meshIndexTypeSize() */ enum class MeshIndexType: UnsignedByte { @@ -253,7 +252,7 @@ enum class MeshIndexType: UnsignedByte { * Unsigned byte * * Corresponds to @ref GL::MeshIndexType::UnsignedByte / - * @val_vk_keyword{INDEX_TYPE_UINT8_EXT,IndexType}. Note that using this + * @ref Vk::MeshIndexTyúe::UnsignedByte. Note that using this * type is discouraged, at least AMD GPUs are known to suggest (via debug * output) using 16-byte types instead for better efficiency. */ @@ -263,7 +262,7 @@ enum class MeshIndexType: UnsignedByte { * Unsigned short * * Corresponds to @ref GL::MeshIndexType::UnsignedShort / - * @val_vk_keyword{INDEX_TYPE_UINT16,IndexType}. + * @ref Vk::MeshIndexType::UnsignedShort. */ UnsignedShort, @@ -271,7 +270,7 @@ enum class MeshIndexType: UnsignedByte { * Unsigned int * * Corresponds to @ref GL::MeshIndexType::UnsignedInt / - * @val_vk_keyword{INDEX_TYPE_UINT32,IndexType}. + * @ref Vk::MeshIndexType::UnsignedInt. */ UnsignedInt }; diff --git a/src/Magnum/Vk/CMakeLists.txt b/src/Magnum/Vk/CMakeLists.txt index b5f3c79d3..5b6701875 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 + Mesh.cpp MeshLayout.cpp Memory.cpp Pipeline.cpp @@ -93,6 +94,7 @@ set(MagnumVk_HEADERS LayerProperties.h Memory.h MemoryAllocateInfo.h + Mesh.h MeshLayout.h Pipeline.h PipelineLayout.h diff --git a/src/Magnum/Vk/CommandBuffer.h b/src/Magnum/Vk/CommandBuffer.h index 2ebd3345a..0fb488259 100644 --- a/src/Magnum/Vk/CommandBuffer.h +++ b/src/Magnum/Vk/CommandBuffer.h @@ -381,6 +381,16 @@ class MAGNUM_VK_EXPORT CommandBuffer { */ CommandBuffer& bindPipeline(Pipeline& pipeline); + /** + * @brief Draw a mesh + * @return Reference to self (for method chaining) + * + * Can be only called inside a render pass with a graphics pipeline + * bound. See @ref Vk-Mesh-drawing for a usage example. + * @see @fn_vk_keyword{CmdDraw}, @fn_vk_keyword{CmdDrawIndexed} + */ + CommandBuffer& draw(Mesh& mesh); + /** * @brief Insert an execution barrier with optional memory dependencies * @param sourceStages Source stages. Has to contain at least @@ -785,6 +795,9 @@ class MAGNUM_VK_EXPORT CommandBuffer { MAGNUM_VK_LOCAL static void endRenderPassImplementationKHR(CommandBuffer& self, const VkSubpassEndInfo& endInfo); MAGNUM_VK_LOCAL static void endRenderPassImplementation12(CommandBuffer& self, const VkSubpassEndInfo& endInfo); + MAGNUM_VK_LOCAL static void bindVertexBuffersImplementationDefault(CommandBuffer& self, UnsignedInt firstBinding, UnsignedInt bindingCount, const VkBuffer* buffers, const UnsignedLong* offsets, const UnsignedLong* strides); + MAGNUM_VK_LOCAL static void bindVertexBuffersImplementationEXT(CommandBuffer& self, UnsignedInt firstBinding, UnsignedInt bindingCount, const VkBuffer* buffers, const UnsignedLong* offsets, const UnsignedLong* strides); + MAGNUM_VK_LOCAL static void copyBufferImplementationDefault(CommandBuffer& self, const CopyBufferInfo& info); MAGNUM_VK_LOCAL static void copyBufferImplementationKHR(CommandBuffer& self, const CopyBufferInfo& info); diff --git a/src/Magnum/Vk/DeviceFeatures.h b/src/Magnum/Vk/DeviceFeatures.h index ca6d4bc00..1d5d4c34c 100644 --- a/src/Magnum/Vk/DeviceFeatures.h +++ b/src/Magnum/Vk/DeviceFeatures.h @@ -70,7 +70,7 @@ enum class DeviceFeature: UnsignedShort { /** * Whether the full 32-bit range is supported for indexed draw calls when - * using @val_vk{INDEX_TYPE_UINT32,IndexType}. + * using @ref MeshIndexType::UnsignedInt. * @see @ref DeviceFeature::IndexTypeUnsignedByte * @todo expose the `maxDrawIndexedIndexValue` limit */ diff --git a/src/Magnum/Vk/Enums.cpp b/src/Magnum/Vk/Enums.cpp index 805c20469..443873d07 100644 --- a/src/Magnum/Vk/Enums.cpp +++ b/src/Magnum/Vk/Enums.cpp @@ -32,6 +32,7 @@ #ifdef MAGNUM_BUILD_DEPRECATED #include "Magnum/Vk/MeshLayout.h" +#include "Magnum/Vk/Mesh.h" #include "Magnum/Vk/PixelFormat.h" #include "Magnum/Vk/VertexFormat.h" #endif @@ -40,12 +41,6 @@ namespace Magnum { namespace Vk { namespace { -constexpr VkIndexType IndexTypeMapping[]{ - VK_INDEX_TYPE_UINT8_EXT, - VK_INDEX_TYPE_UINT16, - VK_INDEX_TYPE_UINT32 -}; - constexpr VkFilter FilterMapping[]{ VK_FILTER_NEAREST, VK_FILTER_LINEAR @@ -75,24 +70,15 @@ bool hasVkPrimitiveTopology(const Magnum::MeshPrimitive primitive) { VkPrimitiveTopology vkPrimitiveTopology(const Magnum::MeshPrimitive primitive) { return VkPrimitiveTopology(meshPrimitive(primitive)); } -#endif -bool hasVkIndexType(const Magnum::MeshIndexType type) { - CORRADE_ASSERT(UnsignedInt(type) - 1 < Containers::arraySize(IndexTypeMapping), - "Vk::hasVkIndexType(): invalid type" << type, {}); - return UnsignedInt(IndexTypeMapping[UnsignedInt(type) - 1]) != ~UnsignedInt{}; +bool hasVkIndexType(const Magnum::MeshIndexType) { + return true; } VkIndexType vkIndexType(const Magnum::MeshIndexType type) { - CORRADE_ASSERT(UnsignedInt(type) - 1 < Containers::arraySize(IndexTypeMapping), - "Vk::vkIndexType(): invalid type" << type, {}); - const VkIndexType out = IndexTypeMapping[UnsignedInt(type) - 1]; - CORRADE_ASSERT(out != VkIndexType(~UnsignedInt{}), - "Vk::vkIndexType(): unsupported type" << type, {}); - return out; + return VkIndexType(meshIndexType(type)); } -#ifdef MAGNUM_BUILD_DEPRECATED bool hasVkFormat(const Magnum::VertexFormat format) { return hasVertexFormat(format); } @@ -104,9 +90,7 @@ bool hasVkFormat(const Magnum::PixelFormat format) { bool hasVkFormat(const Magnum::CompressedPixelFormat format) { return hasPixelFormat(format); } -#endif -#ifdef MAGNUM_BUILD_DEPRECATED VkFormat vkFormat(const Magnum::VertexFormat format) { return VkFormat(vertexFormat(format)); } diff --git a/src/Magnum/Vk/Enums.h b/src/Magnum/Vk/Enums.h index f976dfcca..0d67bc260 100644 --- a/src/Magnum/Vk/Enums.h +++ b/src/Magnum/Vk/Enums.h @@ -51,33 +51,19 @@ CORRADE_DEPRECATED("use hasMeshPrimitive() instead") MAGNUM_VK_EXPORT bool hasVk * @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 - -Returns @cpp false @ce if Vulkan doesn't support such type, @cpp true @ce -otherwise. The @p type 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 vkIndexType(), @vk_extension{EXT,index_type_uint8} -*/ -MAGNUM_VK_EXPORT bool hasVkIndexType(Magnum::MeshIndexType type); + * @brief Check availability of a generic index type + * @m_deprecated_since_latest All generic index types are available in Vulkan. + */ +CORRADE_DEPRECATED("all generic index types are available in Vulkan") MAGNUM_VK_EXPORT bool hasVkIndexType(Magnum::MeshIndexType type); /** -@brief Convert generic mesh index type to Vulkan mesh index type - -Not all generic index types have a Vulkan equivalent and this function expects -that given type is available. Use @ref hasVkIndexType() to query availability -of given index type. -@see @ref vkPrimitiveTopology() -*/ -MAGNUM_VK_EXPORT VkIndexType vkIndexType(Magnum::MeshIndexType type); + * @brief @copybrief meshIndexType() + * @m_deprecated_since_latest Use @ref meshIndexType() instead. + */ +CORRADE_DEPRECATED("use meshIndexType() instead") MAGNUM_VK_EXPORT VkIndexType vkIndexType(Magnum::MeshIndexType type); -#ifdef MAGNUM_BUILD_DEPRECATED /** * @brief @copybrief hasVertexFormat() * @m_deprecated_since_latest Use @ref hasVertexFormat() instead. @@ -97,9 +83,7 @@ CORRADE_DEPRECATED("use hasPixelFormat() instead") MAGNUM_VK_EXPORT bool hasVkFo * instead. */ CORRADE_DEPRECATED("use hasPixelFormat() instead") MAGNUM_VK_EXPORT bool hasVkFormat(Magnum::CompressedPixelFormat format); -#endif -#ifdef MAGNUM_BUILD_DEPRECATED /** * @brief @copybrief vertexFormat() * @m_deprecated_since_latest Use @ref vertexFormat() instead. diff --git a/src/Magnum/Vk/Implementation/DeviceState.cpp b/src/Magnum/Vk/Implementation/DeviceState.cpp index ffbdd3e4b..93c01d05f 100644 --- a/src/Magnum/Vk/Implementation/DeviceState.cpp +++ b/src/Magnum/Vk/Implementation/DeviceState.cpp @@ -85,6 +85,12 @@ DeviceState::DeviceState(Device& device, Containers::Array()) { + cmdBindVertexBuffersImplementation = &CommandBuffer::bindVertexBuffersImplementationEXT; + } else { + cmdBindVertexBuffersImplementation = &CommandBuffer::bindVertexBuffersImplementationDefault; + } + if(device.isExtensionEnabled()) { cmdCopyBufferImplementation = &CommandBuffer::copyBufferImplementationKHR; cmdCopyImageImplementation = &CommandBuffer::copyImageImplementationKHR; diff --git a/src/Magnum/Vk/Implementation/DeviceState.h b/src/Magnum/Vk/Implementation/DeviceState.h index 7bd797646..583fabaa6 100644 --- a/src/Magnum/Vk/Implementation/DeviceState.h +++ b/src/Magnum/Vk/Implementation/DeviceState.h @@ -56,6 +56,8 @@ struct DeviceState { void(*cmdNextSubpassImplementation)(CommandBuffer&, const VkSubpassEndInfo&, const VkSubpassBeginInfo&); void(*cmdEndRenderPassImplementation)(CommandBuffer&, const VkSubpassEndInfo&); + void(*cmdBindVertexBuffersImplementation)(CommandBuffer&, UnsignedInt, UnsignedInt, const VkBuffer*, const UnsignedLong*, const UnsignedLong*); + void(*cmdCopyBufferImplementation)(CommandBuffer&, const CopyBufferInfo&); void(*cmdCopyImageImplementation)(CommandBuffer&, const CopyImageInfo&); void(*cmdCopyBufferToImageImplementation)(CommandBuffer&, const CopyBufferToImageInfo&); diff --git a/src/Magnum/Vk/Mesh.cpp b/src/Magnum/Vk/Mesh.cpp new file mode 100644 index 000000000..d174419df --- /dev/null +++ b/src/Magnum/Vk/Mesh.cpp @@ -0,0 +1,250 @@ +/* + 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 "Mesh.h" +#include "CommandBuffer.h" + +#include + +#include "Magnum/Mesh.h" +#include "Magnum/Vk/Buffer.h" +#include "Magnum/Vk/Device.h" +#include "Magnum/Vk/MeshLayout.h" +#include "Magnum/Vk/RasterizationPipelineCreateInfo.h" +#include "Magnum/Vk/Implementation/DeviceState.h" + +namespace Magnum { namespace Vk { + +namespace { + +constexpr MeshIndexType IndexTypeMapping[]{ + MeshIndexType::UnsignedByte, + MeshIndexType::UnsignedShort, + MeshIndexType::UnsignedInt +}; + +} + +MeshIndexType meshIndexType(const Magnum::MeshIndexType type) { + CORRADE_ASSERT(UnsignedInt(type) - 1 < Containers::arraySize(IndexTypeMapping), + "Vk::meshIndexType(): invalid type" << type, {}); + return IndexTypeMapping[UnsignedInt(type) - 1]; +} + +Debug& operator<<(Debug& debug, const MeshIndexType value) { + debug << "Vk::MeshIndexType" << Debug::nospace; + + switch(value) { + /* LCOV_EXCL_START */ + #define _c(value) case Vk::MeshIndexType::value: return debug << "::" << Debug::nospace << #value; + _c(UnsignedByte) + _c(UnsignedShort) + _c(UnsignedInt) + #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 << ")"; +} + +struct Mesh::State { + Containers::ArrayTuple vertexBufferData; + Containers::ArrayView vertexBuffers; + Containers::ArrayView vertexBufferOffsets; + Containers::ArrayView vertexBufferStrides; + Containers::ArrayView ownedVertexBuffers; + + VkBuffer indexBuffer{}; + Buffer ownedIndexBuffer{NoCreate}; + UnsignedLong indexBufferOffset{}; + MeshIndexType indexType{}; +}; + +Mesh::Mesh(const MeshLayout& layout): Mesh{MeshLayout{layout.vkPipelineVertexInputStateCreateInfo(), layout.vkPipelineInputAssemblyStateCreateInfo()}} {} + +Mesh::Mesh(MeshLayout&& layout): _layout{std::move(layout)} { + /* Since we know the count of buffer bindings, we can directly allocate + all needed memory upfront */ + if(const UnsignedInt count = _layout.vkPipelineVertexInputStateCreateInfo().vertexBindingDescriptionCount) { + _state.emplace(); + _state->vertexBufferData = Containers::ArrayTuple{ + {Containers::ValueInit, count, _state->vertexBuffers}, + {Containers::ValueInit, count, _state->vertexBufferOffsets}, + {Containers::ValueInit, count, _state->vertexBufferStrides}, + {Containers::NoInit, count, _state->ownedVertexBuffers} + }; + + /** @tod use DirectInit once ArrayTuple can do that */ + for(Buffer& b: _state->ownedVertexBuffers) new(&b) Buffer{NoCreate}; + } +} + +/* We don't have any internal self-pointers so a default is fine, and + MeshLayout has its own constructor to handle those (if any) */ +Mesh::Mesh(Mesh&&) noexcept = default; + +Mesh::~Mesh() = default; + +Mesh& Mesh::operator=(Mesh&&) noexcept = default; + +UnsignedInt Mesh::count() const { + /* The inverse value is used to detect & assert on forgotten setCount() + in CommandBuffer::draw(), return 0 in that case as well */ + return _count == ~UnsignedInt{} ? 0 : _count; +} + +std::size_t Mesh::addVertexBufferInternal(UnsignedInt binding, VkBuffer buffer, UnsignedLong offset) { + /* Find this binding in the layout */ + for(std::size_t i = 0, max = _layout.vkPipelineVertexInputStateCreateInfo().vertexBindingDescriptionCount; i != max; ++i) { + if(_layout.vkPipelineVertexInputStateCreateInfo().pVertexBindingDescriptions[i].binding == binding) { + _state->vertexBuffers[i] = buffer; + _state->vertexBufferOffsets[i] = offset; + /* Save the stride as well in case a dynamic state would need it */ + _state->vertexBufferStrides[i] = _layout.vkPipelineVertexInputStateCreateInfo().pVertexBindingDescriptions[i].stride; + return i; + } + } + + CORRADE_ASSERT_UNREACHABLE("Vk::Mesh::addVertexBuffer(): binding" << binding << "not present among" << _layout.vkPipelineVertexInputStateCreateInfo().vertexBindingDescriptionCount << "bindings in the layout", ~std::size_t{}); +} + +Mesh& Mesh::addVertexBuffer(const UnsignedInt binding, const VkBuffer buffer, const UnsignedLong offset) { + addVertexBufferInternal(binding, buffer, offset); + return *this; +} + +Mesh& Mesh::addVertexBuffer(const UnsignedInt binding, Buffer&& buffer, const UnsignedLong offset) { + const std::size_t index = addVertexBufferInternal(binding, buffer, offset); + #ifdef CORRADE_GRACEFUL_ASSERT + if(index == ~std::size_t{}) return *this; + #endif + _state->ownedVertexBuffers[index] = std::move(buffer); + return *this; +} + +Mesh& Mesh::setIndexBuffer(const VkBuffer buffer, const UnsignedLong offset, const MeshIndexType indexType) { + /* If the mesh has no vertex buffer bindings, the state isn't populated + in the constructor. Do it here. */ + if(!_state) _state.emplace(); + + _state->indexBuffer = buffer; + _state->indexBufferOffset = offset; + _state->indexType = indexType; + return *this; +} + +Mesh& Mesh::setIndexBuffer(const VkBuffer buffer, const UnsignedLong offset, const Magnum::MeshIndexType indexType) { + return setIndexBuffer(buffer, offset, meshIndexType(indexType)); +} + +Mesh& Mesh::setIndexBuffer(Buffer&& buffer, const UnsignedLong offset, const MeshIndexType indexType) { + setIndexBuffer(buffer, offset, indexType); + _state->ownedIndexBuffer = std::move(buffer); + return *this; +} + +Mesh& Mesh::setIndexBuffer(Buffer&& buffer, const UnsignedLong offset, const Magnum::MeshIndexType indexType) { + return setIndexBuffer(std::move(buffer), offset, meshIndexType(indexType)); +} + +Containers::ArrayView Mesh::vertexBuffers() { + return _state ? _state->vertexBuffers : nullptr; +} + +Containers::ArrayView Mesh::vertexBufferOffsets() const { + return _state ? _state->vertexBufferOffsets : nullptr; +} + +Containers::ArrayView Mesh::vertexBufferStrides() const { + return _state ? _state->vertexBufferStrides : nullptr; +} + +bool Mesh::isIndexed() const { return _state && _state->indexBuffer; } + +VkBuffer Mesh::indexBuffer() { + CORRADE_ASSERT(isIndexed(), "Vk::Mesh::indexBuffer(): the mesh is not indexed", {}); + return _state->indexBuffer; +} + +UnsignedLong Mesh::indexBufferOffset() const { + CORRADE_ASSERT(isIndexed(), "Vk::Mesh::indexBufferOffset(): the mesh is not indexed", {}); + return _state->indexBufferOffset; +} + +MeshIndexType Mesh::indexType() const { + CORRADE_ASSERT(isIndexed(), "Vk::Mesh::indexType(): the mesh is not indexed", {}); + return _state->indexType; +} + +CommandBuffer& CommandBuffer::draw(Mesh& mesh) { + CORRADE_ASSERT(mesh.isCountSet(), + "Vk::CommandBuffer::draw(): Mesh::setCount() was never called, probably a mistake?", *this); + + if(!mesh.count() || !mesh.instanceCount()) return *this; + + if(_dynamicRasterizationStates & DynamicRasterizationState::MeshPrimitive) + (**_device).CmdSetPrimitiveTopologyEXT(_handle, mesh.layout().vkPipelineInputAssemblyStateCreateInfo().topology); + + const VkVertexInputBindingDescription* bindings = mesh.layout().vkPipelineVertexInputStateCreateInfo().pVertexBindingDescriptions; + for(std::size_t i = 0, max = mesh.layout().vkPipelineVertexInputStateCreateInfo().vertexBindingDescriptionCount; i != max; ++i) { + /** @todo don't call this for each binding, detect ranges */ + _device->state().cmdBindVertexBuffersImplementation(*this, + bindings[i].binding, 1, + mesh.vertexBuffers() + i, + mesh.vertexBufferOffsets() + i, + _dynamicRasterizationStates & DynamicRasterizationState::VertexInputBindingStride ? + mesh.vertexBufferStrides() + i : nullptr + ); + } + + if(mesh.isIndexed()) { + (**_device).CmdBindIndexBuffer(_handle, mesh.indexBuffer(), mesh.indexBufferOffset(), VkIndexType(mesh.indexType())); + (**_device).CmdDrawIndexed(_handle, mesh.count(), mesh.instanceCount(), mesh.indexOffset(), mesh.vertexOffset(), mesh.instanceOffset()); + } else { + (**_device).CmdDraw(_handle, mesh.count(), mesh.instanceCount(), mesh.vertexOffset(), mesh.instanceOffset()); + } + + return *this; +} + +void CommandBuffer::bindVertexBuffersImplementationDefault(CommandBuffer& self, const UnsignedInt firstBinding, const UnsignedInt bindingCount, const VkBuffer* const buffers, const UnsignedLong* const offsets, const UnsignedLong* const strides) { + CORRADE_ASSERT(!strides, + "Vk::CommandBuffer::draw(): dynamic strides supplied for an implementation without extended dynamic state", + /* Calling this even in case the assert blows up to avoid validation + layer errors about unbound attributes when CORRADE_GRACEFUL_ASSERT + is enabled */ + (**self._device).CmdBindVertexBuffers(self, firstBinding, bindingCount, buffers, offsets)); + #ifdef CORRADE_NO_ASSERT + static_cast(strides); + #endif + (**self._device).CmdBindVertexBuffers(self, firstBinding, bindingCount, buffers, offsets); +} + +void CommandBuffer::bindVertexBuffersImplementationEXT(CommandBuffer& self, const UnsignedInt firstBinding, const UnsignedInt bindingCount, const VkBuffer* const buffers, const UnsignedLong* const offsets, const UnsignedLong* const strides) { + return (**self._device).CmdBindVertexBuffers2EXT(self, firstBinding, bindingCount, buffers, offsets, nullptr, strides); +} + +}} diff --git a/src/Magnum/Vk/Mesh.h b/src/Magnum/Vk/Mesh.h new file mode 100644 index 000000000..c6a821ce4 --- /dev/null +++ b/src/Magnum/Vk/Mesh.h @@ -0,0 +1,406 @@ +#ifndef Magnum_Vk_Mesh_h +#define Magnum_Vk_Mesh_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::Mesh, enum @ref Magnum::Vk::MeshIndexType, function @ref Magnum::Vk::meshIndexType() + * @m_since_latest + */ + +#include + +#include "Magnum/Vk/MeshLayout.h" + +namespace Magnum { namespace Vk { + +/** +@brief Mesh index type +@m_since_latest + +Wraps a @type_vk_keyword{IndexType}. +*/ +enum class MeshIndexType: Int { + /** + * @ref Magnum::UnsignedByte "UnsignedByte" + * @requires_vk_feature @ref DeviceFeature::IndexTypeUnsignedByte + */ + UnsignedByte = VK_INDEX_TYPE_UINT8_EXT, + + /** @ref Magnum::UnsignedShort "UnsignedShort" */ + UnsignedShort = VK_INDEX_TYPE_UINT16, + + /** + * @ref Magnum::UnsignedInt "UnsignedInt" + * @see @ref DeviceFeature::FullDrawIndexUnsignedInt + */ + UnsignedInt = VK_INDEX_TYPE_UINT32 +}; + +/** +@debugoperatorenum{MeshIndexType} +@m_since_latest +*/ +MAGNUM_VK_EXPORT Debug& operator<<(Debug& debug, MeshIndexType value); + +/** +@brief Convert a generic index type to Vulkan index type +@m_since_latest + +@see @ref meshPrimitive(), @ref vertexFormat() +*/ +MAGNUM_VK_EXPORT MeshIndexType meshIndexType(Magnum::MeshIndexType type); + +/** +@brief Mesh +@m_since_latest + +Connects @ref MeshLayout with concrete vertex/index @ref Buffer instances and +manages related information such as vertex or instance count. + +@section Vk-Mesh-populating Populating a mesh + +Continuing from the @ref Vk-MeshLayout-usage "mesh layout setup", the @ref Mesh +gets concrete buffers bound using @ref addVertexBuffer() and vertex count +specified with @ref setCount(): + +@snippet MagnumVk.cpp Mesh-populating + +For an indexed mesh, the index buffer can be specified via @ref setIndexBuffer(). +With an index buffer present, @ref setCount() then describes count of indices, +instead of vertices: + +@snippet MagnumVk.cpp Mesh-populating-indexed + +@subsection Vk-Mesh-populating-owned Transferring buffer and layout ownership + +To simplify resource management, it's possible to have the @ref Buffer +instances owned by the @ref Mesh, as well as the @ref MeshLayout, either using +@ref std::move() or by directly passing a r-value. If a single buffer is used +for multiple bindings (for example as both a vertex and an index buffer), +perform the move last: + +@snippet MagnumVk.cpp Mesh-populating-owned + +@section Vk-Mesh-drawing Drawing a mesh + +Assuming a rasterization pipeline with the same @ref MeshLayout was bound, a +mesh can be then drawn using @ref CommandBuffer::draw(). The function takes +care of binding all buffers and executing an appropriate draw command: + +@snippet MagnumVk.cpp Mesh-drawing + +@subsection Vk-Mesh-drawing-dynamic Dynamic pipeline state + +Both the @ref MeshPrimitive set in @ref MeshLayout constructor and binding +stride set in @ref MeshLayout::addBinding() can be set as dynamic in the +pipeline using @ref DynamicRasterizationState::MeshPrimitive and +@relativeref{DynamicRasterizationState,VertexInputBindingStride}, assuming +@ref DeviceFeature::ExtendedDynamicState is supported and enabled. The +@ref CommandBuffer::draw() function then checks what dynamic state is enabled +in the currently bound pipeline and implicitly sets all dynamic states. + +Taking this to the extreme, with these two dynamic states and a dedicated +binding for each attribute you can make the pipeline accept basically any mesh +as long as just the attribute locations and types are the same --- offsets and +strided of particular attributes are then fully dynamic. + +@snippet MagnumVk.cpp Mesh-drawing-dynamic + + + +@m_class{m-note m-success} + +@par + The performance aspect of this approach is a whole different topic, of + course. It isn't expected to be as fast as if the vertex and primitive + layout was fixed, but it *may* be faster than having to create and bind a + whole new pipeline several times over when drawing a large set of + layout-incompatible meshes. +*/ +class MAGNUM_VK_EXPORT Mesh { + public: + /** + * @brief Construct with a reference to external @ref MeshLayout + * + * Assumes @p layout stays in scope for the whole lifetime of the + * @ref Mesh instance. + */ + explicit Mesh(const MeshLayout& layout); + + /** @brief Construct with taking over @ref MeshLayout ownership */ + explicit Mesh(MeshLayout&& layout); + + /** @brief Copying is not allowed */ + Mesh(const Mesh&) = delete; + + /** @brief Move constructor */ + Mesh(Mesh&&) noexcept; + + /** + * @brief Destructor + * + * If any buffers were added using @ref addVertexBuffer(UnsignedInt, Buffer&&, UnsignedLong) + * or @ref setIndexBuffer(Buffer&&, UnsignedLong, MeshIndexType), their + * owned instanced are destructed at this point. + */ + ~Mesh(); + + /** @brief Copying is not allowed */ + Mesh& operator=(const Mesh&) = delete; + + /** @brief Move assignment */ + Mesh& operator=(Mesh&&) noexcept; + + /** @brief Vertex/index count */ + UnsignedInt count() const; + + /** + * @brief Set vertex/index count + * @return Reference to self (for method chaining) + * + * If the mesh is indexed, the value is treated as index count, + * otherwise the value is vertex count. If set to @cpp 0 @ce, no draw + * commands are issued when calling @ref CommandBuffer::draw(). + * + * @attention To prevent nothing being rendered by accident, this + * function has to be always called, even to just set the count to + * @cpp 0 @ce. + * + * @see @ref isIndexed(), @ref setInstanceCount(), + * @ref setVertexOffset(), @ref setIndexOffset() + */ + Mesh& setCount(UnsignedInt count) { + _count = count; + return *this; + } + + /** @brief Vertex offset */ + UnsignedInt vertexOffset() const { return _vertexOffset; } + + /** + * @brief Set vertex offset + * @return Reference to self (for method chaining) + * + * For non-indexed meshes specifies the first vertex that will be + * drawn, for indexed meshes specifies the offset added to each index. + * Default is @cpp 0 @ce. + * @see @ref isIndexed(), @ref setIndexOffset(), @ref setCount() + */ + Mesh& setVertexOffset(UnsignedInt offset) { + _vertexOffset = offset; + return *this; + } + + /** @brief Index offset */ + UnsignedInt indexOffset() const { return _indexOffset; } + + /** + * @brief Set index offset + * @return Reference to self (for method chaining) + * + * Expects that the mesh is indexed. Specifies the first index that + * will be drawn. Default is @cpp 0 @ce. + * @see @ref isIndexed(), @ref setVertexOffset(), @ref setCount() + */ + Mesh& setIndexOffset(UnsignedInt offset) { + _indexOffset = offset; + return *this; + } + + /** @brief Instance count */ + UnsignedInt instanceCount() const { return _instanceCount; } + + /** + * @brief Set instance count + * @return Reference to self (for method chaining) + * + * If set to @cpp 0 @ce, no draw commands are issued when calling + * @ref CommandBuffer::draw(). Default is @cpp 1 @ce. + * @see @ref setCount(), @ref setInstanceOffset() + */ + Mesh& setInstanceCount(UnsignedInt count) { + _instanceCount = count; + return *this; + } + + /** @brief Instance offset */ + UnsignedInt instanceOffset() const { return _instanceOffset; } + + /** + * @brief Set instance offset + * @return Reference to self (for method chaining) + * + * Specifies the first instance that will be drawn. Default is + * @cpp 0 @ce. + * @see @ref setInstanceCount() + */ + Mesh& setInstanceOffset(UnsignedInt offset) { + _instanceOffset = offset; + return *this; + } + + /** + * @brief Add a vertex buffer + * @param binding Binding corresponding to a particular + * @ref MeshLayout::addBinding() call + * @param buffer A @ref Buffer instance or a raw Vulkan buffer + * handle + * @param offset Offset into the buffer, in bytes + * @return Reference to self (for method chaining) + * + * @see @ref setCount(), @ref setVertexOffset() + */ + Mesh& addVertexBuffer(UnsignedInt binding, VkBuffer buffer, UnsignedLong offset); + + /** + * @brief Add a vertex buffer and take over its ownership + * @return Reference to self (for method chaining) + * + * Compared to @ref addVertexBuffer(UnsignedInt, VkBuffer, UnsignedLong) + * the @p buffer instance ownership is transferred to the class and + * thus doesn't have to be managed separately. + */ + Mesh& addVertexBuffer(UnsignedInt binding, Buffer&& buffer, UnsignedLong offset); + + /** + * @brief Set an index buffer + * @param buffer A @ref Buffer instance or a raw Vulkan buffer + * handle + * @param offset Offset into the buffer, in bytes + * @param indexType Index type + * @return Reference to self (for method chaining) + * + * @see @ref setCount(), @ref setIndexOffset() + */ + Mesh& setIndexBuffer(VkBuffer buffer, UnsignedLong offset, MeshIndexType indexType); + /** @overload */ + Mesh& setIndexBuffer(VkBuffer buffer, UnsignedLong offset, Magnum::MeshIndexType indexType); + + /** + * @brief Set an index buffer and take over its ownership + * @return Reference to self (for method chaining) + * + * Compared to @ref setIndexBuffer(VkBuffer, UnsignedLong, MeshIndexType) + * the @p buffer instance ownership is transferred to the class and + * thus doesn't have to be managed separately. + */ + Mesh& setIndexBuffer(Buffer&& buffer, UnsignedLong offset, MeshIndexType indexType); + /** @overload */ + Mesh& setIndexBuffer(Buffer&& buffer, UnsignedLong offset, Magnum::MeshIndexType indexType); + + /** @brief Layout of this mesh */ + const MeshLayout& layout() const { return _layout; } + + /** + * @brief Vertex buffers + * + * Has the same length as the vertex buffer binding array in + * @ref layout(), the buffers correspond to binding IDs at the same + * index. + */ + Containers::ArrayView vertexBuffers(); + + /** + * @brief Vertex buffer offsets + * + * Has the same length as the vertex buffer binding array in + * @ref layout(), offsets correspond to @ref vertexBuffers() at the + * same index. + */ + Containers::ArrayView vertexBufferOffsets() const; + + /** + * @brief Vertex buffer strides + * + * Has the same length as the vertex buffer binding array in + * @ref layout(). The strides are the same as strides in the layout + * at the same index, but here in a form that's usable by + * @fn_vk{CmdBindVertexBuffers2} if + * @ref DynamicRasterizationState::VertexInputBindingStride is enabled. + */ + Containers::ArrayView vertexBufferStrides() const; + + /** + * @brief Whether the mesh is indexed + * + * The mesh is considered indexed if @ref setIndexBuffer() was called. + */ + bool isIndexed() const; + + /** + * @brief Index buffer + * + * Expects that the mesh is indexed. + * @see @ref isIndexed() + */ + VkBuffer indexBuffer(); + + /** + * @brief Index buffer offset + * + * Expects that the mesh is indexed. + * @see @ref isIndexed() + */ + UnsignedLong indexBufferOffset() const; + + /** + * @brief Index type + * + * Expects that the mesh is indexed. + * @see @ref isIndexed() + */ + MeshIndexType indexType() const; + + #ifdef DOXYGEN_GENERATING_OUTPUT + private: + #endif + #ifndef CORRADE_NO_ASSERT + /* Used by CommandBuffer::draw() for a sanity assert */ + bool isCountSet() const { return _count != ~UnsignedInt{}; } + #endif + + private: + /* This is all here and not in the State struct in order to avoid + unnecessary allocations for buffer-less meshes -- like with GL, we + want `draw(Mesh{MeshLayout{MeshPrimitive::Triangle}}.setCount(3))` + to be performant enough to not need to invent any alternatives. The + MeshLayout class does a similar thing. */ + UnsignedInt _count = ~UnsignedInt{}, + _vertexOffset = 0, + _indexOffset = 0, + _instanceCount = 1, + _instanceOffset = 0; + MeshLayout _layout; + + MAGNUM_VK_LOCAL std::size_t addVertexBufferInternal(UnsignedInt binding, VkBuffer buffer, UnsignedLong offset); + + struct State; + Containers::Pointer _state; +}; + +}} + +#endif diff --git a/src/Magnum/Vk/MeshLayout.h b/src/Magnum/Vk/MeshLayout.h index d27ee00d4..99a4169bb 100644 --- a/src/Magnum/Vk/MeshLayout.h +++ b/src/Magnum/Vk/MeshLayout.h @@ -159,7 +159,7 @@ In case @ref isMeshPrimitiveImplementationSpecific() returns @cpp false @ce for 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() +@see @ref meshIndexType(), @ref vertexFormat() */ MAGNUM_VK_EXPORT MeshPrimitive meshPrimitive(Magnum::MeshPrimitive primitive); @@ -182,7 +182,7 @@ describing how vertex attributes are organized in buffers and what's the layout of each attribute. Used as an input for creating a @ref Vk-Pipeline-creation-rasterization "rasterization pipeline". -@section Vk-MeshLayout-usage Usage +@section Vk-MeshLayout-usage Mesh layout setup 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 @@ -191,8 +191,9 @@ 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. +The `Binding` is then subsequently used as a binding index for a concrete +vertex buffer when drawing, which is described in the @ref Mesh class +documentation. @subsection Vk-MeshLayout-usage-comparison Layout comparison @@ -417,6 +418,11 @@ class MAGNUM_VK_EXPORT MeshLayout { MAGNUM_VK_LOCAL bool hasNoExternalPointers() const; #endif + /* These are here instead of in the State struct in order to avoid + unnecessary allocations for buffer-less layouts -- like with GL, we + want `draw(Mesh{MeshLayout{MeshPrimitive::Triangle}}.setCount(3))` + to be performant enough to not need to invent any alternatives. The + Mesh class does a similar thing. */ VkPipelineVertexInputStateCreateInfo _vertexInfo; VkPipelineInputAssemblyStateCreateInfo _assemblyInfo; diff --git a/src/Magnum/Vk/RasterizationPipelineCreateInfo.h b/src/Magnum/Vk/RasterizationPipelineCreateInfo.h index aa046cac3..34ce4ddbf 100644 --- a/src/Magnum/Vk/RasterizationPipelineCreateInfo.h +++ b/src/Magnum/Vk/RasterizationPipelineCreateInfo.h @@ -159,8 +159,9 @@ enum class DynamicRasterizationState: UnsignedByte { /** * Only the @ref MeshPrimitive topology class set in @ref MeshLayout and * passed to @ref RasterizationPipelineCreateInfo is used and the specific - * topology order and adjacency is expected to be set dynamically using - * @fn_vk{CmdSetPrimitiveTopologyEXT}. + * topology order and adjacency is expected to be set dynamically. + * @ref CommandBuffer::draw() does this automatically if a pipeline with + * this dynamic state is bound. * @requires_vk_feature @ref DeviceFeature::ExtendedDynamicState * @m_keywords{VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY_EXT} */ @@ -191,10 +192,10 @@ enum class DynamicRasterizationState: UnsignedByte { /** * Stride set in @ref MeshLayout::addBinding() and passed to * @ref RasterizationPipelineCreateInfo is ignored and expected to be set - * dynamically using @fn_vk{CmdBindVertexBuffers2EXT} + * dynamically. @ref CommandBuffer::draw() does this automatically if a + * pipeline with this dynamic state is bound. * @requires_vk_feature @ref DeviceFeature::ExtendedDynamicState * @m_keywords{VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE_EXT} - * @todoc link to the actual API when exposed */ VertexInputBindingStride, diff --git a/src/Magnum/Vk/Test/CMakeLists.txt b/src/Magnum/Vk/Test/CMakeLists.txt index 6655ef8fa..ab7e1d679 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(VkMeshTest MeshTest.cpp LIBRARIES MagnumVkTestLib) corrade_add_test(VkMeshLayoutTest MeshLayoutTest.cpp LIBRARIES MagnumVkTestLib) corrade_add_test(VkPipelineTest PipelineTest.cpp LIBRARIES MagnumVkTestLib) corrade_add_test(VkPipelineLayoutTest PipelineLayoutTest.cpp LIBRARIES MagnumVk) @@ -140,6 +141,7 @@ set_target_properties( VkIntegrationTest VkLayerPropertiesTest VkMemoryTest + VkMeshTest VkMeshLayoutTest VkPipelineTest VkPipelineLayoutTest @@ -154,14 +156,34 @@ set_target_properties( PROPERTIES FOLDER "Magnum/Vk/Test") if(BUILD_VK_TESTS) + # Otherwise CMake complains that Corrade::PluginManager is not found, wtf + find_package(Corrade REQUIRED PluginManager) + if(CORRADE_TARGET_ANDROID) set(VK_TEST_DIR ".") else() set(VK_TEST_DIR ${CMAKE_CURRENT_SOURCE_DIR}) endif() + # CMake before 3.8 has broken $ expressions for iOS (see + # https://gitlab.kitware.com/cmake/cmake/merge_requests/404) and since + # Corrade doesn't support dynamic plugins on iOS, this sorta works around + # that. Should be revisited when updating Travis to newer Xcode (xcode7.3 + # has CMake 3.6). + if(NOT BUILD_PLUGINS_STATIC) + if(WITH_ANYIMAGEIMPORTER) + set(ANYIMAGEIMPORTER_PLUGIN_FILENAME $) + endif() + if(WITH_TGAIMPORTER) + set(TGAIMPORTER_PLUGIN_FILENAME $) + endif() + endif() + + # First replace ${} variables, then $<> generator expressions configure_file(${CMAKE_CURRENT_SOURCE_DIR}/configure.h.cmake - ${CMAKE_CURRENT_BINARY_DIR}/configure.h) + ${CMAKE_CURRENT_BINARY_DIR}/configure.h.in) + file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/$/configure.h + INPUT ${CMAKE_CURRENT_BINARY_DIR}/configure.h.in) corrade_add_test(VkBufferVkTest BufferVkTest.cpp LIBRARIES MagnumVkTestLib MagnumVulkanTester) corrade_add_test(VkCommandBufferVkTest CommandBufferVkTest.cpp LIBRARIES MagnumVulkanTester) @@ -176,17 +198,37 @@ 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(VkMeshVkTest MeshVkTest.cpp + LIBRARIES MagnumVkTestLib MagnumDebugTools MagnumVulkanTester + FILES + MeshTestFiles/flat.spv + MeshTestFiles/flat.tga + MeshTestFiles/noop.spv + MeshTestFiles/noop.tga + MeshTestFiles/vertexcolor.spv + MeshTestFiles/vertexcolor.tga) + target_include_directories(VkMeshVkTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/$) + if(BUILD_PLUGINS_STATIC) + if(WITH_ANYIMAGEIMPORTER) + target_link_libraries(VkMeshVkTest PRIVATE AnyImageImporter) + endif() + if(WITH_TGAIMPORTER) + target_link_libraries(VkMeshVkTest PRIVATE TgaImporter) + endif() + endif() + corrade_add_test(VkPipelineVkTest PipelineVkTest.cpp LIBRARIES MagnumVkTestLib MagnumVulkanTester FILES triangle-shaders.spv compute-noop.spv) - target_include_directories(VkPipelineVkTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) + target_include_directories(VkPipelineVkTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/$) corrade_add_test(VkPipelineLayoutVkTest PipelineLayoutVkTest.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 FILES triangle-shaders.spv) - target_include_directories(VkShaderVkTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) + target_include_directories(VkShaderVkTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/$) corrade_add_test(VkVersionVkTest VersionVkTest.cpp LIBRARIES MagnumVk) set_target_properties( @@ -203,6 +245,7 @@ if(BUILD_VK_TESTS) VkImageViewVkTest VkInstanceVkTest VkMemoryVkTest + VkMeshVkTest VkPipelineVkTest VkPipelineLayoutVkTest VkQueueVkTest diff --git a/src/Magnum/Vk/Test/EnumsTest.cpp b/src/Magnum/Vk/Test/EnumsTest.cpp index 39406e0bb..a365fbffc 100644 --- a/src/Magnum/Vk/Test/EnumsTest.cpp +++ b/src/Magnum/Vk/Test/EnumsTest.cpp @@ -38,10 +38,6 @@ namespace Magnum { namespace Vk { namespace Test { namespace { struct EnumsTest: TestSuite::Tester { explicit EnumsTest(); - void mapVkIndexType(); - void mapVkIndexTypeUnsupported(); - void mapVkIndexTypeInvalid(); - void mapVkFilter(); void mapVkFilterInvalid(); @@ -55,11 +51,7 @@ struct EnumsTest: TestSuite::Tester { }; EnumsTest::EnumsTest() { - addTests({&EnumsTest::mapVkIndexType, - &EnumsTest::mapVkIndexTypeUnsupported, - &EnumsTest::mapVkIndexTypeInvalid, - - &EnumsTest::mapVkFilter, + addTests({&EnumsTest::mapVkFilter, &EnumsTest::mapVkFilterInvalid, &EnumsTest::mapVkSamplerMipmapMode, @@ -71,74 +63,6 @@ EnumsTest::EnumsTest() { &EnumsTest::mapVkSamplerAddressModeInvalid}); } -void EnumsTest::mapVkIndexType() { - CORRADE_VERIFY(hasVkIndexType(Magnum::MeshIndexType::UnsignedShort)); - CORRADE_COMPARE(vkIndexType(Magnum::MeshIndexType::UnsignedShort), VK_INDEX_TYPE_UINT16); - - CORRADE_VERIFY(hasVkIndexType(Magnum::MeshIndexType::UnsignedInt)); - CORRADE_COMPARE(vkIndexType(Magnum::MeshIndexType::UnsignedInt), VK_INDEX_TYPE_UINT32); - - /* Ensure all generic index types 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 type = Magnum::MeshIndexType(i); - #ifdef __GNUC__ - #pragma GCC diagnostic push - #pragma GCC diagnostic error "-Wswitch" - #endif - switch(type) { - #define _c(type) \ - case Magnum::MeshIndexType::type: \ - CORRADE_VERIFY(UnsignedInt(vkIndexType(Magnum::MeshIndexType::type)) >= 0); \ - break; - #include "Magnum/Implementation/meshIndexTypeMapping.hpp" - #undef _c - } - #ifdef __GNUC__ - #pragma GCC diagnostic pop - #endif - } -} - -void EnumsTest::mapVkIndexTypeUnsupported() { - #ifdef CORRADE_NO_ASSERT - CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); - #endif - - #if 1 - CORRADE_SKIP("All index formats are supported."); - #else - CORRADE_VERIFY(!hasVkIndexType(Magnum::MeshIndexType::UnsignedByte)); - std::ostringstream out; - { - Error redirectError{&out}; - vkIndexType(Magnum::MeshIndexType::UnsignedByte); - } - CORRADE_COMPARE(out.str(), - "Vk::vkIndexType(): unsupported type MeshIndexType::UnsignedByte\n"); - #endif -} - -void EnumsTest::mapVkIndexTypeInvalid() { - #ifdef CORRADE_NO_ASSERT - CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); - #endif - - std::ostringstream out; - Error redirectError{&out}; - - hasVkIndexType(Magnum::MeshIndexType(0x0)); - hasVkIndexType(Magnum::MeshIndexType(0x12)); - vkIndexType(Magnum::MeshIndexType(0x0)); - vkIndexType(Magnum::MeshIndexType(0x12)); - CORRADE_COMPARE(out.str(), - "Vk::hasVkIndexType(): invalid type MeshIndexType(0x0)\n" - "Vk::hasVkIndexType(): invalid type MeshIndexType(0x12)\n" - "Vk::vkIndexType(): invalid type MeshIndexType(0x0)\n" - "Vk::vkIndexType(): invalid type MeshIndexType(0x12)\n"); -} - void EnumsTest::mapVkFilter() { CORRADE_COMPARE(vkFilter(SamplerFilter::Nearest), VK_FILTER_NEAREST); CORRADE_COMPARE(vkFilter(SamplerFilter::Linear), VK_FILTER_LINEAR); diff --git a/src/Magnum/Vk/Test/MeshTest.cpp b/src/Magnum/Vk/Test/MeshTest.cpp new file mode 100644 index 000000000..f8af4f3d3 --- /dev/null +++ b/src/Magnum/Vk/Test/MeshTest.cpp @@ -0,0 +1,278 @@ +/* + 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 "Magnum/Mesh.h" +#include "Magnum/Vk/Buffer.h" +#include "Magnum/Vk/Device.h" +#include "Magnum/Vk/Mesh.h" + +namespace Magnum { namespace Vk { namespace Test { namespace { + +struct MeshTest: TestSuite::Tester { + explicit MeshTest(); + + void mapIndexType(); + void mapIndexTypeInvalid(); + + void construct(); + void countsOffsets(); + + void addVertexBuffer(); + void addVertexBufferOwned(); + void addVertexBufferNoSuchBinding(); + + template void setIndexBuffer(); + template void setIndexBufferOwned(); + + void indexPropertiesNotIndexed(); + + void debugIndexType(); +}; + +MeshTest::MeshTest() { + addTests({&MeshTest::mapIndexType, + &MeshTest::mapIndexTypeInvalid, + + &MeshTest::construct, + &MeshTest::countsOffsets, + + &MeshTest::addVertexBuffer, + &MeshTest::addVertexBufferOwned, + &MeshTest::addVertexBufferNoSuchBinding, + + &MeshTest::setIndexBuffer, + &MeshTest::setIndexBuffer, + &MeshTest::setIndexBufferOwned, + &MeshTest::setIndexBufferOwned, + + &MeshTest::indexPropertiesNotIndexed, + + &MeshTest::debugIndexType}); +} + +template struct IndexTypeTraits; +template<> struct IndexTypeTraits { + static const char* name() { return "MeshIndexType"; } +}; +template<> struct IndexTypeTraits { + static const char* name() { return "Magnum::MeshIndexType"; } +}; + +void MeshTest::mapIndexType() { + CORRADE_COMPARE(meshIndexType(Magnum::MeshIndexType::UnsignedByte), MeshIndexType::UnsignedByte); + CORRADE_COMPARE(meshIndexType(Magnum::MeshIndexType::UnsignedShort), MeshIndexType::UnsignedShort); + CORRADE_COMPARE(meshIndexType(Magnum::MeshIndexType::UnsignedInt), MeshIndexType::UnsignedInt); +} + +void MeshTest::mapIndexTypeInvalid() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + meshIndexType(Magnum::MeshIndexType(0x0)); + meshIndexType(Magnum::MeshIndexType(0x12)); + CORRADE_COMPARE(out.str(), + "Vk::meshIndexType(): invalid type MeshIndexType(0x0)\n" + "Vk::meshIndexType(): invalid type MeshIndexType(0x12)\n"); +} + +void MeshTest::construct() { + MeshLayout layout{MeshPrimitive::Triangles}; + layout.vkPipelineVertexInputStateCreateInfo().sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2; + layout.vkPipelineInputAssemblyStateCreateInfo().sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2; + Mesh mesh{layout}; + /* These should be copies of the original layout */ + CORRADE_COMPARE(mesh.layout().vkPipelineVertexInputStateCreateInfo().sType, VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2); + CORRADE_COMPARE(mesh.layout().vkPipelineInputAssemblyStateCreateInfo().sType, VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2); + CORRADE_COMPARE(mesh.count(), 0); + CORRADE_COMPARE(mesh.vertexOffset(), 0); + CORRADE_COMPARE(mesh.indexOffset(), 0); + CORRADE_COMPARE(mesh.instanceCount(), 1); + CORRADE_COMPARE(mesh.instanceOffset(), 0); + CORRADE_VERIFY(mesh.vertexBuffers().empty()); + CORRADE_VERIFY(mesh.vertexBufferOffsets().empty()); + CORRADE_VERIFY(mesh.vertexBufferStrides().empty()); + CORRADE_VERIFY(!mesh.isIndexed()); +} + +void MeshTest::countsOffsets() { + Mesh mesh{MeshLayout{MeshPrimitive::Triangles}}; + mesh.setCount(15) + .setVertexOffset(3) + .setIndexOffset(5) + .setInstanceCount(7) + .setInstanceOffset(9); + CORRADE_COMPARE(mesh.count(), 15); + CORRADE_COMPARE(mesh.vertexOffset(), 3); + CORRADE_COMPARE(mesh.indexOffset(), 5); + CORRADE_COMPARE(mesh.instanceCount(), 7); + CORRADE_COMPARE(mesh.instanceOffset(), 9); +} + +void MeshTest::addVertexBuffer() { + Mesh mesh{MeshLayout{MeshPrimitive::TriangleFan} + .addBinding(1, 2) + .addInstancedBinding(5, 3) + }; + CORRADE_COMPARE_AS(mesh.vertexBuffers(), Containers::arrayView({ + VkBuffer{}, VkBuffer{} + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(mesh.vertexBufferOffsets(), Containers::arrayView({ + 0, 0 + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(mesh.vertexBufferStrides(), Containers::arrayView({ + 0, 0 + }), TestSuite::Compare::Container); + + mesh.addVertexBuffer(5, reinterpret_cast(0xdead), 15); + CORRADE_COMPARE_AS(mesh.vertexBuffers(), Containers::arrayView({ + VkBuffer{}, reinterpret_cast(0xdead) + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(mesh.vertexBufferOffsets(), Containers::arrayView({ + 0, 15 + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(mesh.vertexBufferStrides(), Containers::arrayView({ + 0, 3 + }), TestSuite::Compare::Container); + + mesh.addVertexBuffer(1, reinterpret_cast(0xbeef), 37); + CORRADE_COMPARE_AS(mesh.vertexBuffers(), Containers::arrayView({ + reinterpret_cast(0xbeef), reinterpret_cast(0xdead) + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(mesh.vertexBufferOffsets(), Containers::arrayView({ + 37, 15 + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(mesh.vertexBufferStrides(), Containers::arrayView({ + 2, 3 + }), TestSuite::Compare::Container); +} + +void MeshTest::addVertexBufferOwned() { + Mesh mesh{MeshLayout{MeshPrimitive::TriangleFan} + .addBinding(1, 2) + .addInstancedBinding(5, 3) + }; + + Device device{NoCreate}; + Buffer a = Buffer::wrap(device, reinterpret_cast(0xdead)); + Buffer b = Buffer::wrap(device, reinterpret_cast(0xbeef)); + mesh.addVertexBuffer(5, std::move(a), 15) + .addVertexBuffer(1, std::move(b), 37); + CORRADE_VERIFY(!a.handle()); + CORRADE_VERIFY(!b.handle()); + + CORRADE_COMPARE_AS(mesh.vertexBuffers(), Containers::arrayView({ + reinterpret_cast(0xbeef), reinterpret_cast(0xdead) + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(mesh.vertexBufferOffsets(), Containers::arrayView({ + 37, 15 + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(mesh.vertexBufferStrides(), Containers::arrayView({ + 2, 3 + }), TestSuite::Compare::Container); +} + +void MeshTest::addVertexBufferNoSuchBinding() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + Mesh noBindings{MeshLayout{MeshPrimitive::Triangles}}; + Mesh differentBindings{MeshLayout{MeshPrimitive::Lines} + .addBinding(1, 2) + .addInstancedBinding(5, 3)}; + + std::ostringstream out; + Error redirectError{&out}; + noBindings.addVertexBuffer(2, VkBuffer{}, 0); + differentBindings.addVertexBuffer(3, Buffer{NoCreate}, 5); + CORRADE_COMPARE(out.str(), + "Vk::Mesh::addVertexBuffer(): binding 2 not present among 0 bindings in the layout\n" + "Vk::Mesh::addVertexBuffer(): binding 3 not present among 2 bindings in the layout\n"); +} + +template void MeshTest::setIndexBuffer() { + setTestCaseTemplateName(IndexTypeTraits::name()); + + Mesh mesh{MeshLayout{MeshPrimitive::Triangles}}; + CORRADE_VERIFY(!mesh.isIndexed()); + + mesh.setIndexBuffer(reinterpret_cast(0xdead), 15, T::UnsignedByte); + CORRADE_VERIFY(mesh.isIndexed()); + CORRADE_COMPARE(mesh.indexBuffer(), reinterpret_cast(0xdead)); + CORRADE_COMPARE(mesh.indexBufferOffset(), 15); + CORRADE_COMPARE(mesh.indexType(), MeshIndexType::UnsignedByte); +} + +template void MeshTest::setIndexBufferOwned() { + setTestCaseTemplateName(IndexTypeTraits::name()); + + Device device{NoCreate}; + Buffer a = Buffer::wrap(device, reinterpret_cast(0xdead)); + + Mesh mesh{MeshLayout{MeshPrimitive::Triangles}}; + mesh.setIndexBuffer(std::move(a), 15, T::UnsignedByte); + CORRADE_VERIFY(!a.handle()); + CORRADE_VERIFY(mesh.isIndexed()); + CORRADE_COMPARE(mesh.indexBuffer(), reinterpret_cast(0xdead)); + CORRADE_COMPARE(mesh.indexBufferOffset(), 15); + CORRADE_COMPARE(mesh.indexType(), MeshIndexType::UnsignedByte); +} + +void MeshTest::indexPropertiesNotIndexed() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + Mesh mesh{MeshLayout{MeshPrimitive::Triangles}}; + CORRADE_VERIFY(!mesh.isIndexed()); + + std::ostringstream out; + Error redirectError{&out}; + mesh.indexBuffer(); + mesh.indexBufferOffset(); + mesh.indexType(); + CORRADE_COMPARE(out.str(), + "Vk::Mesh::indexBuffer(): the mesh is not indexed\n" + "Vk::Mesh::indexBufferOffset(): the mesh is not indexed\n" + "Vk::Mesh::indexType(): the mesh is not indexed\n"); +} + +void MeshTest::debugIndexType() { + std::ostringstream out; + Debug{&out} << MeshIndexType::UnsignedShort << MeshIndexType(-10007655); + CORRADE_COMPARE(out.str(), "Vk::MeshIndexType::UnsignedShort Vk::MeshIndexType(-10007655)\n"); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::Vk::Test::MeshTest) diff --git a/src/Magnum/Vk/Test/MeshTestFiles/convert.sh b/src/Magnum/Vk/Test/MeshTestFiles/convert.sh new file mode 100755 index 000000000..0e65958ac --- /dev/null +++ b/src/Magnum/Vk/Test/MeshTestFiles/convert.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +for i in $(ls *.spvasm); do + magnum-shaderconverter $i ${i%asm} +done diff --git a/src/Magnum/Vk/Test/MeshTestFiles/flat.spv b/src/Magnum/Vk/Test/MeshTestFiles/flat.spv new file mode 100644 index 0000000000000000000000000000000000000000..a833ae87770f8b4bf669877330669b2fe6461a51 GIT binary patch literal 444 zcmX|+%MJli5Jbxi#&aGd>}14}wMb-G`VI>|U>4Rs+eV_!PPI_~A3tubI~1ygdg?j`_x5Tgm9D_OSQkh1t_%#$)^T88&P=S&-Hp=P zUCa|N91rYkW9j*!JsgeN@5r7T$sd0lO>)%HLNnGQCr3Y=8{k`Wc25i}@@Zht^UhbX yCpXB^2Z;CH!oBEX-2d`_3j4-;nJW!$J6R1q_}dc)BhK?LEY4Ss>E}jtWB&)PTMm5y literal 0 HcmV?d00001 diff --git a/src/Magnum/Vk/Test/MeshTestFiles/flat.spvasm b/src/Magnum/Vk/Test/MeshTestFiles/flat.spvasm new file mode 100644 index 000000000..18dcfda06 --- /dev/null +++ b/src/Magnum/Vk/Test/MeshTestFiles/flat.spvasm @@ -0,0 +1,35 @@ + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %ver "ver" %position %gl_Position + OpEntryPoint Fragment %fra "fra" %fragmentColor + OpExecutionMode %fra OriginUpperLeft + OpDecorate %position Location 0 + OpDecorate %gl_Position BuiltIn Position + OpDecorate %fragmentColor Location 0 + + %void = OpTypeVoid + %10 = OpTypeFunction %void + %float = OpTypeFloat 32 + %v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float + %position = OpVariable %_ptr_Input_v4float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_Position = OpVariable %_ptr_Output_v4float Output +%fragmentColor = OpVariable %_ptr_Output_v4float Output + + %9 = OpConstant %float 0 + %11 = OpConstant %float 1 + %12 = OpConstantComposite %v4float %11 %9 %9 %11 + + %ver = OpFunction %void None %10 + %ver_ = OpLabel + %16 = OpLoad %v4float %position + OpStore %gl_Position %16 + OpReturn + OpFunctionEnd + + %fra = OpFunction %void None %10 + %fra_ = OpLabel + OpStore %fragmentColor %12 + OpReturn + OpFunctionEnd diff --git a/src/Magnum/Vk/Test/MeshTestFiles/flat.tga b/src/Magnum/Vk/Test/MeshTestFiles/flat.tga new file mode 100644 index 0000000000000000000000000000000000000000..b41ef549b7e65eb6c83eae2f3dd3cfa284cb38e0 GIT binary patch literal 338 pcmZQz;9`IQ1qLAGm@h9c|DOn=9i*Xwc^(R@X2 zZ-uW>4~Sy>D&8T{`SRy4c86TT>83q(dq=o-0NLj~a{qp0yZimC<~>jDn-DKdPycwK TIN?d}UM=aTKL}P@xOlFJ9U_|L*#vAi^0>}IZJ7o}r literal 0 HcmV?d00001 diff --git a/src/Magnum/Vk/Test/MeshTestFiles/vertexcolor.spvasm b/src/Magnum/Vk/Test/MeshTestFiles/vertexcolor.spvasm new file mode 100644 index 000000000..a8154302e --- /dev/null +++ b/src/Magnum/Vk/Test/MeshTestFiles/vertexcolor.spvasm @@ -0,0 +1,40 @@ + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %ver "ver" %position %color %gl_Position %interpolatedColorOut + OpEntryPoint Fragment %fra "fra" %interpolatedColorIn %fragmentColor + OpExecutionMode %fra OriginUpperLeft + + OpDecorate %gl_Position BuiltIn Position + OpDecorate %position Location 0 + OpDecorate %color Location 1 + OpDecorate %interpolatedColorIn Location 0 + OpDecorate %interpolatedColorOut Location 0 + OpDecorate %fragmentColor Location 0 + %void = OpTypeVoid + %10 = OpTypeFunction %void + %float = OpTypeFloat 32 + %v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float + %position = OpVariable %_ptr_Input_v4float Input + %color = OpVariable %_ptr_Input_v4float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_Position = OpVariable %_ptr_Output_v4float Output +%interpolatedColorOut = OpVariable %_ptr_Output_v4float Output +%interpolatedColorIn = OpVariable %_ptr_Input_v4float Input +%fragmentColor = OpVariable %_ptr_Output_v4float Output + + %ver = OpFunction %void None %10 + %ver_ = OpLabel + %16 = OpLoad %v4float %position + %17 = OpLoad %v4float %color + OpStore %gl_Position %16 + OpStore %interpolatedColorOut %17 + OpReturn + OpFunctionEnd + + %fra = OpFunction %void None %10 + %fra_ = OpLabel + %19 = OpLoad %v4float %interpolatedColorIn + OpStore %fragmentColor %19 + OpReturn + OpFunctionEnd diff --git a/src/Magnum/Vk/Test/MeshTestFiles/vertexcolor.tga b/src/Magnum/Vk/Test/MeshTestFiles/vertexcolor.tga new file mode 100644 index 0000000000000000000000000000000000000000..228b40b326db6ebfb9ba8b3bf1d02d49e7056de6 GIT binary patch literal 1362 zcma)+A+GH<6h#B8qR!xT3LZsf;0c5qhnlnsGz;=aK}I1VK_LMlAqE$iTww5k!IKQW zWUzXks;W}ZPb5pRPnNd!IonPQ`x6~@^tPgp z1%1uvIHA*s&I7vi=-Q!M|HK*fiP3M2d1360aVN&_n6P5D1#`@pYr@14lLk!gF{Q(Z zI@5+H&Z;+7zp&=U+7s(`tY5KV!EQ75n6TG~jRQ9I*xX@7ovrfS#wX6H7f#ld zb1Tj-xG>|k33rURYrw@GmpYuNbK;$=vfrjB&Z`@*PrTXjcE!5|?`M3N@Y{$#2K?3I zV}}=YUcB=~_N$z?`H2haBE$mW+d8y=z5~>h@vivcTqYUW$vP+BBq6yS7O5*gxlI=;ZBZ;~s-X-a5lDSKgiWC=8T1j~)m6KF&QhPmdS&d{pkWEjv9a+?6 z@h(eev&>zVRAk9)jw?Csa|njNliC3zo_L+t)JBP z^~5#R(L`Mn@0xVB$=o$bMU%`nxl!7&)2@>iZ(4fM@|#vZY4wZNe*Ta9@$qZ-;y+5m BU=9EP literal 0 HcmV?d00001 diff --git a/src/Magnum/Vk/Test/MeshVkTest.cpp b/src/Magnum/Vk/Test/MeshVkTest.cpp new file mode 100644 index 000000000..529cb6abd --- /dev/null +++ b/src/Magnum/Vk/Test/MeshVkTest.cpp @@ -0,0 +1,878 @@ +/* + 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 +#include + +#include "Magnum/ImageView.h" +#include "Magnum/PixelFormat.h" +#include "Magnum/DebugTools/CompareImage.h" +#include "Magnum/Math/Color.h" +#include "Magnum/Math/Range.h" +#include "Magnum/Trade/AbstractImporter.h" +#include "Magnum/Vk/BufferCreateInfo.h" +#include "Magnum/Vk/CommandBuffer.h" +#include "Magnum/Vk/CommandPoolCreateInfo.h" +#include "Magnum/Vk/DeviceCreateInfo.h" +#include "Magnum/Vk/DeviceFeatures.h" +#include "Magnum/Vk/DeviceProperties.h" +#include "Magnum/Vk/ExtensionProperties.h" +#include "Magnum/Vk/Extensions.h" +#include "Magnum/Vk/Fence.h" +#include "Magnum/Vk/FramebufferCreateInfo.h" +#include "Magnum/Vk/ImageCreateInfo.h" +#include "Magnum/Vk/ImageViewCreateInfo.h" +#include "Magnum/Vk/Mesh.h" +#include "Magnum/Vk/PipelineLayoutCreateInfo.h" +#include "Magnum/Vk/PixelFormat.h" +#include "Magnum/Vk/RasterizationPipelineCreateInfo.h" +#include "Magnum/Vk/RenderPassCreateInfo.h" +#include "Magnum/Vk/ShaderCreateInfo.h" +#include "Magnum/Vk/ShaderSet.h" +#include "Magnum/Vk/VertexFormat.h" +#include "Magnum/Vk/VulkanTester.h" + +#include "configure.h" + +namespace Magnum { namespace Vk { namespace Test { namespace { + +struct MeshVkTest: VulkanTester { + explicit MeshVkTest(); + + void setup(Device& device); + void setup() { setup(device()); } + void setupRobustness2(); + void setupExtendedDynamicState(); + void teardown(); + + void cmdDraw(); + void cmdDrawIndexed(); + void cmdDrawTwoAttributes(); + void cmdDrawTwoAttributesTwoBindings(); + void cmdDrawNullBindingRobustness2(); + void cmdDrawZeroCount(); + void cmdDrawNoCountSet(); + + void cmdDrawDynamicPrimitive(); + void cmdDrawDynamicStride(); + void cmdDrawDynamicStrideInsufficientImplementation(); + + Queue _queue{NoCreate}; + Device _deviceRobustness2{NoCreate}, _deviceExtendedDynamicState{NoCreate}; + CommandPool _pool{NoCreate}; + Image _color{NoCreate}; + RenderPass _renderPass{NoCreate}; + ImageView _colorView{NoCreate}; + Framebuffer _framebuffer{NoCreate}; + PipelineLayout _pipelineLayout{NoCreate}; + Buffer _pixels{NoCreate}; + + private: + PluginManager::Manager _manager{"nonexistent"}; +}; + +using namespace Containers::Literals; +using namespace Math::Literals; + +const struct { + const char* name; + UnsignedInt count; + UnsignedInt instanceCount; +} CmdDrawZeroCountData[] { + {"zero elements", 0, 1}, + {"zero instances", 4, 0} +}; + +MeshVkTest::MeshVkTest() { + addTests({&MeshVkTest::cmdDraw, + &MeshVkTest::cmdDrawIndexed, + &MeshVkTest::cmdDrawTwoAttributes, + &MeshVkTest::cmdDrawTwoAttributesTwoBindings}, + &MeshVkTest::setup, + &MeshVkTest::teardown); + + addTests({&MeshVkTest::cmdDrawNullBindingRobustness2}, + &MeshVkTest::setupRobustness2, + &MeshVkTest::teardown); + + addInstancedTests({&MeshVkTest::cmdDrawZeroCount}, + Containers::arraySize(CmdDrawZeroCountData), + &MeshVkTest::setup, + &MeshVkTest::teardown); + + addTests({&MeshVkTest::cmdDrawNoCountSet}, + &MeshVkTest::setup, + &MeshVkTest::teardown); + + addTests({&MeshVkTest::cmdDrawDynamicPrimitive, + &MeshVkTest::cmdDrawDynamicStride}, + &MeshVkTest::setupExtendedDynamicState, + &MeshVkTest::teardown); + + addTests({&MeshVkTest::cmdDrawDynamicStrideInsufficientImplementation}, + &MeshVkTest::setup, + &MeshVkTest::teardown); + + /* Load the plugins directly from the build tree. Otherwise they're either + static and already loaded or not present in the build tree */ + #ifdef ANYIMAGEIMPORTER_PLUGIN_FILENAME + CORRADE_INTERNAL_ASSERT_OUTPUT(_manager.load(ANYIMAGEIMPORTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + #ifdef TGAIMPORTER_PLUGIN_FILENAME + CORRADE_INTERNAL_ASSERT_OUTPUT(_manager.load(TGAIMPORTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif +} + +void MeshVkTest::setup(Device& device) { + _pool = CommandPool{device, CommandPoolCreateInfo{ + device.properties().pickQueueFamily(QueueFlag::Graphics)}}; + _color = Image{device, ImageCreateInfo2D{ + ImageUsage::ColorAttachment|ImageUsage::TransferSource, + PixelFormat::RGBA8Srgb, {32, 32}, 1 + }, Vk::MemoryFlag::DeviceLocal}; + _renderPass = RenderPass{device, RenderPassCreateInfo{} + .setAttachments({AttachmentDescription{ + _color.format(), + AttachmentLoadOperation::Clear, + AttachmentStoreOperation::Store, + ImageLayout::Undefined, + ImageLayout::TransferSource + }}) + .addSubpass(SubpassDescription{}.setColorAttachments({ + AttachmentReference{0, ImageLayout::ColorAttachment} + })) + /* So the color data are visible for the transfer */ + .setDependencies({SubpassDependency{ + 0, SubpassDependency::External, + PipelineStage::ColorAttachmentOutput, + PipelineStage::Transfer, + Access::ColorAttachmentWrite, + Access::TransferRead + }}) + }; + _colorView = ImageView{device, ImageViewCreateInfo2D{_color}}; + _framebuffer = Framebuffer{device, FramebufferCreateInfo{_renderPass, { + _colorView + }, {32, 32}}}; + _pipelineLayout = PipelineLayout{device, PipelineLayoutCreateInfo{}}; + _pixels = Buffer{device, BufferCreateInfo{ + BufferUsage::TransferDestination, 32*32*4 + }, Vk::MemoryFlag::HostVisible}; +} + +void MeshVkTest::setupRobustness2() { + DeviceProperties properties = pickDevice(instance()); + /* If the extension / feature isn't supported, do nothing */ + if(!properties.enumerateExtensionProperties().isSupported() || + !(properties.features() & DeviceFeature::NullDescriptor)) + return; + + /* Create the device only if not already, to avoid spamming the output */ + if(!_deviceRobustness2.handle()) _deviceRobustness2.create(instance(), DeviceCreateInfo{std::move(properties)} + .addQueues(QueueFlag::Graphics, {0.0f}, {_queue}) + .addEnabledExtensions() + .setEnabledFeatures(DeviceFeature::NullDescriptor) + ); + + setup(_deviceRobustness2); +} + +void MeshVkTest::setupExtendedDynamicState() { + DeviceProperties properties = pickDevice(instance()); + /* If the extension / feature isn't supported, do nothing */ + if(!properties.enumerateExtensionProperties().isSupported() || + !(properties.features() & DeviceFeature::ExtendedDynamicState)) + return; + + /* Create the device only if not already, to avoid spamming the output */ + if(!_deviceExtendedDynamicState.handle()) _deviceExtendedDynamicState.create(instance(), DeviceCreateInfo{std::move(properties)} + .addQueues(QueueFlag::Graphics, {0.0f}, {_queue}) + .addEnabledExtensions() + .setEnabledFeatures(DeviceFeature::ExtendedDynamicState) + ); + + setup(_deviceExtendedDynamicState); +} + +void MeshVkTest::teardown() { + _pool = CommandPool{NoCreate}; + _renderPass = RenderPass{NoCreate}; + _color = Image{NoCreate}; + _colorView = ImageView{NoCreate}; + _framebuffer = Framebuffer{NoCreate}; + _pipelineLayout = PipelineLayout{NoCreate}; + _pixels = Buffer{NoCreate}; +} + +const struct Quad { + Vector3 position; + Vector3 color; +} QuadData[] { + {{-0.5f, -0.5f, 0.0f}, 0xff0000_srgbf}, + {{ 0.5f, -0.5f, 0.0f}, 0x00ff00_srgbf}, + {{-0.5f, 0.5f, 0.0f}, 0x0000ff_srgbf}, + {{ 0.5f, 0.5f, 0.0f}, 0xffffff_srgbf} +}; + +constexpr UnsignedShort QuadIndexData[] { + 0, 1, 2, 2, 1, 3 +}; + +void MeshVkTest::cmdDraw() { + /* This is the most simple binding (no offsets, single attribute, single + buffer) to test the basic workflow. The cmdDrawIndexed() test and others + pile on the complexity, but when everything goes wrong it's good to have + a simple test case. */ + + Mesh mesh{MeshLayout{MeshPrimitive::TriangleStrip} + .addBinding(0, sizeof(Vector3)) + .addAttribute(0, 0, VertexFormat::Vector3, 0) + }; + { + Buffer buffer{device(), BufferCreateInfo{ + BufferUsage::VertexBuffer, + sizeof(Vector3)*4 + }, MemoryFlag::HostVisible}; + /** @todo ffs fucking casts!!! */ + Utility::copy( + Containers::stridedArrayView(QuadData).slice(&Quad::position), + Containers::arrayCast(Containers::arrayView(buffer.dedicatedMemory().map()))); + mesh.addVertexBuffer(0, std::move(buffer), 0) + .setCount(4); + } + + Shader shader{device(), ShaderCreateInfo{ + Utility::Directory::read(Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/flat.spv")) + }}; + + ShaderSet shaderSet; + shaderSet + .addShader(ShaderStage::Vertex, shader, "ver"_s) + .addShader(ShaderStage::Fragment, shader, "fra"_s); + + Pipeline pipeline{device(), RasterizationPipelineCreateInfo{ + shaderSet, mesh.layout(), _pipelineLayout, _renderPass, 0, 1} + .setViewport({{}, Vector2{_framebuffer.size().xy()}}) + }; + + CommandBuffer cmd = _pool.allocate(); + cmd.begin() + .beginRenderPass(Vk::RenderPassBeginInfo{_renderPass, _framebuffer} + .clearColor(0, 0x1f1f1f_srgbf) + ) + .bindPipeline(pipeline) + .draw(mesh) + .endRenderPass() + .copyImageToBuffer({_color, Vk::ImageLayout::TransferSource, _pixels, { + Vk::BufferImageCopy2D{0, Vk::ImageAspect::Color, 0, {{}, _framebuffer.size().xy()}} + }}) + .pipelineBarrier(Vk::PipelineStage::Transfer, Vk::PipelineStage::Host, { + {Vk::Access::TransferWrite, Vk::Access::HostRead, _pixels} + }) + .end(); + + queue().submit({SubmitInfo{}.setCommandBuffers({cmd})}).wait(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + CORRADE_COMPARE_WITH((ImageView2D{Magnum::PixelFormat::RGBA8Unorm, + _framebuffer.size().xy(), + _pixels.dedicatedMemory().mapRead()}), + Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/flat.tga"), + DebugTools::CompareImageToFile{_manager}); +} + +void MeshVkTest::cmdDrawIndexed() { + Mesh mesh{MeshLayout{MeshPrimitive::Triangles} + .addBinding(0, sizeof(Vector3)) + .addAttribute(0, 0, VertexFormat::Vector3, 0) + }; + { + Buffer buffer{device(), BufferCreateInfo{ + BufferUsage::VertexBuffer|BufferUsage::IndexBuffer, + /* Artifical offset at the beginning to test that the offset is + used correctly in both cases */ + 32 + 12*4 + sizeof(QuadIndexData) + }, MemoryFlag::HostVisible}; + Containers::Array data = buffer.dedicatedMemory().map(); + /** @todo ffs fucking casts!!! */ + Utility::copy(Containers::stridedArrayView(QuadData).slice(&Quad::position), + Containers::arrayCast(data.slice(32, 32 + 12*4))); + Utility::copy(Containers::arrayCast(QuadIndexData), + Containers::stridedArrayView(data).suffix(32 + 12*4)); + mesh.addVertexBuffer(0, buffer, 32) + .setIndexBuffer(std::move(buffer), 32 + 12*4, MeshIndexType::UnsignedShort) + .setCount(6); + } + + Shader shader{device(), ShaderCreateInfo{ + Utility::Directory::read(Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/flat.spv")) + }}; + + ShaderSet shaderSet; + shaderSet + .addShader(ShaderStage::Vertex, shader, "ver"_s) + .addShader(ShaderStage::Fragment, shader, "fra"_s); + + Pipeline pipeline{device(), RasterizationPipelineCreateInfo{ + shaderSet, mesh.layout(), _pipelineLayout, _renderPass, 0, 1} + .setViewport({{}, Vector2{_framebuffer.size().xy()}}) + }; + + CommandBuffer cmd = _pool.allocate(); + cmd.begin() + .beginRenderPass(Vk::RenderPassBeginInfo{_renderPass, _framebuffer} + .clearColor(0, 0x1f1f1f_srgbf) + ) + .bindPipeline(pipeline) + .draw(mesh) + .endRenderPass() + .copyImageToBuffer({_color, Vk::ImageLayout::TransferSource, _pixels, { + Vk::BufferImageCopy2D{0, Vk::ImageAspect::Color, 0, {{}, _framebuffer.size().xy()}} + }}) + .pipelineBarrier(Vk::PipelineStage::Transfer, Vk::PipelineStage::Host, { + {Vk::Access::TransferWrite, Vk::Access::HostRead, _pixels} + }) + .end(); + + queue().submit({SubmitInfo{}.setCommandBuffers({cmd})}).wait(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + CORRADE_COMPARE_WITH((ImageView2D{Magnum::PixelFormat::RGBA8Unorm, + _framebuffer.size().xy(), + _pixels.dedicatedMemory().mapRead()}), + Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/flat.tga"), + DebugTools::CompareImageToFile{_manager}); +} + +void MeshVkTest::cmdDrawTwoAttributes() { + Mesh mesh{MeshLayout{MeshPrimitive::TriangleStrip} + .addBinding(0, sizeof(Quad)) + .addAttribute(0, 0, VertexFormat::Vector3, offsetof(Quad, position)) + .addAttribute(1, 0, VertexFormat::Vector3, offsetof(Quad, color)) + }; + { + Buffer buffer{device(), BufferCreateInfo{ + BufferUsage::VertexBuffer, + sizeof(QuadData) + }, MemoryFlag::HostVisible}; + /** @todo ffs fucking casts!!! */ + Utility::copy(Containers::arrayCast(QuadData), + Containers::stridedArrayView(buffer.dedicatedMemory().map())); + mesh.addVertexBuffer(0, std::move(buffer), 0) + .setCount(4); + } + + Shader shader{device(), ShaderCreateInfo{ + Utility::Directory::read(Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/vertexcolor.spv")) + }}; + + ShaderSet shaderSet; + shaderSet + .addShader(ShaderStage::Vertex, shader, "ver"_s) + .addShader(ShaderStage::Fragment, shader, "fra"_s); + + Pipeline pipeline{device(), RasterizationPipelineCreateInfo{ + shaderSet, mesh.layout(), _pipelineLayout, _renderPass, 0, 1} + .setViewport({{}, Vector2{_framebuffer.size().xy()}}) + }; + + CommandBuffer cmd = _pool.allocate(); + cmd.begin() + .beginRenderPass(Vk::RenderPassBeginInfo{_renderPass, _framebuffer} + .clearColor(0, 0x1f1f1f_srgbf) + ) + .bindPipeline(pipeline) + .draw(mesh) + .endRenderPass() + .copyImageToBuffer({_color, Vk::ImageLayout::TransferSource, _pixels, { + Vk::BufferImageCopy2D{0, Vk::ImageAspect::Color, 0, {{}, _framebuffer.size().xy()}} + }}) + .pipelineBarrier(Vk::PipelineStage::Transfer, Vk::PipelineStage::Host, { + {Vk::Access::TransferWrite, Vk::Access::HostRead, _pixels} + }) + .end(); + + queue().submit({SubmitInfo{}.setCommandBuffers({cmd})}).wait(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + CORRADE_COMPARE_WITH((ImageView2D{Magnum::PixelFormat::RGBA8Unorm, + _framebuffer.size().xy(), + _pixels.dedicatedMemory().mapRead()}), + Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/vertexcolor.tga"), + /* ARM Mali (Android) has some minor off-by-one differences */ + (DebugTools::CompareImageToFile{_manager, 0.5f, 0.012f})); +} + +void MeshVkTest::cmdDrawTwoAttributesTwoBindings() { + Mesh mesh{MeshLayout{MeshPrimitive::TriangleStrip} + .addBinding(0, sizeof(Vector3)) + .addBinding(1, sizeof(Vector3)) + .addAttribute(0, 0, VertexFormat::Vector3, 0) + .addAttribute(1, 1, VertexFormat::Vector3, 0) + }; + { + Buffer positions{device(), BufferCreateInfo{ + BufferUsage::VertexBuffer, + sizeof(Vector3)*4 + }, MemoryFlag::HostVisible}; + Buffer colors{device(), BufferCreateInfo{ + BufferUsage::VertexBuffer, + sizeof(Vector3)*4 + }, MemoryFlag::HostVisible}; + /** @todo ffs fucking casts!!! */ + Utility::copy(Containers::stridedArrayView(QuadData).slice(&Quad::position), + Containers::arrayCast(Containers::arrayView(positions.dedicatedMemory().map()))); + Utility::copy(Containers::stridedArrayView(QuadData).slice(&Quad::color), + Containers::arrayCast(Containers::arrayView(colors.dedicatedMemory().map()))); + mesh.addVertexBuffer(0, std::move(positions), 0) + .addVertexBuffer(1, std::move(colors), 0) + .setCount(4); + } + + Shader shader{device(), ShaderCreateInfo{ + Utility::Directory::read(Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/vertexcolor.spv")) + }}; + + ShaderSet shaderSet; + shaderSet + .addShader(ShaderStage::Vertex, shader, "ver"_s) + .addShader(ShaderStage::Fragment, shader, "fra"_s); + + Pipeline pipeline{device(), RasterizationPipelineCreateInfo{ + shaderSet, mesh.layout(), _pipelineLayout, _renderPass, 0, 1} + .setViewport({{}, Vector2{_framebuffer.size().xy()}}) + }; + + CommandBuffer cmd = _pool.allocate(); + cmd.begin() + .beginRenderPass(Vk::RenderPassBeginInfo{_renderPass, _framebuffer} + .clearColor(0, 0x1f1f1f_srgbf) + ) + .bindPipeline(pipeline) + .draw(mesh) + .endRenderPass() + .copyImageToBuffer({_color, Vk::ImageLayout::TransferSource, _pixels, { + Vk::BufferImageCopy2D{0, Vk::ImageAspect::Color, 0, {{}, _framebuffer.size().xy()}} + }}) + .pipelineBarrier(Vk::PipelineStage::Transfer, Vk::PipelineStage::Host, { + {Vk::Access::TransferWrite, Vk::Access::HostRead, _pixels} + }) + .end(); + + queue().submit({SubmitInfo{}.setCommandBuffers({cmd})}).wait(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + CORRADE_COMPARE_WITH((ImageView2D{Magnum::PixelFormat::RGBA8Unorm, + _framebuffer.size().xy(), + _pixels.dedicatedMemory().mapRead()}), + Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/vertexcolor.tga"), + /* ARM Mali (Android) has some minor off-by-one differences */ + (DebugTools::CompareImageToFile{_manager, 0.5f, 0.012f})); +} + +void MeshVkTest::cmdDrawNullBindingRobustness2() { + if(!(_deviceRobustness2.enabledFeatures() & DeviceFeature::NullDescriptor)) + CORRADE_SKIP("DeviceFeature::NullDescriptor not supported, can't test."); + + Mesh mesh{MeshLayout{MeshPrimitive::TriangleStrip} + .addBinding(0, sizeof(Vector3)) + .addBinding(1, sizeof(Vector3)) + .addAttribute(0, 0, VertexFormat::Vector3, 0) + .addAttribute(1, 1, VertexFormat::Vector3, 0) + }; + { + Buffer positions{_deviceRobustness2, BufferCreateInfo{ + BufferUsage::VertexBuffer, + sizeof(Vector3)*4 + }, MemoryFlag::HostVisible}; + /** @todo ffs fucking casts!!! */ + Utility::copy(Containers::stridedArrayView(QuadData).slice(&Quad::position), + Containers::arrayCast(Containers::arrayView(positions.dedicatedMemory().map()))); + mesh.addVertexBuffer(0, std::move(positions), 0) + .setCount(4); + } + + Shader shader{_deviceRobustness2, ShaderCreateInfo{ + Utility::Directory::read(Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/vertexcolor.spv")) + }}; + + ShaderSet shaderSet; + shaderSet + .addShader(ShaderStage::Vertex, shader, "ver"_s) + .addShader(ShaderStage::Fragment, shader, "fra"_s); + + Pipeline pipeline{_deviceRobustness2, RasterizationPipelineCreateInfo{ + shaderSet, mesh.layout(), _pipelineLayout, _renderPass, 0, 1} + .setViewport({{}, Vector2{_framebuffer.size().xy()}}) + }; + + CommandBuffer cmd = _pool.allocate(); + cmd.begin() + .beginRenderPass(Vk::RenderPassBeginInfo{_renderPass, _framebuffer} + .clearColor(0, 0x1f1f1f_srgbf) + ) + .bindPipeline(pipeline) + .draw(mesh) + .endRenderPass() + .copyImageToBuffer({_color, Vk::ImageLayout::TransferSource, _pixels, { + Vk::BufferImageCopy2D{0, Vk::ImageAspect::Color, 0, {{}, _framebuffer.size().xy()}} + }}) + .pipelineBarrier(Vk::PipelineStage::Transfer, Vk::PipelineStage::Host, { + {Vk::Access::TransferWrite, Vk::Access::HostRead, _pixels} + }) + .end(); + + _queue.submit({SubmitInfo{}.setCommandBuffers({cmd})}).wait(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + CORRADE_COMPARE_WITH((ImageView2D{Magnum::PixelFormat::RGBA8Unorm, + _framebuffer.size().xy(), + _pixels.dedicatedMemory().mapRead()}), + Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/nullcolor.tga"), + /* ARM Mali (Android) has some minor off-by-one differences */ + (DebugTools::CompareImageToFile{_manager})); +} + +void MeshVkTest::cmdDrawZeroCount() { + auto&& data = CmdDrawZeroCountData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + Mesh mesh{MeshLayout{MeshPrimitive::Triangles} + .addBinding(0, sizeof(Vector3)) + .addAttribute(0, 0, VertexFormat::Vector3, 0) + }; + /* Deliberately not setting up any buffer -- the draw() should be a no-op + and thus no draw validation (and error messages) should happen */ + mesh.setCount(data.count) + .setInstanceCount(data.instanceCount); + + Shader shader{device(), ShaderCreateInfo{ + Utility::Directory::read(Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/flat.spv")) + }}; + + ShaderSet shaderSet; + shaderSet + .addShader(ShaderStage::Vertex, shader, "ver"_s) + .addShader(ShaderStage::Fragment, shader, "fra"_s); + + Pipeline pipeline{device(), RasterizationPipelineCreateInfo{ + shaderSet, mesh.layout(), _pipelineLayout, _renderPass, 0, 1} + .setViewport({{}, Vector2{_framebuffer.size().xy()}}) + }; + + CommandBuffer cmd = _pool.allocate(); + cmd.begin() + .beginRenderPass(Vk::RenderPassBeginInfo{_renderPass, _framebuffer} + .clearColor(0, 0x1f1f1f_srgbf) + ) + .bindPipeline(pipeline) + .draw(mesh) + .endRenderPass() + .copyImageToBuffer({_color, Vk::ImageLayout::TransferSource, _pixels, { + Vk::BufferImageCopy2D{0, Vk::ImageAspect::Color, 0, {{}, _framebuffer.size().xy()}} + }}) + .pipelineBarrier(Vk::PipelineStage::Transfer, Vk::PipelineStage::Host, { + {Vk::Access::TransferWrite, Vk::Access::HostRead, _pixels} + }) + .end(); + + queue().submit({SubmitInfo{}.setCommandBuffers({cmd})}).wait(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + CORRADE_COMPARE_WITH((ImageView2D{Magnum::PixelFormat::RGBA8Unorm, + _framebuffer.size().xy(), + _pixels.dedicatedMemory().mapRead()}), + Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/noop.tga"), + DebugTools::CompareImageToFile{_manager}); +} + +void MeshVkTest::cmdDrawNoCountSet() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + Mesh mesh{MeshLayout{MeshPrimitive::Triangles}}; + + Shader shader{device(), ShaderCreateInfo{ + Utility::Directory::read(Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/noop.spv")) + }}; + + ShaderSet shaderSet; + shaderSet + .addShader(ShaderStage::Vertex, shader, "ver"_s) + .addShader(ShaderStage::Fragment, shader, "fra"_s); + + Pipeline pipeline{device(), RasterizationPipelineCreateInfo{ + shaderSet, mesh.layout(), _pipelineLayout, _renderPass, 0, 1} + .setViewport({{}, Vector2{_framebuffer.size().xy()}}) + }; + + CommandBuffer cmd = _pool.allocate(); + cmd.begin() + .beginRenderPass(Vk::RenderPassBeginInfo{_renderPass, _framebuffer} + .clearColor(0, 0x1f1f1f_srgbf) + ) + .bindPipeline(pipeline); + + std::ostringstream out; + Error redirectError{&out}; + cmd.draw(mesh); + CORRADE_COMPARE(out.str(), "Vk::CommandBuffer::draw(): Mesh::setCount() was never called, probably a mistake?\n"); +} + +void MeshVkTest::cmdDrawDynamicPrimitive() { + if(!(_deviceExtendedDynamicState.enabledFeatures() & DeviceFeature::ExtendedDynamicState)) + CORRADE_SKIP("DeviceFeature::ExtendedDynamicState not supported, can't test."); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + Mesh mesh{MeshLayout{MeshPrimitive::TriangleStrip} + .addBinding(0, sizeof(Vector3)) + .addAttribute(0, 0, VertexFormat::Vector3, 0) + }; + { + Buffer buffer{_deviceExtendedDynamicState, BufferCreateInfo{ + BufferUsage::VertexBuffer, + sizeof(Vector3)*4 + }, MemoryFlag::HostVisible}; + /** @todo ffs fucking casts!!! */ + Utility::copy( + Containers::stridedArrayView(QuadData).slice(&Quad::position), + Containers::arrayCast(Containers::arrayView(buffer.dedicatedMemory().map()))); + mesh.addVertexBuffer(0, std::move(buffer), 0) + .setCount(4); + } + + Shader shader{_deviceExtendedDynamicState, ShaderCreateInfo{ + Utility::Directory::read(Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/flat.spv")) + }}; + + ShaderSet shaderSet; + shaderSet + .addShader(ShaderStage::Vertex, shader, "ver"_s) + .addShader(ShaderStage::Fragment, shader, "fra"_s); + + /* Create the pipeline with Triangles while the mesh is TriangleStrip */ + MeshLayout pipelineLayout{MeshPrimitive::Triangles}; + pipelineLayout + .addBinding(0, sizeof(Vector3)) + .addAttribute(0, 0, VertexFormat::Vector3, 0); + Pipeline pipeline{_deviceExtendedDynamicState, RasterizationPipelineCreateInfo{ + shaderSet, pipelineLayout, _pipelineLayout, _renderPass, 0, 1} + .setViewport({{}, Vector2{_framebuffer.size().xy()}}) + .setDynamicStates(DynamicRasterizationState::MeshPrimitive) + }; + + CommandBuffer cmd = _pool.allocate(); + cmd.begin() + .beginRenderPass(Vk::RenderPassBeginInfo{_renderPass, _framebuffer} + .clearColor(0, 0x1f1f1f_srgbf) + ) + .bindPipeline(pipeline) + .draw(mesh) + .endRenderPass() + .copyImageToBuffer({_color, Vk::ImageLayout::TransferSource, _pixels, { + Vk::BufferImageCopy2D{0, Vk::ImageAspect::Color, 0, {{}, _framebuffer.size().xy()}} + }}) + .pipelineBarrier(Vk::PipelineStage::Transfer, Vk::PipelineStage::Host, { + {Vk::Access::TransferWrite, Vk::Access::HostRead, _pixels} + }) + .end(); + + _queue.submit({SubmitInfo{}.setCommandBuffers({cmd})}).wait(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + CORRADE_COMPARE_WITH((ImageView2D{Magnum::PixelFormat::RGBA8Unorm, + _framebuffer.size().xy(), + _pixels.dedicatedMemory().mapRead()}), + Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/flat.tga"), + DebugTools::CompareImageToFile{_manager}); +} + +void MeshVkTest::cmdDrawDynamicStride() { + if(!(_deviceExtendedDynamicState.enabledFeatures() & DeviceFeature::ExtendedDynamicState)) + CORRADE_SKIP("DeviceFeature::ExtendedDynamicState not supported, can't test."); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + Mesh mesh{MeshLayout{MeshPrimitive::TriangleStrip} + .addBinding(0, sizeof(Vector3)) + .addAttribute(0, 0, VertexFormat::Vector3, 0) + }; + { + Buffer buffer{_deviceExtendedDynamicState, BufferCreateInfo{ + BufferUsage::VertexBuffer, + sizeof(Vector3)*4 + }, MemoryFlag::HostVisible}; + /** @todo ffs fucking casts!!! */ + Utility::copy( + Containers::stridedArrayView(QuadData).slice(&Quad::position), + Containers::arrayCast(Containers::arrayView(buffer.dedicatedMemory().map()))); + mesh.addVertexBuffer(0, std::move(buffer), 0) + .setCount(4); + } + + Shader shader{_deviceExtendedDynamicState, ShaderCreateInfo{ + Utility::Directory::read(Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/flat.spv")) + }}; + + ShaderSet shaderSet; + shaderSet + .addShader(ShaderStage::Vertex, shader, "ver"_s) + .addShader(ShaderStage::Fragment, shader, "fra"_s); + + /* Create the pipeline with a 1 kB stride, while the actual stride is + different */ + MeshLayout pipelineLayout{MeshPrimitive::TriangleStrip}; + pipelineLayout + .addBinding(0, 1024) + .addAttribute(0, 0, VertexFormat::Vector3, 0); + Pipeline pipeline{_deviceExtendedDynamicState, RasterizationPipelineCreateInfo{ + shaderSet, pipelineLayout, _pipelineLayout, _renderPass, 0, 1} + .setViewport({{}, Vector2{_framebuffer.size().xy()}}) + .setDynamicStates(DynamicRasterizationState::VertexInputBindingStride) + }; + + CommandBuffer cmd = _pool.allocate(); + cmd.begin() + .beginRenderPass(Vk::RenderPassBeginInfo{_renderPass, _framebuffer} + .clearColor(0, 0x1f1f1f_srgbf) + ) + .bindPipeline(pipeline) + .draw(mesh) + .endRenderPass() + .copyImageToBuffer({_color, Vk::ImageLayout::TransferSource, _pixels, { + Vk::BufferImageCopy2D{0, Vk::ImageAspect::Color, 0, {{}, _framebuffer.size().xy()}} + }}) + .pipelineBarrier(Vk::PipelineStage::Transfer, Vk::PipelineStage::Host, { + {Vk::Access::TransferWrite, Vk::Access::HostRead, _pixels} + }) + .end(); + + _queue.submit({SubmitInfo{}.setCommandBuffers({cmd})}).wait(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + CORRADE_COMPARE_WITH((ImageView2D{Magnum::PixelFormat::RGBA8Unorm, + _framebuffer.size().xy(), + _pixels.dedicatedMemory().mapRead()}), + Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/flat.tga"), + DebugTools::CompareImageToFile{_manager}); +} + +void MeshVkTest::cmdDrawDynamicStrideInsufficientImplementation() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + if(device().isExtensionEnabled()) + CORRADE_SKIP("VK_EXT_extended_dynamic_state enabled, can't test."); + + Mesh mesh{MeshLayout{MeshPrimitive::TriangleStrip} + .addBinding(0, sizeof(Vector3)) + .addAttribute(0, 0, VertexFormat::Vector3, 0) + }; + { + Buffer buffer{device(), BufferCreateInfo{ + BufferUsage::VertexBuffer, + sizeof(Vector3)*4 + }, MemoryFlag::HostVisible}; + /** @todo ffs fucking casts!!! */ + Utility::copy( + Containers::stridedArrayView(QuadData).slice(&Quad::position), + Containers::arrayCast(Containers::arrayView(buffer.dedicatedMemory().map()))); + mesh.addVertexBuffer(0, std::move(buffer), 0) + .setCount(4); + } + + Shader shader{device(), ShaderCreateInfo{ + Utility::Directory::read(Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/flat.spv")) + }}; + + ShaderSet shaderSet; + shaderSet + .addShader(ShaderStage::Vertex, shader, "ver"_s) + .addShader(ShaderStage::Fragment, shader, "fra"_s); + + /* Create a pipeline without any dynamic state and then wrap it with fake + enabled vertex input binding stride -- doing so directly would trigger + validation layer failures (using dynamic state from a non-enabled ext), + which we don't want */ + Pipeline pipeline{device(), RasterizationPipelineCreateInfo{ + shaderSet, mesh.layout(), _pipelineLayout, _renderPass, 0, 1} + .setViewport({{}, Vector2{_framebuffer.size().xy()}}) + }; + Pipeline fakeDynamicStatePipeline = Pipeline::wrap(device(), + PipelineBindPoint::Rasterization, pipeline, + DynamicRasterizationState::VertexInputBindingStride); + + CommandBuffer cmd = _pool.allocate(); + cmd.begin() + .beginRenderPass(Vk::RenderPassBeginInfo{_renderPass, _framebuffer} + .clearColor(0, 0x1f1f1f_srgbf) + ) + .bindPipeline(fakeDynamicStatePipeline); + + std::ostringstream out; + Error redirectError{&out}; + cmd.draw(mesh); + CORRADE_COMPARE(out.str(), "Vk::CommandBuffer::draw(): dynamic strides supplied for an implementation without extended dynamic state\n"); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::Vk::Test::MeshVkTest) diff --git a/src/Magnum/Vk/Test/configure.h.cmake b/src/Magnum/Vk/Test/configure.h.cmake index 78fdf20a1..cbb448d13 100644 --- a/src/Magnum/Vk/Test/configure.h.cmake +++ b/src/Magnum/Vk/Test/configure.h.cmake @@ -23,4 +23,6 @@ DEALINGS IN THE SOFTWARE. */ +#cmakedefine ANYIMAGEIMPORTER_PLUGIN_FILENAME "${ANYIMAGEIMPORTER_PLUGIN_FILENAME}" +#cmakedefine TGAIMPORTER_PLUGIN_FILENAME "${TGAIMPORTER_PLUGIN_FILENAME}" #define VK_TEST_DIR "${VK_TEST_DIR}" diff --git a/src/Magnum/Vk/VertexFormat.h b/src/Magnum/Vk/VertexFormat.h index 6aaae1243..55e6c65f7 100644 --- a/src/Magnum/Vk/VertexFormat.h +++ b/src/Magnum/Vk/VertexFormat.h @@ -363,6 +363,7 @@ cast to @ref VertexFormat. Not all generic vertex formats have a Vulkan equivalent and this function expects that given format is available. Use @ref hasVertexFormat() to query availability of given format. +@see @ref meshIndexType(), @ref meshPrimitive() */ MAGNUM_VK_EXPORT VertexFormat vertexFormat(Magnum::VertexFormat format); diff --git a/src/Magnum/Vk/Vk.h b/src/Magnum/Vk/Vk.h index 50207fcdb..2d3fc2810 100644 --- a/src/Magnum/Vk/Vk.h +++ b/src/Magnum/Vk/Vk.h @@ -97,6 +97,8 @@ enum class MemoryFlag: UnsignedInt; typedef Containers::EnumSet MemoryFlags; enum class MemoryHeapFlag: UnsignedInt; typedef Containers::EnumSet MemoryHeapFlags; +class Mesh; +enum class MeshIndexType: Int; class MeshLayout; enum class MeshPrimitive: Int; class Pipeline;