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 #ifdef MAGNUM_TARGET_GL
{ {
/* This snippet is also used by GL::Mesh, bear that in mind when updating */ /* 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}); Trade::MeshData data = DOXYGEN_ELLIPSIS(Trade::MeshData{MeshPrimitive::Points, 0});
GL::Mesh mesh = MeshTools::compile(data); GL::Mesh mesh = MeshTools::compile(data);
/* [MeshData-usage-compile] */ /* [MeshData-gpu-opengl] */
} }
{ {
Trade::MeshData data{MeshPrimitive::Points, 0}; Trade::MeshData data{MeshPrimitive::Points, 0};
/* [MeshData-usage] */ /* [MeshData-gpu-opengl-direct] */
/* Check that we have at least positions and normals */
GL::Mesh mesh{data.primitive()}; GL::Mesh mesh{data.primitive()};
if(!data.hasAttribute(Trade::MeshAttribute::Position) || mesh.setCount(data.indexCount());
!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] */
}
{ /* Upload index data and configure their layout */
Trade::MeshData data{MeshPrimitive::Points, 0}; GL::Buffer indices{data.indexData()};
GL::Mesh mesh{data.primitive()}; mesh.setIndexBuffer(indices, 0, data.indexType());
/* [MeshData-usage-advanced] */
/* Upload the original packed vertex data */
GL::Buffer vertices;
vertices.setData(data.vertexData());
/* 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, mesh.addVertexBuffer(vertices,
data.attributeOffset(Trade::MeshAttribute::Position), data.attributeOffset(Trade::MeshAttribute::Position),
data.attributeStride(Trade::MeshAttribute::Position), data.attributeStride(Trade::MeshAttribute::Position),
@ -844,40 +821,73 @@ mesh.addVertexBuffer(vertices,
data.attributeStride(Trade::MeshAttribute::Normal), data.attributeStride(Trade::MeshAttribute::Normal),
GL::DynamicAttribute{Shaders::PhongGL::Normal{}, GL::DynamicAttribute{Shaders::PhongGL::Normal{},
data.attributeFormat(Trade::MeshAttribute::Normal)}); data.attributeFormat(Trade::MeshAttribute::Normal)});
/* [MeshData-gpu-opengl-direct] */
/* 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] */
} }
#endif #endif
{ {
Trade::MeshData data{MeshPrimitive::Points, 0}; Trade::MeshData data{MeshPrimitive::Points, 0};
/* [MeshData-usage-mutable] */ /* [MeshData-access] */
/* Check prerequisites */ if(data.primitive() != MeshPrimitive::Triangles ||
if(!(data.vertexDataFlags() & Trade::DataFlag::Mutable) || !data.isIndexed() ||
!data.hasAttribute(Trade::MeshAttribute::Position) || !data.hasAttribute(Trade::MeshAttribute::Position))
data.attributeFormat(Trade::MeshAttribute::Position) != VertexFormat::Vector3)
Fatal{} << "Oh well"; 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 */ /* Scale the mesh two times */
MeshTools::transformPointsInPlace(Matrix4::scaling(Vector3{2.0f}), Matrix4 transformation = Matrix4::scaling(Vector3{2.0f});
data.mutableAttribute<Vector3>(Trade::MeshAttribute::Position)); for(Vector3& i: data.mutableAttribute<Vector3>(Trade::MeshAttribute::Position))
/* [MeshData-usage-mutable] */ i = transformation.transformPoint(i);
/* [MeshData-access-mutable] */
} }
{ {
Trade::MeshData data{MeshPrimitive::Points, 0}; 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}; Float weights[]{0.25f, 0.5f};
/* Calculate morphed positions with the above weights, assuming the mesh has /* Calculate morphed positions with the above weights */
a Vector3 Position attribute in morph targets 0 and 1 */
Containers::Array<Vector3> positions = data.positions3DAsArray(0, -1); Containers::Array<Vector3> positions = data.positions3DAsArray(0, -1);
for(Int morphTargetId: {0, 1}) { for(Int morphTargetId: {0, 1}) {
Containers::StridedArrayView1D<const Vector3> morphed = Containers::StridedArrayView1D<const Vector3> morphed =
@ -885,12 +895,12 @@ for(Int morphTargetId: {0, 1}) {
for(std::size_t i = 0; i != data.vertexCount(); ++i) for(std::size_t i = 0; i != data.vertexCount(); ++i)
positions[i] += morphed[i]*weights[morphTargetId]; positions[i] += morphed[i]*weights[morphTargetId];
} }
/* [MeshData-usage-morph-targets] */ /* [MeshData-access-morph-targets] */
} }
{ {
Trade::MeshData data{MeshPrimitive::Points, 0}; Trade::MeshData data{MeshPrimitive::Points, 0};
/* [MeshData-usage-special-layouts] */ /* [MeshData-special-layouts] */
if(data.attributeStride(Trade::MeshAttribute::Position) <= 0 || if(data.attributeStride(Trade::MeshAttribute::Position) <= 0 ||
data.attributeStride(Trade::MeshAttribute::Normal) <= 0 || data.attributeStride(Trade::MeshAttribute::Normal) <= 0 ||
(data.isIndexed() && !data.indices().isContiguous())) (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 // Now it's safe to use the Position and Normal attributes and the index buffer
// in a GPU mesh // 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 @ref Trade::AbstractImporter::mesh() or from the @ref Primitives library, the
simplest possible way is to use @ref MeshTools::compile(): 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 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 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 returned from @ref AbstractImporter::mesh(), from particular functions in
the @ref Primitives library, can be passed to the @ref Primitives library, can be passed to
@ref AbstractSceneConverter::convert(const MeshData&), @ref AbstractSceneConverter::convert(const MeshData&),
@ref AbstractSceneConverter::add(const MeshData&, Containers::StringView), @ref AbstractSceneConverter::add(const MeshData&, Containers::StringView) and
and related APIs, as well as used in various @ref MeshTools algorithms. Like related APIs, as well as used in various @ref MeshTools algorithms. Like with
with other @ref Trade types, the internal representation is fixed upon other @ref Trade types, the internal representation is fixed upon construction
construction and allows only optional in-place modification of the data itself, and allows only optional in-place modification of the index and vertex data
but not of the overall structure. 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 If the goal is creating a @ref GL::Mesh instance to be rendered by builtin
shaders, a simple yet efficient way is to use @ref MeshTools::compile(): 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 This works also with any custom shader that follows the attribute binding
known by Magnum that are present in it. It's however rather opaque and doesn't defined in @ref Shaders::GenericGL.
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.
@m_class{m-note m-success} @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 A generic mesh setup using the high-level utility is used in the
@ref examples-primitives and @ref examples-viewer examples. @ref examples-primitives and @ref examples-viewer examples.
@section Trade-MeshData-usage Basic usage @section Trade-MeshData-gpu-direct Setting GPU mesh properties directly
The second simplest usage is accessing attributes through the convenience If you need more control, for example in presence of custom attributes, if
functions @ref positions2DAsArray(), @ref positions3DAsArray(), you have a custom shader that doesn't follow the attribute binding defined in
@ref tangentsAsArray(), @ref bitangentsAsArray(), @ref normalsAsArray(), @ref Shaders::GenericGL, or if you want to use the mesh with a 3rd party
@ref tangentsAsArray(), @ref textureCoordinates2DAsArray(), renderer, you can access the index and attribute data and properties directly.
@ref colorsAsArray(), @ref jointIdsAsArray(), @ref weightsAsArray() and The @ref MeshData class internally stores a contiguous blob of data, which you
@ref objectIdsAsArray(). You're expected to check for attribute presence first can directly upload, and then use the associated metadata to let the GPU know
with either @ref hasAttribute() (or @ref attributeCount(MeshAttribute, Int) const, as there can be multiple sets of texture coordinates, for example). If of the format and layout. The following is again creating an OpenGL mesh, but a
you are creating a @ref GL::Mesh, the usual path forward is then to similar workflow would be for Vulkan or any other GPU API:
@ref MeshTools::interleave() attributes of interest, upload them to a
@ref GL::Buffer and configure attribute binding for the mesh. @snippet Trade.cpp MeshData-gpu-opengl-direct
The mesh can be also indexed, in which case the index buffer is exposed through If using a shader that follows the @ref Shaders::GenericGL attribute binding
@ref indicesAsArray(). and only has some custom attributes on top, you can use
@ref MeshTools::compile(const Trade::MeshData&, GL::Buffer&, GL::Buffer&)
@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&)
for a combined way that gives you both the flexibility needed for custom 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 @subsection Trade-MeshData-access-mutable Mutable data access
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:
@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 @snippet Trade.cpp MeshData-access-mutable
to do a similar operation with normals and tangents as well.
<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() By default, named attribute access (either through the @ref positions3DAsArray()
etc. convenience accesors or via @ref attribute(MeshAttribute, UnsignedInt, Int) const "attribute()" 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 targets can have the additional attributes accessed by passing a
`morphTargetId` argument to these functions: `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 If a base attribute doesn't have a corresponding morph target attribute (which
can be checked using @ref hasAttribute(MeshAttribute, Int) const with 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 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, check and expect that only GPU-compatible layout features are used. However,
when configuring meshes directly like shown in the 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. 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 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()" 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 @section Trade-MeshData-populating Populating an instance
A @ref MeshData instance by default takes over the ownership of an If the goal is creating a @ref MeshData instance from individual attributes as
@relativeref{Corrade,Containers::Array} containing the vertex / index data opposed to getting it from an importer or as an output of another operation and
together with a @ref MeshIndexData instance and a list of 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 @ref MeshAttributeData describing various index and vertex properties. For
example, an interleaved indexed mesh with 3D positions and RGBA colors would 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 look like this --- and variants with just vertex data or just index data or

Loading…
Cancel
Save