Browse Source

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.
pull/495/head
Vladimír Vondruš 5 years ago
parent
commit
d4ab9f4cb0
  1. 5
      doc/changelog.dox
  2. 150
      doc/snippets/MagnumVk.cpp
  3. 11
      doc/vulkan-mapping.dox
  4. 2
      doc/vulkan-support.dox
  5. 2
      package/ci/unix-desktop-vulkan.sh
  6. 13
      src/Magnum/Mesh.h
  7. 2
      src/Magnum/Vk/CMakeLists.txt
  8. 13
      src/Magnum/Vk/CommandBuffer.h
  9. 2
      src/Magnum/Vk/DeviceFeatures.h
  10. 24
      src/Magnum/Vk/Enums.cpp
  11. 32
      src/Magnum/Vk/Enums.h
  12. 6
      src/Magnum/Vk/Implementation/DeviceState.cpp
  13. 2
      src/Magnum/Vk/Implementation/DeviceState.h
  14. 250
      src/Magnum/Vk/Mesh.cpp
  15. 406
      src/Magnum/Vk/Mesh.h
  16. 14
      src/Magnum/Vk/MeshLayout.h
  17. 9
      src/Magnum/Vk/RasterizationPipelineCreateInfo.h
  18. 49
      src/Magnum/Vk/Test/CMakeLists.txt
  19. 78
      src/Magnum/Vk/Test/EnumsTest.cpp
  20. 278
      src/Magnum/Vk/Test/MeshTest.cpp
  21. 5
      src/Magnum/Vk/Test/MeshTestFiles/convert.sh
  22. BIN
      src/Magnum/Vk/Test/MeshTestFiles/flat.spv
  23. 35
      src/Magnum/Vk/Test/MeshTestFiles/flat.spvasm
  24. BIN
      src/Magnum/Vk/Test/MeshTestFiles/flat.tga
  25. BIN
      src/Magnum/Vk/Test/MeshTestFiles/noop.spv
  26. 30
      src/Magnum/Vk/Test/MeshTestFiles/noop.spvasm
  27. BIN
      src/Magnum/Vk/Test/MeshTestFiles/noop.tga
  28. BIN
      src/Magnum/Vk/Test/MeshTestFiles/nullcolor.tga
  29. BIN
      src/Magnum/Vk/Test/MeshTestFiles/vertexcolor.spv
  30. 40
      src/Magnum/Vk/Test/MeshTestFiles/vertexcolor.spvasm
  31. BIN
      src/Magnum/Vk/Test/MeshTestFiles/vertexcolor.tga
  32. 878
      src/Magnum/Vk/Test/MeshVkTest.cpp
  33. 2
      src/Magnum/Vk/Test/configure.h.cmake
  34. 1
      src/Magnum/Vk/VertexFormat.h
  35. 2
      src/Magnum/Vk/Vk.h

5
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

150
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 */

11
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

2
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} | |

2
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 \

13
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
};

2
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

13
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);

2
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
*/

24
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));
}

32
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.

6
src/Magnum/Vk/Implementation/DeviceState.cpp

@ -85,6 +85,12 @@ DeviceState::DeviceState(Device& device, Containers::Array<std::pair<Containers:
cmdEndRenderPassImplementation = &CommandBuffer::endRenderPassImplementationDefault;
}
if(device.isExtensionEnabled<Extensions::EXT::extended_dynamic_state>()) {
cmdBindVertexBuffersImplementation = &CommandBuffer::bindVertexBuffersImplementationEXT;
} else {
cmdBindVertexBuffersImplementation = &CommandBuffer::bindVertexBuffersImplementationDefault;
}
if(device.isExtensionEnabled<Extensions::KHR::copy_commands2>()) {
cmdCopyBufferImplementation = &CommandBuffer::copyBufferImplementationKHR;
cmdCopyImageImplementation = &CommandBuffer::copyImageImplementationKHR;

2
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&);

250
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š <mosra@centrum.cz>
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 <Corrade/Containers/Array.h>
#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<VkBuffer> vertexBuffers;
Containers::ArrayView<UnsignedLong> vertexBufferOffsets;
Containers::ArrayView<UnsignedLong> vertexBufferStrides;
Containers::ArrayView<Buffer> 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<const VkBuffer> Mesh::vertexBuffers() {
return _state ? _state->vertexBuffers : nullptr;
}
Containers::ArrayView<const UnsignedLong> Mesh::vertexBufferOffsets() const {
return _state ? _state->vertexBufferOffsets : nullptr;
}
Containers::ArrayView<const UnsignedLong> 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<void>(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);
}
}}

406
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š <mosra@centrum.cz>
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 <Corrade/Containers/Pointer.h>
#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
<b></b>
@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<const VkBuffer> 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<const UnsignedLong> 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<const UnsignedLong> 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> _state;
};
}}
#endif

14
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;

9
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,

49
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 $<TARGET_FILE*> 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 $<TARGET_FILE:AnyImageImporter>)
endif()
if(WITH_TGAIMPORTER)
set(TGAIMPORTER_PLUGIN_FILENAME $<TARGET_FILE:TgaImporter>)
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}/$<CONFIG>/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}/$<CONFIG>)
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}/$<CONFIG>)
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}/$<CONFIG>)
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

78
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);

278
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š <mosra@centrum.cz>
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 <sstream>
#include <Corrade/TestSuite/Tester.h>
#include <Corrade/TestSuite/Compare/Container.h>
#include <Corrade/Utility/DebugStl.h>
#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<class T> void setIndexBuffer();
template<class T> 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<MeshIndexType>,
&MeshTest::setIndexBuffer<Magnum::MeshIndexType>,
&MeshTest::setIndexBufferOwned<MeshIndexType>,
&MeshTest::setIndexBufferOwned<Magnum::MeshIndexType>,
&MeshTest::indexPropertiesNotIndexed,
&MeshTest::debugIndexType});
}
template<class> struct IndexTypeTraits;
template<> struct IndexTypeTraits<MeshIndexType> {
static const char* name() { return "MeshIndexType"; }
};
template<> struct IndexTypeTraits<Magnum::MeshIndexType> {
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<UnsignedLong>({
0, 0
}), TestSuite::Compare::Container);
CORRADE_COMPARE_AS(mesh.vertexBufferStrides(), Containers::arrayView<UnsignedLong>({
0, 0
}), TestSuite::Compare::Container);
mesh.addVertexBuffer(5, reinterpret_cast<VkBuffer>(0xdead), 15);
CORRADE_COMPARE_AS(mesh.vertexBuffers(), Containers::arrayView({
VkBuffer{}, reinterpret_cast<VkBuffer>(0xdead)
}), TestSuite::Compare::Container);
CORRADE_COMPARE_AS(mesh.vertexBufferOffsets(), Containers::arrayView<UnsignedLong>({
0, 15
}), TestSuite::Compare::Container);
CORRADE_COMPARE_AS(mesh.vertexBufferStrides(), Containers::arrayView<UnsignedLong>({
0, 3
}), TestSuite::Compare::Container);
mesh.addVertexBuffer(1, reinterpret_cast<VkBuffer>(0xbeef), 37);
CORRADE_COMPARE_AS(mesh.vertexBuffers(), Containers::arrayView({
reinterpret_cast<VkBuffer>(0xbeef), reinterpret_cast<VkBuffer>(0xdead)
}), TestSuite::Compare::Container);
CORRADE_COMPARE_AS(mesh.vertexBufferOffsets(), Containers::arrayView<UnsignedLong>({
37, 15
}), TestSuite::Compare::Container);
CORRADE_COMPARE_AS(mesh.vertexBufferStrides(), Containers::arrayView<UnsignedLong>({
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<VkBuffer>(0xdead));
Buffer b = Buffer::wrap(device, reinterpret_cast<VkBuffer>(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<VkBuffer>(0xbeef), reinterpret_cast<VkBuffer>(0xdead)
}), TestSuite::Compare::Container);
CORRADE_COMPARE_AS(mesh.vertexBufferOffsets(), Containers::arrayView<UnsignedLong>({
37, 15
}), TestSuite::Compare::Container);
CORRADE_COMPARE_AS(mesh.vertexBufferStrides(), Containers::arrayView<UnsignedLong>({
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<class T> void MeshTest::setIndexBuffer() {
setTestCaseTemplateName(IndexTypeTraits<T>::name());
Mesh mesh{MeshLayout{MeshPrimitive::Triangles}};
CORRADE_VERIFY(!mesh.isIndexed());
mesh.setIndexBuffer(reinterpret_cast<VkBuffer>(0xdead), 15, T::UnsignedByte);
CORRADE_VERIFY(mesh.isIndexed());
CORRADE_COMPARE(mesh.indexBuffer(), reinterpret_cast<VkBuffer>(0xdead));
CORRADE_COMPARE(mesh.indexBufferOffset(), 15);
CORRADE_COMPARE(mesh.indexType(), MeshIndexType::UnsignedByte);
}
template<class T> void MeshTest::setIndexBufferOwned() {
setTestCaseTemplateName(IndexTypeTraits<T>::name());
Device device{NoCreate};
Buffer a = Buffer::wrap(device, reinterpret_cast<VkBuffer>(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<VkBuffer>(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)

5
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

BIN
src/Magnum/Vk/Test/MeshTestFiles/flat.spv

Binary file not shown.

35
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

BIN
src/Magnum/Vk/Test/MeshTestFiles/flat.tga

Binary file not shown.

BIN
src/Magnum/Vk/Test/MeshTestFiles/noop.spv

Binary file not shown.

30
src/Magnum/Vk/Test/MeshTestFiles/noop.spvasm

@ -0,0 +1,30 @@
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Vertex %ver "ver" %gl_Position
OpEntryPoint Fragment %fra "fra" %fragmentColor
OpExecutionMode %fra OriginUpperLeft
OpDecorate %gl_Position BuiltIn Position
OpDecorate %fragmentColor Location 0
%void = OpTypeVoid
%10 = OpTypeFunction %void
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%_ptr_Output_v4float = OpTypePointer Output %v4float
%gl_Position = OpVariable %_ptr_Output_v4float Output
%fragmentColor = OpVariable %_ptr_Output_v4float Output
%9 = OpConstant %float 0
%11 = OpConstantComposite %v4float %9 %9 %9 %9
%ver = OpFunction %void None %10
%ver_ = OpLabel
OpStore %gl_Position %11
OpReturn
OpFunctionEnd
%fra = OpFunction %void None %10
%fra_ = OpLabel
OpStore %fragmentColor %11
OpReturn
OpFunctionEnd

BIN
src/Magnum/Vk/Test/MeshTestFiles/noop.tga

Binary file not shown.

BIN
src/Magnum/Vk/Test/MeshTestFiles/nullcolor.tga

Binary file not shown.

BIN
src/Magnum/Vk/Test/MeshTestFiles/vertexcolor.spv

Binary file not shown.

40
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

BIN
src/Magnum/Vk/Test/MeshTestFiles/vertexcolor.tga

Binary file not shown.

878
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š <mosra@centrum.cz>
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 <sstream>
#include <Corrade/Containers/Array.h>
#include <Corrade/Containers/StringView.h>
#include <Corrade/PluginManager/Manager.h>
#include <Corrade/Utility/Algorithms.h>
#include <Corrade/Utility/DebugStl.h>
#include <Corrade/Utility/Directory.h>
#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<Trade::AbstractImporter> _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<Extensions::EXT::robustness2>() ||
!(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<Extensions::EXT::robustness2>()
.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<Extensions::EXT::extended_dynamic_state>() ||
!(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<Extensions::EXT::extended_dynamic_state>()
.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<Vector3>(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<char, MemoryMapDeleter> data = buffer.dedicatedMemory().map();
/** @todo ffs fucking casts!!! */
Utility::copy(Containers::stridedArrayView(QuadData).slice(&Quad::position),
Containers::arrayCast<Vector3>(data.slice(32, 32 + 12*4)));
Utility::copy(Containers::arrayCast<const char>(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<const char>(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<Vector3>(Containers::arrayView(positions.dedicatedMemory().map())));
Utility::copy(Containers::stridedArrayView(QuadData).slice(&Quad::color),
Containers::arrayCast<Vector3>(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<Vector3>(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<Vector3>(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<Vector3>(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<Extensions::EXT::extended_dynamic_state>())
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<Vector3>(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)

2
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}"

1
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);

2
src/Magnum/Vk/Vk.h

@ -97,6 +97,8 @@ enum class MemoryFlag: UnsignedInt;
typedef Containers::EnumSet<MemoryFlag> MemoryFlags;
enum class MemoryHeapFlag: UnsignedInt;
typedef Containers::EnumSet<MemoryHeapFlag> MemoryHeapFlags;
class Mesh;
enum class MeshIndexType: Int;
class MeshLayout;
enum class MeshPrimitive: Int;
class Pipeline;

Loading…
Cancel
Save