mirror of https://github.com/mosra/magnum.git
Browse Source
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
35 changed files with 2182 additions and 159 deletions
@ -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); |
||||
} |
||||
|
||||
}} |
||||
@ -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 |
||||
@ -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) |
||||
@ -0,0 +1,5 @@
|
||||
#!/bin/bash |
||||
|
||||
for i in $(ls *.spvasm); do |
||||
magnum-shaderconverter $i ${i%asm} |
||||
done |
||||
Binary file not shown.
@ -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 |
||||
Binary file not shown.
Binary file not shown.
@ -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 |
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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 |
||||
Binary file not shown.
@ -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) |
||||
Loading…
Reference in new issue