You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

442 lines
16 KiB

#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, 2022 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}.
@m_enum_values_as_keywords
@see @ref Magnum::MeshIndexType, @ref meshIndexType(),
@ref Mesh::setIndexBuffer()
*/
enum class MeshIndexType: Int {
/**
* @ref Magnum::UnsignedByte "UnsignedByte".
*
* @m_class{m-note m-success}
*
* @par
* Discouraged on contemporary GPU architectures, prefer to use 16-bit
* indices instead.
*
* @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
In case @ref isMeshIndexTypeImplementationSpecific() returns @cpp false @ce for
@p type, maps it to a corresponding Vulkan type. In case
@ref isMeshIndexTypeImplementationSpecific() returns @cpp true @ce, assumes
@p type stores a Vulkan-specific format and returns @ref meshIndexTypeUnwrap()
cast to @ref MeshIndexType.
@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. Expected to have been created with
* @ref BufferUsage::VertexBuffer.
* @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. Expected to have been created with
* @ref BufferUsage::IndexBuffer.
* @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
* @brief Set an index buffer with a generic index type
*
* Note that implementation-specific values are passed as-is with
* @ref meshIndexTypeUnwrap(). It's the user responsibility to ensure
* an implementation-specific actually represents a valid Vulkan index
* type.
*/
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
* @brief Set an index buffer with a generic index type and take over its ownership
*
* Note that implementation-specific values are passed as-is with
* @ref meshIndexTypeUnwrap(). It's the user responsibility to ensure
* an implementation-specific actually represents a valid Vulkan index
* type.
*/
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