Skip to content

Commit c606c21

Browse files
authored
Merge pull request #1013 from zeux/mlc-moretests
meshletcodec: Add more tests and documentation
2 parents 63b36ed + 261d3b7 commit c606c21

File tree

3 files changed

+150
-2
lines changed

3 files changed

+150
-2
lines changed

README.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,50 @@ When referenced vertex indices are not sequential, the index codec will use arou
391391

392392
Index buffer codec only supports triangle list topology; when encoding triangle strips or line lists, use `meshopt_encodeIndexSequence`/`meshopt_decodeIndexSequence` instead. This codec typically encodes indices into ~1 byte per index, but compressing the results further with a general purpose compressor can improve the results to 1-3 bits per index.
393393

394+
395+
### Meshlet compression
396+
397+
When using mesh shading or clustered raytracing, meshlet vertex reference and triangle data can be compressed similarly to index data. This library provides a dedicated codec that exploits locality inherent in meshlet data. Unlike vertex and index buffer codecs that work on entire buffers, the meshlet codec encodes each meshlet independently; this allows applications to have more flexibility in structuring the runtime storage and adjust the decoded data during decoding. This also means that in some applications, additional data describing the meshlet (vertex/triangle count, encoded size) will need to be encoded into the meshlet stream, if it isn't already available during decoding.
398+
399+
The input to the encoder is the vertex index array and micro-index buffer for a single meshlet, as produced by `meshopt_buildMeshlets`. To encode a meshlet, allocate a target buffer using the worst case bound and call the encoding function:
400+
401+
```c++
402+
std::vector<unsigned char> mbuf(meshopt_encodeMeshletBound(max_vertices, max_triangles));
403+
404+
for (const meshopt_Meshlet& m : meshlets)
405+
{
406+
size_t msize = meshopt_encodeMeshlet(&mbuf[0], mbuf.size(),
407+
&meshlet_vertices[m.vertex_offset], m.vertex_count, &meshlet_triangles[m.triangle_offset], m.triangle_count);
408+
409+
// write m.vertex_count, m.triangle_count, msize and mbuf[0..msize-1] to the output stream
410+
}
411+
```
412+
413+
To decode the data at runtime, call the decoding function:
414+
415+
```c++
416+
uint16_t* vertices = ...;
417+
uint8_t* triangles = ...;
418+
419+
// automatically deduces `vertex_size=2` and `triangle_size=3` based on pointer types
420+
int res = meshopt_decodeMeshlet(vertices, m.vertex_count, triangles, m.triangle_count, stream, encoded_size);
421+
assert(res == 0);
422+
```
423+
424+
Vertex indices can be decoded as 16-bit or 32-bit elements; triangle data can be decoded as byte triplets (3 bytes per triangle, matching the `meshopt_buildMeshlets` output format) or as 32-bit packed elements (4 bytes per triangle, `a | (b << 8) | (c << 16)`). Output buffers must have available space aligned to 4 bytes; for example, decoding a 3-triangle stream using 3 bytes per triangle needs to be able to write 12 bytes to the output triangles array.
425+
426+
When using the C++ API, `meshopt_decodeMeshlet` will automatically deduce the element sizes based on the types of vertex and triangle pointers; when using the C API, the sizes need to be specified explicitly.
427+
428+
Decoder is heavily optimized and can directly target write-combined memory; you can expect it to run at 7-10 GB/s on modern desktop CPUs.
429+
430+
Note that meshlet encoding assumes that the meshlet data was optimized; meshlets should be processed using `meshopt_optimizeMeshlet` before encoding. Additionally, vertex references should have a high degree of reference locality; this can be achieved by building meshlets from meshes optimized for vertex cache/fetch, or linearizing the vertex reference data (and reordering the vertex buffer accordingly). Feeding unoptimized data into the encoder will produce poor compression ratios. Codec preserves the order of triangles, however it can rotate each triangle to improve compression ratio (which means the provoking vertex may change).
431+
432+
Meshlets without vertex references are supported; passing `NULL` vertices and `0` vertex count during encoding and decoding will produce encoded meshlets with just triangle data. Note that parameters supplied during decoding must match those used during encoding; if a meshlet was encoded with vertex references, it must be decoded with the same number of vertex references.
433+
434+
The meshlet codec targets 5-7 bits per triangle for triangle data; when vertex references are encoded, the encoded size strongly depends on how linear the references are, but it's typical to see 9-12 bits per triangle in aggregate. To reduce the compressed size further, it's possible to compress the resulting encoded data with a general purpose compressor, which usually achieves 5-8 bits/triangle in aggregate; note that in this case general purpose compressors should be applied to a stream with many encoded meshlets at once to amortize their overhead.
435+
436+
> Note: this codec is currently experimental and the data format and APIs are subject to change.
437+
394438
### Point cloud compression
395439

396440
The vertex encoding algorithms can be used to compress arbitrary streams of attribute data; one other use case besides triangle meshes is point cloud data. Typically point clouds come with position, color and possibly other attributes but don't have an implied point order.
@@ -716,6 +760,8 @@ Applications may configure the library to change the attributes of experimental
716760
Currently, the following APIs are experimental:
717761
718762
- `meshopt_SimplifyPermissive` mode for `meshopt_simplify*` functions (and associated `meshopt_SimplifyVertex_*` flags)
763+
- `meshopt_encodeMeshlet` and `meshopt_encodeMeshletBound` functions
764+
- `meshopt_decodeMeshlet` and `meshopt_decodeMeshletRaw` functions
719765
720766
## License
721767

demo/tests.cpp

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2788,7 +2788,7 @@ static void decodeMeshletSafety()
27882788
2, 1, 3,
27892789
3, 5, 4,
27902790
2, 0, 6,
2791-
7, 7, 7, // clang-format :-/
2791+
6, 6, 6, // clang-format :-/
27922792
};
27932793

27942794
const unsigned int vertices[7] = {
@@ -2825,6 +2825,15 @@ static void decodeMeshletSafety()
28252825
for (size_t i = 1; i < size; ++i)
28262826
assert(meshopt_decodeMeshlet(rv, 7, rt, 5, enc + i, size - i) < 0);
28272827

2828+
// because SIMD implementation is specialized by size, we need to test truncated inputs for short representations
2829+
unsigned short rvs[7 + 1]; // 32b alignment
2830+
unsigned char rts[5 * 3 + 1]; // 32b alignment
2831+
2832+
for (size_t i = 1; i < size; ++i)
2833+
assert(meshopt_decodeMeshlet(rvs, 7, rts, 5, enc, i) < 0);
2834+
for (size_t i = 1; i < size; ++i)
2835+
assert(meshopt_decodeMeshlet(rvs, 7, rts, 5, enc + i, size - i) < 0);
2836+
28282837
// when using decodeMeshletRaw, the output buffer sizes must be 16b aligned
28292838
unsigned int rvr[8], rtr[8];
28302839

@@ -2842,6 +2851,73 @@ static void decodeMeshletSafety()
28422851
assert(memcmp(rt, rtr, 5 * sizeof(unsigned int)) == 0);
28432852
}
28442853

2854+
static void decodeMeshletBasic()
2855+
{
2856+
const unsigned char triangles[5 * 3] = {
2857+
0, 1, 2,
2858+
2, 1, 3,
2859+
4, 3, 5,
2860+
2, 0, 6,
2861+
6, 6, 6, // clang-format :-/
2862+
};
2863+
2864+
const unsigned int vertices[7] = {
2865+
5,
2866+
12,
2867+
140,
2868+
0,
2869+
12389,
2870+
123456789,
2871+
7,
2872+
};
2873+
2874+
const unsigned char encoded[46] = {
2875+
0x0a, 0x0c, 0xfe, 0x19, 0x01, 0xc8, 0x60, 0x00, 0x00, 0x5e, 0x39, 0xb7, 0x0e, 0x1d, 0x9a, 0xb7,
2876+
0x0e, 0x00, 0x00, 0x00, 0x00, 0x04, 0x03, 0x05, 0x02, 0x00, 0x06, 0x06, 0x06, 0x06, 0x00, 0x00,
2877+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x87, 0xff, 0x2c, 0xff, 0x0f, // clang-format :-/
2878+
};
2879+
2880+
unsigned int rv[7];
2881+
unsigned char rt[5 * 3 + 1];
2882+
int rc = meshopt_decodeMeshlet(rv, 7, rt, 5, encoded, sizeof(encoded));
2883+
2884+
assert(rc == 0);
2885+
assert(memcmp(rv, vertices, sizeof(vertices)) == 0);
2886+
assert(memcmp(rt, triangles, sizeof(triangles)) == 0);
2887+
}
2888+
2889+
static void decodeMeshletTypical()
2890+
{
2891+
const unsigned char triangles[44 * 3] = {
2892+
0, 1, 2, 0, 2, 3, 3, 2, 4, 3, 4, 5, 0, 3, 6, 6, 3, 5, 0, 6, 7, 7, 6, 8,
2893+
8, 6, 5, 7, 8, 9, 8, 5, 10, 10, 5, 4, 10, 4, 11, 11, 4, 12, 11, 12, 13, 10, 11, 14,
2894+
10, 14, 8, 14, 11, 15, 15, 11, 13, 15, 13, 16, 15, 16, 14, 16, 13, 17, 14, 16, 18, 14, 18, 8,
2895+
18, 16, 19, 19, 16, 20, 20, 16, 17, 20, 17, 21, 20, 21, 22, 20, 22, 19, 22, 21, 23, 19, 22, 24,
2896+
19, 24, 25, 19, 25, 18, 18, 25, 26, 18, 26, 8, 8, 26, 9, 22, 23, 27, 22, 27, 24, 27, 23, 28,
2897+
27, 28, 29, 27, 29, 24, 29, 28, 30, 24, 29, 31, // clang-format :-/
2898+
};
2899+
2900+
const unsigned int vertices[32] = {
2901+
10, 11, 9, 12, 8, 13, 14, 15, 16, 17, 18, 19, 0, 20, 21, 22,
2902+
23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // clang-format :-/
2903+
};
2904+
2905+
const unsigned char encoded[53] = {
2906+
0x14, 0x05, 0x04, 0x09, 0x08, 0x27, 0x26, 0x05, 0x05, 0x04, 0x08, 0x0d, 0x0e, 0x08, 0x11, 0x13,
2907+
0x12, 0x08, 0x09, 0x16, 0x17, 0x18, 0x18, 0x0d, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x0c,
2908+
0x02, 0x38, 0x24, 0x43, 0x34, 0x20, 0x80, 0x61, 0x03, 0x61, 0x16, 0x26, 0x03, 0x10, 0x66, 0x10,
2909+
0x12, 0xe3, 0x61, 0x10, 0x66, // clang-format :-/
2910+
};
2911+
2912+
unsigned int rv[32];
2913+
unsigned char rt[44 * 3];
2914+
int rc = meshopt_decodeMeshlet(rv, 32, rt, 44, encoded, sizeof(encoded));
2915+
2916+
assert(rc == 0);
2917+
assert(memcmp(rv, vertices, sizeof(vertices)) == 0);
2918+
assert(memcmp(rt, triangles, sizeof(triangles)) == 0);
2919+
}
2920+
28452921
void runTests()
28462922
{
28472923
decodeIndexV0();
@@ -2965,4 +3041,6 @@ void runTests()
29653041

29663042
encodeMeshletBound();
29673043
decodeMeshletSafety();
3044+
decodeMeshletBasic();
3045+
decodeMeshletTypical();
29683046
}

src/meshoptimizer.h

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,10 +296,34 @@ MESHOPTIMIZER_API size_t meshopt_encodeIndexSequenceBound(size_t index_count, si
296296
MESHOPTIMIZER_API int meshopt_decodeIndexSequence(void* destination, size_t index_count, size_t index_size, const unsigned char* buffer, size_t buffer_size);
297297

298298
/**
299-
* Experimental: Meshlet encoder (work in progress; data format and APIs are subject to change)
299+
* Experimental: Meshlet encoder
300+
* Encodes meshlet data into an array of bytes that is generally smaller and compresses better compared to original.
301+
* Returns encoded data size on success, 0 on error; the only error condition is if buffer doesn't have enough space
302+
* This function encodes a single meshlet; when encoding multiple meshlets, additional headers may be necessary to store vertex/triangle count and encoded size.
303+
* For maximum efficiency the meshlet being encoded should be optimized using meshopt_optimizeMeshlet; additionally, vertex reference data should be optimized for locality (fetch).
304+
*
305+
* buffer must contain enough space for the encoded meshlet (use meshopt_encodeMeshletBound to compute worst case size)
306+
* vertices may be NULL, in which case vertex_count must be 0 and only triangle data is encoded
307+
* vertex_count and triangle_count must be <= 256.
300308
*/
301309
MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_encodeMeshlet(unsigned char* buffer, size_t buffer_size, const unsigned int* vertices, size_t vertex_count, const unsigned char* triangles, size_t triangle_count);
302310
MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_encodeMeshletBound(size_t max_vertices, size_t max_triangles);
311+
312+
/**
313+
* Experimental: Meshlet decoder
314+
* Decodes meshlet data from an array of bytes generated by meshopt_encodeMeshlet
315+
* Returns 0 if decoding was successful, and an error code otherwise
316+
* The decoder is safe to use for untrusted input, but it may produce garbage data.
317+
*
318+
* vertices must contain enough space for the resulting vertex data, aligned to 4 bytes (align(vertex_count * vertex_size, 4) bytes)
319+
* vertex_size must be 2 (16-bit vertex references) or 4 (32-bit vertex references)
320+
* triangles must contain enough space for the resulting triangle data, aligned to 4 bytes (align(triangle_count * triangle_size, 4) bytes)
321+
* triangle_size must be 3 (8-bit triangle indices) or 4 (32-bit packed triangles, stored as (a) | (b << 8) | (c << 16))
322+
* vertex_count, triangle_count match those used during encoding exactly; buffer_size must be equal to the encoded size returned by meshopt_encodeMeshlet.
323+
* vertices may be NULL, in which case vertex_count must be 0 and the meshlet must contain just triangle data
324+
*
325+
* When using "raw" decoding (meshopt_decodeMeshletRaw), both vertices and triangles should have available space further aligned to 16 bytes for efficient SIMD decoding.
326+
*/
303327
MESHOPTIMIZER_EXPERIMENTAL int meshopt_decodeMeshlet(void* vertices, size_t vertex_count, size_t vertex_size, void* triangles, size_t triangle_count, size_t triangle_size, const unsigned char* buffer, size_t buffer_size);
304328
MESHOPTIMIZER_EXPERIMENTAL int meshopt_decodeMeshletRaw(unsigned int* vertices, size_t vertex_count, unsigned int* triangles, size_t triangle_count, const unsigned char* buffer, size_t buffer_size);
305329

0 commit comments

Comments
 (0)