Browse Source

Trade: redo MeshData class-level documentation to make more sense.

Back in 2020 when I wrote this I didn't really expect the MeshData to be
directly used for much more than putting them on a GPU, mostly because
that used to be the primary use case with the old MeshData2D /
MeshData3D. So the documentation was focusing mainly on populating a GPU
mesh, and any docs for CPU-side access were added rather hastily.

As now the asset processing use case is much larger, the original docs no
longer made sense. Let's hope this is better.
pull/659/head
Vladimír Vondruš 2 years ago
parent
commit
55504a2d40
  1. 120
      doc/snippets/Trade.cpp
  2. 2
      src/Magnum/GL/Mesh.h
  3. 178
      src/Magnum/Trade/MeshData.h

120
doc/snippets/Trade.cpp

@ -792,48 +792,25 @@ Trade::MeshAttributeData normals{Trade::MeshAttribute::Normal,
#ifdef MAGNUM_TARGET_GL
{
/* This snippet is also used by GL::Mesh, bear that in mind when updating */
/* [MeshData-usage-compile] */
/* [MeshData-gpu-opengl] */
Trade::MeshData data = DOXYGEN_ELLIPSIS(Trade::MeshData{MeshPrimitive::Points, 0});
GL::Mesh mesh = MeshTools::compile(data);
/* [MeshData-usage-compile] */
/* [MeshData-gpu-opengl] */
}
{
Trade::MeshData data{MeshPrimitive::Points, 0};
/* [MeshData-usage] */
/* Check that we have at least positions and normals */
/* [MeshData-gpu-opengl-direct] */
GL::Mesh mesh{data.primitive()};
if(!data.hasAttribute(Trade::MeshAttribute::Position) ||
!data.hasAttribute(Trade::MeshAttribute::Normal))
Fatal{} << "Oh well";
/* Interleave vertex data */
GL::Buffer vertices;
vertices.setData(MeshTools::interleave(data.positions3DAsArray(),
data.normalsAsArray()));
mesh.addVertexBuffer(std::move(vertices), 0,
Shaders::PhongGL::Position{}, Shaders::PhongGL::Normal{});
/* Set up an index buffer, if the mesh is indexed */
if(data.isIndexed()) {
GL::Buffer indices;
indices.setData(data.indicesAsArray());
mesh.setIndexBuffer(std::move(indices), 0, MeshIndexType::UnsignedInt)
.setCount(data.indexCount());
} else mesh.setCount(data.vertexCount());
/* [MeshData-usage] */
}
mesh.setCount(data.indexCount());
{
Trade::MeshData data{MeshPrimitive::Points, 0};
GL::Mesh mesh{data.primitive()};
/* [MeshData-usage-advanced] */
/* Upload the original packed vertex data */
GL::Buffer vertices;
vertices.setData(data.vertexData());
/* Upload index data and configure their layout */
GL::Buffer indices{data.indexData()};
mesh.setIndexBuffer(indices, 0, data.indexType());
/* Set up the position and normal attributes */
/* Upload vertex data and set up position and normal attributes */
GL::Buffer vertices{data.vertexData()};
mesh.addVertexBuffer(vertices,
data.attributeOffset(Trade::MeshAttribute::Position),
data.attributeStride(Trade::MeshAttribute::Position),
@ -844,40 +821,73 @@ mesh.addVertexBuffer(vertices,
data.attributeStride(Trade::MeshAttribute::Normal),
GL::DynamicAttribute{Shaders::PhongGL::Normal{},
data.attributeFormat(Trade::MeshAttribute::Normal)});
/* Upload the original packed index data */
if(data.isIndexed()) {
GL::Buffer indices;
indices.setData(data.indexData());
mesh.setIndexBuffer(std::move(indices), 0, data.indexType())
.setCount(data.indexCount());
} else mesh.setCount(data.vertexCount());
/* [MeshData-usage-advanced] */
/* [MeshData-gpu-opengl-direct] */
}
#endif
{
Trade::MeshData data{MeshPrimitive::Points, 0};
/* [MeshData-usage-mutable] */
/* Check prerequisites */
if(!(data.vertexDataFlags() & Trade::DataFlag::Mutable) ||
!data.hasAttribute(Trade::MeshAttribute::Position) ||
data.attributeFormat(Trade::MeshAttribute::Position) != VertexFormat::Vector3)
/* [MeshData-access] */
if(data.primitive() != MeshPrimitive::Triangles ||
!data.isIndexed() ||
!data.hasAttribute(Trade::MeshAttribute::Position))
Fatal{} << "Oh well";
/* Calculate the face area */
Containers::Array<UnsignedInt> indices = data.indicesAsArray();
Containers::Array<Vector3> positions = data.positions3DAsArray();
Float area = 0.0f;
for(std::size_t i = 0; i < indices.size(); i += 3)
area += Math::cross(
positions[indices[i + 1]] - positions[indices[i]],
positions[indices[i + 2]] - positions[indices[i]]).length()*0.5f;
/* [MeshData-access] */
static_cast<void>(area);
}
{
Trade::MeshData data{MeshPrimitive::Points, 0};
/* [MeshData-access-direct] */
DOXYGEN_ELLIPSIS()
if(data.indexType() != MeshIndexType::UnsignedInt ||
data.attributeFormat(Trade::MeshAttribute::Position) != VertexFormat::Vector3)
Fatal{} << "Dang";
Containers::StridedArrayView1D<const UnsignedInt> indices =
data.indices<UnsignedInt>();
Containers::StridedArrayView1D<const Vector3> positions =
data.attribute<Vector3>(Trade::MeshAttribute::Position);
/* [MeshData-access-direct] */
static_cast<void>(indices);
static_cast<void>(positions);
}
{
Trade::MeshData data{MeshPrimitive::Points, 0};
/* [MeshData-access-mutable] */
DOXYGEN_ELLIPSIS()
if(data.attributeFormat(Trade::MeshAttribute::Position) != VertexFormat::Vector3)
Fatal{} << "Sigh";
/* Scale the mesh two times */
MeshTools::transformPointsInPlace(Matrix4::scaling(Vector3{2.0f}),
data.mutableAttribute<Vector3>(Trade::MeshAttribute::Position));
/* [MeshData-usage-mutable] */
Matrix4 transformation = Matrix4::scaling(Vector3{2.0f});
for(Vector3& i: data.mutableAttribute<Vector3>(Trade::MeshAttribute::Position))
i = transformation.transformPoint(i);
/* [MeshData-access-mutable] */
}
{
Trade::MeshData data{MeshPrimitive::Points, 0};
/* [MeshData-usage-morph-targets] */
/* [MeshData-access-morph-targets] */
if(!data.hasAttribute(Trade::MeshAttribute::Position, 0) ||
!data.hasAttribute(Trade::MeshAttribute::Position, 1))
Fatal{} << "Positions not present in morph targets 0 and 1";
Float weights[]{0.25f, 0.5f};
/* Calculate morphed positions with the above weights, assuming the mesh has
a Vector3 Position attribute in morph targets 0 and 1 */
/* Calculate morphed positions with the above weights */
Containers::Array<Vector3> positions = data.positions3DAsArray(0, -1);
for(Int morphTargetId: {0, 1}) {
Containers::StridedArrayView1D<const Vector3> morphed =
@ -885,12 +895,12 @@ for(Int morphTargetId: {0, 1}) {
for(std::size_t i = 0; i != data.vertexCount(); ++i)
positions[i] += morphed[i]*weights[morphTargetId];
}
/* [MeshData-usage-morph-targets] */
/* [MeshData-access-morph-targets] */
}
{
Trade::MeshData data{MeshPrimitive::Points, 0};
/* [MeshData-usage-special-layouts] */
/* [MeshData-special-layouts] */
if(data.attributeStride(Trade::MeshAttribute::Position) <= 0 ||
data.attributeStride(Trade::MeshAttribute::Normal) <= 0 ||
(data.isIndexed() && !data.indices().isContiguous()))
@ -898,7 +908,7 @@ if(data.attributeStride(Trade::MeshAttribute::Position) <= 0 ||
// Now it's safe to use the Position and Normal attributes and the index buffer
// in a GPU mesh
/* [MeshData-usage-special-layouts] */
/* [MeshData-special-layouts] */
}
{

2
src/Magnum/GL/Mesh.h

@ -252,7 +252,7 @@ If you have a @ref Trade::MeshData instance that you got for example from
@ref Trade::AbstractImporter::mesh() or from the @ref Primitives library, the
simplest possible way is to use @ref MeshTools::compile():
@snippet Trade.cpp MeshData-usage-compile
@snippet Trade.cpp MeshData-gpu-opengl
This one-liner uploads the data and configures the mesh for all attributes
known by Magnum that are present in it, making it suitable to be drawn by

178
src/Magnum/Trade/MeshData.h

@ -769,25 +769,23 @@ information such as primitive type. Populated instances of this class are
returned from @ref AbstractImporter::mesh(), from particular functions in
the @ref Primitives library, can be passed to
@ref AbstractSceneConverter::convert(const MeshData&),
@ref AbstractSceneConverter::add(const MeshData&, Containers::StringView),
and related APIs, as well as used in various @ref MeshTools algorithms. Like
with other @ref Trade types, the internal representation is fixed upon
construction and allows only optional in-place modification of the data itself,
but not of the overall structure.
@ref AbstractSceneConverter::add(const MeshData&, Containers::StringView) and
related APIs, as well as used in various @ref MeshTools algorithms. Like with
other @ref Trade types, the internal representation is fixed upon construction
and allows only optional in-place modification of the index and vertex data
itself, but not of the overall structure.
@section Trade-MeshData-usage-compile Quick usage with MeshTools::compile()
@section Trade-MeshData-gpu-opengl Populating an OpenGL mesh
If all you want is to create a @ref GL::Mesh that can be rendered by builtin
shaders, a simple yet efficient way is to use @ref MeshTools::compile():
If the goal is creating a @ref GL::Mesh instance to be rendered by builtin
shaders, the most straightforward way is to use @ref MeshTools::compile(). This
one-liner uploads the data and configures the mesh for all builtin attributes
listed in the @ref MeshAttribute enum that are present in it:
@snippet Trade.cpp MeshData-usage-compile
@snippet Trade.cpp MeshData-gpu-opengl
This one-liner uploads the data and configures the mesh for all attributes
known by Magnum that are present in it. It's however rather opaque and doesn't
give you any opportunity to do anything with the mesh data before they get sent
to the GPU. It also won't be able to deal with any custom attributes that the
mesh contains. Continue below to see how to achieve a similar effect with
lower-level APIs.
This works also with any custom shader that follows the attribute binding
defined in @ref Shaders::GenericGL.
@m_class{m-note m-success}
@ -795,66 +793,88 @@ lower-level APIs.
A generic mesh setup using the high-level utility is used in the
@ref examples-primitives and @ref examples-viewer examples.
@section Trade-MeshData-usage Basic usage
The second simplest usage is accessing attributes through the convenience
functions @ref positions2DAsArray(), @ref positions3DAsArray(),
@ref tangentsAsArray(), @ref bitangentsAsArray(), @ref normalsAsArray(),
@ref tangentsAsArray(), @ref textureCoordinates2DAsArray(),
@ref colorsAsArray(), @ref jointIdsAsArray(), @ref weightsAsArray() and
@ref objectIdsAsArray(). You're expected to check for attribute presence first
with either @ref hasAttribute() (or @ref attributeCount(MeshAttribute, Int) const, as there can be multiple sets of texture coordinates, for example). If
you are creating a @ref GL::Mesh, the usual path forward is then to
@ref MeshTools::interleave() attributes of interest, upload them to a
@ref GL::Buffer and configure attribute binding for the mesh.
The mesh can be also indexed, in which case the index buffer is exposed through
@ref indicesAsArray().
@snippet Trade.cpp MeshData-usage
@section Trade-MeshData-usage-advanced Advanced usage
The @ref positions2DAsArray(), ... functions shown above always return a
newly-allocated @relativeref{Corrade,Containers::Array} instance in a
well-defined canonical type. While that's convenient and fine at a smaller
scale, it can take significant amount of time for large models. Or maybe the
imported data is already in a well-optimized layout and format that you want to
preserve. The @ref MeshData class internally stores a contiguous blob of data,
which you can directly upload, and then use provided metadata to let the GPU
know of the format and layout. There's a lot of possible types of each
attribute (floats, packed integers, ...), so @ref GL::DynamicAttribute accepts
also a pair of @ref GL::Attribute defined by the shader and the actual
@ref VertexFormat, figuring out the GL-specific properties such as component
count or element data type for you:
@snippet Trade.cpp MeshData-usage-advanced
This approach is especially useful when dealing with custom attributes. See
also @ref MeshTools::compile(const Trade::MeshData&, GL::Buffer&, GL::Buffer&)
@section Trade-MeshData-gpu-direct Setting GPU mesh properties directly
If you need more control, for example in presence of custom attributes, if
you have a custom shader that doesn't follow the attribute binding defined in
@ref Shaders::GenericGL, or if you want to use the mesh with a 3rd party
renderer, you can access the index and attribute data and properties directly.
The @ref MeshData class internally stores a contiguous blob of data, which you
can directly upload, and then use the associated metadata to let the GPU know
of the format and layout. The following is again creating an OpenGL mesh, but a
similar workflow would be for Vulkan or any other GPU API:
@snippet Trade.cpp MeshData-gpu-opengl-direct
If using a shader that follows the @ref Shaders::GenericGL attribute binding
and only has some custom attributes on top, you can use
@ref MeshTools::compile(const Trade::MeshData&, GL::Buffer&, GL::Buffer&)
for a combined way that gives you both the flexibility needed for custom
attributes as well as the convenience for builtin attributes.
attributes as well as the convenience for builtin attributes. See its
documentation for an example.
@section Trade-MeshData-access Accessing mesh data
When access to individual attributes from the CPU side is desired, for example
to inspect the topology or to pass the data to a physics simulation, the
simplest way is accessing attributes through the convenience
@ref indicesAsArray(), @ref positions3DAsArray(), @ref normalsAsArray() etc.
functions. Unless the mesh has a known layout, you're expected to check for
index buffer and attribute presence first with @ref isIndexed() and
@ref hasAttribute() (or, in case you need to access for example secondary
texture coordinates, @ref attributeCount(MeshAttribute, Int) const). Apart from
that, the convenience functions abstract away the internal layout of the mesh
data, giving you always a contiguous array in a predictable type.
@snippet Trade.cpp MeshData-access
If allocation is undesirable, the @ref indicesInto(), @ref positions3DInto()
etc. variants take a target view where to put the output instead of returning a
newly created array. The most efficient way is without copies or conversions
however, by direct accessing the index and attribute data using @ref indices()
and @ref attribute(). In that case you additionally need to be sure about the
data types used or decide based on @ref indexType() and @ref attributeFormat().
Replacing the above with with direct data access would look like this:
@snippet Trade.cpp MeshData-access-direct
There are also non-templated type-erased @ref indices() and @ref attribute()
overloads returning @cpp void @ce views, useful for example when a dispatch
based on the actual type is deferred to an external function. Compared to using
the template versions you however lose the type safety checks implemented in
@ref MeshData itself.
@section Trade-MeshData-usage-mutable Mutable data access
@m_class{m-note m-success}
@par
If you're loading a mesh from a file, the @ref magnum-sceneconverter "magnum-sceneconverter"
command-line utility can be used to conveniently inspect its layout and
used data formats before writing a code that processes it.
The interfaces implicitly provide @cpp const @ce views on the contained index
and vertex data through the @ref indexData(), @ref vertexData(),
@ref indices() and @ref attribute() accessors. This is done because in general
case the data can also refer to a memory-mapped file or constant memory. In
cases when it's desirable to modify the data in-place, there's the
@ref mutableIndexData(), @ref mutableVertexData(), @ref mutableIndices() and
@ref mutableAttribute() set of functions. To use these, you need to check that
the data are mutable using @ref indexDataFlags() or @ref vertexDataFlags()
first, and if not then you may want to make a mutable copy first using
@ref MeshTools::copy(). The following snippet applies a transformation to the
mesh positions:
@subsection Trade-MeshData-access-mutable Mutable data access
@snippet Trade.cpp MeshData-usage-mutable
In a general case, mesh index and vertex data can also refer to a memory-mapped
file or constant memory and thus @ref indices() and @ref attribute() return
@cpp const @ce views. When it's desirable to modify the data in-place, there's
the @ref mutableIndexData(), @ref mutableVertexData(), @ref mutableIndices()
and @ref mutableAttribute() set of functions. To use these, you need to
additionally check that the data are mutable using @ref indexDataFlags() or
@ref vertexDataFlags() first. Further continuing from the above, this snippet
applies a transformation to the mesh positions in-place:
If the transformation includes a rotation or non-uniform scaling, you may want
to do a similar operation with normals and tangents as well.
@snippet Trade.cpp MeshData-access-mutable
<b></b>
@m_class{m-note m-success}
@par
@ref MeshTools::transform3D() and other APIs in the @ref MeshTools
namespace provide a broad set of utilities for mesh transformation,
filtering and merging, operating on both whole @ref MeshData instances and
individual data views.
@section Trade-MeshData-usage-morph-targets Morph targets
@subsection Trade-MeshData-access-morph-targets Morph targets
By default, named attribute access (either through the @ref positions3DAsArray()
etc. convenience accesors or via @ref attribute(MeshAttribute, UnsignedInt, Int) const "attribute()"
@ -862,7 +882,7 @@ and similar) searches only through the base attributes. Meshes that have morph
targets can have the additional attributes accessed by passing a
`morphTargetId` argument to these functions:
@snippet Trade.cpp MeshData-usage-morph-targets
@snippet Trade.cpp MeshData-access-morph-targets
If a base attribute doesn't have a corresponding morph target attribute (which
can be checked using @ref hasAttribute(MeshAttribute, Int) const with
@ -899,10 +919,10 @@ advanced data layout features; and conversely all Magnum APIs *taking* a
When passing mesh data to the GPU, the @ref MeshTools::compile() utility will
check and expect that only GPU-compatible layout features are used. However,
when configuring meshes directly like shown in the
@ref Trade-MeshData-usage-advanced chapter above, you may want to check the
@ref Trade-MeshData-gpu-direct chapter above, you may want to check the
constraints explicitly before passing the values over.
@snippet Trade.cpp MeshData-usage-special-layouts
@snippet Trade.cpp MeshData-special-layouts
In order to convert a mesh with a special data layout to something the GPU
vertex pipeline is able to consume, @ref MeshTools::interleave(const Trade::MeshData&, Containers::ArrayView<const Trade::MeshAttributeData>, InterleaveFlags) "MeshTools::interleave()"
@ -916,9 +936,17 @@ and how to handle it.
@section Trade-MeshData-populating Populating an instance
A @ref MeshData instance by default takes over the ownership of an
@relativeref{Corrade,Containers::Array} containing the vertex / index data
together with a @ref MeshIndexData instance and a list of
If the goal is creating a @ref MeshData instance from individual attributes as
opposed to getting it from an importer or as an output of another operation and
making a copy of the data is acceptable, easiest is to pass them to the
@ref MeshTools::interleave(MeshPrimitive, const Trade::MeshIndexData&, Containers::ArrayView<const Trade::MeshAttributeData>) "MeshTools::interleave()"
helper and letting it handle all data massaging internally. See its
documentation for a usage example.
Otherwise, creating a @ref MeshData instance from scratch requires you to have
all vertex data contained in a single chunk of memory. By default it takes over
the ownership of an @relativeref{Corrade,Containers::Array} containing the
vertex / index data together with a @ref MeshIndexData instance and a list of
@ref MeshAttributeData describing various index and vertex properties. For
example, an interleaved indexed mesh with 3D positions and RGBA colors would
look like this --- and variants with just vertex data or just index data or

Loading…
Cancel
Save