Those have 3 pointers at least, my limit for passing by value is trivial
copyability and two pointers. I hope that reflects the actual HW at
least vaguely, heh.
And add a comment explaining why we don't check the pointer for empty
meshes -- otherwise empty interleaved meshes would fail with stuff like
Trade::MeshData: attribute 0 [0xc:0xc] is not contained in passed
vertexData array [0x0:0x0]
which ... helps nobody.
The array size is always last, defaulting to 0. This makes it consistent
with the offset-only constructor and removes two unnecessary overloads.
It's a breaking change, but I don't think array attributes have many
users yet -- and better to do this now than later. In any case, sorry
about breaking your code.
While 27f6cc309d made it easier to create
references to attribute-less meshes by avoiding a branch on
attributeCount() (and then using a constructor with explicit vertex
count), code still had to branch on isIndexed() because indices() could
be called only on indexed meshes. This change allows code like
Trade::MeshData reference{data.primitive(),
{}, data.indexData(), Trade::MeshIndexData{data.indices()},
{}, data.vertexData(), Trade::meshAttributeDataNonOwningArray(data.attributeData()),
data.vertexCount()};
which works correctly for all cases and doesn't introduce extra branches
and code paths that would need to be tested. While definitely better, I
might still give up at some point and introduce some
Trade::MeshData::nonOwningImmutableReference() helper for this.
This code was apparently done before the batch casters were a thing. Now
it's no reason to do the same in two places, additionally in the future
the castInto() could get SIMD-optimized and this can benefit from that.
Originally wanted to offload this to someone else, but then realized I
need those for generic vertex attribute definitions, which I need for
instancing, which I need now. So here it is, at the bottom of the
dependency chain.
Until now, except for an attribute-less index-less mesh, the vertex
count was only implicitly taken from passed attributes, but it was
severely limiting:
- There was no way to set vertex count for an attribute-less
indexed mesh, which didn't make sense
- All code that made non-owning MeshData instances referencing another
MeshData had to explicitly handle the attribute-less corner case to
avoid vertex count getting lost
- Offset-only attributes couldn't be used to specify static layout of
meshes with dynamic vertex count, causing unnecessary extra
allocations especially in the Primitives library.
Originally this was done in order to make handling of deserialized data
much simpler (as for those attributes also need to only contain an
offset into some unknown data array), but seems this could be very
useful elsewhere as well -- for example when the layout is known
beforehand but the actual data not yet -- such as in the Line and
Gradient primitives (going to switch them to this in the next commit).
What still unfortunately has to be known in advance is the actual vertex
count (as supplying it directly to MeshData would mean adding 6 new
constructor overloads, and there's enough of those already). Might
revisit later.
Before it was a 32-byte structure with 3 bytes free (or a 20-byte
structure with 3 bytes free), now it's a 24-byte structure with 5 bytes
free. Exploiting the fact that strides can't be too high for a GPU
anyway (so 2 bytes is enough instead of 8), and vertex count is capped
to 32bit by MeshData anyway (so no need for 8 also), saving 10 bytes on
a 64-bit build.
It only resets count of released thing to zero, not going all nuclear.
Otherwise it wouldn't be possible to release attribute data and then
vertex data as releasing one would wipe the other.
Allow to make:
- an indexed mesh with zero indices
- a mesh with non-zero attribute count but zero vertices
- a mesh with non-zero vertex count but zero attributes
All of these are valid use cases as explained in the tests, and will
also make the release*() behavior defined better.
It made testing and everything harder than strictly necessary. OTOH
still keeping MeshData as a friend and accessing members directly since
those are heavily interconnected anyway.
This makes it possible to have fully allocation-less MeshData, with
statically defined indices and attributes. Only the final MeshData
construction needs to be done at runtime because Array is not constexpr,
but that isn't anything heavy anyway.
With API analogous to the (relatively) new AnimationData -- with one
buffer containing all index data and one buffer containing all vertex
data, both meant to be uploaded as-is to the GPU.
This will eventually replace MeshData2D and MeshData3D, backwards
compatibility and wiring up to other APIs will be done in follow-up
commits.