diff --git a/doc/snippets/Trade.cpp b/doc/snippets/Trade.cpp index f2a2ab1e7..8372f7be5 100644 --- a/doc/snippets/Trade.cpp +++ b/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 indices = data.indicesAsArray(); +Containers::Array 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(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 indices = + data.indices(); +Containers::StridedArrayView1D positions = + data.attribute(Trade::MeshAttribute::Position); +/* [MeshData-access-direct] */ +static_cast(indices); +static_cast(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(Trade::MeshAttribute::Position)); -/* [MeshData-usage-mutable] */ +Matrix4 transformation = Matrix4::scaling(Vector3{2.0f}); +for(Vector3& i: data.mutableAttribute(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 positions = data.positions3DAsArray(0, -1); for(Int morphTargetId: {0, 1}) { Containers::StridedArrayView1D 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] */ } { diff --git a/src/Magnum/GL/Mesh.h b/src/Magnum/GL/Mesh.h index 00fa1f2ed..21e4e4b5e 100644 --- a/src/Magnum/GL/Mesh.h +++ b/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 diff --git a/src/Magnum/Trade/MeshData.h b/src/Magnum/Trade/MeshData.h index 6ef70ce6b..5b4e9d6e8 100644 --- a/src/Magnum/Trade/MeshData.h +++ b/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 + + + +@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, 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) "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