From c101fc65e313021da0cedea77978a843bca3d16d Mon Sep 17 00:00:00 2001 From: wangshilin Date: Thu, 18 Sep 2025 20:24:47 +0800 Subject: [PATCH 1/6] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E6=96=87=E6=A1=A3=EF=BC=8C=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B?= =?UTF-8?q?=E3=80=82Signed-off-by:=20wangshilin=20?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .git-blame-ignore-revs | 4 + BUILD.gn | 45 +++ COPYING.md | 3 + OAT.xml | 85 +++++ README.OpenSource.md | 11 + README.md | 569 ++----------------------------- README_meshoptimizer.md | 558 ++++++++++++++++++++++++++++++ README_zh.md | 51 +++ src/allocator.cpp | 3 + src/clusterizer.cpp | 3 + src/indexcodec.cpp | 3 + src/indexgenerator.cpp | 3 + src/meshoptimizer.h | 2 +- src/overdrawanalyzer.cpp | 3 + src/overdrawoptimizer.cpp | 3 + src/quantization.cpp | 3 + src/simplifier.cpp | 3 + src/spatialorder.cpp | 3 + src/stripifier.cpp | 3 + src/vcacheanalyzer.cpp | 3 + src/vcacheoptimizer.cpp | 3 + src/vertexcodec.cpp | 3 + src/vertexfilter.cpp | 3 + src/vfetchanalyzer.cpp | 3 + src/vfetchoptimizer.cpp | 3 + testcase/decode_meshopt_test.cpp | 178 ++++++++++ 26 files changed, 1018 insertions(+), 536 deletions(-) create mode 100644 BUILD.gn create mode 100644 COPYING.md create mode 100644 OAT.xml create mode 100644 README.OpenSource.md create mode 100644 README_meshoptimizer.md create mode 100644 README_zh.md create mode 100644 testcase/decode_meshopt_test.cpp diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 77aa716c..1d666ca0 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -1,3 +1,7 @@ +# MIT License + +# Copyright (c) 2016-2024 Arseny Kapoulkine + # This file contains a list of Git commit hashes that should be hidden from the # regular Git history. Typically, this includes commits involving mass auto-formatting # or other normalizations. Commit hashes *must* use the full 40-character notation. diff --git a/BUILD.gn b/BUILD.gn new file mode 100644 index 00000000..5efefa04 --- /dev/null +++ b/BUILD.gn @@ -0,0 +1,45 @@ +# Copyright (c) 2021 Huawei Device Co., Ltd. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build/ohos.gni") + +config("meshoptimizer_headers_config") { + include_dirs = ["src"] +} + +ohos_shared_library("meshoptimizer") { + sources = [ + "src/meshoptimizer.h", + "src/allocator.cpp", + "src/clusterizer.cpp", + "src/indexcodec.cpp", + "src/indexgenerator.cpp", + "src/overdrawanalyzer.cpp", + "src/overdrawoptimizer.cpp", + "src/quantization.cpp", + "src/simplifier.cpp", + "src/spatialorder.cpp", + "src/stripifier.cpp", + "src/vcacheanalyzer.cpp", + "src/vcacheoptimizer.cpp", + "src/vertexcodec.cpp", + "src/vertexfilter.cpp", + "src/vfetchanalyzer.cpp", + "src/vfetchanalyzer.cpp", + ] + public_configs = [ ":meshoptimizer_headers_config" ] + + part_name = "meshoptimizer" + subsystem_name = "thirdparty" +} \ No newline at end of file diff --git a/COPYING.md b/COPYING.md new file mode 100644 index 00000000..c6121a8a --- /dev/null +++ b/COPYING.md @@ -0,0 +1,3 @@ +Uses meshoptimizer. Copyright (c) 2016-2025 Arseny Kapoulkine +Sen the README file for the current license terms. +This project is licensed under the MIT license.By downloading any component from this repository you acknowledge that you accept terms specified in the LICENSE file. \ No newline at end of file diff --git a/OAT.xml b/OAT.xml new file mode 100644 index 00000000..333c6fc5 --- /dev/null +++ b/OAT.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/README.OpenSource.md b/README.OpenSource.md new file mode 100644 index 00000000..1c90619d --- /dev/null +++ b/README.OpenSource.md @@ -0,0 +1,11 @@ +[ + { + "Name": "meshoptimizer", + "License": "MIT", + "License File": "LICENSE", + "Version Number": "v0.22", + "Owner": "pengyonglong2@h-partners.com", + "Upstream URL": "https://github.com/zeux/meshoptimizer", + "Description": "Meshoptimizer is an efficient mesh optimization library that reduces the storage size of 3D mesh data and enhances rendering performance through various techniques. It is mainly used in the fields of graphics and game development, especially when dealing with 3D models containing a large number of polygons. It can significantly reduce the rendering burden and improve operational efficiency." + } +] \ No newline at end of file diff --git a/README.md b/README.md index 07a6791f..d1a96974 100644 --- a/README.md +++ b/README.md @@ -1,558 +1,57 @@ -# 🐇 meshoptimizer [![Actions Status](https://github.com/zeux/meshoptimizer/workflows/build/badge.svg)](https://github.com/zeux/meshoptimizer/actions) [![codecov.io](https://codecov.io/github/zeux/meshoptimizer/coverage.svg?branch=master)](https://codecov.io/github/zeux/meshoptimizer?branch=master) ![MIT](https://img.shields.io/badge/license-MIT-blue.svg) [![GitHub](https://img.shields.io/badge/repo-github-green.svg)](https://github.com/zeux/meshoptimizer) +# meshoptimizer +Meshoptimizer is an efficient mesh optimization library that reduces the storage size of 3D mesh data and enhances rendering performance through various techniques. It is mainly used in the fields of graphics and game development, especially when dealing with 3D models containing a large number of polygons, it can significantly reduce the rendering burden and improve operational efficiency. -## Purpose - -When a GPU renders triangle meshes, various stages of the GPU pipeline have to process vertex and index data. The efficiency of these stages depends on the data you feed to them; this library provides algorithms to help optimize meshes for these stages, as well as algorithms to reduce the mesh complexity and storage overhead. - -The library provides a C and C++ interface for all algorithms; you can use it from C/C++ or from other languages via FFI (such as P/Invoke). If you want to use this library from Rust, you should use [meshopt crate](https://crates.io/crates/meshopt). JavaScript interface for some algorithms is available through [meshoptimizer.js](https://www.npmjs.com/package/meshoptimizer). - -[gltfpack](./gltf/README.md), which is a tool that can automatically optimize glTF files, is developed and distributed alongside the library. - -## Installing - -meshoptimizer is hosted on GitHub; you can download the latest release using git: +## Use Cases of meshoptimizer +The introduction of meshoptimizer on OpenHarmony is mainly for decompressing the Ext_meshopt_compress extension format in gltf. +## Directory Structure ``` -git clone -b v0.22 https://github.com/zeux/meshoptimizer.git +demo # demo file +extern # third-party source code +gltf # gltfpack is a tool that can automatically optimize gltf files, reducing download size and improving loading and rendering speed +js # this folder contains JavaScript/WebAssembly modules that can be used to access some functions of the meshoptimizer library +src # C++ source code directory of meshoptimizer containing decompression functions +CMakeLists.txt # build description file +LICENSE.md # license statement +README.MD # software description ``` -Alternatively you can [download the .zip archive from GitHub](https://github.com/zeux/meshoptimizer/archive/v0.22.zip). -The library is also available as a Linux package in several distributions ([ArchLinux](https://aur.archlinux.org/packages/meshoptimizer/), [Debian](https://packages.debian.org/libmeshoptimizer), [FreeBSD](https://www.freshports.org/misc/meshoptimizer/), [Nix](https://mynixos.com/nixpkgs/package/meshoptimizer), [Ubuntu](https://packages.ubuntu.com/libmeshoptimizer)), as well as a [Vcpkg port](https://github.com/microsoft/vcpkg/tree/master/ports/meshoptimizer) (see [installation instructions](https://learn.microsoft.com/en-us/vcpkg/get_started/get-started)) and a [Conan package](https://conan.io/center/recipes/meshoptimizer). +## The adaptation of meshoptimizer for OpenHarmony +OpenHarmony only compiles the source code and header files under the src/ directory in the meshoptimizer repository. meshoptimizer is introduced into the thirdparty directory of OpenHarmony and compiled using the method of dependent components in OpenHarmony. -[gltfpack](./gltf/README.md) is available as a pre-built binary on [Releases page](https://github.com/zeux/meshoptimizer/releases) or via [npm package](https://www.npmjs.com/package/gltfpack). Native binaries are recommended since they are more efficient and support texture compression. - -## Building - -meshoptimizer is distributed as a set of C++ source files. To include it into your project, you can use one of the two options: - -* Use CMake to build the library (either as a standalone project or as part of your project) -* Add source files to your project's build system - -The source files are organized in such a way that you don't need to change your build-system settings, and you only need to add the source files for the algorithms you use. They should build without warnings or special compilation options on all major compilers. - -## Pipeline - -When optimizing a mesh, you should typically feed it through a set of optimizations (the order is important!): - -1. Indexing -2. (optional, discussed last) Simplification -3. Vertex cache optimization -4. Overdraw optimization -5. Vertex fetch optimization -6. Vertex quantization -7. Shadow indexing -8. (optional) Vertex/index buffer compression - -## Indexing - -Most algorithms in this library assume that a mesh has a vertex buffer and an index buffer. For algorithms to work well and also for GPU to render your mesh efficiently, the vertex buffer has to have no redundant vertices; you can generate an index buffer from an unindexed vertex buffer or reindex an existing (potentially redundant) index buffer as follows: - -First, generate a remap table from your existing vertex (and, optionally, index) data: - -```c++ -size_t index_count = face_count * 3; -size_t unindexed_vertex_count = face_count * 3; -std::vector remap(index_count); // allocate temporary memory for the remap table -size_t vertex_count = meshopt_generateVertexRemap(&remap[0], NULL, index_count, &unindexed_vertices[0], unindexed_vertex_count, sizeof(Vertex)); +## Main Code Download +``` +repo init -u https://gitee.com/openharmony/manifest.git -b master --no-repo-verify +repo sync -c +repo forall -c 'git lfs pull' ``` -Note that in this case we only have an unindexed vertex buffer; when input mesh has an index buffer, it will need to be passed to `meshopt_generateVertexRemap` instead of `NULL`, along with the correct source vertex count. In either case, the remap table is generated based on binary equivalence of the input vertices, so the resulting mesh will render the same way. Binary equivalence considers all input bytes, including padding which should be zero-initialized if the vertex structure has gaps. -After generating the remap table, you can allocate space for the target vertex buffer (`vertex_count` elements) and index buffer (`index_count` elements) and generate them: - -```c++ -meshopt_remapIndexBuffer(indices, NULL, index_count, &remap[0]); -meshopt_remapVertexBuffer(vertices, &unindexed_vertices[0], unindexed_vertex_count, sizeof(Vertex), &remap[0]); +## Depend on the modules in use. +``` +deps = ["//third_party/meshoptimizer:meshoptimizer"] ``` -You can then further optimize the resulting buffers by calling the other functions on them in-place. -`meshopt_generateVertexRemap` uses binary equivalence of vertex data, which is generally a reasonable default; however, in some cases some attributes may have floating point drift causing extra vertices to be generated. For such cases, it may be necessary to quantize some attributes (most importantly, normals and tangents) before generating the remap, or use a custom weld algorithm that supports per-attribute tolerance instead. - -## Vertex cache optimization - -When the GPU renders the mesh, it has to run the vertex shader for each vertex; usually GPUs have a built-in fixed size cache that stores the transformed vertices (the result of running the vertex shader), and uses this cache to reduce the number of vertex shader invocations. This cache is usually small, 16-32 vertices, and can have different replacement policies; to use this cache efficiently, you have to reorder your triangles to maximize the locality of reused vertex references like so: - -```c++ -meshopt_optimizeVertexCache(indices, indices, index_count, vertex_count); +## Preprocessing +``` +./build/prebuilts_download.sh ``` -## Overdraw optimization -After transforming the vertices, GPU sends the triangles for rasterization which results in generating pixels that are usually first ran through the depth test, and pixels that pass it get the pixel shader executed to generate the final color. As pixel shaders get more expensive, it becomes more and more important to reduce overdraw. While in general improving overdraw requires view-dependent operations, this library provides an algorithm to reorder triangles to minimize the overdraw from all directions, which you should run after vertex cache optimization like this: - -```c++ -meshopt_optimizeOverdraw(indices, indices, index_count, &vertices[0].x, vertex_count, sizeof(Vertex), 1.05f); +## Compilation +``` +./build.sh --product-name rk3568 --ccache ``` -The overdraw optimizer needs to read vertex positions as a float3 from the vertex; the code snippet above assumes that the vertex stores position as `float x, y, z`. -When performing the overdraw optimization you have to specify a floating-point threshold parameter. The algorithm tries to maintain a balance between vertex cache efficiency and overdraw; the threshold determines how much the algorithm can compromise the vertex cache hit ratio, with 1.05 meaning that the resulting ratio should be at most 5% worse than before the optimization. +## Relevant References +https://github.com/zeux/meshoptimizer -## Vertex fetch optimization - -After the final triangle order has been established, we still can optimize the vertex buffer for memory efficiency. Before running the vertex shader GPU has to fetch the vertex attributes from the vertex buffer; the fetch is usually backed by a memory cache, and as such optimizing the data for the locality of memory access is important. You can do this by running this code: - -```c++ -meshopt_optimizeVertexFetch(vertices, indices, index_count, vertices, vertex_count, sizeof(Vertex)); -``` - -This will reorder the vertices in the vertex buffer to try to improve the locality of reference, and rewrite the indices in place to match; if the vertex data is stored using multiple streams, you should use `meshopt_optimizeVertexFetchRemap` instead. This optimization has to be performed on the final index buffer since the optimal vertex order depends on the triangle order. - -Note that the algorithm does not try to model cache replacement precisely and instead just orders vertices in the order of use, which generally produces results that are close to optimal. - -## Vertex quantization - -To optimize memory bandwidth when fetching the vertex data even further, and to reduce the amount of memory required to store the mesh, it is often beneficial to quantize the vertex attributes to smaller types. While this optimization can technically run at any part of the pipeline (and sometimes doing quantization as the first step can improve indexing by merging almost identical vertices), it generally is easier to run this after all other optimizations since some of them require access to float3 positions. - -Quantization is usually domain specific; it's common to quantize normals using 3 8-bit integers but you can use higher-precision quantization (for example using 10 bits per component in a 10_10_10_2 format), or a different encoding to use just 2 components. For positions and texture coordinate data the two most common storage formats are half precision floats, and 16-bit normalized integers that encode the position relative to the AABB of the mesh or the UV bounding rectangle. - -The number of possible combinations here is very large but this library does provide the building blocks, specifically functions to quantize floating point values to normalized integers, as well as half-precision floats. For example, here's how you can quantize a normal: - -```c++ -unsigned int normal = - (meshopt_quantizeUnorm(v.nx, 10) << 20) | - (meshopt_quantizeUnorm(v.ny, 10) << 10) | - meshopt_quantizeUnorm(v.nz, 10); -``` - -and here's how you can quantize a position: - -```c++ -unsigned short px = meshopt_quantizeHalf(v.x); -unsigned short py = meshopt_quantizeHalf(v.y); -unsigned short pz = meshopt_quantizeHalf(v.z); -``` - -Since quantized vertex attributes often need to remain in their compact representations for efficient transfer and storage, they are usually dequantized during vertex processing by configuring the GPU vertex input correctly to expect normalized integers or half precision floats, which often needs no or minimal changes to the shader code. When CPU dequantization is required instead, `meshopt_dequantizeHalf` can be used to convert half precision values back to single precision; for normalized integer formats, the dequantization just requires dividing by 2^N-1 for unorm and 2^(N-1)-1 for snorm variants, for example manually reversing `meshopt_quantizeUnorm(v, 10)` can be done by dividing by 1023. - -## Shadow indexing - -Many rendering pipelines require meshes to be rendered to depth-only targets, such as shadow maps or during a depth pre-pass, in addition to color/G-buffer targets. While using the same geometry data for both cases is possible, reducing the number of unique vertices for depth-only rendering can be beneficial, especially when the source geometry has many attribute seams due to faceted shading or lightmap texture seams. - -To achieve this, this library provides the `meshopt_generateShadowIndexBuffer` algorithm, which generates a second (shadow) index buffer that can be used with the original vertex data: - -```c++ -std::vector shadow_indices(index_count); -// note: this assumes Vertex starts with float3 positions and should be adjusted accordingly for quantized positions -meshopt_generateShadowIndexBuffer(&shadow_indices[0], indices, index_count, &vertices[0].x, vertex_count, sizeof(float) * 3, sizeof(Vertex)); -``` - -Because the vertex data is shared, shadow indexing should be done after other optimizations of the vertex/index data. However, it's possible (and recommended) to optimize the resulting shadow index buffer for vertex cache: - -```c++ -meshopt_optimizeVertexCache(&shadow_indices[0], &shadow_indices[0], index_count, vertex_count); -``` - -In some cases, it may be beneficial to split the vertex positions into a separate buffer to maximize efficiency for depth-only rendering. Note that the example above assumes only positions are relevant for shadow rendering, but more complex materials may require adding texture coordinates (for alpha testing) or skinning data to the vertex portion used as a key. `meshopt_generateShadowIndexBufferMulti` can be useful for these cases if the relevant data is not contiguous. - -## Vertex/index buffer compression - -In case storage size or transmission bandwidth is of importance, you might want to additionally compress vertex and index data. While several mesh compression libraries, like Google Draco, are available, they typically are designed to maximize the compression ratio at the cost of disturbing the vertex/index order (which makes the meshes inefficient to render on GPU) or decompression performance. They also frequently don't support custom game-ready quantized vertex formats and thus require to re-quantize the data after loading it, introducing extra quantization errors and making decoding slower. - -Alternatively you can use general purpose compression libraries like zstd or Oodle to compress vertex/index data - however these compressors aren't designed to exploit redundancies in vertex/index data and as such compression rates can be unsatisfactory. - -To that end, this library provides algorithms to "encode" vertex and index data. The result of the encoding is generally significantly smaller than initial data, and remains compressible with general purpose compressors - so you can either store encoded data directly (for modest compression ratios and maximum decoding performance), or further compress it with zstd/Oodle to maximize compression ratio. - -> Note: this compression scheme is available as a glTF extension [EXT_meshopt_compression](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Vendor/EXT_meshopt_compression/README.md). - -To encode, you need to allocate target buffers (preferably using the worst case bound) and call encoding functions: - -```c++ -std::vector vbuf(meshopt_encodeVertexBufferBound(vertex_count, sizeof(Vertex))); -vbuf.resize(meshopt_encodeVertexBuffer(&vbuf[0], vbuf.size(), vertices, vertex_count, sizeof(Vertex))); - -std::vector ibuf(meshopt_encodeIndexBufferBound(index_count, vertex_count)); -ibuf.resize(meshopt_encodeIndexBuffer(&ibuf[0], ibuf.size(), indices, index_count)); -``` - -You can then either serialize `vbuf`/`ibuf` as is, or compress them further. To decode the data at runtime, call decoding functions: - -```c++ -int resvb = meshopt_decodeVertexBuffer(vertices, vertex_count, sizeof(Vertex), &vbuf[0], vbuf.size()); -int resib = meshopt_decodeIndexBuffer(indices, index_count, &ibuf[0], ibuf.size()); -assert(resvb == 0 && resib == 0); -``` - -Note that vertex encoding assumes that vertex buffer was optimized for vertex fetch, and that vertices are quantized; index encoding assumes that the vertex/index buffers were optimized for vertex cache and vertex fetch. Feeding unoptimized data into the encoders will produce poor compression ratios. Both codecs are lossless - the only lossy step is quantization that happens before encoding. - -Decoding functions are heavily optimized and can directly target write-combined memory; you can expect both decoders to run at 1-3 GB/s on modern desktop CPUs. Compression ratios depend on the data; vertex data compression ratio is typically around 2-4x (compared to already quantized data), index data compression ratio is around 5-6x (compared to raw 16-bit index data). General purpose lossless compressors can further improve on these results. - -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. - -The following guarantees on data compatibility are provided for point releases (*no* guarantees are given for development branch): - -- Data encoded with older versions of the library can always be decoded with newer versions; -- Data encoded with newer versions of the library can be decoded with older versions, provided that encoding versions are set correctly; if binary stability of encoded data is important, use `meshopt_encodeVertexVersion` and `meshopt_encodeIndexVersion` to 'pin' the data versions. - -Due to a very high decoding performance and compatibility with general purpose lossless compressors, the compression is a good fit for the use on the web. To that end, meshoptimizer provides both vertex and index decoders compiled into WebAssembly and wrapped into a module with JavaScript-friendly interface, `js/meshopt_decoder.js`, that you can use to decode meshes that were encoded offline: - -```js -// ready is a Promise that is resolved when (asynchronous) WebAssembly compilation finishes -await MeshoptDecoder.ready; - -// decode from *Data (Uint8Array) into *Buffer (Uint8Array) -MeshoptDecoder.decodeVertexBuffer(vertexBuffer, vertexCount, vertexSize, vertexData); -MeshoptDecoder.decodeIndexBuffer(indexBuffer, indexCount, indexSize, indexData); -``` - -[Usage example](https://meshoptimizer.org/demo/) is available, with source in `demo/index.html`; this example uses .GLB files encoded using `gltfpack`. - -## Point cloud compression - -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. - -To compress point clouds efficiently, it's recommended to first preprocess the points by sorting them using the spatial sort algorithm: - -```c++ -std::vector remap(point_count); -meshopt_spatialSortRemap(&remap[0], positions, point_count, sizeof(vec3)); - -// for each attribute stream -meshopt_remapVertexBuffer(positions, positions, point_count, sizeof(vec3), &remap[0]); -``` - -After this the resulting arrays should be quantized (e.g. using 16-bit fixed point numbers for positions and 8-bit color components), and the result can be compressed using `meshopt_encodeVertexBuffer` as described in the previous section. To decompress, `meshopt_decodeVertexBuffer` will recover the quantized data that can be used directly or converted back to original floating-point data. The compression ratio depends on the nature of source data, for colored points it's typical to get 35-40 bits per point as a result. - -## Advanced compression - -Both vertex and index codecs are designed to be used in a three-stage pipeline: - -- Preparation (quantization, filtering, ordering) -- Encoding (`meshopt_encodeVertexBuffer`/`meshopt_encodeIndexBuffer`) -- Optional compression (LZ4/zlib/zstd/Oodle) - -The preparation stage is crucial for achieving good compression ratios; this section will cover some techniques that can be used to improve the results. - -The index codec targets 1 byte per triangle as a best case; on real-world data, it's typical to achieve 1-1.2 bytes per triangle. To reach this, the data needs to be optimized for vertex cache and vertex fetch. Optimizations that do not disrupt triangle locality (such as overdraw) are safe to use in between. -To reduce the data size further, it's possible to use `meshopt_optimizeVertexCacheStrip` instead of `meshopt_optimizeVertexCache` when optimizing for vertex cache. This trades off some efficiency in vertex transform for smaller vertex and index data. - -When referenced vertex indices are not sequential, the index codec will use around 2 bytes per index. This can happen when the referenced vertices are a sparse subset of the vertex buffer, such as when encoding LODs. General-purpose compression can be especially helpful in this case. - -The vertex codec tries to take advantage of the inherent locality of sequential vertices and identify bit patterns that repeat in consecutive vertices. Typically, vertex cache + vertex fetch provides a reasonably local vertex traversal order; without an index buffer, it is recommended to sort vertices spatially to improve the compression ratio. -It is crucial to correctly specify the stride when encoding vertex data; however, it does not matter whether the vertices are interleaved or deinterleaved, as the codecs perform full byte deinterleaving internally. - -For optimal compression results, the values must be quantized to small integers. It can be valuable to use bit counts that are not multiples of 8. For example, instead of using 16 bits to represent texture coordinates, use 12-bit integers and divide by 4095 in the shader. Alternatively, using half-precision floats can often achieve good results. -For single-precision floating-point data, it's recommended to use `meshopt_quantizeFloat` to remove entropy from the lower bits of the mantissa. Due to current limitations of the codec, the bit count needs to be 15 (23-8) for good results (7 can be used for more extreme compression). -For normal or tangent vectors, using octahedral encoding is recommended over three components as it reduces redundancy. Similarly to other quantized values, consider using 10-12 bits per component instead of 16. - -> Note: vertex codec v0 is limited to taking advantage of redundancy in high bits of each byte. Because of this, packing multiple 10-bit values into 32 bits will reduce compression ratio, and when storing a 12-bit value in 16 bits, high bits should be zeroed out. This limitation may be lifted in future versions of the codec. - -To further leverage the inherent structure of some data, the preparation stage can use filters that encode and decode the data in a lossy manner. This is similar to quantization but can be used without having to change the shader code. After decoding, the filter transformation needs to be reversed. This library provides three filters: - -- Octahedral filter (`meshopt_encodeFilterOct`/`meshopt_decodeFilterOct`) encodes quantized (snorm) normal or tangent vectors using octahedral encoding. Any number of bits <= 16 can be used with 4 bytes or 8 bytes per vector. -- Quaternion filter (`meshopt_encodeFilterQuat`/`meshopt_decodeFilterQuat`) encodes quantized (snorm) quaternion vectors; this can be used to encode rotations or tangent frames. Any number of bits between 4 and 16 can be used with 8 bytes per vector. -- Exponential filter (`meshopt_encodeFilterExp`/`meshopt_decodeFilterExp`) encodes single-precision floating-point vectors; this can be used to encode arbitrary floating-point data more efficiently. In addition to an arbitrary bit count (<= 24), the filter takes a "mode" parameter that allows specifying how the exponent sharing is performed to trade off compression ratio and quality: - - - `meshopt_EncodeExpSeparate` does not share exponents and results in the largest output - - `meshopt_EncodeExpSharedVector` shares exponents between different components of the same vector - - `meshopt_EncodeExpSharedComponent` shares exponents between the same component in different vectors - - `meshopt_EncodeExpClamped` does not share exponents but clamps the exponent range to reduce exponent entropy - -Note that all filters are lossy and require the data to be deinterleaved with one attribute per stream; this faciliates efficient SIMD implementation of filter decoders, allowing the overall decompression speed to be close to that of the raw codec. - -## Triangle strip conversion - -On most hardware, indexed triangle lists are the most efficient way to drive the GPU. However, in some cases triangle strips might prove beneficial: - -- On some older GPUs, triangle strips may be a bit more efficient to render -- On extremely memory constrained systems, index buffers for triangle strips could save a bit of memory - -This library provides an algorithm for converting a vertex cache optimized triangle list to a triangle strip: - -```c++ -std::vector strip(meshopt_stripifyBound(index_count)); -unsigned int restart_index = ~0u; -size_t strip_size = meshopt_stripify(&strip[0], indices, index_count, vertex_count, restart_index); -``` - -Typically you should expect triangle strips to have ~50-60% of indices compared to triangle lists (~1.5-1.8 indices per triangle) and have ~5% worse ACMR. -Note that triangle strips can be stitched with or without restart index support. Using restart indices can result in ~10% smaller index buffers, but on some GPUs restart indices may result in decreased performance. - -To reduce the triangle strip size further, it's recommended to use `meshopt_optimizeVertexCacheStrip` instead of `meshopt_optimizeVertexCache` when optimizing for vertex cache. This trades off some efficiency in vertex transform for smaller index buffers. - -## Deinterleaved geometry - -All of the examples above assume that geometry is represented as a single vertex buffer and a single index buffer. This requires storing all vertex attributes - position, normal, texture coordinate, skinning weights etc. - in a single contiguous struct. However, in some cases using multiple vertex streams may be preferable. In particular, if some passes require only positional data - such as depth pre-pass or shadow map - then it may be beneficial to split it from the rest of the vertex attributes to make sure the bandwidth use during these passes is optimal. On some mobile GPUs a position-only attribute stream also improves efficiency of tiling algorithms. - -Most of the functions in this library either only need the index buffer (such as vertex cache optimization) or only need positional information (such as overdraw optimization). However, several tasks require knowledge about all vertex attributes. - -For indexing, `meshopt_generateVertexRemap` assumes that there's just one vertex stream; when multiple vertex streams are used, it's necessary to use `meshopt_generateVertexRemapMulti` as follows: - -```c++ -meshopt_Stream streams[] = { - {&unindexed_pos[0], sizeof(float) * 3, sizeof(float) * 3}, - {&unindexed_nrm[0], sizeof(float) * 3, sizeof(float) * 3}, - {&unindexed_uv[0], sizeof(float) * 2, sizeof(float) * 2}, -}; - -std::vector remap(index_count); -size_t vertex_count = meshopt_generateVertexRemapMulti(&remap[0], NULL, index_count, index_count, streams, sizeof(streams) / sizeof(streams[0])); -``` - -After this `meshopt_remapVertexBuffer` needs to be called once for each vertex stream to produce the correctly reindexed stream. For shadow indexing, similarly `meshopt_generateShadowIndexBufferMulti` is available as a replacement. - -Instead of calling `meshopt_optimizeVertexFetch` for reordering vertices in a single vertex buffer for efficiency, calling `meshopt_optimizeVertexFetchRemap` and then calling `meshopt_remapVertexBuffer` for each stream again is recommended. - -Finally, when compressing vertex data, `meshopt_encodeVertexBuffer` should be used on each vertex stream separately - this allows the encoder to best utilize corellation between attribute values for different vertices. - -## Simplification - -All algorithms presented so far don't affect visual appearance at all, with the exception of quantization that has minimal controlled impact. However, fundamentally the most effective way at reducing the rendering or transmission cost of a mesh is to make the mesh simpler. - -This library provides two simplification algorithms that reduce the number of triangles in the mesh. Given a vertex and an index buffer, they generate a second index buffer that uses existing vertices in the vertex buffer. This index buffer can be used directly for rendering with the original vertex buffer (preferably after vertex cache optimization), or a new compact vertex/index buffer can be generated using `meshopt_optimizeVertexFetch` that uses the optimal number and order of vertices. - -The first simplification algorithm, `meshopt_simplify`, follows the topology of the original mesh in an attempt to preserve attribute seams, borders and overall appearance. For meshes with inconsistent topology or many seams, such as faceted meshes, it can result in simplifier getting "stuck" and not being able to simplify the mesh fully. Therefore it's critical that identical vertices are "welded" together, that is, the input vertex buffer does not contain duplicates. Additionally, it may be worthwhile to weld the vertices without taking into account vertex attributes that aren't critical and can be rebuilt later. - -```c++ -float threshold = 0.2f; -size_t target_index_count = size_t(index_count * threshold); -float target_error = 1e-2f; - -std::vector lod(index_count); -float lod_error = 0.f; -lod.resize(meshopt_simplify(&lod[0], indices, index_count, &vertices[0].x, vertex_count, sizeof(Vertex), - target_index_count, target_error, /* options= */ 0, &lod_error)); -``` - -Target error is an approximate measure of the deviation from the original mesh using distance normalized to `[0..1]` range (e.g. `1e-2f` means that simplifier will try to maintain the error to be below 1% of the mesh extents). Note that the simplifier attempts to produce the requested number of indices at minimal error, but because of topological restrictions and error limit it is not guaranteed to reach the target index count and can stop earlier. - -To disable the error limit, `target_error` can be set to `FLT_MAX`. This makes it more likely that the simplifier will reach the target index count, but it may produce a mesh that looks significantly different from the original, so using the resulting error to control viewing distance would be required. Conversely, setting `target_index_count` to 0 will simplify the input mesh as much as possible within the specified error limit; this can be useful for generating LODs that should look good at a given viewing distance. - -The second simplification algorithm, `meshopt_simplifySloppy`, doesn't follow the topology of the original mesh. This means that it doesn't preserve attribute seams or borders, but it can collapse internal details that are too small to matter better because it can merge mesh features that are topologically disjoint but spatially close. - -```c++ -float threshold = 0.2f; -size_t target_index_count = size_t(index_count * threshold); -float target_error = 1e-1f; - -std::vector lod(index_count); -float lod_error = 0.f; -lod.resize(meshopt_simplifySloppy(&lod[0], indices, index_count, &vertices[0].x, vertex_count, sizeof(Vertex), - target_index_count, target_error, &lod_error)); -``` - -This algorithm will not stop early due to topology restrictions but can still do so if target index count can't be reached without introducing an error larger than target. It is 5-6x faster than `meshopt_simplify` when simplification ratio is large, and is able to reach ~20M triangles/sec on a desktop CPU (`meshopt_simplify` works at ~3M triangles/sec). - -Both algorithms can also return the resulting normalized deviation that can be used to choose the correct level of detail based on screen size or solid angle; the error can be converted to object space by multiplying by the scaling factor returned by `meshopt_simplifyScale`. For example, given a mesh with a precomputed LOD and a prescaled error, the screen-space normalized error can be computed and used for LOD selection: - -```c++ -// lod_factor can be 1 or can be adjusted for more or less aggressive LOD selection -float d = max(0, distance(camera_position, mesh_center) - mesh_radius); -float e = d * (tan(camera_fovy / 2) * 2 / screen_height); // 1px in mesh space -bool lod_ok = e * lod_factor >= lod_error; -``` - -When a sequence of LOD meshes is generated that all use the original vertex buffer, care must be taken to order vertices optimally to not penalize mobile GPU architectures that are only capable of transforming a sequential vertex buffer range. It's recommended in this case to first optimize each LOD for vertex cache, then assemble all LODs in one large index buffer starting from the coarsest LOD (the one with fewest triangles), and call `meshopt_optimizeVertexFetch` on the final large index buffer. This will make sure that coarser LODs require a smaller vertex range and are efficient wrt vertex fetch and transform. - -## Advanced simplification - -The main simplification algorithm, `meshopt_simplify`, exposes additional options and functions that can be used to control the simplification process in more detail. - -For basic customization, a number of options can be passed via `options` bitmask that adjust the behavior of the simplifier: - -- `meshopt_SimplifyLockBorder` restricts the simplifier from collapsing edges that are on the border of the mesh. This can be useful for simplifying mesh subsets independently, so that the LODs can be combined without introducing cracks. -- `meshopt_SimplifyErrorAbsolute` changes the error metric from relative to absolute both for the input error limit as well as for the resulting error. This can be used instead of `meshopt_simplifyScale`. -- `meshopt_SimplifySparse` improves simplification performance assuming input indices are a sparse subset of the mesh. This can be useful when simplifying small mesh subsets independently, and is intended to be used for meshlet simplification. For consistency, it is recommended to use absolute errors when sparse simplification is desired, as this flag changes the meaning of the relative errors. -- `meshopt_SimplifyPrune` allows the simplifier to remove isolated components regardless of the topological restrictions inside the component. This is generally recommended for full-mesh simplification as it can improve quality and reduce triangle count; note that with this option, triangles connected to locked vertices may be removed as part of their component. - -While `meshopt_simplify` is aware of attribute discontinuities by default (and infers them through the supplied index buffer) and tries to preserve them, it can be useful to provide information about attribute values. This allows the simplifier to take attribute error into account which can improve shading (by using vertex normals), texture deformation (by using texture coordinates), and may be necessary to preserve vertex colors when textures are not used in the first place. This can be done by using a variant of the simplification function that takes attribute values and weight factors, `meshopt_simplifyWithAttributes`: - -```c++ -const float nrm_weight = 0.5f; -const float attr_weights[3] = {nrm_weight, nrm_weight, nrm_weight}; - -std::vector lod(index_count); -float lod_error = 0.f; -lod.resize(meshopt_simplifyWithAttributes(&lod[0], indices, index_count, &vertices[0].x, vertex_count, sizeof(Vertex), - &vertices[0].nx, sizeof(Vertex), attr_weights, 3, /* vertex_lock= */ NULL, - target_index_count, target_error, /* options= */ 0, &lod_error)); -``` - -The attributes are passed as a separate buffer (in the example above it's a subset of the same vertex buffer) and should be stored as consecutive floats; attribute weights are used to control the importance of each attribute in the simplification process. For normalized attributes like normals and vertex colors, a weight around 1.0 is usually appropriate; internally, a change of `1/weight` in attribute value over a distance `d` is approximately equivalent to a change of `d` in position. Using higher weights may be appropriate to preserve attribute quality at the cost of position quality. If the attribute has a different scale (e.g. unnormalized vertex colors in [0..255] range), the weight should be divided by the scaling factor (1/255 in this example). - -Both the target error and the resulting error combine positional error and attribute error, so the error can be used to control the LOD while taking attribute quality into account, assuming carefully chosen weights. - -When using `meshopt_simplifyWithAttributes`, it is also possible to lock certain vertices by providing a `vertex_lock` array that contains a boolean value for each vertex in the mesh. This can be useful to preserve certain vertices, such as the boundary of the mesh, with more control than `meshopt_SimplifyLockBorder` option provides. - -Simplification currently assumes that the input mesh is using the same material for all triangles. If the mesh uses multiple materials, it is possible to split the mesh into subsets based on the material and simplify each subset independently, using `meshopt_SimplifyLockBorder` or `vertex_lock` to preserve material boundaries; however, this limits the collapses and as a result may reduce the resulting quality. An alternative approach is to encode information about the material into the vertex buffer, ensuring that all three vertices referencing the same triangle have the same material ID; this may require duplicating vertices on the boundary between materials. After this, simplification can be performed as usual, and after simplification per-triangle material information can be computed from the vertex material IDs. There is no need to inform the simplifier of the value of the material ID: the implicit boundaries created by duplicating vertices with conflicting material IDs will be preserved automatically. - -## Point cloud simplification - -In addition to triangle mesh simplification, this library provides a function to simplify point clouds. The algorithm reduces the point cloud to a specified number of points while preserving the overall appearance, and can optionally take per-point colors into account: - -```c++ -const float color_weight = 1; -std::vector indices(target_count); -indices.resize(meshopt_simplifyPoints(&indices[0], &points[0].x, points.size(), sizeof(Point), - &points[0].r, sizeof(Point), color_weight, target_count)); -``` - -The resulting indices can be used to render the simplified point cloud; to reduce the memory footprint, the point cloud can be reindexed to create an array of points from the indices. - -## Mesh shading - -Modern GPUs are beginning to deviate from the traditional rasterization model. NVidia GPUs starting from Turing and AMD GPUs starting from RDNA2 provide a new programmable geometry pipeline that, instead of being built around index buffers and vertex shaders, is built around mesh shaders - a new shader type that allows to provide a batch of work to the rasterizer. - -Using mesh shaders in context of traditional mesh rendering provides an opportunity to use a variety of optimization techniques, starting from more efficient vertex reuse, using various forms of culling (e.g. cluster frustum or occlusion culling) and in-memory compression to maximize the utilization of GPU hardware. Beyond traditional rendering mesh shaders provide a richer programming model that can synthesize new geometry more efficiently than common alternatives such as geometry shaders. Mesh shading can be accessed via Vulkan or Direct3D 12 APIs; please refer to [Introduction to Turing Mesh Shaders](https://developer.nvidia.com/blog/introduction-turing-mesh-shaders/) and [Mesh Shaders and Amplification Shaders: Reinventing the Geometry Pipeline](https://devblogs.microsoft.com/directx/coming-to-directx-12-mesh-shaders-and-amplification-shaders-reinventing-the-geometry-pipeline/) for additional information. - -To use mesh shaders for conventional rendering efficiently, geometry needs to be converted into a series of meshlets; each meshlet represents a small subset of the original mesh and comes with a small set of vertices and a separate micro-index buffer that references vertices in the meshlet. This information can be directly fed to the rasterizer from the mesh shader. This library provides algorithms to create meshlet data for a mesh, and - assuming geometry is static - can compute bounding information that can be used to perform cluster culling, a technique that can reject a meshlet if it's invisible on screen. - -To generate meshlet data, this library provides `meshopt_buildMeshlets` algorithm, which tries to balance topological efficiency (by maximizing vertex reuse inside meshlets) with culling efficiency (by minimizing meshlet radius and triangle direction divergence) and produces GPU-friendly data. As an alternative (that can be useful for load-time processing), `meshopt_buildMeshletsScan` can create the meshlet data using a vertex cache-optimized index buffer as a starting point by greedily aggregating consecutive triangles until they go over the meshlet limits. `meshopt_buildMeshlets` is recommended for offline data processing even if cone culling is not used. - -```c++ -const size_t max_vertices = 64; -const size_t max_triangles = 124; -const float cone_weight = 0.0f; - -size_t max_meshlets = meshopt_buildMeshletsBound(indices.size(), max_vertices, max_triangles); -std::vector meshlets(max_meshlets); -std::vector meshlet_vertices(max_meshlets * max_vertices); -std::vector meshlet_triangles(max_meshlets * max_triangles * 3); - -size_t meshlet_count = meshopt_buildMeshlets(meshlets.data(), meshlet_vertices.data(), meshlet_triangles.data(), indices.data(), - indices.size(), &vertices[0].x, vertices.size(), sizeof(Vertex), max_vertices, max_triangles, cone_weight); -``` - -To generate the meshlet data, `max_vertices` and `max_triangles` need to be set within limits supported by the hardware; for NVidia the values of 64 and 124 are recommended (`max_triangles` must be divisible by 4 so 124 is the value closest to official NVidia's recommended 126). `cone_weight` should be left as 0 if cluster cone culling is not used, and set to a value between 0 and 1 to balance cone culling efficiency with other forms of culling like frustum or occlusion culling. - -Each resulting meshlet refers to a portion of `meshlet_vertices` and `meshlet_triangles` arrays; the arrays are overallocated for the worst case so it's recommended to trim them before saving them as an asset / uploading them to the GPU: - -```c++ -const meshopt_Meshlet& last = meshlets[meshlet_count - 1]; - -meshlet_vertices.resize(last.vertex_offset + last.vertex_count); -meshlet_triangles.resize(last.triangle_offset + ((last.triangle_count * 3 + 3) & ~3)); -meshlets.resize(meshlet_count); -``` - -However depending on the application other strategies of storing the data can be useful; for example, `meshlet_vertices` serves as indices into the original vertex buffer but it might be worthwhile to generate a mini vertex buffer for each meshlet to remove the extra indirection when accessing vertex data, or it might be desirable to compress vertex data as vertices in each meshlet are likely to be very spatially coherent. - -For optimal performance, it is recommended to further optimize each meshlet in isolation for better triangle and vertex locality by calling `meshopt_optimizeMeshlet` on vertex and index data like so: - -```c++ -meshopt_optimizeMeshlet(&meshlet_vertices[m.vertex_offset], &meshlet_triangles[m.triangle_offset], m.triangle_count, m.vertex_count); -``` - -Different applications will choose different strategies for rendering meshlets; on a GPU capable of mesh shading, meshlets can be rendered directly; for example, a basic GLSL shader for `VK_EXT_mesh_shader` extension could look like this (parts omitted for brevity): - -```glsl -layout(binding = 0) readonly buffer Meshlets { Meshlet meshlets[]; }; -layout(binding = 1) readonly buffer MeshletVertices { uint meshlet_vertices[]; }; -layout(binding = 2) readonly buffer MeshletTriangles { uint8_t meshlet_triangles[]; }; - -void main() { - Meshlet meshlet = meshlets[gl_WorkGroupID.x]; - SetMeshOutputsEXT(meshlet.vertex_count, meshlet.triangle_count); - - for (uint i = gl_LocalInvocationIndex; i < meshlet.vertex_count; i += gl_WorkGroupSize.x) { - uint index = meshlet_vertices[meshlet.vertex_offset + i]; - gl_MeshVerticesEXT[i].gl_Position = world_view_projection * vec4(vertex_positions[index], 1); - } - - for (uint i = gl_LocalInvocationIndex; i < meshlet.triangle_count; i += gl_WorkGroupSize.x) { - uint offset = meshlet.triangle_offset + i * 3; - gl_PrimitiveTriangleIndicesEXT[i] = uvec3( - meshlet_triangles[offset], meshlet_triangles[offset + 1], meshlet_triangles[offset + 2]); - } -} -``` - -After generating the meshlet data, it's also possible to generate extra data for each meshlet that can be saved and used at runtime to perform cluster culling, where each meshlet can be discarded if it's guaranteed to be invisible. To generate the data, `meshlet_computeMeshletBounds` can be used: - -```c++ -meshopt_Bounds bounds = meshopt_computeMeshletBounds(&meshlet_vertices[m.vertex_offset], &meshlet_triangles[m.triangle_offset], - m.triangle_count, &vertices[0].x, vertices.size(), sizeof(Vertex)); -``` - -The resulting `bounds` values can be used to perform frustum or occlusion culling using the bounding sphere, or cone culling using the cone axis/angle (which will reject the entire meshlet if all triangles are guaranteed to be back-facing from the camera point of view): - -```c++ -if (dot(normalize(cone_apex - camera_position), cone_axis) >= cone_cutoff) reject(); -``` - -## Efficiency analyzers - -While the only way to get precise performance data is to measure performance on the target GPU, it can be valuable to measure the impact of these optimization in a GPU-independent manner. To this end, the library provides analyzers for all three major optimization routines. For each optimization there is a corresponding analyze function, like `meshopt_analyzeOverdraw`, that returns a struct with statistics. - -`meshopt_analyzeVertexCache` returns vertex cache statistics. The common metric to use is ACMR - average cache miss ratio, which is the ratio of the total number of vertex invocations to the triangle count. The worst-case ACMR is 3 (GPU has to process 3 vertices for each triangle); on regular grids the optimal ACMR approaches 0.5. On real meshes it usually is in [0.5..1.5] range depending on the amount of vertex splits. One other useful metric is ATVR - average transformed vertex ratio - which represents the ratio of vertex shader invocations to the total vertices, and has the best case of 1.0 regardless of mesh topology (each vertex is transformed once). - -`meshopt_analyzeVertexFetch` returns vertex fetch statistics. The main metric it uses is overfetch - the ratio between the number of bytes read from the vertex buffer to the total number of bytes in the vertex buffer. Assuming non-redundant vertex buffers, the best case is 1.0 - each byte is fetched once. - -`meshopt_analyzeOverdraw` returns overdraw statistics. The main metric it uses is overdraw - the ratio between the number of pixel shader invocations to the total number of covered pixels, as measured from several different orthographic cameras. The best case for overdraw is 1.0 - each pixel is shaded once. - -Note that all analyzers use approximate models for the relevant GPU units, so the numbers you will get as the result are only a rough approximation of the actual performance. - -## Specialized processing - -In addition to the core optimization techniques, the library provides several specialized algorithms for specific rendering techniques and pipeline optimizations that require a particular configuration of vertex and index data. - -### Geometry shader adjacency - -For algorithms that use geometry shaders and require adjacency information, this library can generate an index buffer with adjacency data: - -```c++ -std::vector adjacency(indices.size() * 2); -meshopt_generateAdjacencyIndexBuffer(&adjacency[0], &indices[0], indices.size(), &vertices[0].x, vertices.size(), sizeof(Vertex)); -``` - -This creates an index buffer suitable for rendering with triangle-with-adjacency topology, providing 3 extra vertices per triangle that represent vertices opposite to each triangle's edge. This data can be used to compute silhouettes and perform other types of local geometric processing in geometry shaders. To render the mesh with adjacency data, the index buffer should be used with `D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST_ADJ`/`VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY`/`GL_TRIANGLES_ADJACENCY` topology. - -Note that the use of geometry shaders may have a performance impact on some GPUs; in some cases alternative implementation strategies may be more efficient. - -### Tessellation with displacement mapping - -For hardware tessellation with crack-free displacement mapping, this library can generate a special index buffer that supports PN-AEN tessellation: - -```c++ -std::vector tess(indices.size() * 4); -meshopt_generateTessellationIndexBuffer(&tess[0], &indices[0], indices.size(), &vertices[0].x, vertices.size(), sizeof(Vertex)); -``` - -This generates a 12-vertex patch for each input triangle with the following layout: - -- 0, 1, 2: original triangle vertices -- 3, 4: opposing edge for edge 0, 1 -- 5, 6: opposing edge for edge 1, 2 -- 7, 8: opposing edge for edge 2, 0 -- 9, 10, 11: dominant vertices for corners 0, 1, 2 - -This allows the use of hardware tessellation to implement PN-AEN and/or displacement mapping without cracks along UV seams or normal discontinuities. To render the mesh, the index buffer should be used with `D3D_PRIMITIVE_TOPOLOGY_12_CONTROL_POINT_PATCHLIST`/`VK_PRIMITIVE_TOPOLOGY_PATCH_LIST` (`patchControlPoints=12`) topology. For more details please refer to the following papers: [Crack-Free Point-Normal Triangles using Adjacent Edge Normals](https://developer.download.nvidia.com/whitepapers/2010/PN-AEN-Triangles-Whitepaper.pdf), [Tessellation on Any Budget](https://www.nvidia.com/content/pdf/gdc2011/john_mcdonald.pdf) and [My Tessellation Has Cracks!](https://developer.download.nvidia.com/assets/gamedev/files/gdc12/GDC12_DUDASH_MyTessellationHasCracks.pdf). - -### Visibility buffers - -To render geometry into visibility buffers, access to primitive index in fragment shader is required. While it is possible to use `SV_PrimitiveID`/`gl_PrimitiveID` in the fragment shader, this can result in suboptimal performance on some GPUs (notably, AMD RDNA1 and all NVidia GPUs), and may not be supported on mobile or console hardware. Using mesh shaders to generate primitive IDs is efficient but requires hardware support that is not universally available. To work around these limitations, this library provides a way to generate a special index buffer that uses provoking vertex to encode primitive IDs: - -```c++ -std::vector provoke(indices.size()); -std::vector reorder(vertices.size() + indices.size() / 3); -reorder.resize(meshopt_generateProvokingIndexBuffer(&provoke[0], &reorder[0], &indices[0], indices.size(), vertices.size())); -``` - -This generates a special index buffer along with a reorder table that satisfies two constraints: - -- `provoke[3 * tri] == tri` -- `reorder[provoke[x]]` refers to the original triangle vertices - -To render the mesh with provoking vertex data, the application should use `provoke` as an index buffer and a vertex shader that passes vertex index (`SV_VertexID`/`gl_VertexIndex`) via a `flat`/`nointerpolation` attribute to the fragment shader as a primitive index, and loads vertex data manually by computing the real vertex index based on `reorder` table (`reorder[gl_VertexIndex]`). For more details please refer to [Variable Rate Shading with Visibility Buffer Rendering](https://advances.realtimerendering.com/s2024/content/Hable/Advances_SIGGRAPH_2024_VisibilityVRS-SIGGRAPH_Advances_2024.pptx); naturally, this technique does not require VRS. - -Note: This assumes the provoking vertex is the first vertex of a triangle, which is true for all graphics APIs except OpenGL/WebGL. For OpenGL/WebGL, you may need to rotate each triangle (abc -> bca) in the resulting index buffer, or use the `glProvokingVertex` function (OpenGL 3.2+) or `WEBGL_provoking_vertex` extension (WebGL2) to change the provoking vertex convention. For WebGL2, this is highly recommended to avoid a variety of emulation slowdowns that happen by default if `flat` attributes are used, such as an implicit use of geometry shaders. - -## Memory management - -Many algorithms allocate temporary memory to store intermediate results or accelerate processing. The amount of memory allocated is a function of various input parameters such as vertex count and index count. By default memory is allocated using `operator new` and `operator delete`; if these operators are overloaded by the application, the overloads will be used instead. Alternatively it's possible to specify custom allocation/deallocation functions using `meshopt_setAllocator`, e.g. - -```c++ -meshopt_setAllocator(malloc, free); -``` - -> Note that the library expects the allocation function to either throw in case of out-of-memory (in which case the exception will propagate to the caller) or abort, so technically the use of `malloc` above isn't safe. If you want to handle out-of-memory errors without using C++ exceptions, you can use `setjmp`/`longjmp` instead. - -Vertex and index decoders (`meshopt_decodeVertexBuffer`, `meshopt_decodeIndexBuffer`, `meshopt_decodeIndexSequence`) do not allocate memory and work completely within the buffer space provided via arguments. - -All functions have bounded stack usage that does not exceed 32 KB for any algorithms. ## License +This project is subject to the license described in LICENCE. -This library is available to anybody free of charge, under the terms of MIT License (see LICENSE.md). +## Related warehouse +meshoptimizer \ No newline at end of file diff --git a/README_meshoptimizer.md b/README_meshoptimizer.md new file mode 100644 index 00000000..07a6791f --- /dev/null +++ b/README_meshoptimizer.md @@ -0,0 +1,558 @@ +# 🐇 meshoptimizer [![Actions Status](https://github.com/zeux/meshoptimizer/workflows/build/badge.svg)](https://github.com/zeux/meshoptimizer/actions) [![codecov.io](https://codecov.io/github/zeux/meshoptimizer/coverage.svg?branch=master)](https://codecov.io/github/zeux/meshoptimizer?branch=master) ![MIT](https://img.shields.io/badge/license-MIT-blue.svg) [![GitHub](https://img.shields.io/badge/repo-github-green.svg)](https://github.com/zeux/meshoptimizer) + +## Purpose + +When a GPU renders triangle meshes, various stages of the GPU pipeline have to process vertex and index data. The efficiency of these stages depends on the data you feed to them; this library provides algorithms to help optimize meshes for these stages, as well as algorithms to reduce the mesh complexity and storage overhead. + +The library provides a C and C++ interface for all algorithms; you can use it from C/C++ or from other languages via FFI (such as P/Invoke). If you want to use this library from Rust, you should use [meshopt crate](https://crates.io/crates/meshopt). JavaScript interface for some algorithms is available through [meshoptimizer.js](https://www.npmjs.com/package/meshoptimizer). + +[gltfpack](./gltf/README.md), which is a tool that can automatically optimize glTF files, is developed and distributed alongside the library. + +## Installing + +meshoptimizer is hosted on GitHub; you can download the latest release using git: + +``` +git clone -b v0.22 https://github.com/zeux/meshoptimizer.git +``` + +Alternatively you can [download the .zip archive from GitHub](https://github.com/zeux/meshoptimizer/archive/v0.22.zip). + +The library is also available as a Linux package in several distributions ([ArchLinux](https://aur.archlinux.org/packages/meshoptimizer/), [Debian](https://packages.debian.org/libmeshoptimizer), [FreeBSD](https://www.freshports.org/misc/meshoptimizer/), [Nix](https://mynixos.com/nixpkgs/package/meshoptimizer), [Ubuntu](https://packages.ubuntu.com/libmeshoptimizer)), as well as a [Vcpkg port](https://github.com/microsoft/vcpkg/tree/master/ports/meshoptimizer) (see [installation instructions](https://learn.microsoft.com/en-us/vcpkg/get_started/get-started)) and a [Conan package](https://conan.io/center/recipes/meshoptimizer). + +[gltfpack](./gltf/README.md) is available as a pre-built binary on [Releases page](https://github.com/zeux/meshoptimizer/releases) or via [npm package](https://www.npmjs.com/package/gltfpack). Native binaries are recommended since they are more efficient and support texture compression. + +## Building + +meshoptimizer is distributed as a set of C++ source files. To include it into your project, you can use one of the two options: + +* Use CMake to build the library (either as a standalone project or as part of your project) +* Add source files to your project's build system + +The source files are organized in such a way that you don't need to change your build-system settings, and you only need to add the source files for the algorithms you use. They should build without warnings or special compilation options on all major compilers. + +## Pipeline + +When optimizing a mesh, you should typically feed it through a set of optimizations (the order is important!): + +1. Indexing +2. (optional, discussed last) Simplification +3. Vertex cache optimization +4. Overdraw optimization +5. Vertex fetch optimization +6. Vertex quantization +7. Shadow indexing +8. (optional) Vertex/index buffer compression + +## Indexing + +Most algorithms in this library assume that a mesh has a vertex buffer and an index buffer. For algorithms to work well and also for GPU to render your mesh efficiently, the vertex buffer has to have no redundant vertices; you can generate an index buffer from an unindexed vertex buffer or reindex an existing (potentially redundant) index buffer as follows: + +First, generate a remap table from your existing vertex (and, optionally, index) data: + +```c++ +size_t index_count = face_count * 3; +size_t unindexed_vertex_count = face_count * 3; +std::vector remap(index_count); // allocate temporary memory for the remap table +size_t vertex_count = meshopt_generateVertexRemap(&remap[0], NULL, index_count, &unindexed_vertices[0], unindexed_vertex_count, sizeof(Vertex)); +``` + +Note that in this case we only have an unindexed vertex buffer; when input mesh has an index buffer, it will need to be passed to `meshopt_generateVertexRemap` instead of `NULL`, along with the correct source vertex count. In either case, the remap table is generated based on binary equivalence of the input vertices, so the resulting mesh will render the same way. Binary equivalence considers all input bytes, including padding which should be zero-initialized if the vertex structure has gaps. + +After generating the remap table, you can allocate space for the target vertex buffer (`vertex_count` elements) and index buffer (`index_count` elements) and generate them: + +```c++ +meshopt_remapIndexBuffer(indices, NULL, index_count, &remap[0]); +meshopt_remapVertexBuffer(vertices, &unindexed_vertices[0], unindexed_vertex_count, sizeof(Vertex), &remap[0]); +``` + +You can then further optimize the resulting buffers by calling the other functions on them in-place. + +`meshopt_generateVertexRemap` uses binary equivalence of vertex data, which is generally a reasonable default; however, in some cases some attributes may have floating point drift causing extra vertices to be generated. For such cases, it may be necessary to quantize some attributes (most importantly, normals and tangents) before generating the remap, or use a custom weld algorithm that supports per-attribute tolerance instead. + +## Vertex cache optimization + +When the GPU renders the mesh, it has to run the vertex shader for each vertex; usually GPUs have a built-in fixed size cache that stores the transformed vertices (the result of running the vertex shader), and uses this cache to reduce the number of vertex shader invocations. This cache is usually small, 16-32 vertices, and can have different replacement policies; to use this cache efficiently, you have to reorder your triangles to maximize the locality of reused vertex references like so: + +```c++ +meshopt_optimizeVertexCache(indices, indices, index_count, vertex_count); +``` + +## Overdraw optimization + +After transforming the vertices, GPU sends the triangles for rasterization which results in generating pixels that are usually first ran through the depth test, and pixels that pass it get the pixel shader executed to generate the final color. As pixel shaders get more expensive, it becomes more and more important to reduce overdraw. While in general improving overdraw requires view-dependent operations, this library provides an algorithm to reorder triangles to minimize the overdraw from all directions, which you should run after vertex cache optimization like this: + +```c++ +meshopt_optimizeOverdraw(indices, indices, index_count, &vertices[0].x, vertex_count, sizeof(Vertex), 1.05f); +``` + +The overdraw optimizer needs to read vertex positions as a float3 from the vertex; the code snippet above assumes that the vertex stores position as `float x, y, z`. + +When performing the overdraw optimization you have to specify a floating-point threshold parameter. The algorithm tries to maintain a balance between vertex cache efficiency and overdraw; the threshold determines how much the algorithm can compromise the vertex cache hit ratio, with 1.05 meaning that the resulting ratio should be at most 5% worse than before the optimization. + +## Vertex fetch optimization + +After the final triangle order has been established, we still can optimize the vertex buffer for memory efficiency. Before running the vertex shader GPU has to fetch the vertex attributes from the vertex buffer; the fetch is usually backed by a memory cache, and as such optimizing the data for the locality of memory access is important. You can do this by running this code: + +```c++ +meshopt_optimizeVertexFetch(vertices, indices, index_count, vertices, vertex_count, sizeof(Vertex)); +``` + +This will reorder the vertices in the vertex buffer to try to improve the locality of reference, and rewrite the indices in place to match; if the vertex data is stored using multiple streams, you should use `meshopt_optimizeVertexFetchRemap` instead. This optimization has to be performed on the final index buffer since the optimal vertex order depends on the triangle order. + +Note that the algorithm does not try to model cache replacement precisely and instead just orders vertices in the order of use, which generally produces results that are close to optimal. + +## Vertex quantization + +To optimize memory bandwidth when fetching the vertex data even further, and to reduce the amount of memory required to store the mesh, it is often beneficial to quantize the vertex attributes to smaller types. While this optimization can technically run at any part of the pipeline (and sometimes doing quantization as the first step can improve indexing by merging almost identical vertices), it generally is easier to run this after all other optimizations since some of them require access to float3 positions. + +Quantization is usually domain specific; it's common to quantize normals using 3 8-bit integers but you can use higher-precision quantization (for example using 10 bits per component in a 10_10_10_2 format), or a different encoding to use just 2 components. For positions and texture coordinate data the two most common storage formats are half precision floats, and 16-bit normalized integers that encode the position relative to the AABB of the mesh or the UV bounding rectangle. + +The number of possible combinations here is very large but this library does provide the building blocks, specifically functions to quantize floating point values to normalized integers, as well as half-precision floats. For example, here's how you can quantize a normal: + +```c++ +unsigned int normal = + (meshopt_quantizeUnorm(v.nx, 10) << 20) | + (meshopt_quantizeUnorm(v.ny, 10) << 10) | + meshopt_quantizeUnorm(v.nz, 10); +``` + +and here's how you can quantize a position: + +```c++ +unsigned short px = meshopt_quantizeHalf(v.x); +unsigned short py = meshopt_quantizeHalf(v.y); +unsigned short pz = meshopt_quantizeHalf(v.z); +``` + +Since quantized vertex attributes often need to remain in their compact representations for efficient transfer and storage, they are usually dequantized during vertex processing by configuring the GPU vertex input correctly to expect normalized integers or half precision floats, which often needs no or minimal changes to the shader code. When CPU dequantization is required instead, `meshopt_dequantizeHalf` can be used to convert half precision values back to single precision; for normalized integer formats, the dequantization just requires dividing by 2^N-1 for unorm and 2^(N-1)-1 for snorm variants, for example manually reversing `meshopt_quantizeUnorm(v, 10)` can be done by dividing by 1023. + +## Shadow indexing + +Many rendering pipelines require meshes to be rendered to depth-only targets, such as shadow maps or during a depth pre-pass, in addition to color/G-buffer targets. While using the same geometry data for both cases is possible, reducing the number of unique vertices for depth-only rendering can be beneficial, especially when the source geometry has many attribute seams due to faceted shading or lightmap texture seams. + +To achieve this, this library provides the `meshopt_generateShadowIndexBuffer` algorithm, which generates a second (shadow) index buffer that can be used with the original vertex data: + +```c++ +std::vector shadow_indices(index_count); +// note: this assumes Vertex starts with float3 positions and should be adjusted accordingly for quantized positions +meshopt_generateShadowIndexBuffer(&shadow_indices[0], indices, index_count, &vertices[0].x, vertex_count, sizeof(float) * 3, sizeof(Vertex)); +``` + +Because the vertex data is shared, shadow indexing should be done after other optimizations of the vertex/index data. However, it's possible (and recommended) to optimize the resulting shadow index buffer for vertex cache: + +```c++ +meshopt_optimizeVertexCache(&shadow_indices[0], &shadow_indices[0], index_count, vertex_count); +``` + +In some cases, it may be beneficial to split the vertex positions into a separate buffer to maximize efficiency for depth-only rendering. Note that the example above assumes only positions are relevant for shadow rendering, but more complex materials may require adding texture coordinates (for alpha testing) or skinning data to the vertex portion used as a key. `meshopt_generateShadowIndexBufferMulti` can be useful for these cases if the relevant data is not contiguous. + +## Vertex/index buffer compression + +In case storage size or transmission bandwidth is of importance, you might want to additionally compress vertex and index data. While several mesh compression libraries, like Google Draco, are available, they typically are designed to maximize the compression ratio at the cost of disturbing the vertex/index order (which makes the meshes inefficient to render on GPU) or decompression performance. They also frequently don't support custom game-ready quantized vertex formats and thus require to re-quantize the data after loading it, introducing extra quantization errors and making decoding slower. + +Alternatively you can use general purpose compression libraries like zstd or Oodle to compress vertex/index data - however these compressors aren't designed to exploit redundancies in vertex/index data and as such compression rates can be unsatisfactory. + +To that end, this library provides algorithms to "encode" vertex and index data. The result of the encoding is generally significantly smaller than initial data, and remains compressible with general purpose compressors - so you can either store encoded data directly (for modest compression ratios and maximum decoding performance), or further compress it with zstd/Oodle to maximize compression ratio. + +> Note: this compression scheme is available as a glTF extension [EXT_meshopt_compression](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Vendor/EXT_meshopt_compression/README.md). + +To encode, you need to allocate target buffers (preferably using the worst case bound) and call encoding functions: + +```c++ +std::vector vbuf(meshopt_encodeVertexBufferBound(vertex_count, sizeof(Vertex))); +vbuf.resize(meshopt_encodeVertexBuffer(&vbuf[0], vbuf.size(), vertices, vertex_count, sizeof(Vertex))); + +std::vector ibuf(meshopt_encodeIndexBufferBound(index_count, vertex_count)); +ibuf.resize(meshopt_encodeIndexBuffer(&ibuf[0], ibuf.size(), indices, index_count)); +``` + +You can then either serialize `vbuf`/`ibuf` as is, or compress them further. To decode the data at runtime, call decoding functions: + +```c++ +int resvb = meshopt_decodeVertexBuffer(vertices, vertex_count, sizeof(Vertex), &vbuf[0], vbuf.size()); +int resib = meshopt_decodeIndexBuffer(indices, index_count, &ibuf[0], ibuf.size()); +assert(resvb == 0 && resib == 0); +``` + +Note that vertex encoding assumes that vertex buffer was optimized for vertex fetch, and that vertices are quantized; index encoding assumes that the vertex/index buffers were optimized for vertex cache and vertex fetch. Feeding unoptimized data into the encoders will produce poor compression ratios. Both codecs are lossless - the only lossy step is quantization that happens before encoding. + +Decoding functions are heavily optimized and can directly target write-combined memory; you can expect both decoders to run at 1-3 GB/s on modern desktop CPUs. Compression ratios depend on the data; vertex data compression ratio is typically around 2-4x (compared to already quantized data), index data compression ratio is around 5-6x (compared to raw 16-bit index data). General purpose lossless compressors can further improve on these results. + +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. + +The following guarantees on data compatibility are provided for point releases (*no* guarantees are given for development branch): + +- Data encoded with older versions of the library can always be decoded with newer versions; +- Data encoded with newer versions of the library can be decoded with older versions, provided that encoding versions are set correctly; if binary stability of encoded data is important, use `meshopt_encodeVertexVersion` and `meshopt_encodeIndexVersion` to 'pin' the data versions. + +Due to a very high decoding performance and compatibility with general purpose lossless compressors, the compression is a good fit for the use on the web. To that end, meshoptimizer provides both vertex and index decoders compiled into WebAssembly and wrapped into a module with JavaScript-friendly interface, `js/meshopt_decoder.js`, that you can use to decode meshes that were encoded offline: + +```js +// ready is a Promise that is resolved when (asynchronous) WebAssembly compilation finishes +await MeshoptDecoder.ready; + +// decode from *Data (Uint8Array) into *Buffer (Uint8Array) +MeshoptDecoder.decodeVertexBuffer(vertexBuffer, vertexCount, vertexSize, vertexData); +MeshoptDecoder.decodeIndexBuffer(indexBuffer, indexCount, indexSize, indexData); +``` + +[Usage example](https://meshoptimizer.org/demo/) is available, with source in `demo/index.html`; this example uses .GLB files encoded using `gltfpack`. + +## Point cloud compression + +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. + +To compress point clouds efficiently, it's recommended to first preprocess the points by sorting them using the spatial sort algorithm: + +```c++ +std::vector remap(point_count); +meshopt_spatialSortRemap(&remap[0], positions, point_count, sizeof(vec3)); + +// for each attribute stream +meshopt_remapVertexBuffer(positions, positions, point_count, sizeof(vec3), &remap[0]); +``` + +After this the resulting arrays should be quantized (e.g. using 16-bit fixed point numbers for positions and 8-bit color components), and the result can be compressed using `meshopt_encodeVertexBuffer` as described in the previous section. To decompress, `meshopt_decodeVertexBuffer` will recover the quantized data that can be used directly or converted back to original floating-point data. The compression ratio depends on the nature of source data, for colored points it's typical to get 35-40 bits per point as a result. + +## Advanced compression + +Both vertex and index codecs are designed to be used in a three-stage pipeline: + +- Preparation (quantization, filtering, ordering) +- Encoding (`meshopt_encodeVertexBuffer`/`meshopt_encodeIndexBuffer`) +- Optional compression (LZ4/zlib/zstd/Oodle) + +The preparation stage is crucial for achieving good compression ratios; this section will cover some techniques that can be used to improve the results. + +The index codec targets 1 byte per triangle as a best case; on real-world data, it's typical to achieve 1-1.2 bytes per triangle. To reach this, the data needs to be optimized for vertex cache and vertex fetch. Optimizations that do not disrupt triangle locality (such as overdraw) are safe to use in between. +To reduce the data size further, it's possible to use `meshopt_optimizeVertexCacheStrip` instead of `meshopt_optimizeVertexCache` when optimizing for vertex cache. This trades off some efficiency in vertex transform for smaller vertex and index data. + +When referenced vertex indices are not sequential, the index codec will use around 2 bytes per index. This can happen when the referenced vertices are a sparse subset of the vertex buffer, such as when encoding LODs. General-purpose compression can be especially helpful in this case. + +The vertex codec tries to take advantage of the inherent locality of sequential vertices and identify bit patterns that repeat in consecutive vertices. Typically, vertex cache + vertex fetch provides a reasonably local vertex traversal order; without an index buffer, it is recommended to sort vertices spatially to improve the compression ratio. +It is crucial to correctly specify the stride when encoding vertex data; however, it does not matter whether the vertices are interleaved or deinterleaved, as the codecs perform full byte deinterleaving internally. + +For optimal compression results, the values must be quantized to small integers. It can be valuable to use bit counts that are not multiples of 8. For example, instead of using 16 bits to represent texture coordinates, use 12-bit integers and divide by 4095 in the shader. Alternatively, using half-precision floats can often achieve good results. +For single-precision floating-point data, it's recommended to use `meshopt_quantizeFloat` to remove entropy from the lower bits of the mantissa. Due to current limitations of the codec, the bit count needs to be 15 (23-8) for good results (7 can be used for more extreme compression). +For normal or tangent vectors, using octahedral encoding is recommended over three components as it reduces redundancy. Similarly to other quantized values, consider using 10-12 bits per component instead of 16. + +> Note: vertex codec v0 is limited to taking advantage of redundancy in high bits of each byte. Because of this, packing multiple 10-bit values into 32 bits will reduce compression ratio, and when storing a 12-bit value in 16 bits, high bits should be zeroed out. This limitation may be lifted in future versions of the codec. + +To further leverage the inherent structure of some data, the preparation stage can use filters that encode and decode the data in a lossy manner. This is similar to quantization but can be used without having to change the shader code. After decoding, the filter transformation needs to be reversed. This library provides three filters: + +- Octahedral filter (`meshopt_encodeFilterOct`/`meshopt_decodeFilterOct`) encodes quantized (snorm) normal or tangent vectors using octahedral encoding. Any number of bits <= 16 can be used with 4 bytes or 8 bytes per vector. +- Quaternion filter (`meshopt_encodeFilterQuat`/`meshopt_decodeFilterQuat`) encodes quantized (snorm) quaternion vectors; this can be used to encode rotations or tangent frames. Any number of bits between 4 and 16 can be used with 8 bytes per vector. +- Exponential filter (`meshopt_encodeFilterExp`/`meshopt_decodeFilterExp`) encodes single-precision floating-point vectors; this can be used to encode arbitrary floating-point data more efficiently. In addition to an arbitrary bit count (<= 24), the filter takes a "mode" parameter that allows specifying how the exponent sharing is performed to trade off compression ratio and quality: + + - `meshopt_EncodeExpSeparate` does not share exponents and results in the largest output + - `meshopt_EncodeExpSharedVector` shares exponents between different components of the same vector + - `meshopt_EncodeExpSharedComponent` shares exponents between the same component in different vectors + - `meshopt_EncodeExpClamped` does not share exponents but clamps the exponent range to reduce exponent entropy + +Note that all filters are lossy and require the data to be deinterleaved with one attribute per stream; this faciliates efficient SIMD implementation of filter decoders, allowing the overall decompression speed to be close to that of the raw codec. + +## Triangle strip conversion + +On most hardware, indexed triangle lists are the most efficient way to drive the GPU. However, in some cases triangle strips might prove beneficial: + +- On some older GPUs, triangle strips may be a bit more efficient to render +- On extremely memory constrained systems, index buffers for triangle strips could save a bit of memory + +This library provides an algorithm for converting a vertex cache optimized triangle list to a triangle strip: + +```c++ +std::vector strip(meshopt_stripifyBound(index_count)); +unsigned int restart_index = ~0u; +size_t strip_size = meshopt_stripify(&strip[0], indices, index_count, vertex_count, restart_index); +``` + +Typically you should expect triangle strips to have ~50-60% of indices compared to triangle lists (~1.5-1.8 indices per triangle) and have ~5% worse ACMR. +Note that triangle strips can be stitched with or without restart index support. Using restart indices can result in ~10% smaller index buffers, but on some GPUs restart indices may result in decreased performance. + +To reduce the triangle strip size further, it's recommended to use `meshopt_optimizeVertexCacheStrip` instead of `meshopt_optimizeVertexCache` when optimizing for vertex cache. This trades off some efficiency in vertex transform for smaller index buffers. + +## Deinterleaved geometry + +All of the examples above assume that geometry is represented as a single vertex buffer and a single index buffer. This requires storing all vertex attributes - position, normal, texture coordinate, skinning weights etc. - in a single contiguous struct. However, in some cases using multiple vertex streams may be preferable. In particular, if some passes require only positional data - such as depth pre-pass or shadow map - then it may be beneficial to split it from the rest of the vertex attributes to make sure the bandwidth use during these passes is optimal. On some mobile GPUs a position-only attribute stream also improves efficiency of tiling algorithms. + +Most of the functions in this library either only need the index buffer (such as vertex cache optimization) or only need positional information (such as overdraw optimization). However, several tasks require knowledge about all vertex attributes. + +For indexing, `meshopt_generateVertexRemap` assumes that there's just one vertex stream; when multiple vertex streams are used, it's necessary to use `meshopt_generateVertexRemapMulti` as follows: + +```c++ +meshopt_Stream streams[] = { + {&unindexed_pos[0], sizeof(float) * 3, sizeof(float) * 3}, + {&unindexed_nrm[0], sizeof(float) * 3, sizeof(float) * 3}, + {&unindexed_uv[0], sizeof(float) * 2, sizeof(float) * 2}, +}; + +std::vector remap(index_count); +size_t vertex_count = meshopt_generateVertexRemapMulti(&remap[0], NULL, index_count, index_count, streams, sizeof(streams) / sizeof(streams[0])); +``` + +After this `meshopt_remapVertexBuffer` needs to be called once for each vertex stream to produce the correctly reindexed stream. For shadow indexing, similarly `meshopt_generateShadowIndexBufferMulti` is available as a replacement. + +Instead of calling `meshopt_optimizeVertexFetch` for reordering vertices in a single vertex buffer for efficiency, calling `meshopt_optimizeVertexFetchRemap` and then calling `meshopt_remapVertexBuffer` for each stream again is recommended. + +Finally, when compressing vertex data, `meshopt_encodeVertexBuffer` should be used on each vertex stream separately - this allows the encoder to best utilize corellation between attribute values for different vertices. + +## Simplification + +All algorithms presented so far don't affect visual appearance at all, with the exception of quantization that has minimal controlled impact. However, fundamentally the most effective way at reducing the rendering or transmission cost of a mesh is to make the mesh simpler. + +This library provides two simplification algorithms that reduce the number of triangles in the mesh. Given a vertex and an index buffer, they generate a second index buffer that uses existing vertices in the vertex buffer. This index buffer can be used directly for rendering with the original vertex buffer (preferably after vertex cache optimization), or a new compact vertex/index buffer can be generated using `meshopt_optimizeVertexFetch` that uses the optimal number and order of vertices. + +The first simplification algorithm, `meshopt_simplify`, follows the topology of the original mesh in an attempt to preserve attribute seams, borders and overall appearance. For meshes with inconsistent topology or many seams, such as faceted meshes, it can result in simplifier getting "stuck" and not being able to simplify the mesh fully. Therefore it's critical that identical vertices are "welded" together, that is, the input vertex buffer does not contain duplicates. Additionally, it may be worthwhile to weld the vertices without taking into account vertex attributes that aren't critical and can be rebuilt later. + +```c++ +float threshold = 0.2f; +size_t target_index_count = size_t(index_count * threshold); +float target_error = 1e-2f; + +std::vector lod(index_count); +float lod_error = 0.f; +lod.resize(meshopt_simplify(&lod[0], indices, index_count, &vertices[0].x, vertex_count, sizeof(Vertex), + target_index_count, target_error, /* options= */ 0, &lod_error)); +``` + +Target error is an approximate measure of the deviation from the original mesh using distance normalized to `[0..1]` range (e.g. `1e-2f` means that simplifier will try to maintain the error to be below 1% of the mesh extents). Note that the simplifier attempts to produce the requested number of indices at minimal error, but because of topological restrictions and error limit it is not guaranteed to reach the target index count and can stop earlier. + +To disable the error limit, `target_error` can be set to `FLT_MAX`. This makes it more likely that the simplifier will reach the target index count, but it may produce a mesh that looks significantly different from the original, so using the resulting error to control viewing distance would be required. Conversely, setting `target_index_count` to 0 will simplify the input mesh as much as possible within the specified error limit; this can be useful for generating LODs that should look good at a given viewing distance. + +The second simplification algorithm, `meshopt_simplifySloppy`, doesn't follow the topology of the original mesh. This means that it doesn't preserve attribute seams or borders, but it can collapse internal details that are too small to matter better because it can merge mesh features that are topologically disjoint but spatially close. + +```c++ +float threshold = 0.2f; +size_t target_index_count = size_t(index_count * threshold); +float target_error = 1e-1f; + +std::vector lod(index_count); +float lod_error = 0.f; +lod.resize(meshopt_simplifySloppy(&lod[0], indices, index_count, &vertices[0].x, vertex_count, sizeof(Vertex), + target_index_count, target_error, &lod_error)); +``` + +This algorithm will not stop early due to topology restrictions but can still do so if target index count can't be reached without introducing an error larger than target. It is 5-6x faster than `meshopt_simplify` when simplification ratio is large, and is able to reach ~20M triangles/sec on a desktop CPU (`meshopt_simplify` works at ~3M triangles/sec). + +Both algorithms can also return the resulting normalized deviation that can be used to choose the correct level of detail based on screen size or solid angle; the error can be converted to object space by multiplying by the scaling factor returned by `meshopt_simplifyScale`. For example, given a mesh with a precomputed LOD and a prescaled error, the screen-space normalized error can be computed and used for LOD selection: + +```c++ +// lod_factor can be 1 or can be adjusted for more or less aggressive LOD selection +float d = max(0, distance(camera_position, mesh_center) - mesh_radius); +float e = d * (tan(camera_fovy / 2) * 2 / screen_height); // 1px in mesh space +bool lod_ok = e * lod_factor >= lod_error; +``` + +When a sequence of LOD meshes is generated that all use the original vertex buffer, care must be taken to order vertices optimally to not penalize mobile GPU architectures that are only capable of transforming a sequential vertex buffer range. It's recommended in this case to first optimize each LOD for vertex cache, then assemble all LODs in one large index buffer starting from the coarsest LOD (the one with fewest triangles), and call `meshopt_optimizeVertexFetch` on the final large index buffer. This will make sure that coarser LODs require a smaller vertex range and are efficient wrt vertex fetch and transform. + +## Advanced simplification + +The main simplification algorithm, `meshopt_simplify`, exposes additional options and functions that can be used to control the simplification process in more detail. + +For basic customization, a number of options can be passed via `options` bitmask that adjust the behavior of the simplifier: + +- `meshopt_SimplifyLockBorder` restricts the simplifier from collapsing edges that are on the border of the mesh. This can be useful for simplifying mesh subsets independently, so that the LODs can be combined without introducing cracks. +- `meshopt_SimplifyErrorAbsolute` changes the error metric from relative to absolute both for the input error limit as well as for the resulting error. This can be used instead of `meshopt_simplifyScale`. +- `meshopt_SimplifySparse` improves simplification performance assuming input indices are a sparse subset of the mesh. This can be useful when simplifying small mesh subsets independently, and is intended to be used for meshlet simplification. For consistency, it is recommended to use absolute errors when sparse simplification is desired, as this flag changes the meaning of the relative errors. +- `meshopt_SimplifyPrune` allows the simplifier to remove isolated components regardless of the topological restrictions inside the component. This is generally recommended for full-mesh simplification as it can improve quality and reduce triangle count; note that with this option, triangles connected to locked vertices may be removed as part of their component. + +While `meshopt_simplify` is aware of attribute discontinuities by default (and infers them through the supplied index buffer) and tries to preserve them, it can be useful to provide information about attribute values. This allows the simplifier to take attribute error into account which can improve shading (by using vertex normals), texture deformation (by using texture coordinates), and may be necessary to preserve vertex colors when textures are not used in the first place. This can be done by using a variant of the simplification function that takes attribute values and weight factors, `meshopt_simplifyWithAttributes`: + +```c++ +const float nrm_weight = 0.5f; +const float attr_weights[3] = {nrm_weight, nrm_weight, nrm_weight}; + +std::vector lod(index_count); +float lod_error = 0.f; +lod.resize(meshopt_simplifyWithAttributes(&lod[0], indices, index_count, &vertices[0].x, vertex_count, sizeof(Vertex), + &vertices[0].nx, sizeof(Vertex), attr_weights, 3, /* vertex_lock= */ NULL, + target_index_count, target_error, /* options= */ 0, &lod_error)); +``` + +The attributes are passed as a separate buffer (in the example above it's a subset of the same vertex buffer) and should be stored as consecutive floats; attribute weights are used to control the importance of each attribute in the simplification process. For normalized attributes like normals and vertex colors, a weight around 1.0 is usually appropriate; internally, a change of `1/weight` in attribute value over a distance `d` is approximately equivalent to a change of `d` in position. Using higher weights may be appropriate to preserve attribute quality at the cost of position quality. If the attribute has a different scale (e.g. unnormalized vertex colors in [0..255] range), the weight should be divided by the scaling factor (1/255 in this example). + +Both the target error and the resulting error combine positional error and attribute error, so the error can be used to control the LOD while taking attribute quality into account, assuming carefully chosen weights. + +When using `meshopt_simplifyWithAttributes`, it is also possible to lock certain vertices by providing a `vertex_lock` array that contains a boolean value for each vertex in the mesh. This can be useful to preserve certain vertices, such as the boundary of the mesh, with more control than `meshopt_SimplifyLockBorder` option provides. + +Simplification currently assumes that the input mesh is using the same material for all triangles. If the mesh uses multiple materials, it is possible to split the mesh into subsets based on the material and simplify each subset independently, using `meshopt_SimplifyLockBorder` or `vertex_lock` to preserve material boundaries; however, this limits the collapses and as a result may reduce the resulting quality. An alternative approach is to encode information about the material into the vertex buffer, ensuring that all three vertices referencing the same triangle have the same material ID; this may require duplicating vertices on the boundary between materials. After this, simplification can be performed as usual, and after simplification per-triangle material information can be computed from the vertex material IDs. There is no need to inform the simplifier of the value of the material ID: the implicit boundaries created by duplicating vertices with conflicting material IDs will be preserved automatically. + +## Point cloud simplification + +In addition to triangle mesh simplification, this library provides a function to simplify point clouds. The algorithm reduces the point cloud to a specified number of points while preserving the overall appearance, and can optionally take per-point colors into account: + +```c++ +const float color_weight = 1; +std::vector indices(target_count); +indices.resize(meshopt_simplifyPoints(&indices[0], &points[0].x, points.size(), sizeof(Point), + &points[0].r, sizeof(Point), color_weight, target_count)); +``` + +The resulting indices can be used to render the simplified point cloud; to reduce the memory footprint, the point cloud can be reindexed to create an array of points from the indices. + +## Mesh shading + +Modern GPUs are beginning to deviate from the traditional rasterization model. NVidia GPUs starting from Turing and AMD GPUs starting from RDNA2 provide a new programmable geometry pipeline that, instead of being built around index buffers and vertex shaders, is built around mesh shaders - a new shader type that allows to provide a batch of work to the rasterizer. + +Using mesh shaders in context of traditional mesh rendering provides an opportunity to use a variety of optimization techniques, starting from more efficient vertex reuse, using various forms of culling (e.g. cluster frustum or occlusion culling) and in-memory compression to maximize the utilization of GPU hardware. Beyond traditional rendering mesh shaders provide a richer programming model that can synthesize new geometry more efficiently than common alternatives such as geometry shaders. Mesh shading can be accessed via Vulkan or Direct3D 12 APIs; please refer to [Introduction to Turing Mesh Shaders](https://developer.nvidia.com/blog/introduction-turing-mesh-shaders/) and [Mesh Shaders and Amplification Shaders: Reinventing the Geometry Pipeline](https://devblogs.microsoft.com/directx/coming-to-directx-12-mesh-shaders-and-amplification-shaders-reinventing-the-geometry-pipeline/) for additional information. + +To use mesh shaders for conventional rendering efficiently, geometry needs to be converted into a series of meshlets; each meshlet represents a small subset of the original mesh and comes with a small set of vertices and a separate micro-index buffer that references vertices in the meshlet. This information can be directly fed to the rasterizer from the mesh shader. This library provides algorithms to create meshlet data for a mesh, and - assuming geometry is static - can compute bounding information that can be used to perform cluster culling, a technique that can reject a meshlet if it's invisible on screen. + +To generate meshlet data, this library provides `meshopt_buildMeshlets` algorithm, which tries to balance topological efficiency (by maximizing vertex reuse inside meshlets) with culling efficiency (by minimizing meshlet radius and triangle direction divergence) and produces GPU-friendly data. As an alternative (that can be useful for load-time processing), `meshopt_buildMeshletsScan` can create the meshlet data using a vertex cache-optimized index buffer as a starting point by greedily aggregating consecutive triangles until they go over the meshlet limits. `meshopt_buildMeshlets` is recommended for offline data processing even if cone culling is not used. + +```c++ +const size_t max_vertices = 64; +const size_t max_triangles = 124; +const float cone_weight = 0.0f; + +size_t max_meshlets = meshopt_buildMeshletsBound(indices.size(), max_vertices, max_triangles); +std::vector meshlets(max_meshlets); +std::vector meshlet_vertices(max_meshlets * max_vertices); +std::vector meshlet_triangles(max_meshlets * max_triangles * 3); + +size_t meshlet_count = meshopt_buildMeshlets(meshlets.data(), meshlet_vertices.data(), meshlet_triangles.data(), indices.data(), + indices.size(), &vertices[0].x, vertices.size(), sizeof(Vertex), max_vertices, max_triangles, cone_weight); +``` + +To generate the meshlet data, `max_vertices` and `max_triangles` need to be set within limits supported by the hardware; for NVidia the values of 64 and 124 are recommended (`max_triangles` must be divisible by 4 so 124 is the value closest to official NVidia's recommended 126). `cone_weight` should be left as 0 if cluster cone culling is not used, and set to a value between 0 and 1 to balance cone culling efficiency with other forms of culling like frustum or occlusion culling. + +Each resulting meshlet refers to a portion of `meshlet_vertices` and `meshlet_triangles` arrays; the arrays are overallocated for the worst case so it's recommended to trim them before saving them as an asset / uploading them to the GPU: + +```c++ +const meshopt_Meshlet& last = meshlets[meshlet_count - 1]; + +meshlet_vertices.resize(last.vertex_offset + last.vertex_count); +meshlet_triangles.resize(last.triangle_offset + ((last.triangle_count * 3 + 3) & ~3)); +meshlets.resize(meshlet_count); +``` + +However depending on the application other strategies of storing the data can be useful; for example, `meshlet_vertices` serves as indices into the original vertex buffer but it might be worthwhile to generate a mini vertex buffer for each meshlet to remove the extra indirection when accessing vertex data, or it might be desirable to compress vertex data as vertices in each meshlet are likely to be very spatially coherent. + +For optimal performance, it is recommended to further optimize each meshlet in isolation for better triangle and vertex locality by calling `meshopt_optimizeMeshlet` on vertex and index data like so: + +```c++ +meshopt_optimizeMeshlet(&meshlet_vertices[m.vertex_offset], &meshlet_triangles[m.triangle_offset], m.triangle_count, m.vertex_count); +``` + +Different applications will choose different strategies for rendering meshlets; on a GPU capable of mesh shading, meshlets can be rendered directly; for example, a basic GLSL shader for `VK_EXT_mesh_shader` extension could look like this (parts omitted for brevity): + +```glsl +layout(binding = 0) readonly buffer Meshlets { Meshlet meshlets[]; }; +layout(binding = 1) readonly buffer MeshletVertices { uint meshlet_vertices[]; }; +layout(binding = 2) readonly buffer MeshletTriangles { uint8_t meshlet_triangles[]; }; + +void main() { + Meshlet meshlet = meshlets[gl_WorkGroupID.x]; + SetMeshOutputsEXT(meshlet.vertex_count, meshlet.triangle_count); + + for (uint i = gl_LocalInvocationIndex; i < meshlet.vertex_count; i += gl_WorkGroupSize.x) { + uint index = meshlet_vertices[meshlet.vertex_offset + i]; + gl_MeshVerticesEXT[i].gl_Position = world_view_projection * vec4(vertex_positions[index], 1); + } + + for (uint i = gl_LocalInvocationIndex; i < meshlet.triangle_count; i += gl_WorkGroupSize.x) { + uint offset = meshlet.triangle_offset + i * 3; + gl_PrimitiveTriangleIndicesEXT[i] = uvec3( + meshlet_triangles[offset], meshlet_triangles[offset + 1], meshlet_triangles[offset + 2]); + } +} +``` + +After generating the meshlet data, it's also possible to generate extra data for each meshlet that can be saved and used at runtime to perform cluster culling, where each meshlet can be discarded if it's guaranteed to be invisible. To generate the data, `meshlet_computeMeshletBounds` can be used: + +```c++ +meshopt_Bounds bounds = meshopt_computeMeshletBounds(&meshlet_vertices[m.vertex_offset], &meshlet_triangles[m.triangle_offset], + m.triangle_count, &vertices[0].x, vertices.size(), sizeof(Vertex)); +``` + +The resulting `bounds` values can be used to perform frustum or occlusion culling using the bounding sphere, or cone culling using the cone axis/angle (which will reject the entire meshlet if all triangles are guaranteed to be back-facing from the camera point of view): + +```c++ +if (dot(normalize(cone_apex - camera_position), cone_axis) >= cone_cutoff) reject(); +``` + +## Efficiency analyzers + +While the only way to get precise performance data is to measure performance on the target GPU, it can be valuable to measure the impact of these optimization in a GPU-independent manner. To this end, the library provides analyzers for all three major optimization routines. For each optimization there is a corresponding analyze function, like `meshopt_analyzeOverdraw`, that returns a struct with statistics. + +`meshopt_analyzeVertexCache` returns vertex cache statistics. The common metric to use is ACMR - average cache miss ratio, which is the ratio of the total number of vertex invocations to the triangle count. The worst-case ACMR is 3 (GPU has to process 3 vertices for each triangle); on regular grids the optimal ACMR approaches 0.5. On real meshes it usually is in [0.5..1.5] range depending on the amount of vertex splits. One other useful metric is ATVR - average transformed vertex ratio - which represents the ratio of vertex shader invocations to the total vertices, and has the best case of 1.0 regardless of mesh topology (each vertex is transformed once). + +`meshopt_analyzeVertexFetch` returns vertex fetch statistics. The main metric it uses is overfetch - the ratio between the number of bytes read from the vertex buffer to the total number of bytes in the vertex buffer. Assuming non-redundant vertex buffers, the best case is 1.0 - each byte is fetched once. + +`meshopt_analyzeOverdraw` returns overdraw statistics. The main metric it uses is overdraw - the ratio between the number of pixel shader invocations to the total number of covered pixels, as measured from several different orthographic cameras. The best case for overdraw is 1.0 - each pixel is shaded once. + +Note that all analyzers use approximate models for the relevant GPU units, so the numbers you will get as the result are only a rough approximation of the actual performance. + +## Specialized processing + +In addition to the core optimization techniques, the library provides several specialized algorithms for specific rendering techniques and pipeline optimizations that require a particular configuration of vertex and index data. + +### Geometry shader adjacency + +For algorithms that use geometry shaders and require adjacency information, this library can generate an index buffer with adjacency data: + +```c++ +std::vector adjacency(indices.size() * 2); +meshopt_generateAdjacencyIndexBuffer(&adjacency[0], &indices[0], indices.size(), &vertices[0].x, vertices.size(), sizeof(Vertex)); +``` + +This creates an index buffer suitable for rendering with triangle-with-adjacency topology, providing 3 extra vertices per triangle that represent vertices opposite to each triangle's edge. This data can be used to compute silhouettes and perform other types of local geometric processing in geometry shaders. To render the mesh with adjacency data, the index buffer should be used with `D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST_ADJ`/`VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY`/`GL_TRIANGLES_ADJACENCY` topology. + +Note that the use of geometry shaders may have a performance impact on some GPUs; in some cases alternative implementation strategies may be more efficient. + +### Tessellation with displacement mapping + +For hardware tessellation with crack-free displacement mapping, this library can generate a special index buffer that supports PN-AEN tessellation: + +```c++ +std::vector tess(indices.size() * 4); +meshopt_generateTessellationIndexBuffer(&tess[0], &indices[0], indices.size(), &vertices[0].x, vertices.size(), sizeof(Vertex)); +``` + +This generates a 12-vertex patch for each input triangle with the following layout: + +- 0, 1, 2: original triangle vertices +- 3, 4: opposing edge for edge 0, 1 +- 5, 6: opposing edge for edge 1, 2 +- 7, 8: opposing edge for edge 2, 0 +- 9, 10, 11: dominant vertices for corners 0, 1, 2 + +This allows the use of hardware tessellation to implement PN-AEN and/or displacement mapping without cracks along UV seams or normal discontinuities. To render the mesh, the index buffer should be used with `D3D_PRIMITIVE_TOPOLOGY_12_CONTROL_POINT_PATCHLIST`/`VK_PRIMITIVE_TOPOLOGY_PATCH_LIST` (`patchControlPoints=12`) topology. For more details please refer to the following papers: [Crack-Free Point-Normal Triangles using Adjacent Edge Normals](https://developer.download.nvidia.com/whitepapers/2010/PN-AEN-Triangles-Whitepaper.pdf), [Tessellation on Any Budget](https://www.nvidia.com/content/pdf/gdc2011/john_mcdonald.pdf) and [My Tessellation Has Cracks!](https://developer.download.nvidia.com/assets/gamedev/files/gdc12/GDC12_DUDASH_MyTessellationHasCracks.pdf). + +### Visibility buffers + +To render geometry into visibility buffers, access to primitive index in fragment shader is required. While it is possible to use `SV_PrimitiveID`/`gl_PrimitiveID` in the fragment shader, this can result in suboptimal performance on some GPUs (notably, AMD RDNA1 and all NVidia GPUs), and may not be supported on mobile or console hardware. Using mesh shaders to generate primitive IDs is efficient but requires hardware support that is not universally available. To work around these limitations, this library provides a way to generate a special index buffer that uses provoking vertex to encode primitive IDs: + +```c++ +std::vector provoke(indices.size()); +std::vector reorder(vertices.size() + indices.size() / 3); +reorder.resize(meshopt_generateProvokingIndexBuffer(&provoke[0], &reorder[0], &indices[0], indices.size(), vertices.size())); +``` + +This generates a special index buffer along with a reorder table that satisfies two constraints: + +- `provoke[3 * tri] == tri` +- `reorder[provoke[x]]` refers to the original triangle vertices + +To render the mesh with provoking vertex data, the application should use `provoke` as an index buffer and a vertex shader that passes vertex index (`SV_VertexID`/`gl_VertexIndex`) via a `flat`/`nointerpolation` attribute to the fragment shader as a primitive index, and loads vertex data manually by computing the real vertex index based on `reorder` table (`reorder[gl_VertexIndex]`). For more details please refer to [Variable Rate Shading with Visibility Buffer Rendering](https://advances.realtimerendering.com/s2024/content/Hable/Advances_SIGGRAPH_2024_VisibilityVRS-SIGGRAPH_Advances_2024.pptx); naturally, this technique does not require VRS. + +Note: This assumes the provoking vertex is the first vertex of a triangle, which is true for all graphics APIs except OpenGL/WebGL. For OpenGL/WebGL, you may need to rotate each triangle (abc -> bca) in the resulting index buffer, or use the `glProvokingVertex` function (OpenGL 3.2+) or `WEBGL_provoking_vertex` extension (WebGL2) to change the provoking vertex convention. For WebGL2, this is highly recommended to avoid a variety of emulation slowdowns that happen by default if `flat` attributes are used, such as an implicit use of geometry shaders. + +## Memory management + +Many algorithms allocate temporary memory to store intermediate results or accelerate processing. The amount of memory allocated is a function of various input parameters such as vertex count and index count. By default memory is allocated using `operator new` and `operator delete`; if these operators are overloaded by the application, the overloads will be used instead. Alternatively it's possible to specify custom allocation/deallocation functions using `meshopt_setAllocator`, e.g. + +```c++ +meshopt_setAllocator(malloc, free); +``` + +> Note that the library expects the allocation function to either throw in case of out-of-memory (in which case the exception will propagate to the caller) or abort, so technically the use of `malloc` above isn't safe. If you want to handle out-of-memory errors without using C++ exceptions, you can use `setjmp`/`longjmp` instead. + +Vertex and index decoders (`meshopt_decodeVertexBuffer`, `meshopt_decodeIndexBuffer`, `meshopt_decodeIndexSequence`) do not allocate memory and work completely within the buffer space provided via arguments. + +All functions have bounded stack usage that does not exceed 32 KB for any algorithms. + +## License + +This library is available to anybody free of charge, under the terms of MIT License (see LICENSE.md). diff --git a/README_zh.md b/README_zh.md new file mode 100644 index 00000000..182a384f --- /dev/null +++ b/README_zh.md @@ -0,0 +1,51 @@ +# meshoptimizer +meshoptimizer 是一个高效的网格优化库,通过多种技术减少3D网格数据的存储大小和提升渲染性能。它主要用于图形学和游戏开发领域,尤其是处理大量多边形的3D模型时,能够显著降低渲染负担,提高运行效率。 + +## meshoptimizer使用场景 +OpenHarmony上引入meshoptimizer,主要用于对gltf中Ext_meshopt_compress扩展格式的解压缩。 + +## 目录结构 +``` +demo # demo文件 +extern # 三方源代码 +gltf # gltfpack 是一款能够自动优化gltf文件的工具,减少下载大小并提升加载和渲染速度 +js # 此文件夹包含可用于访问meshoptimizer库部分功能的Javascript/WebAssembly模块 +src # meshoptimizer C++源代码目录包含解压缩功能 +CMakeLists.txt # 编译描述文件 +LICENSE.md # 版权说明 +README.MD # 软件说明 +``` + +## OpenHarmony对于meshoptimizer的适配 +OpenHarmony只编译使用meshoptimizer仓库中src/目录下的源代码和头文件。meshoptimizer引入openharmony的thirdparty目录下,使用OpenHarmony中依赖部件的方式进行编译。 + +## 主干代码下载 +``` +repo init -u https://gitee.com/openharmony/manifest.git -b master --no-repo-verify +repo sync -c +repo forall -c 'git lfs pull' +``` + +## 在使用的模块进行依赖 +``` +deps = ["//third_party/meshoptimizer:meshoptimizer"] +``` + +## 预处理 +``` +./build/prebuilts_download.sh +``` + +## 编译 +``` +./build.sh --product-name rk3568 --ccache +``` + +## 相关参考 +https://github.com/zeux/meshoptimizer + +## 许可证 +本项目遵从LICENCE中所描述的许可证 + +## 相关仓 +meshoptimizer \ No newline at end of file diff --git a/src/allocator.cpp b/src/allocator.cpp index b8cb33c2..02c56f84 100644 --- a/src/allocator.cpp +++ b/src/allocator.cpp @@ -1,4 +1,7 @@ // This file is part of meshoptimizer library; see meshoptimizer.h for version/license details +// MIT License + +// Copyright (c) 2016-2024 Arseny Kapoulkine #include "meshoptimizer.h" void meshopt_setAllocator(void* (MESHOPTIMIZER_ALLOC_CALLCONV* allocate)(size_t), void (MESHOPTIMIZER_ALLOC_CALLCONV* deallocate)(void*)) diff --git a/src/clusterizer.cpp b/src/clusterizer.cpp index 738add5f..d03c65d6 100644 --- a/src/clusterizer.cpp +++ b/src/clusterizer.cpp @@ -1,4 +1,7 @@ // This file is part of meshoptimizer library; see meshoptimizer.h for version/license details +// MIT License + +// Copyright (c) 2016-2024 Arseny Kapoulkine #include "meshoptimizer.h" #include diff --git a/src/indexcodec.cpp b/src/indexcodec.cpp index b3004600..c78f036b 100644 --- a/src/indexcodec.cpp +++ b/src/indexcodec.cpp @@ -1,4 +1,7 @@ // This file is part of meshoptimizer library; see meshoptimizer.h for version/license details +// MIT License + +// Copyright (c) 2016-2024 Arseny Kapoulkine #include "meshoptimizer.h" #include diff --git a/src/indexgenerator.cpp b/src/indexgenerator.cpp index 0d53020e..ed1fbe90 100644 --- a/src/indexgenerator.cpp +++ b/src/indexgenerator.cpp @@ -1,4 +1,7 @@ // This file is part of meshoptimizer library; see meshoptimizer.h for version/license details +// MIT License + +// Copyright (c) 2016-2024 Arseny Kapoulkine #include "meshoptimizer.h" #include diff --git a/src/meshoptimizer.h b/src/meshoptimizer.h index 77be5371..cb548250 100644 --- a/src/meshoptimizer.h +++ b/src/meshoptimizer.h @@ -1,7 +1,7 @@ /** * meshoptimizer - version 0.22 * - * Copyright (C) 2016-2024, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) + * Copyright (C) 2016-2024 Arseny Kapoulkine (arseny.kapoulkine@gmail.com) * Report bugs and download new versions at https://github.com/zeux/meshoptimizer * * This library is distributed under the MIT License. See notice at the end of this file. diff --git a/src/overdrawanalyzer.cpp b/src/overdrawanalyzer.cpp index 31cf6f14..49b13cd5 100644 --- a/src/overdrawanalyzer.cpp +++ b/src/overdrawanalyzer.cpp @@ -1,4 +1,7 @@ // This file is part of meshoptimizer library; see meshoptimizer.h for version/license details +// MIT License + +// Copyright (c) 2016-2024 Arseny Kapoulkine #include "meshoptimizer.h" #include diff --git a/src/overdrawoptimizer.cpp b/src/overdrawoptimizer.cpp index cc22dbcf..144e5b92 100644 --- a/src/overdrawoptimizer.cpp +++ b/src/overdrawoptimizer.cpp @@ -1,4 +1,7 @@ // This file is part of meshoptimizer library; see meshoptimizer.h for version/license details +// MIT License + +// Copyright (c) 2016-2024 Arseny Kapoulkine #include "meshoptimizer.h" #include diff --git a/src/quantization.cpp b/src/quantization.cpp index 149835f5..26ac8ad5 100644 --- a/src/quantization.cpp +++ b/src/quantization.cpp @@ -1,4 +1,7 @@ // This file is part of meshoptimizer library; see meshoptimizer.h for version/license details +// MIT License + +// Copyright (c) 2016-2024 Arseny Kapoulkine #include "meshoptimizer.h" #include diff --git a/src/simplifier.cpp b/src/simplifier.cpp index af64cbda..e15aff68 100644 --- a/src/simplifier.cpp +++ b/src/simplifier.cpp @@ -1,4 +1,7 @@ // This file is part of meshoptimizer library; see meshoptimizer.h for version/license details +// MIT License + +// Copyright (c) 2016-2024 Arseny Kapoulkine #include "meshoptimizer.h" #include diff --git a/src/spatialorder.cpp b/src/spatialorder.cpp index 7b1a0694..d174a2cc 100644 --- a/src/spatialorder.cpp +++ b/src/spatialorder.cpp @@ -1,4 +1,7 @@ // This file is part of meshoptimizer library; see meshoptimizer.h for version/license details +// MIT License + +// Copyright (c) 2016-2024 Arseny Kapoulkine #include "meshoptimizer.h" #include diff --git a/src/stripifier.cpp b/src/stripifier.cpp index 4043195a..89c5687c 100644 --- a/src/stripifier.cpp +++ b/src/stripifier.cpp @@ -1,4 +1,7 @@ // This file is part of meshoptimizer library; see meshoptimizer.h for version/license details +// MIT License + +// Copyright (c) 2016-2024 Arseny Kapoulkine #include "meshoptimizer.h" #include diff --git a/src/vcacheanalyzer.cpp b/src/vcacheanalyzer.cpp index 36827438..726a35cb 100644 --- a/src/vcacheanalyzer.cpp +++ b/src/vcacheanalyzer.cpp @@ -1,4 +1,7 @@ // This file is part of meshoptimizer library; see meshoptimizer.h for version/license details +// MIT License + +// Copyright (c) 2016-2024 Arseny Kapoulkine #include "meshoptimizer.h" #include diff --git a/src/vcacheoptimizer.cpp b/src/vcacheoptimizer.cpp index e4ecc71d..b9626141 100644 --- a/src/vcacheoptimizer.cpp +++ b/src/vcacheoptimizer.cpp @@ -1,4 +1,7 @@ // This file is part of meshoptimizer library; see meshoptimizer.h for version/license details +// MIT License + +// Copyright (c) 2016-2024 Arseny Kapoulkine #include "meshoptimizer.h" #include diff --git a/src/vertexcodec.cpp b/src/vertexcodec.cpp index 1dbd2e35..17e7095e 100644 --- a/src/vertexcodec.cpp +++ b/src/vertexcodec.cpp @@ -1,4 +1,7 @@ // This file is part of meshoptimizer library; see meshoptimizer.h for version/license details +// MIT License + +// Copyright (c) 2016-2024 Arseny Kapoulkine #include "meshoptimizer.h" #include diff --git a/src/vertexfilter.cpp b/src/vertexfilter.cpp index 1f456bb9..bdcf6cfe 100644 --- a/src/vertexfilter.cpp +++ b/src/vertexfilter.cpp @@ -1,4 +1,7 @@ // This file is part of meshoptimizer library; see meshoptimizer.h for version/license details +// MIT License + +// Copyright (c) 2016-2024 Arseny Kapoulkine #include "meshoptimizer.h" #include diff --git a/src/vfetchanalyzer.cpp b/src/vfetchanalyzer.cpp index 51dca873..0c9c8541 100644 --- a/src/vfetchanalyzer.cpp +++ b/src/vfetchanalyzer.cpp @@ -1,4 +1,7 @@ // This file is part of meshoptimizer library; see meshoptimizer.h for version/license details +// MIT License + +// Copyright (c) 2016-2024 Arseny Kapoulkine #include "meshoptimizer.h" #include diff --git a/src/vfetchoptimizer.cpp b/src/vfetchoptimizer.cpp index 465d6df5..78bf2dac 100644 --- a/src/vfetchoptimizer.cpp +++ b/src/vfetchoptimizer.cpp @@ -1,4 +1,7 @@ // This file is part of meshoptimizer library; see meshoptimizer.h for version/license details +// MIT License + +// Copyright (c) 2016-2024 Arseny Kapoulkine #include "meshoptimizer.h" #include diff --git a/testcase/decode_meshopt_test.cpp b/testcase/decode_meshopt_test.cpp new file mode 100644 index 00000000..57abc715 --- /dev/null +++ b/testcase/decode_meshopt_test.cpp @@ -0,0 +1,178 @@ +// Copyright (c) 2021 Huawei Device Co., Ltd. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "../src/meshoptimizer" +#include +#include +#include +#include +#include + +// Testbench for meshopt_decodeVertexBuffer +void test_meshopt_decodeVertexBuffer() { + unsigned char buffer[12]; + int result; + + // Invalid buffer size + unsigned char vertex_data[12]; + result = meshopt_decodeVertexBuffer(vertex_data, 10, 4, buffer, 0); + assert(result == -2); + + // Invalid data_header + buffer[0] = 0x00; + result = meshopt_decodeVertexBuffer(vertex_data, 10, 4, buffer, sizeof(buffer)); + assert(result == -1); + + // Unsupported version + buffer[0] = 0x1F; + result = meshopt_decodeVertexBuffer(vertex_data, 10, 4, buffer, sizeof(buffer)); + assert(result == -1); + + // Insufficient buffer size for tail + buffer[0] = 0xa1; + result = meshopt_decodeVertexBuffer(vertex_data, 10, 4, buffer, 1); + assert(result == -2); + + // Successful decoding + buffer[0] = 0xa0; + size_t buffer_size = 37; + result = meshopt_decodeVertexBuffer(vertex_data, 1, 4, buffer, buffer_size); + assert(result == 0); +} + +void test_meshopt_decodeFilterOct() { + // test the case where the stride is 4 + signed char buffer[4] = { + 0, 64, 64, 64, + }; + meshopt_decodeFilterOct(buffer, 1, 4); + assert(buffer[0] == 0); + assert(buffer[1] == 127); + assert(buffer[2] == 0); + assert(buffer[3] == 64); + + short buffer_8[8] = { + 0, 64, 64, 64, + 0, 64, 64, 64, + }; + // test the case where the stride is 8 + meshopt_decodeFilterOct(buffer_8, 1, 8); + assert(buffer[0] == 0); + assert(buffer[1] == 32767); + assert(buffer[2] == 0); + assert(buffer[3] == 64); + assert(buffer[4] == 0); + assert(buffer[5] == 64); + assert(buffer[6] == 64); + assert(buffer[7] == 64); +} + +void test_meshopt_decodeFilterQuat() { + short buffer_8[8] = { + 0, 64, 64, 64, + 0, 64, 64, 64, + }; + + // test the case where the stride is 8 + meshopt_decodeFilterQuat(buffer_8, 1, 8) + assert(buffer[0] == 9695); + assert(buffer[1] == 0); + assert(buffer[2] == 22132); + assert(buffer[3] == 22132); + assert(buffer[4] == 0); + assert(buffer[5] == 64); + assert(buffer[6] == 64); + assert(buffer[7] == 64); +} + +void test_meshopt_decodeFilterExp() { + // test the case where the stride is 4 + unsigned int buffer[4] = { + 0, 64, 64, 64, + }; + meshopt_decodeFilterExp(buffer, 1, 4); + assert(buffer[0] == 0); + assert(buffer[1] == 64); + assert(buffer[2] == 64); + assert(buffer[3] == 64); + + unsigned int buffer[8] = { + 0, 64, 64, 64, + 0, 64, 64, 64, + }; + + // test the case where the stride is 8 + meshopt_decodeFilterExp(buffer_8, 1, 8); + assert(buffer[0] == 0); + assert(buffer[1] == 1115684864); + assert(buffer[2] == 64); + assert(buffer[3] == 64); + assert(buffer[4] == 0); + assert(buffer[5] == 64); + assert(buffer[6] == 64); + assert(buffer[7] == 64); +} + +void test_meshopt_decodeIndexBuffer() { + const size_t index_count = 6; + const size_t index_size = 2; + const size_t buffer_size = 1 + index_count / 3 + 16; + const size_t buffer_size_small = 1 + index_count / 3 + 15; + + unsigned char buffer[buffer_size]; + memset(buffer, 0); + + buffer[0] = 0xe0; + buffer[1] = 0x01; + + // target buffer for testing + uint16_t destination[index_count]; + memset(destination, 0, sizeof(destination)); + + int result = meshopt_decodeIndexBuffer(destination, index_count, index_size, buffer, buffer_size); + assert(result == 0); + + // verify the contents of the target buffer + assert(destination[0] == 65535); + assert(destination[0] == 65535); + assert(destination[0] == 65535); + assert(destination[0] == 65535); + assert(destination[0] == 65535); + assert(destination[0] == 0); + + // test different input conditions + buffer[0] = 0x20; + result = meshopt_decodeIndexBuffer(destination, index_count, index_size, buffer, buffer_size); + assert(result == -1); + + buffer[0] = 0xe0; + buffer[1] = 0xFF; + result = meshopt_decodeIndexBuffer(destination, index_count, index_size, buffer, buffer_size); + assert(result == -2); + + buffer[0] = 0xe0; + buffer[1] = 0x01; + result = meshopt_decodeIndexBuffer(destination, index_count, index_size, buffer, buffer_size); + assert(result == -2); +} + +int main() { + test_meshopt_decodeVertexBuffer(); + test_meshopt_decodeFilterOct(); + test_meshopt_decodeFilterQuat(); + test_meshopt_decodeFilterExp(); + test_meshopt_decodeIndexBuffer(); + std::count << "All test cases passed!" << std::endl; + return 0; +} \ No newline at end of file From e47ae7fbffcf7746d5a739011b825734aa5589ee Mon Sep 17 00:00:00 2001 From: wangshilin Date: Mon, 22 Sep 2025 18:57:58 +0800 Subject: [PATCH 2/6] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=95=B4=E4=BD=93?= =?UTF-8?q?=E5=8D=8F=E8=AE=AE=E4=B8=BAMIT=EF=BC=8C=E5=88=A0=E9=99=A4OAT.xm?= =?UTF-8?q?l=E4=B8=AD=E5=A4=9A=E4=BD=99=E7=9A=84=E6=B3=A8=E9=87=8A?= =?UTF-8?q?=EF=BC=8C=E5=A2=9E=E5=87=8Fsrc=E7=9B=AE=E5=BD=95=E4=B8=8B?= =?UTF-8?q?=E7=9A=84=E8=BF=87=E6=BB=A4=E6=9D=A1=E4=BB=B6=EF=BC=8Creadme.op?= =?UTF-8?q?ensource.md=E4=B8=ADowner=E7=9A=84=E4=BF=AE=E6=94=B9=EF=BC=8C?= =?UTF-8?q?=E6=BA=90=E4=BB=A3=E7=A0=81=E7=9A=84=E6=B5=8B=E8=AF=95=E6=96=87?= =?UTF-8?q?=E4=BB=B6tests.cpp=20=E6=94=BE=E5=88=B0=E4=BA=86testcase?= =?UTF-8?q?=E7=9B=AE=E5=BD=95=E4=B8=8B=E3=80=82Readme=E6=96=87=E6=A1=A3?= =?UTF-8?q?=E4=BF=AE=E6=94=B9=EF=BC=8C=E5=8E=BB=E6=8E=89=E5=92=8C=E5=AD=97?= =?UTF-8?q?=EF=BC=8C=E6=8A=8A=E5=9B=BE=E5=85=B4=E5=AD=A6=E6=8D=A2=E6=88=90?= =?UTF-8?q?=E4=BA=86=E5=85=B7=E4=BD=93=E8=A7=A3=E9=87=8A=EF=BC=8C=E8=A7=A3?= =?UTF-8?q?=E9=87=8Agltf=E5=85=A8=E7=A7=B0=EF=BC=8C=E6=8F=90=E4=BE=9BOH?= =?UTF-8?q?=E4=B8=8A=E7=9A=84=E4=BD=BF=E7=94=A8=E6=96=B9=E6=B3=95.Signed-o?= =?UTF-8?q?ff-by:=20wangshilin=20?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BUILD.gn | 12 +- OAT.xml | 46 +- README.OpenSource.md | 2 +- README.md | 23 +- README_zh.md | 24 +- src/allocator.cpp | 3 - src/clusterizer.cpp | 3 - src/indexcodec.cpp | 3 - src/indexgenerator.cpp | 3 - src/overdrawanalyzer.cpp | 3 - src/overdrawoptimizer.cpp | 3 - src/quantization.cpp | 3 - src/simplifier.cpp | 3 - src/spatialorder.cpp | 3 - src/stripifier.cpp | 3 - src/vcacheanalyzer.cpp | 3 - src/vcacheoptimizer.cpp | 3 - src/vertexcodec.cpp | 3 - src/vertexfilter.cpp | 3 - src/vfetchanalyzer.cpp | 3 - src/vfetchoptimizer.cpp | 3 - testcase/decode_meshopt_test.cpp | 12 +- testcase/tests.cpp | 1965 ++++++++++++++++++++++++++++++ 23 files changed, 2013 insertions(+), 119 deletions(-) create mode 100644 testcase/tests.cpp diff --git a/BUILD.gn b/BUILD.gn index 5efefa04..c60c1f91 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -1,16 +1,6 @@ # Copyright (c) 2021 Huawei Device Co., Ltd. -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +# MIT LICENSE import("//build/ohos.gni") diff --git a/OAT.xml b/OAT.xml index 333c6fc5..2465eaa5 100644 --- a/OAT.xml +++ b/OAT.xml @@ -1,20 +1,6 @@ @@ -22,7 +8,6 @@ - @@ -36,50 +21,23 @@ - - + - - - - - - - - - - - - - - - - - - - diff --git a/README.OpenSource.md b/README.OpenSource.md index 1c90619d..7130c6ea 100644 --- a/README.OpenSource.md +++ b/README.OpenSource.md @@ -4,7 +4,7 @@ "License": "MIT", "License File": "LICENSE", "Version Number": "v0.22", - "Owner": "pengyonglong2@h-partners.com", + "Owner": "wangshilin20@h-partners.com", "Upstream URL": "https://github.com/zeux/meshoptimizer", "Description": "Meshoptimizer is an efficient mesh optimization library that reduces the storage size of 3D mesh data and enhances rendering performance through various techniques. It is mainly used in the fields of graphics and game development, especially when dealing with 3D models containing a large number of polygons. It can significantly reduce the rendering burden and improve operational efficiency." } diff --git a/README.md b/README.md index d1a96974..3bf2a52c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ # meshoptimizer -Meshoptimizer is an efficient mesh optimization library that reduces the storage size of 3D mesh data and enhances rendering performance through various techniques. It is mainly used in the fields of graphics and game development, especially when dealing with 3D models containing a large number of polygons, it can significantly reduce the rendering burden and improve operational efficiency. +Mesh optimizer is an efficient mesh optimization library that reduces the storage size of 3D mesh data and improves rendering performance through various techniques. It is mainly used for optimizing 3D models (such as pixel silver optimization, vertex caching optimization, over drawing optimization, vertex acquisition optimization, vertex quantization, shadow indexing), especially when dealing with a large number of polygon 3D models, which can significantly reduce rendering burden and improve running efficiency. ## Use Cases of meshoptimizer +Gltf (Graphics Library Transmission Format) is a royalty free specification aimed at improving the efficiency of application transmission and loading of 3D scenes and models. The introduction of meshoptimizer on OpenHarmony is mainly for decompressing the Ext_meshopt_compress extension format in gltf. ## Directory Structure @@ -54,4 +55,22 @@ https://github.com/zeux/meshoptimizer This project is subject to the license described in LICENCE. ## Related warehouse -meshoptimizer \ No newline at end of file +meshoptimizer + +##Usage in OpenHarmony +###Object oriented +Developers of system applications and 3D graphics engines. +###Guidance and reference +``` +deps = ["//third_party/meshoptimizer:meshoptimizer"] +``` +Introduce this third-party library. +``` +include "meshoptimizer.h" +meshopt_decodeVertexBuffer(); +meshopt_decodeFilterOct(); +meshopt_decodeFilterQuat(); +meshopt_decodeFilterExp(); +meshopt_decodeIndexBuffer(); +``` +Introduce the meshoptimizer. h header file and use the corresponding functions. For specific instructions on the functions, please refer to: https://github.com/zeux/meshoptimizer \ No newline at end of file diff --git a/README_zh.md b/README_zh.md index 182a384f..2f013ff2 100644 --- a/README_zh.md +++ b/README_zh.md @@ -1,7 +1,8 @@ # meshoptimizer -meshoptimizer 是一个高效的网格优化库,通过多种技术减少3D网格数据的存储大小和提升渲染性能。它主要用于图形学和游戏开发领域,尤其是处理大量多边形的3D模型时,能够显著降低渲染负担,提高运行效率。 +meshoptimizer 是一个高效的网格优化库,通过多种技术减少3D网格数据的存储大小,提升渲染性能。它主要用于对3D模型的优化(素银优化,顶点缓存优化,过度绘制优化,顶点获取优化,顶点量化,阴影索引),尤其是处理大量多边形的3D模型时,能够显著降低渲染负担,提高运行效率。 ## meshoptimizer使用场景 +gltf(Graphics Library Transmission Format)是一种免版税的规范,旨在提高应用程序传输和加载3D场景及模型的效率。 OpenHarmony上引入meshoptimizer,主要用于对gltf中Ext_meshopt_compress扩展格式的解压缩。 ## 目录结构 @@ -48,4 +49,23 @@ https://github.com/zeux/meshoptimizer 本项目遵从LICENCE中所描述的许可证 ## 相关仓 -meshoptimizer \ No newline at end of file +meshoptimizer + +## OpenHarmony中的使用 +### 面向对象 +系统应用的开发者,3D图形引擎相关开发者。 +### 指导参考 +``` +deps = ["//third_party/meshoptimizer:meshoptimizer"] +``` +引入该三方库。 +``` +include "meshoptimizer.h" + +meshopt_decodeVertexBuffer(); +meshopt_decodeFilterOct(); +meshopt_decodeFilterQuat(); +meshopt_decodeFilterExp(); +meshopt_decodeIndexBuffer(); +``` +引入meshoptimizer.h头文件,使用相应的函数,函数具体指导参考:https://github.com/zeux/meshoptimizer \ No newline at end of file diff --git a/src/allocator.cpp b/src/allocator.cpp index 02c56f84..b8cb33c2 100644 --- a/src/allocator.cpp +++ b/src/allocator.cpp @@ -1,7 +1,4 @@ // This file is part of meshoptimizer library; see meshoptimizer.h for version/license details -// MIT License - -// Copyright (c) 2016-2024 Arseny Kapoulkine #include "meshoptimizer.h" void meshopt_setAllocator(void* (MESHOPTIMIZER_ALLOC_CALLCONV* allocate)(size_t), void (MESHOPTIMIZER_ALLOC_CALLCONV* deallocate)(void*)) diff --git a/src/clusterizer.cpp b/src/clusterizer.cpp index d03c65d6..738add5f 100644 --- a/src/clusterizer.cpp +++ b/src/clusterizer.cpp @@ -1,7 +1,4 @@ // This file is part of meshoptimizer library; see meshoptimizer.h for version/license details -// MIT License - -// Copyright (c) 2016-2024 Arseny Kapoulkine #include "meshoptimizer.h" #include diff --git a/src/indexcodec.cpp b/src/indexcodec.cpp index c78f036b..b3004600 100644 --- a/src/indexcodec.cpp +++ b/src/indexcodec.cpp @@ -1,7 +1,4 @@ // This file is part of meshoptimizer library; see meshoptimizer.h for version/license details -// MIT License - -// Copyright (c) 2016-2024 Arseny Kapoulkine #include "meshoptimizer.h" #include diff --git a/src/indexgenerator.cpp b/src/indexgenerator.cpp index ed1fbe90..0d53020e 100644 --- a/src/indexgenerator.cpp +++ b/src/indexgenerator.cpp @@ -1,7 +1,4 @@ // This file is part of meshoptimizer library; see meshoptimizer.h for version/license details -// MIT License - -// Copyright (c) 2016-2024 Arseny Kapoulkine #include "meshoptimizer.h" #include diff --git a/src/overdrawanalyzer.cpp b/src/overdrawanalyzer.cpp index 49b13cd5..31cf6f14 100644 --- a/src/overdrawanalyzer.cpp +++ b/src/overdrawanalyzer.cpp @@ -1,7 +1,4 @@ // This file is part of meshoptimizer library; see meshoptimizer.h for version/license details -// MIT License - -// Copyright (c) 2016-2024 Arseny Kapoulkine #include "meshoptimizer.h" #include diff --git a/src/overdrawoptimizer.cpp b/src/overdrawoptimizer.cpp index 144e5b92..cc22dbcf 100644 --- a/src/overdrawoptimizer.cpp +++ b/src/overdrawoptimizer.cpp @@ -1,7 +1,4 @@ // This file is part of meshoptimizer library; see meshoptimizer.h for version/license details -// MIT License - -// Copyright (c) 2016-2024 Arseny Kapoulkine #include "meshoptimizer.h" #include diff --git a/src/quantization.cpp b/src/quantization.cpp index 26ac8ad5..149835f5 100644 --- a/src/quantization.cpp +++ b/src/quantization.cpp @@ -1,7 +1,4 @@ // This file is part of meshoptimizer library; see meshoptimizer.h for version/license details -// MIT License - -// Copyright (c) 2016-2024 Arseny Kapoulkine #include "meshoptimizer.h" #include diff --git a/src/simplifier.cpp b/src/simplifier.cpp index e15aff68..af64cbda 100644 --- a/src/simplifier.cpp +++ b/src/simplifier.cpp @@ -1,7 +1,4 @@ // This file is part of meshoptimizer library; see meshoptimizer.h for version/license details -// MIT License - -// Copyright (c) 2016-2024 Arseny Kapoulkine #include "meshoptimizer.h" #include diff --git a/src/spatialorder.cpp b/src/spatialorder.cpp index d174a2cc..7b1a0694 100644 --- a/src/spatialorder.cpp +++ b/src/spatialorder.cpp @@ -1,7 +1,4 @@ // This file is part of meshoptimizer library; see meshoptimizer.h for version/license details -// MIT License - -// Copyright (c) 2016-2024 Arseny Kapoulkine #include "meshoptimizer.h" #include diff --git a/src/stripifier.cpp b/src/stripifier.cpp index 89c5687c..4043195a 100644 --- a/src/stripifier.cpp +++ b/src/stripifier.cpp @@ -1,7 +1,4 @@ // This file is part of meshoptimizer library; see meshoptimizer.h for version/license details -// MIT License - -// Copyright (c) 2016-2024 Arseny Kapoulkine #include "meshoptimizer.h" #include diff --git a/src/vcacheanalyzer.cpp b/src/vcacheanalyzer.cpp index 726a35cb..36827438 100644 --- a/src/vcacheanalyzer.cpp +++ b/src/vcacheanalyzer.cpp @@ -1,7 +1,4 @@ // This file is part of meshoptimizer library; see meshoptimizer.h for version/license details -// MIT License - -// Copyright (c) 2016-2024 Arseny Kapoulkine #include "meshoptimizer.h" #include diff --git a/src/vcacheoptimizer.cpp b/src/vcacheoptimizer.cpp index b9626141..e4ecc71d 100644 --- a/src/vcacheoptimizer.cpp +++ b/src/vcacheoptimizer.cpp @@ -1,7 +1,4 @@ // This file is part of meshoptimizer library; see meshoptimizer.h for version/license details -// MIT License - -// Copyright (c) 2016-2024 Arseny Kapoulkine #include "meshoptimizer.h" #include diff --git a/src/vertexcodec.cpp b/src/vertexcodec.cpp index 17e7095e..1dbd2e35 100644 --- a/src/vertexcodec.cpp +++ b/src/vertexcodec.cpp @@ -1,7 +1,4 @@ // This file is part of meshoptimizer library; see meshoptimizer.h for version/license details -// MIT License - -// Copyright (c) 2016-2024 Arseny Kapoulkine #include "meshoptimizer.h" #include diff --git a/src/vertexfilter.cpp b/src/vertexfilter.cpp index bdcf6cfe..1f456bb9 100644 --- a/src/vertexfilter.cpp +++ b/src/vertexfilter.cpp @@ -1,7 +1,4 @@ // This file is part of meshoptimizer library; see meshoptimizer.h for version/license details -// MIT License - -// Copyright (c) 2016-2024 Arseny Kapoulkine #include "meshoptimizer.h" #include diff --git a/src/vfetchanalyzer.cpp b/src/vfetchanalyzer.cpp index 0c9c8541..51dca873 100644 --- a/src/vfetchanalyzer.cpp +++ b/src/vfetchanalyzer.cpp @@ -1,7 +1,4 @@ // This file is part of meshoptimizer library; see meshoptimizer.h for version/license details -// MIT License - -// Copyright (c) 2016-2024 Arseny Kapoulkine #include "meshoptimizer.h" #include diff --git a/src/vfetchoptimizer.cpp b/src/vfetchoptimizer.cpp index 78bf2dac..465d6df5 100644 --- a/src/vfetchoptimizer.cpp +++ b/src/vfetchoptimizer.cpp @@ -1,7 +1,4 @@ // This file is part of meshoptimizer library; see meshoptimizer.h for version/license details -// MIT License - -// Copyright (c) 2016-2024 Arseny Kapoulkine #include "meshoptimizer.h" #include diff --git a/testcase/decode_meshopt_test.cpp b/testcase/decode_meshopt_test.cpp index 57abc715..22457b0e 100644 --- a/testcase/decode_meshopt_test.cpp +++ b/testcase/decode_meshopt_test.cpp @@ -1,16 +1,6 @@ // Copyright (c) 2021 Huawei Device Co., Ltd. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// MIT LISENCE #include "../src/meshoptimizer" #include diff --git a/testcase/tests.cpp b/testcase/tests.cpp new file mode 100644 index 00000000..ab114835 --- /dev/null +++ b/testcase/tests.cpp @@ -0,0 +1,1965 @@ +#include "../src/meshoptimizer.h" + +#include +#include +#include +#include + +#include + +// This file uses assert() to verify algorithm correctness +#undef NDEBUG +#include + +struct PV +{ + unsigned short px, py, pz; + unsigned char nu, nv; // octahedron encoded normal, aliases .pw + unsigned short tx, ty; +}; + +// note: 4 6 5 triangle here is a combo-breaker: +// we encode it without rotating, a=next, c=next - this means we do *not* bump next to 6 +// which means that the next triangle can't be encoded via next sequencing! +static const unsigned int kIndexBuffer[] = {0, 1, 2, 2, 1, 3, 4, 6, 5, 7, 8, 9}; + +static const unsigned char kIndexDataV0[] = { + 0xe0, 0xf0, 0x10, 0xfe, 0xff, 0xf0, 0x0c, 0xff, 0x02, 0x02, 0x02, 0x00, 0x76, 0x87, 0x56, 0x67, + 0x78, 0xa9, 0x86, 0x65, 0x89, 0x68, 0x98, 0x01, 0x69, 0x00, 0x00, // clang-format :-/ +}; + +// note: this exercises two features of v1 format, restarts (0 1 2) and last +static const unsigned int kIndexBufferTricky[] = {0, 1, 2, 2, 1, 3, 0, 1, 2, 2, 1, 5, 2, 1, 4}; + +static const unsigned char kIndexDataV1[] = { + 0xe1, 0xf0, 0x10, 0xfe, 0x1f, 0x3d, 0x00, 0x0a, 0x00, 0x76, 0x87, 0x56, 0x67, 0x78, 0xa9, 0x86, + 0x65, 0x89, 0x68, 0x98, 0x01, 0x69, 0x00, 0x00, // clang-format :-/ +}; + +static const unsigned int kIndexSequence[] = {0, 1, 51, 2, 49, 1000}; + +static const unsigned char kIndexSequenceV1[] = { + 0xd1, 0x00, 0x04, 0xcd, 0x01, 0x04, 0x07, 0x98, 0x1f, 0x00, 0x00, 0x00, 0x00, // clang-format :-/ +}; + +static const PV kVertexBuffer[] = { + {0, 0, 0, 0, 0, 0, 0}, + {300, 0, 0, 0, 0, 500, 0}, + {0, 300, 0, 0, 0, 0, 500}, + {300, 300, 0, 0, 0, 500, 500}, +}; + +static const unsigned char kVertexDataV0[] = { + 0xa0, 0x01, 0x3f, 0x00, 0x00, 0x00, 0x58, 0x57, 0x58, 0x01, 0x26, 0x00, 0x00, 0x00, 0x01, + 0x0c, 0x00, 0x00, 0x00, 0x58, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x3f, 0x00, 0x00, 0x00, 0x17, 0x18, 0x17, 0x01, 0x26, 0x00, 0x00, 0x00, 0x01, 0x0c, 0x00, + 0x00, 0x00, 0x17, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // clang-format :-/ +}; + +static void decodeIndexV0() +{ + const size_t index_count = sizeof(kIndexBuffer) / sizeof(kIndexBuffer[0]); + + std::vector buffer(kIndexDataV0, kIndexDataV0 + sizeof(kIndexDataV0)); + + unsigned int decoded[index_count]; + assert(meshopt_decodeIndexBuffer(decoded, index_count, &buffer[0], buffer.size()) == 0); + assert(memcmp(decoded, kIndexBuffer, sizeof(kIndexBuffer)) == 0); +} + +static void decodeIndexV1() +{ + const size_t index_count = sizeof(kIndexBufferTricky) / sizeof(kIndexBufferTricky[0]); + + std::vector buffer(kIndexDataV1, kIndexDataV1 + sizeof(kIndexDataV1)); + + unsigned int decoded[index_count]; + assert(meshopt_decodeIndexBuffer(decoded, index_count, &buffer[0], buffer.size()) == 0); + assert(memcmp(decoded, kIndexBufferTricky, sizeof(kIndexBufferTricky)) == 0); +} + +static void decodeIndex16() +{ + const size_t index_count = sizeof(kIndexBuffer) / sizeof(kIndexBuffer[0]); + const size_t vertex_count = 10; + + std::vector buffer(meshopt_encodeIndexBufferBound(index_count, vertex_count)); + buffer.resize(meshopt_encodeIndexBuffer(&buffer[0], buffer.size(), kIndexBuffer, index_count)); + + unsigned short decoded[index_count]; + assert(meshopt_decodeIndexBuffer(decoded, index_count, &buffer[0], buffer.size()) == 0); + + for (size_t i = 0; i < index_count; ++i) + assert(decoded[i] == kIndexBuffer[i]); +} + +static void encodeIndexMemorySafe() +{ + const size_t index_count = sizeof(kIndexBuffer) / sizeof(kIndexBuffer[0]); + const size_t vertex_count = 10; + + std::vector buffer(meshopt_encodeIndexBufferBound(index_count, vertex_count)); + buffer.resize(meshopt_encodeIndexBuffer(&buffer[0], buffer.size(), kIndexBuffer, index_count)); + + // check that encode is memory-safe; note that we reallocate the buffer for each try to make sure ASAN can verify buffer access + for (size_t i = 0; i <= buffer.size(); ++i) + { + std::vector shortbuffer(i); + size_t result = meshopt_encodeIndexBuffer(i == 0 ? NULL : &shortbuffer[0], i, kIndexBuffer, index_count); + + if (i == buffer.size()) + assert(result == buffer.size()); + else + assert(result == 0); + } +} + +static void decodeIndexMemorySafe() +{ + const size_t index_count = sizeof(kIndexBuffer) / sizeof(kIndexBuffer[0]); + const size_t vertex_count = 10; + + std::vector buffer(meshopt_encodeIndexBufferBound(index_count, vertex_count)); + buffer.resize(meshopt_encodeIndexBuffer(&buffer[0], buffer.size(), kIndexBuffer, index_count)); + + // check that decode is memory-safe; note that we reallocate the buffer for each try to make sure ASAN can verify buffer access + unsigned int decoded[index_count]; + + for (size_t i = 0; i <= buffer.size(); ++i) + { + std::vector shortbuffer(buffer.begin(), buffer.begin() + i); + int result = meshopt_decodeIndexBuffer(decoded, index_count, i == 0 ? NULL : &shortbuffer[0], i); + + if (i == buffer.size()) + assert(result == 0); + else + assert(result < 0); + } +} + +static void decodeIndexRejectExtraBytes() +{ + const size_t index_count = sizeof(kIndexBuffer) / sizeof(kIndexBuffer[0]); + const size_t vertex_count = 10; + + std::vector buffer(meshopt_encodeIndexBufferBound(index_count, vertex_count)); + buffer.resize(meshopt_encodeIndexBuffer(&buffer[0], buffer.size(), kIndexBuffer, index_count)); + + // check that decoder doesn't accept extra bytes after a valid stream + std::vector largebuffer(buffer); + largebuffer.push_back(0); + + unsigned int decoded[index_count]; + assert(meshopt_decodeIndexBuffer(decoded, index_count, &largebuffer[0], largebuffer.size()) < 0); +} + +static void decodeIndexRejectMalformedHeaders() +{ + const size_t index_count = sizeof(kIndexBuffer) / sizeof(kIndexBuffer[0]); + const size_t vertex_count = 10; + + std::vector buffer(meshopt_encodeIndexBufferBound(index_count, vertex_count)); + buffer.resize(meshopt_encodeIndexBuffer(&buffer[0], buffer.size(), kIndexBuffer, index_count)); + + // check that decoder doesn't accept malformed headers + std::vector brokenbuffer(buffer); + brokenbuffer[0] = 0; + + unsigned int decoded[index_count]; + assert(meshopt_decodeIndexBuffer(decoded, index_count, &brokenbuffer[0], brokenbuffer.size()) < 0); +} + +static void decodeIndexRejectInvalidVersion() +{ + const size_t index_count = sizeof(kIndexBuffer) / sizeof(kIndexBuffer[0]); + const size_t vertex_count = 10; + + std::vector buffer(meshopt_encodeIndexBufferBound(index_count, vertex_count)); + buffer.resize(meshopt_encodeIndexBuffer(&buffer[0], buffer.size(), kIndexBuffer, index_count)); + + // check that decoder doesn't accept invalid version + std::vector brokenbuffer(buffer); + brokenbuffer[0] |= 0x0f; + + unsigned int decoded[index_count]; + assert(meshopt_decodeIndexBuffer(decoded, index_count, &brokenbuffer[0], brokenbuffer.size()) < 0); +} + +static void decodeIndexMalformedVByte() +{ + const unsigned char input[] = { + 0xe1, 0x20, 0x20, 0x20, 0xff, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0xff, 0xff, 0xff, 0xff, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, // clang-format :-/ + }; + + unsigned int decoded[66]; + assert(meshopt_decodeIndexBuffer(decoded, 66, input, sizeof(input)) < 0); +} + +static void roundtripIndexTricky() +{ + const size_t index_count = sizeof(kIndexBufferTricky) / sizeof(kIndexBufferTricky[0]); + const size_t vertex_count = 6; + + std::vector buffer(meshopt_encodeIndexBufferBound(index_count, vertex_count)); + buffer.resize(meshopt_encodeIndexBuffer(&buffer[0], buffer.size(), kIndexBufferTricky, index_count)); + + unsigned int decoded[index_count]; + assert(meshopt_decodeIndexBuffer(decoded, index_count, &buffer[0], buffer.size()) == 0); + assert(memcmp(decoded, kIndexBufferTricky, sizeof(kIndexBufferTricky)) == 0); +} + +static void encodeIndexEmpty() +{ + std::vector buffer(meshopt_encodeIndexBufferBound(0, 0)); + buffer.resize(meshopt_encodeIndexBuffer(&buffer[0], buffer.size(), NULL, 0)); + + assert(meshopt_decodeIndexBuffer(static_cast(NULL), 0, &buffer[0], buffer.size()) == 0); +} + +static void decodeIndexSequence() +{ + const size_t index_count = sizeof(kIndexSequence) / sizeof(kIndexSequence[0]); + + std::vector buffer(kIndexSequenceV1, kIndexSequenceV1 + sizeof(kIndexSequenceV1)); + + unsigned int decoded[index_count]; + assert(meshopt_decodeIndexSequence(decoded, index_count, &buffer[0], buffer.size()) == 0); + assert(memcmp(decoded, kIndexSequence, sizeof(kIndexSequence)) == 0); +} + +static void decodeIndexSequence16() +{ + const size_t index_count = sizeof(kIndexSequence) / sizeof(kIndexSequence[0]); + const size_t vertex_count = 1001; + + std::vector buffer(meshopt_encodeIndexSequenceBound(index_count, vertex_count)); + buffer.resize(meshopt_encodeIndexSequence(&buffer[0], buffer.size(), kIndexSequence, index_count)); + + unsigned short decoded[index_count]; + assert(meshopt_decodeIndexSequence(decoded, index_count, &buffer[0], buffer.size()) == 0); + + for (size_t i = 0; i < index_count; ++i) + assert(decoded[i] == kIndexSequence[i]); +} + +static void encodeIndexSequenceMemorySafe() +{ + const size_t index_count = sizeof(kIndexSequence) / sizeof(kIndexSequence[0]); + const size_t vertex_count = 1001; + + std::vector buffer(meshopt_encodeIndexSequenceBound(index_count, vertex_count)); + buffer.resize(meshopt_encodeIndexSequence(&buffer[0], buffer.size(), kIndexSequence, index_count)); + + // check that encode is memory-safe; note that we reallocate the buffer for each try to make sure ASAN can verify buffer access + for (size_t i = 0; i <= buffer.size(); ++i) + { + std::vector shortbuffer(i); + size_t result = meshopt_encodeIndexSequence(i == 0 ? NULL : &shortbuffer[0], i, kIndexSequence, index_count); + + if (i == buffer.size()) + assert(result == buffer.size()); + else + assert(result == 0); + } +} + +static void decodeIndexSequenceMemorySafe() +{ + const size_t index_count = sizeof(kIndexSequence) / sizeof(kIndexSequence[0]); + const size_t vertex_count = 1001; + + std::vector buffer(meshopt_encodeIndexSequenceBound(index_count, vertex_count)); + buffer.resize(meshopt_encodeIndexSequence(&buffer[0], buffer.size(), kIndexSequence, index_count)); + + // check that decode is memory-safe; note that we reallocate the buffer for each try to make sure ASAN can verify buffer access + unsigned int decoded[index_count]; + + for (size_t i = 0; i <= buffer.size(); ++i) + { + std::vector shortbuffer(buffer.begin(), buffer.begin() + i); + int result = meshopt_decodeIndexSequence(decoded, index_count, i == 0 ? NULL : &shortbuffer[0], i); + + if (i == buffer.size()) + assert(result == 0); + else + assert(result < 0); + } +} + +static void decodeIndexSequenceRejectExtraBytes() +{ + const size_t index_count = sizeof(kIndexSequence) / sizeof(kIndexSequence[0]); + const size_t vertex_count = 1001; + + std::vector buffer(meshopt_encodeIndexSequenceBound(index_count, vertex_count)); + buffer.resize(meshopt_encodeIndexSequence(&buffer[0], buffer.size(), kIndexSequence, index_count)); + + // check that decoder doesn't accept extra bytes after a valid stream + std::vector largebuffer(buffer); + largebuffer.push_back(0); + + unsigned int decoded[index_count]; + assert(meshopt_decodeIndexSequence(decoded, index_count, &largebuffer[0], largebuffer.size()) < 0); +} + +static void decodeIndexSequenceRejectMalformedHeaders() +{ + const size_t index_count = sizeof(kIndexSequence) / sizeof(kIndexSequence[0]); + const size_t vertex_count = 1001; + + std::vector buffer(meshopt_encodeIndexSequenceBound(index_count, vertex_count)); + buffer.resize(meshopt_encodeIndexSequence(&buffer[0], buffer.size(), kIndexSequence, index_count)); + + // check that decoder doesn't accept malformed headers + std::vector brokenbuffer(buffer); + brokenbuffer[0] = 0; + + unsigned int decoded[index_count]; + assert(meshopt_decodeIndexSequence(decoded, index_count, &brokenbuffer[0], brokenbuffer.size()) < 0); +} + +static void decodeIndexSequenceRejectInvalidVersion() +{ + const size_t index_count = sizeof(kIndexSequence) / sizeof(kIndexSequence[0]); + const size_t vertex_count = 1001; + + std::vector buffer(meshopt_encodeIndexSequenceBound(index_count, vertex_count)); + buffer.resize(meshopt_encodeIndexSequence(&buffer[0], buffer.size(), kIndexSequence, index_count)); + + // check that decoder doesn't accept invalid version + std::vector brokenbuffer(buffer); + brokenbuffer[0] |= 0x0f; + + unsigned int decoded[index_count]; + assert(meshopt_decodeIndexSequence(decoded, index_count, &brokenbuffer[0], brokenbuffer.size()) < 0); +} + +static void encodeIndexSequenceEmpty() +{ + std::vector buffer(meshopt_encodeIndexSequenceBound(0, 0)); + buffer.resize(meshopt_encodeIndexSequence(&buffer[0], buffer.size(), NULL, 0)); + + assert(meshopt_decodeIndexSequence(static_cast(NULL), 0, &buffer[0], buffer.size()) == 0); +} + +static void decodeVertexV0() +{ + const size_t vertex_count = sizeof(kVertexBuffer) / sizeof(kVertexBuffer[0]); + + std::vector buffer(kVertexDataV0, kVertexDataV0 + sizeof(kVertexDataV0)); + + PV decoded[vertex_count]; + assert(meshopt_decodeVertexBuffer(decoded, vertex_count, sizeof(PV), &buffer[0], buffer.size()) == 0); + assert(memcmp(decoded, kVertexBuffer, sizeof(kVertexBuffer)) == 0); +} + +static void encodeVertexMemorySafe() +{ + const size_t vertex_count = sizeof(kVertexBuffer) / sizeof(kVertexBuffer[0]); + + std::vector buffer(meshopt_encodeVertexBufferBound(vertex_count, sizeof(PV))); + buffer.resize(meshopt_encodeVertexBuffer(&buffer[0], buffer.size(), kVertexBuffer, vertex_count, sizeof(PV))); + + // check that encode is memory-safe; note that we reallocate the buffer for each try to make sure ASAN can verify buffer access + for (size_t i = 0; i <= buffer.size(); ++i) + { + std::vector shortbuffer(i); + size_t result = meshopt_encodeVertexBuffer(i == 0 ? NULL : &shortbuffer[0], i, kVertexBuffer, vertex_count, sizeof(PV)); + + if (i == buffer.size()) + assert(result == buffer.size()); + else + assert(result == 0); + } +} + +static void decodeVertexMemorySafe() +{ + const size_t vertex_count = sizeof(kVertexBuffer) / sizeof(kVertexBuffer[0]); + + std::vector buffer(meshopt_encodeVertexBufferBound(vertex_count, sizeof(PV))); + buffer.resize(meshopt_encodeVertexBuffer(&buffer[0], buffer.size(), kVertexBuffer, vertex_count, sizeof(PV))); + + // check that decode is memory-safe; note that we reallocate the buffer for each try to make sure ASAN can verify buffer access + PV decoded[vertex_count]; + + for (size_t i = 0; i <= buffer.size(); ++i) + { + std::vector shortbuffer(buffer.begin(), buffer.begin() + i); + int result = meshopt_decodeVertexBuffer(decoded, vertex_count, sizeof(PV), i == 0 ? NULL : &shortbuffer[0], i); + (void)result; + + if (i == buffer.size()) + assert(result == 0); + else + assert(result < 0); + } +} + +static void decodeVertexRejectExtraBytes() +{ + const size_t vertex_count = sizeof(kVertexBuffer) / sizeof(kVertexBuffer[0]); + + std::vector buffer(meshopt_encodeVertexBufferBound(vertex_count, sizeof(PV))); + buffer.resize(meshopt_encodeVertexBuffer(&buffer[0], buffer.size(), kVertexBuffer, vertex_count, sizeof(PV))); + + // check that decoder doesn't accept extra bytes after a valid stream + std::vector largebuffer(buffer); + largebuffer.push_back(0); + + PV decoded[vertex_count]; + assert(meshopt_decodeVertexBuffer(decoded, vertex_count, sizeof(PV), &largebuffer[0], largebuffer.size()) < 0); +} + +static void decodeVertexRejectMalformedHeaders() +{ + const size_t vertex_count = sizeof(kVertexBuffer) / sizeof(kVertexBuffer[0]); + + std::vector buffer(meshopt_encodeVertexBufferBound(vertex_count, sizeof(PV))); + buffer.resize(meshopt_encodeVertexBuffer(&buffer[0], buffer.size(), kVertexBuffer, vertex_count, sizeof(PV))); + + // check that decoder doesn't accept malformed headers + std::vector brokenbuffer(buffer); + brokenbuffer[0] = 0; + + PV decoded[vertex_count]; + assert(meshopt_decodeVertexBuffer(decoded, vertex_count, sizeof(PV), &brokenbuffer[0], brokenbuffer.size()) < 0); +} + +static void decodeVertexBitGroups() +{ + unsigned char data[16 * 4]; + + // this tests 0/2/4/8 bit groups in one stream + for (size_t i = 0; i < 16; ++i) + { + data[i * 4 + 0] = 0; + data[i * 4 + 1] = (unsigned char)(i * 1); + data[i * 4 + 2] = (unsigned char)(i * 2); + data[i * 4 + 3] = (unsigned char)(i * 8); + } + + std::vector buffer(meshopt_encodeVertexBufferBound(16, 4)); + buffer.resize(meshopt_encodeVertexBuffer(&buffer[0], buffer.size(), data, 16, 4)); + + unsigned char decoded[16 * 4]; + assert(meshopt_decodeVertexBuffer(decoded, 16, 4, &buffer[0], buffer.size()) == 0); + assert(memcmp(decoded, data, sizeof(data)) == 0); +} + +static void decodeVertexBitGroupSentinels() +{ + unsigned char data[16 * 4]; + + // this tests 0/2/4/8 bit groups and sentinels in one stream + for (size_t i = 0; i < 16; ++i) + { + if (i == 7 || i == 13) + { + data[i * 4 + 0] = 42; + data[i * 4 + 1] = 42; + data[i * 4 + 2] = 42; + data[i * 4 + 3] = 42; + } + else + { + data[i * 4 + 0] = 0; + data[i * 4 + 1] = (unsigned char)(i * 1); + data[i * 4 + 2] = (unsigned char)(i * 2); + data[i * 4 + 3] = (unsigned char)(i * 8); + } + } + + std::vector buffer(meshopt_encodeVertexBufferBound(16, 4)); + buffer.resize(meshopt_encodeVertexBuffer(&buffer[0], buffer.size(), data, 16, 4)); + + unsigned char decoded[16 * 4]; + assert(meshopt_decodeVertexBuffer(decoded, 16, 4, &buffer[0], buffer.size()) == 0); + assert(memcmp(decoded, data, sizeof(data)) == 0); +} + +static void decodeVertexLarge() +{ + unsigned char data[128 * 4]; + + // this tests 0/2/4/8 bit groups in one stream + for (size_t i = 0; i < 128; ++i) + { + data[i * 4 + 0] = 0; + data[i * 4 + 1] = (unsigned char)(i * 1); + data[i * 4 + 2] = (unsigned char)(i * 2); + data[i * 4 + 3] = (unsigned char)(i * 8); + } + + std::vector buffer(meshopt_encodeVertexBufferBound(128, 4)); + buffer.resize(meshopt_encodeVertexBuffer(&buffer[0], buffer.size(), data, 128, 4)); + + unsigned char decoded[128 * 4]; + assert(meshopt_decodeVertexBuffer(decoded, 128, 4, &buffer[0], buffer.size()) == 0); + assert(memcmp(decoded, data, sizeof(data)) == 0); +} + +static void encodeVertexEmpty() +{ + std::vector buffer(meshopt_encodeVertexBufferBound(0, 16)); + buffer.resize(meshopt_encodeVertexBuffer(&buffer[0], buffer.size(), NULL, 0, 16)); + + assert(meshopt_decodeVertexBuffer(NULL, 0, 16, &buffer[0], buffer.size()) == 0); +} + +static void decodeFilterOct8() +{ + const unsigned char data[4 * 4] = { + 0, 1, 127, 0, + 0, 187, 127, 1, + 255, 1, 127, 0, + 14, 130, 127, 1, // clang-format :-/ + }; + + const unsigned char expected[4 * 4] = { + 0, 1, 127, 0, + 0, 159, 82, 1, + 255, 1, 127, 0, + 1, 130, 241, 1, // clang-format :-/ + }; + + // Aligned by 4 + unsigned char full[4 * 4]; + memcpy(full, data, sizeof(full)); + meshopt_decodeFilterOct(full, 4, 4); + assert(memcmp(full, expected, sizeof(full)) == 0); + + // Tail processing for unaligned data + unsigned char tail[3 * 4]; + memcpy(tail, data, sizeof(tail)); + meshopt_decodeFilterOct(tail, 3, 4); + assert(memcmp(tail, expected, sizeof(tail)) == 0); +} + +static void decodeFilterOct12() +{ + const unsigned short data[4 * 4] = { + 0, 1, 2047, 0, + 0, 1870, 2047, 1, + 2017, 1, 2047, 0, + 14, 1300, 2047, 1, // clang-format :-/ + }; + + const unsigned short expected[4 * 4] = { + 0, 16, 32767, 0, + 0, 32621, 3088, 1, + 32764, 16, 471, 0, + 307, 28541, 16093, 1, // clang-format :-/ + }; + + // Aligned by 4 + unsigned short full[4 * 4]; + memcpy(full, data, sizeof(full)); + meshopt_decodeFilterOct(full, 4, 8); + assert(memcmp(full, expected, sizeof(full)) == 0); + + // Tail processing for unaligned data + unsigned short tail[3 * 4]; + memcpy(tail, data, sizeof(tail)); + meshopt_decodeFilterOct(tail, 3, 8); + assert(memcmp(tail, expected, sizeof(tail)) == 0); +} + +static void decodeFilterQuat12() +{ + const unsigned short data[4 * 4] = { + 0, 1, 0, 0x7fc, + 0, 1870, 0, 0x7fd, + 2017, 1, 0, 0x7fe, + 14, 1300, 0, 0x7ff, // clang-format :-/ + }; + + const unsigned short expected[4 * 4] = { + 32767, 0, 11, 0, + 0, 25013, 0, 21166, + 11, 0, 23504, 22830, + 158, 14715, 0, 29277, // clang-format :-/ + }; + + // Aligned by 4 + unsigned short full[4 * 4]; + memcpy(full, data, sizeof(full)); + meshopt_decodeFilterQuat(full, 4, 8); + assert(memcmp(full, expected, sizeof(full)) == 0); + + // Tail processing for unaligned data + unsigned short tail[3 * 4]; + memcpy(tail, data, sizeof(tail)); + meshopt_decodeFilterQuat(tail, 3, 8); + assert(memcmp(tail, expected, sizeof(tail)) == 0); +} + +static void decodeFilterExp() +{ + const unsigned int data[4] = { + 0, + 0xff000003, + 0x02fffff7, + 0xfe7fffff, // clang-format :-/ + }; + + const unsigned int expected[4] = { + 0, + 0x3fc00000, + 0xc2100000, + 0x49fffffe, // clang-format :-/ + }; + + // Aligned by 4 + unsigned int full[4]; + memcpy(full, data, sizeof(full)); + meshopt_decodeFilterExp(full, 4, 4); + assert(memcmp(full, expected, sizeof(full)) == 0); + + // Tail processing for unaligned data + unsigned int tail[3]; + memcpy(tail, data, sizeof(tail)); + meshopt_decodeFilterExp(tail, 3, 4); + assert(memcmp(tail, expected, sizeof(tail)) == 0); +} + +static void encodeFilterOct8() +{ + const float data[4 * 4] = { + 1, 0, 0, 0, + 0, -1, 0, 0, + 0.7071068f, 0, 0.707168f, 1, + -0.7071068f, 0, -0.707168f, 1, // clang-format :-/ + }; + + const unsigned char expected[4 * 4] = { + 0x7f, 0, 0x7f, 0, + 0, 0x81, 0x7f, 0, + 0x3f, 0, 0x7f, 0x7f, + 0x81, 0x40, 0x7f, 0x7f, // clang-format :-/ + }; + + unsigned char encoded[4 * 4]; + meshopt_encodeFilterOct(encoded, 4, 4, 8, data); + + assert(memcmp(encoded, expected, sizeof(expected)) == 0); + + signed char decoded[4 * 4]; + memcpy(decoded, encoded, sizeof(decoded)); + meshopt_decodeFilterOct(decoded, 4, 4); + + for (size_t i = 0; i < 4 * 4; ++i) + assert(fabsf(decoded[i] / 127.f - data[i]) < 1e-2f); +} + +static void encodeFilterOct12() +{ + const float data[4 * 4] = { + 1, 0, 0, 0, + 0, -1, 0, 0, + 0.7071068f, 0, 0.707168f, 1, + -0.7071068f, 0, -0.707168f, 1, // clang-format :-/ + }; + + const unsigned short expected[4 * 4] = { + 0x7ff, 0, 0x7ff, 0, + 0x0, 0xf801, 0x7ff, 0, + 0x3ff, 0, 0x7ff, 0x7fff, + 0xf801, 0x400, 0x7ff, 0x7fff, // clang-format :-/ + }; + + unsigned short encoded[4 * 4]; + meshopt_encodeFilterOct(encoded, 4, 8, 12, data); + + assert(memcmp(encoded, expected, sizeof(expected)) == 0); + + short decoded[4 * 4]; + memcpy(decoded, encoded, sizeof(decoded)); + meshopt_decodeFilterOct(decoded, 4, 8); + + for (size_t i = 0; i < 4 * 4; ++i) + assert(fabsf(decoded[i] / 32767.f - data[i]) < 1e-3f); +} + +static void encodeFilterQuat12() +{ + const float data[4 * 4] = { + 1, 0, 0, 0, + 0, -1, 0, 0, + 0.7071068f, 0, 0, 0.707168f, + -0.7071068f, 0, 0, -0.707168f, // clang-format :-/ + }; + + const unsigned short expected[4 * 4] = { + 0, 0, 0, 0x7fc, + 0, 0, 0, 0x7fd, + 0x7ff, 0, 0, 0x7ff, + 0x7ff, 0, 0, 0x7ff, // clang-format :-/ + }; + + unsigned short encoded[4 * 4]; + meshopt_encodeFilterQuat(encoded, 4, 8, 12, data); + + assert(memcmp(encoded, expected, sizeof(expected)) == 0); + + short decoded[4 * 4]; + memcpy(decoded, encoded, sizeof(decoded)); + meshopt_decodeFilterQuat(decoded, 4, 8); + + for (size_t i = 0; i < 4; ++i) + { + float dx = decoded[i * 4 + 0] / 32767.f; + float dy = decoded[i * 4 + 1] / 32767.f; + float dz = decoded[i * 4 + 2] / 32767.f; + float dw = decoded[i * 4 + 3] / 32767.f; + + float dp = + data[i * 4 + 0] * dx + + data[i * 4 + 1] * dy + + data[i * 4 + 2] * dz + + data[i * 4 + 3] * dw; + + assert(fabsf(fabsf(dp) - 1.f) < 1e-4f); + } +} + +static void encodeFilterExp() +{ + const float data[4] = { + 1, + -23.4f, + -0.1f, + 11.0f, + }; + + // separate exponents: each component gets its own value + const unsigned int expected1[4] = { + 0xf3002000, + 0xf7ffd133, + 0xefffcccd, + 0xf6002c00, + }; + + // shared exponents (vector): all components of each vector get the same value + const unsigned int expected2[4] = { + 0xf7000200, + 0xf7ffd133, + 0xf6ffff9a, + 0xf6002c00, + }; + + // shared exponents (component): each component gets the same value across all vectors + const unsigned int expected3[4] = { + 0xf3002000, + 0xf7ffd133, + 0xf3fffccd, + 0xf7001600, + }; + + unsigned int encoded1[4]; + meshopt_encodeFilterExp(encoded1, 2, 8, 15, data, meshopt_EncodeExpSeparate); + + unsigned int encoded2[4]; + meshopt_encodeFilterExp(encoded2, 2, 8, 15, data, meshopt_EncodeExpSharedVector); + + unsigned int encoded3[4]; + meshopt_encodeFilterExp(encoded3, 2, 8, 15, data, meshopt_EncodeExpSharedComponent); + + assert(memcmp(encoded1, expected1, sizeof(expected1)) == 0); + assert(memcmp(encoded2, expected2, sizeof(expected2)) == 0); + assert(memcmp(encoded3, expected3, sizeof(expected3)) == 0); + + float decoded1[4]; + memcpy(decoded1, encoded1, sizeof(decoded1)); + meshopt_decodeFilterExp(decoded1, 2, 8); + + float decoded2[4]; + memcpy(decoded2, encoded2, sizeof(decoded2)); + meshopt_decodeFilterExp(decoded2, 2, 8); + + float decoded3[4]; + memcpy(decoded3, encoded3, sizeof(decoded3)); + meshopt_decodeFilterExp(decoded3, 2, 8); + + for (size_t i = 0; i < 4; ++i) + { + assert(fabsf(decoded1[i] - data[i]) < 1e-3f); + assert(fabsf(decoded2[i] - data[i]) < 1e-3f); + assert(fabsf(decoded3[i] - data[i]) < 1e-3f); + } +} + +static void encodeFilterExpZero() +{ + const float data[4] = { + 0.f, + -0.f, + 1.1754944e-38f, + -1.1754944e-38f, + }; + const unsigned int expected[4] = { + 0xf2000000, + 0xf2000000, + 0x8e000000, + 0x8e000000, + }; + + unsigned int encoded[4]; + meshopt_encodeFilterExp(encoded, 4, 4, 15, data, meshopt_EncodeExpSeparate); + + assert(memcmp(encoded, expected, sizeof(expected)) == 0); + + float decoded[4]; + memcpy(decoded, encoded, sizeof(decoded)); + meshopt_decodeFilterExp(&decoded, 4, 4); + + for (size_t i = 0; i < 4; ++i) + assert(decoded[i] == 0); +} + +static void encodeFilterExpAlias() +{ + const float data[4] = { + 1, + -23.4f, + -0.1f, + 11.0f, + }; + + // separate exponents: each component gets its own value + const unsigned int expected1[4] = { + 0xf3002000, + 0xf7ffd133, + 0xefffcccd, + 0xf6002c00, + }; + + // shared exponents (vector): all components of each vector get the same value + const unsigned int expected2[4] = { + 0xf7000200, + 0xf7ffd133, + 0xf6ffff9a, + 0xf6002c00, + }; + + // shared exponents (component): each component gets the same value across all vectors + const unsigned int expected3[4] = { + 0xf3002000, + 0xf7ffd133, + 0xf3fffccd, + 0xf7001600, + }; + + unsigned int encoded1[4]; + memcpy(encoded1, data, sizeof(data)); + meshopt_encodeFilterExp(encoded1, 2, 8, 15, reinterpret_cast(encoded1), meshopt_EncodeExpSeparate); + + unsigned int encoded2[4]; + memcpy(encoded2, data, sizeof(data)); + meshopt_encodeFilterExp(encoded2, 2, 8, 15, reinterpret_cast(encoded2), meshopt_EncodeExpSharedVector); + + unsigned int encoded3[4]; + memcpy(encoded3, data, sizeof(data)); + meshopt_encodeFilterExp(encoded3, 2, 8, 15, reinterpret_cast(encoded3), meshopt_EncodeExpSharedComponent); + + assert(memcmp(encoded1, expected1, sizeof(expected1)) == 0); + assert(memcmp(encoded2, expected2, sizeof(expected2)) == 0); + assert(memcmp(encoded3, expected3, sizeof(expected3)) == 0); +} + +static void encodeFilterExpClamp() +{ + const float data[4] = { + 1, + -23.4f, + -0.1f, + 11.0f, + }; + + // separate exponents: each component gets its own value + // note: third value is exponent clamped + const unsigned int expected[4] = { + 0xf3002000, + 0xf7ffd133, + 0xf2fff99a, + 0xf6002c00, + }; + + unsigned int encoded[4]; + meshopt_encodeFilterExp(encoded, 2, 8, 15, data, meshopt_EncodeExpClamped); + + assert(memcmp(encoded, expected, sizeof(expected)) == 0); + + float decoded[4]; + memcpy(decoded, encoded, sizeof(decoded)); + meshopt_decodeFilterExp(decoded, 2, 8); + + for (size_t i = 0; i < 4; ++i) + assert(fabsf(decoded[i] - data[i]) < 1e-3f); +} + +static void clusterBoundsDegenerate() +{ + const float vbd[] = {0, 0, 0, 0, 0, 0, 0, 0, 0}; + const unsigned int ibd[] = {0, 0, 0}; + const unsigned int ib1[] = {0, 1, 2}; + + // all of the bounds below are degenerate as they use 0 triangles, one topology-degenerate triangle and one position-degenerate triangle respectively + meshopt_Bounds bounds0 = meshopt_computeClusterBounds(NULL, 0, NULL, 0, 12); + meshopt_Bounds boundsd = meshopt_computeClusterBounds(ibd, 3, vbd, 3, 12); + meshopt_Bounds bounds1 = meshopt_computeClusterBounds(ib1, 3, vbd, 3, 12); + + assert(bounds0.center[0] == 0 && bounds0.center[1] == 0 && bounds0.center[2] == 0 && bounds0.radius == 0); + assert(boundsd.center[0] == 0 && boundsd.center[1] == 0 && boundsd.center[2] == 0 && boundsd.radius == 0); + assert(bounds1.center[0] == 0 && bounds1.center[1] == 0 && bounds1.center[2] == 0 && bounds1.radius == 0); + + const float vb1[] = {1, 0, 0, 0, 1, 0, 0, 0, 1}; + const unsigned int ib2[] = {0, 1, 2, 0, 2, 1}; + + // these bounds have a degenerate cone since the cluster has two triangles with opposite normals + meshopt_Bounds bounds2 = meshopt_computeClusterBounds(ib2, 6, vb1, 3, 12); + + assert(bounds2.cone_apex[0] == 0 && bounds2.cone_apex[1] == 0 && bounds2.cone_apex[2] == 0); + assert(bounds2.cone_axis[0] == 0 && bounds2.cone_axis[1] == 0 && bounds2.cone_axis[2] == 0); + assert(bounds2.cone_cutoff == 1); + assert(bounds2.cone_axis_s8[0] == 0 && bounds2.cone_axis_s8[1] == 0 && bounds2.cone_axis_s8[2] == 0); + assert(bounds2.cone_cutoff_s8 == 127); + + // however, the bounding sphere needs to be in tact (here we only check bbox for simplicity) + assert(bounds2.center[0] - bounds2.radius <= 0 && bounds2.center[0] + bounds2.radius >= 1); + assert(bounds2.center[1] - bounds2.radius <= 0 && bounds2.center[1] + bounds2.radius >= 1); + assert(bounds2.center[2] - bounds2.radius <= 0 && bounds2.center[2] + bounds2.radius >= 1); +} + +static size_t allocCount; +static size_t freeCount; + +static void* customAlloc(size_t size) +{ + allocCount++; + + return malloc(size); +} + +static void customFree(void* ptr) +{ + freeCount++; + + free(ptr); +} + +static void customAllocator() +{ + meshopt_setAllocator(customAlloc, customFree); + + assert(allocCount == 0 && freeCount == 0); + + float vb[] = {1, 0, 0, 0, 1, 0, 0, 0, 1}; + unsigned int ib[] = {0, 1, 2}; + unsigned short ibs[] = {0, 1, 2}; + + // meshopt_computeClusterBounds doesn't allocate + meshopt_computeClusterBounds(ib, 3, vb, 3, 12); + assert(allocCount == 0 && freeCount == 0); + + // ... unless IndexAdapter is used + meshopt_computeClusterBounds(ibs, 3, vb, 3, 12); + assert(allocCount == 1 && freeCount == 1); + + // meshopt_optimizeVertexFetch allocates internal remap table and temporary storage for in-place remaps + meshopt_optimizeVertexFetch(vb, ib, 3, vb, 3, 12); + assert(allocCount == 3 && freeCount == 3); + + // ... plus one for IndexAdapter + meshopt_optimizeVertexFetch(vb, ibs, 3, vb, 3, 12); + assert(allocCount == 6 && freeCount == 6); + + meshopt_setAllocator(operator new, operator delete); + + // customAlloc & customFree should not get called anymore + meshopt_optimizeVertexFetch(vb, ib, 3, vb, 3, 12); + assert(allocCount == 6 && freeCount == 6); + + allocCount = freeCount = 0; +} + +static void emptyMesh() +{ + meshopt_optimizeVertexCache(NULL, NULL, 0, 0); + meshopt_optimizeVertexCacheFifo(NULL, NULL, 0, 0, 16); + meshopt_optimizeOverdraw(NULL, NULL, 0, NULL, 0, 12, 1.f); +} + +static void simplify() +{ + // 0 + // 1 2 + // 3 4 5 + unsigned int ib[] = { + 0, 2, 1, + 1, 2, 3, + 3, 2, 4, + 2, 5, 4, // clang-format :-/ + }; + + float vb[] = { + 0, 4, 0, + 0, 1, 0, + 2, 2, 0, + 0, 0, 0, + 1, 0, 0, + 4, 0, 0, // clang-format :-/ + }; + + unsigned int expected[] = { + 0, + 5, + 3, + }; + + float error; + assert(meshopt_simplify(ib, ib, 12, vb, 6, 12, 3, 1e-2f, 0, &error) == 3); + assert(error == 0.f); + assert(memcmp(ib, expected, sizeof(expected)) == 0); +} + +static void simplifyStuck() +{ + // tetrahedron can't be simplified due to collapse error restrictions + float vb1[] = {0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1}; + unsigned int ib1[] = {0, 1, 2, 0, 2, 3, 0, 3, 1, 2, 1, 3}; + + assert(meshopt_simplify(ib1, ib1, 12, vb1, 4, 12, 6, 1e-3f) == 12); + + // 5-vertex strip can't be simplified due to topology restriction since middle triangle has flipped winding + float vb2[] = {0, 0, 0, 1, 0, 0, 2, 0, 0, 0.5f, 1, 0, 1.5f, 1, 0}; + unsigned int ib2[] = {0, 1, 3, 3, 1, 4, 1, 2, 4}; // ok + unsigned int ib3[] = {0, 1, 3, 1, 3, 4, 1, 2, 4}; // flipped + + assert(meshopt_simplify(ib2, ib2, 9, vb2, 5, 12, 6, 1e-3f) == 6); + assert(meshopt_simplify(ib3, ib3, 9, vb2, 5, 12, 6, 1e-3f) == 9); + + // 4-vertex quad with a locked corner can't be simplified due to border error-induced restriction + float vb4[] = {0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0}; + unsigned int ib4[] = {0, 1, 3, 0, 3, 2}; + + assert(meshopt_simplify(ib4, ib4, 6, vb4, 4, 12, 3, 1e-3f) == 6); + + // 4-vertex quad with a locked corner can't be simplified due to border error-induced restriction + float vb5[] = {0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0}; + unsigned int ib5[] = {0, 1, 4, 0, 3, 2}; + + assert(meshopt_simplify(ib5, ib5, 6, vb5, 5, 12, 3, 1e-3f) == 6); +} + +static void simplifySloppyStuck() +{ + const float vb[] = {0, 0, 0, 0, 0, 0, 0, 0, 0}; + const unsigned int ib[] = {0, 1, 2, 0, 1, 2}; + + unsigned int* target = NULL; + + // simplifying down to 0 triangles results in 0 immediately + assert(meshopt_simplifySloppy(target, ib, 3, vb, 3, 12, 0, 0.f) == 0); + + // simplifying down to 2 triangles given that all triangles are degenerate results in 0 as well + assert(meshopt_simplifySloppy(target, ib, 6, vb, 3, 12, 6, 0.f) == 0); +} + +static void simplifyPointsStuck() +{ + const float vb[] = {0, 0, 0, 0, 0, 0, 0, 0, 0}; + + // simplifying down to 0 points results in 0 immediately + assert(meshopt_simplifyPoints(NULL, vb, 3, 12, NULL, 0, 0, 0) == 0); +} + +static void simplifyFlip() +{ + // this mesh has been constructed by taking a tessellated irregular grid with a square cutout + // and progressively collapsing edges until the only ones left violate border or flip constraints. + // there is only one valid non-flip collapse, so we validate that we take it; when flips are allowed, + // the wrong collapse is picked instead. + float vb[] = { + 1.000000f, 1.000000f, -1.000000f, + 1.000000f, 1.000000f, 1.000000f, + 1.000000f, -1.000000f, 1.000000f, + 1.000000f, -0.200000f, -0.200000f, + 1.000000f, 0.200000f, -0.200000f, + 1.000000f, -0.200000f, 0.200000f, + 1.000000f, 0.200000f, 0.200000f, + 1.000000f, 0.500000f, -0.500000f, + 1.000000f, -1.000000f, 0.000000f, // clang-format :-/ + }; + + // the collapse we expect is 7 -> 0 + unsigned int ib[] = { + 7, 4, 3, + 1, 2, 5, + 7, 1, 6, + 7, 8, 0, // gets removed + 7, 6, 4, + 8, 5, 2, + 8, 7, 3, + 8, 3, 5, + 5, 6, 1, + 7, 0, 1, // gets removed + }; + + unsigned int expected[] = { + 0, 4, 3, + 1, 2, 5, + 0, 1, 6, + 0, 6, 4, + 8, 5, 2, + 8, 0, 3, + 8, 3, 5, + 5, 6, 1, // clang-format :-/ + }; + + assert(meshopt_simplify(ib, ib, 30, vb, 9, 12, 3, 1e-3f) == 24); + assert(memcmp(ib, expected, sizeof(expected)) == 0); +} + +static void simplifyScale() +{ + const float vb[] = {0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3}; + + assert(meshopt_simplifyScale(vb, 4, 12) == 3.f); +} + +static void simplifyDegenerate() +{ + float vb[] = { + 0.000000f, 0.000000f, 0.000000f, + 0.000000f, 1.000000f, 0.000000f, + 0.000000f, 2.000000f, 0.000000f, + 1.000000f, 0.000000f, 0.000000f, + 2.000000f, 0.000000f, 0.000000f, + 1.000000f, 1.000000f, 0.000000f, // clang-format :-/ + }; + + // 0 1 2 + // 3 5 + // 4 + + unsigned int ib[] = { + 0, 1, 3, + 3, 1, 5, + 1, 2, 5, + 3, 5, 4, + 1, 0, 1, // these two degenerate triangles create a fake reverse edge + 0, 3, 0, // which breaks border classification + }; + + unsigned int expected[] = { + 0, 1, 4, + 4, 1, 2, // clang-format :-/ + }; + + assert(meshopt_simplify(ib, ib, 18, vb, 6, 12, 3, 1e-3f) == 6); + assert(memcmp(ib, expected, sizeof(expected)) == 0); +} + +static void simplifyLockBorder() +{ + float vb[] = { + 0.000000f, 0.000000f, 0.000000f, + 0.000000f, 1.000000f, 0.000000f, + 0.000000f, 2.000000f, 0.000000f, + 1.000000f, 0.000000f, 0.000000f, + 1.000000f, 1.000000f, 0.000000f, + 1.000000f, 2.000000f, 0.000000f, + 2.000000f, 0.000000f, 0.000000f, + 2.000000f, 1.000000f, 0.000000f, + 2.000000f, 2.000000f, 0.000000f, // clang-format :-/ + }; + + // 0 1 2 + // 3 4 5 + // 6 7 8 + + unsigned int ib[] = { + 0, 1, 3, + 3, 1, 4, + 1, 2, 4, + 4, 2, 5, + 3, 4, 6, + 6, 4, 7, + 4, 5, 7, + 7, 5, 8, // clang-format :-/ + }; + + unsigned int expected[] = { + 0, 1, 3, + 1, 2, 3, + 3, 2, 5, + 6, 3, 7, + 3, 5, 7, + 7, 5, 8, // clang-format :-/ + }; + + assert(meshopt_simplify(ib, ib, 24, vb, 9, 12, 3, 1e-3f, meshopt_SimplifyLockBorder) == 18); + assert(memcmp(ib, expected, sizeof(expected)) == 0); +} + +static void simplifyAttr(bool skip_g) +{ + float vb[8 * 3][6]; + + for (int y = 0; y < 8; ++y) + { + // first four rows are a blue gradient, next four rows are a yellow gradient + float r = (y < 4) ? 0.8f + y * 0.05f : 0.f; + float g = (y < 4) ? 0.8f + y * 0.05f : 0.f; + float b = (y < 4) ? 0.f : 0.8f + (7 - y) * 0.05f; + + for (int x = 0; x < 3; ++x) + { + vb[y * 3 + x][0] = float(x); + vb[y * 3 + x][1] = float(y); + vb[y * 3 + x][2] = 0.03f * x + 0.03f * (y % 2) + (x == 2 && y == 7) * 0.03f; + vb[y * 3 + x][3] = r; + vb[y * 3 + x][4] = g; + vb[y * 3 + x][5] = b; + } + } + + unsigned int ib[7 * 2][6]; + + for (int y = 0; y < 7; ++y) + { + for (int x = 0; x < 2; ++x) + { + ib[y * 2 + x][0] = (y + 0) * 3 + (x + 0); + ib[y * 2 + x][1] = (y + 0) * 3 + (x + 1); + ib[y * 2 + x][2] = (y + 1) * 3 + (x + 0); + ib[y * 2 + x][3] = (y + 1) * 3 + (x + 0); + ib[y * 2 + x][4] = (y + 0) * 3 + (x + 1); + ib[y * 2 + x][5] = (y + 1) * 3 + (x + 1); + } + } + + float attr_weights[3] = {0.5f, skip_g ? 0.f : 0.5f, 0.5f}; + + // *0 1 *2 + // 3 4 5 + // 6 7 8 + // *9 10 *11 + // *12 13 *14 + // 15 16 17 + // 18 19 20 + // *21 22 *23 + unsigned int expected[3][6] = { + {0, 2, 11, 0, 11, 9}, + {9, 11, 12, 12, 11, 14}, + {12, 14, 23, 12, 23, 21}, + }; + + assert(meshopt_simplifyWithAttributes(ib[0], ib[0], 7 * 2 * 6, vb[0], 8 * 3, 6 * sizeof(float), vb[0] + 3, 6 * sizeof(float), attr_weights, 3, NULL, 6 * 3, 1e-2f) == 18); + assert(memcmp(ib, expected, sizeof(expected)) == 0); +} + +static void simplifyLockFlags() +{ + float vb[] = { + 0, 0, 0, + 0, 1, 0, + 0, 2, 0, + 1, 0, 0, + 1, 1, 0, + 1, 2, 0, + 2, 0, 0, + 2, 1, 0, + 2, 2, 0, // clang-format :-/ + }; + + unsigned char lock[9] = { + 1, 1, 1, + 1, 0, 1, + 1, 1, 1, // clang-format :-/ + }; + + // 0 1 2 + // 3 4 5 + // 6 7 8 + + unsigned int ib[] = { + 0, 1, 3, + 3, 1, 4, + 1, 2, 4, + 4, 2, 5, + 3, 4, 6, + 6, 4, 7, + 4, 5, 7, + 7, 5, 8, // clang-format :-/ + }; + + unsigned int expected[] = { + 0, 1, 3, + 1, 2, 3, + 3, 2, 5, + 6, 3, 7, + 3, 5, 7, + 7, 5, 8, // clang-format :-/ + }; + + assert(meshopt_simplifyWithAttributes(ib, ib, 24, vb, 9, 12, NULL, 0, NULL, 0, lock, 3, 1e-3f, 0) == 18); + assert(memcmp(ib, expected, sizeof(expected)) == 0); +} + +static void simplifyLockFlagsSeam() +{ + float vb[] = { + 0, 0, 0, + 0, 1, 0, + 0, 1, 0, + 0, 2, 0, + 1, 0, 0, + 1, 1, 0, + 1, 1, 0, + 1, 2, 0, + 2, 0, 0, + 2, 1, 0, + 2, 1, 0, + 2, 2, 0, // clang-format :-/ + }; + + unsigned char lock0[12] = { + 1, 0, 0, 1, + 0, 0, 0, 0, + 1, 0, 0, 1, // clang-format :-/ + }; + + unsigned char lock1[12] = { + 1, 0, 0, 1, + 1, 0, 0, 1, + 1, 0, 0, 1, // clang-format :-/ + }; + + unsigned char lock2[12] = { + 1, 0, 1, 1, + 1, 0, 1, 1, + 1, 0, 1, 1, // clang-format :-/ + }; + + unsigned char lock3[12] = { + 1, 1, 0, 1, + 1, 1, 0, 1, + 1, 1, 0, 1, // clang-format :-/ + }; + + // 0 1-2 3 + // 4 5-6 7 + // 8 9-10 11 + + unsigned int ib[] = { + 0, 1, 4, + 4, 1, 5, + 4, 5, 8, + 8, 5, 9, + 2, 3, 6, + 6, 3, 7, + 6, 7, 10, + 10, 7, 11, // clang-format :-/ + }; + + unsigned int res[24]; + // with no locks, we should be able to collapse the entire mesh (vertices 1-2 and 9-10 are locked but others can move towards them) + assert(meshopt_simplifyWithAttributes(res, ib, 24, vb, 12, 12, NULL, 0, NULL, 0, NULL, 0, 1.f, 0) == 0); + + // with corners locked, we should get two quads + assert(meshopt_simplifyWithAttributes(res, ib, 24, vb, 12, 12, NULL, 0, NULL, 0, lock0, 0, 1.f, 0) == 12); + + // with both sides locked, we can only collapse the seam spine + assert(meshopt_simplifyWithAttributes(res, ib, 24, vb, 12, 12, NULL, 0, NULL, 0, lock1, 0, 1.f, 0) == 18); + + // with seam spine locked, we can collapse nothing; note that we intentionally test two different lock configurations + // they each lock only one side of the seam spine, which should be equivalent + assert(meshopt_simplifyWithAttributes(res, ib, 24, vb, 12, 12, NULL, 0, NULL, 0, lock2, 0, 1.f, 0) == 24); + assert(meshopt_simplifyWithAttributes(res, ib, 24, vb, 12, 12, NULL, 0, NULL, 0, lock3, 0, 1.f, 0) == 24); +} + +static void simplifySparse() +{ + float vb[] = { + 0, 0, 100, + 0, 1, 0, + 0, 2, 100, + 1, 0, 0.1f, + 1, 1, 0.1f, + 1, 2, 0.1f, + 2, 0, 100, + 2, 1, 0, + 2, 2, 100, // clang-format :-/ + }; + + float vba[] = { + 100, + 0.5f, + 100, + 0.5f, + 0.5f, + 0, + 100, + 0.5f, + 100, // clang-format :-/ + }; + + float aw[] = { + 0.5f}; + + unsigned char lock[9] = { + 8, 1, 8, + 1, 0, 1, + 8, 1, 8, // clang-format :-/ + }; + + // 1 + // 3 4 5 + // 7 + + unsigned int ib[] = { + 3, 1, 4, + 1, 5, 4, + 3, 4, 7, + 4, 5, 7, // clang-format :-/ + }; + + unsigned int res[12]; + + // vertices 3-4-5 are slightly elevated along Z which guides the collapses when only using geometry + unsigned int expected[] = { + 1, 5, 3, + 3, 5, 7, // clang-format :-/ + }; + + assert(meshopt_simplify(res, ib, 12, vb, 9, 12, 6, 1e-3f, meshopt_SimplifySparse) == 6); + assert(memcmp(res, expected, sizeof(expected)) == 0); + + // vertices 1-4-7 have a crease in the attribute value which guides the collapses the opposite way when weighing attributes sufficiently + unsigned int expecteda[] = { + 3, 1, 7, + 1, 5, 7, // clang-format :-/ + }; + + assert(meshopt_simplifyWithAttributes(res, ib, 12, vb, 9, 12, vba, sizeof(float), aw, 1, lock, 6, 1e-1f, meshopt_SimplifySparse) == 6); + assert(memcmp(res, expecteda, sizeof(expecteda)) == 0); + + // a final test validates that destination can alias when using sparsity + assert(meshopt_simplify(ib, ib, 12, vb, 9, 12, 6, 1e-3f, meshopt_SimplifySparse) == 6); + assert(memcmp(ib, expected, sizeof(expected)) == 0); +} + +static void simplifyErrorAbsolute() +{ + float vb[] = { + 0, 0, 0, + 0, 1, 0, + 0, 2, 0, + 1, 0, 0, + 1, 1, 1, + 1, 2, 0, + 2, 0, 0, + 2, 1, 0, + 2, 2, 0, // clang-format :-/ + }; + + // 0 1 2 + // 3 4 5 + // 6 7 8 + + unsigned int ib[] = { + 0, 1, 3, + 3, 1, 4, + 1, 2, 4, + 4, 2, 5, + 3, 4, 6, + 6, 4, 7, + 4, 5, 7, + 7, 5, 8, // clang-format :-/ + }; + + float error = 0.f; + assert(meshopt_simplify(ib, ib, 24, vb, 9, 12, 18, 2.f, meshopt_SimplifyLockBorder | meshopt_SimplifyErrorAbsolute, &error) == 18); + assert(fabsf(error - 0.85f) < 0.01f); +} + +static void simplifySeam() +{ + // xyz+attr + float vb[] = { + 0, 0, 0, 0, + 0, 1, 0, 0, + 0, 1, 0, 1, + 0, 2, 0, 1, + 1, 0, 0, 0, + 1, 1, 0.3f, 0, + 1, 1, 0.3f, 1, + 1, 2, 0, 1, + 2, 0, 0, 0, + 2, 1, 0.1f, 0, + 2, 1, 0.1f, 1, + 2, 2, 0, 1, + 3, 0, 0, 0, + 3, 1, 0, 0, + 3, 1, 0, 1, + 3, 2, 0, 1, // clang-format :-/ + }; + + // 0 1-2 3 + // 4 5-6 7 + // 8 9-10 11 + // 12 13-14 15 + + unsigned int ib[] = { + 0, 1, 4, + 4, 1, 5, + 2, 3, 6, + 6, 3, 7, + 4, 5, 8, + 8, 5, 9, + 6, 7, 10, + 10, 7, 11, + 8, 9, 12, + 12, 9, 13, + 10, 11, 14, + 14, 11, 15, // clang-format :-/ + }; + + // note: vertices 1-2 and 13-14 are classified as locked, because they are on a seam & a border + // 0 1-2 3 + // 5-6 + // 9-10 + // 12 13-14 15 + unsigned int expected[] = { + 0, 1, 13, + 2, 3, 14, + 0, 13, 12, + 14, 3, 15, // clang-format :-/ + }; + + unsigned int res[36]; + float error = 0.f; + + assert(meshopt_simplify(res, ib, 36, vb, 16, 16, 12, 1.f, 0, &error) == 12); + assert(memcmp(res, expected, sizeof(expected)) == 0); + assert(fabsf(error - 0.09f) < 0.01f); // note: the error is not zero because there is a difference in height between the seam vertices + + float aw = 1; + assert(meshopt_simplifyWithAttributes(res, ib, 36, vb, 16, 16, vb + 3, 16, &aw, 1, NULL, 12, 2.f, 0, &error) == 12); + assert(memcmp(res, expected, sizeof(expected)) == 0); + assert(fabsf(error - 0.09f) < 0.01f); // note: this is the same error as above because the attribute is constant on either side of the seam +} + +static void simplifySeamFake() +{ + // xyz+attr + float vb[] = { + 0, 0, 0, 0, + 1, 0, 0, 1, + 1, 0, 0, 2, + 0, 0, 0, 3, // clang-format :-/ + }; + + unsigned int ib[] = { + 0, 1, 2, + 2, 1, 3, // clang-format :-/ + }; + + assert(meshopt_simplify(ib, ib, 6, vb, 4, 16, 0, 1.f, 0, NULL) == 6); +} + +static void simplifyDebug() +{ + // 0 + // 1 2 + // 3 4 5 + unsigned int ib[] = { + 0, 2, 1, + 1, 2, 3, + 3, 2, 4, + 2, 5, 4, // clang-format :-/ + }; + + float vb[] = { + 0, 4, 0, + 0, 1, 0, + 2, 2, 0, + 0, 0, 0, + 1, 0, 0, + 4, 0, 0, // clang-format :-/ + }; + + unsigned int expected[] = { + 0 | (9u << 28), + 5 | (9u << 28), + 3 | (9u << 28), + }; + + const unsigned int meshopt_SimplifyInternalDebug = 1 << 30; + + float error; + assert(meshopt_simplify(ib, ib, 12, vb, 6, 12, 3, 1e-2f, meshopt_SimplifyInternalDebug, &error) == 3); + assert(error == 0.f); + assert(memcmp(ib, expected, sizeof(expected)) == 0); +} + +static void simplifyPrune() +{ + // 0 + // 1 2 + // 3 4 5 + // + + // 6 7 8 (same position) + unsigned int ib[] = { + 0, 2, 1, + 1, 2, 3, + 3, 2, 4, + 2, 5, 4, + 6, 7, 8, // clang-format :-/ + }; + + float vb[] = { + 0, 4, 0, + 0, 1, 0, + 2, 2, 0, + 0, 0, 0, + 1, 0, 0, + 4, 0, 0, + 1, 1, 1, + 1, 1, 1, + 1, 1, 1, // clang-format :-/ + }; + + unsigned int expected[] = { + 0, + 5, + 3, + }; + + float error; + assert(meshopt_simplify(ib, ib, 15, vb, 9, 12, 3, 1e-2f, meshopt_SimplifyPrune, &error) == 3); + assert(error == 0.f); + assert(memcmp(ib, expected, sizeof(expected)) == 0); + + // re-run prune with and without sparsity on a small subset to make sure the component code correctly handles sparse subsets + assert(meshopt_simplify(ib, ib, 3, vb, 9, 12, 3, 1e-2f, meshopt_SimplifyPrune, &error) == 3); + assert(meshopt_simplify(ib, ib, 3, vb, 9, 12, 3, 1e-2f, meshopt_SimplifyPrune | meshopt_SimplifySparse, &error) == 3); + assert(memcmp(ib, expected, sizeof(expected)) == 0); +} + +static void simplifyPruneCleanup() +{ + unsigned int ib[] = { + 0, 1, 2, + 3, 4, 5, + 6, 7, 8, // clang-format :-/ + }; + + float vb[] = { + 0, 0, 0, + 0, 1, 0, + 1, 0, 0, + 0, 0, 1, + 0, 2, 1, + 2, 0, 1, + 0, 0, 2, + 0, 4, 2, + 4, 0, 2, // clang-format :-/ + }; + + unsigned int expected[] = { + 6, + 7, + 8, + }; + + float error; + assert(meshopt_simplify(ib, ib, 9, vb, 9, 12, 3, 1.f, meshopt_SimplifyLockBorder | meshopt_SimplifyPrune, &error) == 3); + assert(fabsf(error - 0.37f) < 0.01f); + assert(memcmp(ib, expected, sizeof(expected)) == 0); +} + +static void adjacency() +{ + // 0 1/4 + // 2/5 3 + const float vb[] = {0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0}; + const unsigned int ib[] = {0, 1, 2, 5, 4, 3}; + + unsigned int adjib[12]; + meshopt_generateAdjacencyIndexBuffer(adjib, ib, 6, vb, 6, 12); + + unsigned int expected[] = { + // patch 0 + 0, 0, + 1, 3, + 2, 2, + + // patch 1 + 5, 0, + 4, 4, + 3, 3, + + // clang-format :-/ + }; + + assert(memcmp(adjib, expected, sizeof(expected)) == 0); +} + +static void tessellation() +{ + // 0 1/4 + // 2/5 3 + const float vb[] = {0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0}; + const unsigned int ib[] = {0, 1, 2, 5, 4, 3}; + + unsigned int tessib[24]; + meshopt_generateTessellationIndexBuffer(tessib, ib, 6, vb, 6, 12); + + unsigned int expected[] = { + // patch 0 + 0, 1, 2, + 0, 1, + 4, 5, + 2, 0, + 0, 1, 2, + + // patch 1 + 5, 4, 3, + 2, 1, + 4, 3, + 3, 5, + 2, 1, 3, + + // clang-format :-/ + }; + + assert(memcmp(tessib, expected, sizeof(expected)) == 0); +} + +static void provoking() +{ + // 0 1 2 + // 3 4 5 + const unsigned int ib[] = { + 0, 1, 3, + 3, 1, 4, + 1, 2, 4, + 4, 2, 5, + 0, 2, 4, + // clang-format :-/ + }; + + unsigned int pib[15]; + unsigned int pre[6 + 5]; // limit is vertex count + triangle count + size_t res = meshopt_generateProvokingIndexBuffer(pib, pre, ib, 15, 6); + + unsigned int expectedib[] = { + 0, 5, 1, + 1, 4, 0, + 2, 4, 1, + 3, 4, 2, + 4, 5, 2, + // clang-format :-/ + }; + + unsigned int expectedre[] = { + 3, 1, 2, 5, 4, 0, + // clang-format :-/ + }; + + assert(res == 6); + assert(memcmp(pib, expectedib, sizeof(expectedib)) == 0); + assert(memcmp(pre, expectedre, sizeof(expectedre)) == 0); +} + +static void quantizeFloat() +{ + volatile float zero = 0.f; // avoids div-by-zero warnings + + assert(meshopt_quantizeFloat(1.2345f, 23) == 1.2345f); + + assert(meshopt_quantizeFloat(1.2345f, 16) == 1.2344971f); + assert(meshopt_quantizeFloat(1.2345f, 8) == 1.2343750f); + assert(meshopt_quantizeFloat(1.2345f, 4) == 1.25f); + assert(meshopt_quantizeFloat(1.2345f, 1) == 1.0); + + assert(meshopt_quantizeFloat(1.f, 0) == 1.0f); + + assert(meshopt_quantizeFloat(1.f / zero, 0) == 1.f / zero); + assert(meshopt_quantizeFloat(-1.f / zero, 0) == -1.f / zero); + + float nanf = meshopt_quantizeFloat(zero / zero, 8); + assert(nanf != nanf); +} + +static void quantizeHalf() +{ + volatile float zero = 0.f; // avoids div-by-zero warnings + + // normal + assert(meshopt_quantizeHalf(1.2345f) == 0x3cf0); + + // overflow + assert(meshopt_quantizeHalf(65535.f) == 0x7c00); + assert(meshopt_quantizeHalf(-65535.f) == 0xfc00); + + // large + assert(meshopt_quantizeHalf(65000.f) == 0x7bef); + assert(meshopt_quantizeHalf(-65000.f) == 0xfbef); + + // small + assert(meshopt_quantizeHalf(0.125f) == 0x3000); + assert(meshopt_quantizeHalf(-0.125f) == 0xb000); + + // very small + assert(meshopt_quantizeHalf(1e-4f) == 0x068e); + assert(meshopt_quantizeHalf(-1e-4f) == 0x868e); + + // underflow + assert(meshopt_quantizeHalf(1e-5f) == 0x0000); + assert(meshopt_quantizeHalf(-1e-5f) == 0x8000); + + // exponent underflow + assert(meshopt_quantizeHalf(1e-20f) == 0x0000); + assert(meshopt_quantizeHalf(-1e-20f) == 0x8000); + + // exponent overflow + assert(meshopt_quantizeHalf(1e20f) == 0x7c00); + assert(meshopt_quantizeHalf(-1e20f) == 0xfc00); + + // inf + assert(meshopt_quantizeHalf(1.f / zero) == 0x7c00); + assert(meshopt_quantizeHalf(-1.f / zero) == 0xfc00); + + // nan + unsigned short nanh = meshopt_quantizeHalf(zero / zero); + assert(nanh == 0x7e00 || nanh == 0xfe00); +} + +static void dequantizeHalf() +{ + volatile float zero = 0.f; // avoids div-by-zero warnings + + // normal + assert(meshopt_dequantizeHalf(0x3cf0) == 1.234375f); + + // large + assert(meshopt_dequantizeHalf(0x7bef) == 64992.f); + assert(meshopt_dequantizeHalf(0xfbef) == -64992.f); + + // small + assert(meshopt_dequantizeHalf(0x3000) == 0.125f); + assert(meshopt_dequantizeHalf(0xb000) == -0.125f); + + // very small + assert(meshopt_dequantizeHalf(0x068e) == 1.00016594e-4f); + assert(meshopt_dequantizeHalf(0x868e) == -1.00016594e-4f); + + // denormal + assert(meshopt_dequantizeHalf(0x00ff) == 0.f); + assert(meshopt_dequantizeHalf(0x80ff) == 0.f); // actually this is -0.f + assert(1.f / meshopt_dequantizeHalf(0x80ff) == -1.f / zero); + + // inf + assert(meshopt_dequantizeHalf(0x7c00) == 1.f / zero); + assert(meshopt_dequantizeHalf(0xfc00) == -1.f / zero); + + // nan + float nanf = meshopt_dequantizeHalf(0x7e00); + assert(nanf != nanf); +} + +void runTests() +{ + decodeIndexV0(); + decodeIndexV1(); + decodeIndex16(); + encodeIndexMemorySafe(); + decodeIndexMemorySafe(); + decodeIndexRejectExtraBytes(); + decodeIndexRejectMalformedHeaders(); + decodeIndexRejectInvalidVersion(); + decodeIndexMalformedVByte(); + roundtripIndexTricky(); + encodeIndexEmpty(); + + decodeIndexSequence(); + decodeIndexSequence16(); + encodeIndexSequenceMemorySafe(); + decodeIndexSequenceMemorySafe(); + decodeIndexSequenceRejectExtraBytes(); + decodeIndexSequenceRejectMalformedHeaders(); + decodeIndexSequenceRejectInvalidVersion(); + encodeIndexSequenceEmpty(); + + decodeVertexV0(); + encodeVertexMemorySafe(); + decodeVertexMemorySafe(); + decodeVertexRejectExtraBytes(); + decodeVertexRejectMalformedHeaders(); + decodeVertexBitGroups(); + decodeVertexBitGroupSentinels(); + decodeVertexLarge(); + encodeVertexEmpty(); + + decodeFilterOct8(); + decodeFilterOct12(); + decodeFilterQuat12(); + decodeFilterExp(); + + encodeFilterOct8(); + encodeFilterOct12(); + encodeFilterQuat12(); + encodeFilterExp(); + encodeFilterExpZero(); + encodeFilterExpAlias(); + encodeFilterExpClamp(); + + clusterBoundsDegenerate(); + + customAllocator(); + + emptyMesh(); + + simplify(); + simplifyStuck(); + simplifySloppyStuck(); + simplifyPointsStuck(); + simplifyFlip(); + simplifyScale(); + simplifyDegenerate(); + simplifyLockBorder(); + simplifyAttr(/* skip_g= */ false); + simplifyAttr(/* skip_g= */ true); + simplifyLockFlags(); + simplifyLockFlagsSeam(); + simplifySparse(); + simplifyErrorAbsolute(); + simplifySeam(); + simplifySeamFake(); + simplifyDebug(); + simplifyPrune(); + simplifyPruneCleanup(); + + adjacency(); + tessellation(); + provoking(); + + quantizeFloat(); + quantizeHalf(); + dequantizeHalf(); +} + +int main() { + runTests(); + std::count << "All test cases passed!" << std::endl; + return 0; +} From 69fcfecb347dccdb0f1f95713368036e3a69407a Mon Sep 17 00:00:00 2001 From: wangshilin Date: Mon, 22 Sep 2025 20:44:30 +0800 Subject: [PATCH 3/6] =?UTF-8?q?OAT.xml=E4=BF=AE=E6=94=B9=20README.md?= =?UTF-8?q?=E4=BF=AE=E6=94=B9=20Signed-off-by:=20wangshilin=20?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .git-blame-ignore-revs | 4 - COPYING.md => COPYING | 0 OAT.xml | 5 +- README.md | 588 ++++++++++++++++++++++++++++++++++++---- README_en.md | 76 ++++++ README_meshoptimizer.md | 558 -------------------------------------- src/meshoptimizer.h | 2 +- 7 files changed, 615 insertions(+), 618 deletions(-) rename COPYING.md => COPYING (100%) create mode 100644 README_en.md delete mode 100644 README_meshoptimizer.md diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 1d666ca0..77aa716c 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -1,7 +1,3 @@ -# MIT License - -# Copyright (c) 2016-2024 Arseny Kapoulkine - # This file contains a list of Git commit hashes that should be hidden from the # regular Git history. Typically, this includes commits involving mass auto-formatting # or other normalizations. Commit hashes *must* use the full 40-character notation. diff --git a/COPYING.md b/COPYING similarity index 100% rename from COPYING.md rename to COPYING diff --git a/OAT.xml b/OAT.xml index 2465eaa5..14a7238b 100644 --- a/OAT.xml +++ b/OAT.xml @@ -5,7 +5,7 @@ MIT LISENSE - + COPYING @@ -16,12 +16,13 @@ MIT LISENSE + - + diff --git a/README.md b/README.md index 3bf2a52c..07a6791f 100644 --- a/README.md +++ b/README.md @@ -1,76 +1,558 @@ -# meshoptimizer -Mesh optimizer is an efficient mesh optimization library that reduces the storage size of 3D mesh data and improves rendering performance through various techniques. It is mainly used for optimizing 3D models (such as pixel silver optimization, vertex caching optimization, over drawing optimization, vertex acquisition optimization, vertex quantization, shadow indexing), especially when dealing with a large number of polygon 3D models, which can significantly reduce rendering burden and improve running efficiency. +# 🐇 meshoptimizer [![Actions Status](https://github.com/zeux/meshoptimizer/workflows/build/badge.svg)](https://github.com/zeux/meshoptimizer/actions) [![codecov.io](https://codecov.io/github/zeux/meshoptimizer/coverage.svg?branch=master)](https://codecov.io/github/zeux/meshoptimizer?branch=master) ![MIT](https://img.shields.io/badge/license-MIT-blue.svg) [![GitHub](https://img.shields.io/badge/repo-github-green.svg)](https://github.com/zeux/meshoptimizer) -## Use Cases of meshoptimizer -Gltf (Graphics Library Transmission Format) is a royalty free specification aimed at improving the efficiency of application transmission and loading of 3D scenes and models. -The introduction of meshoptimizer on OpenHarmony is mainly for decompressing the Ext_meshopt_compress extension format in gltf. +## Purpose + +When a GPU renders triangle meshes, various stages of the GPU pipeline have to process vertex and index data. The efficiency of these stages depends on the data you feed to them; this library provides algorithms to help optimize meshes for these stages, as well as algorithms to reduce the mesh complexity and storage overhead. + +The library provides a C and C++ interface for all algorithms; you can use it from C/C++ or from other languages via FFI (such as P/Invoke). If you want to use this library from Rust, you should use [meshopt crate](https://crates.io/crates/meshopt). JavaScript interface for some algorithms is available through [meshoptimizer.js](https://www.npmjs.com/package/meshoptimizer). + +[gltfpack](./gltf/README.md), which is a tool that can automatically optimize glTF files, is developed and distributed alongside the library. + +## Installing + +meshoptimizer is hosted on GitHub; you can download the latest release using git: -## Directory Structure ``` -demo # demo file -extern # third-party source code -gltf # gltfpack is a tool that can automatically optimize gltf files, reducing download size and improving loading and rendering speed -js # this folder contains JavaScript/WebAssembly modules that can be used to access some functions of the meshoptimizer library -src # C++ source code directory of meshoptimizer containing decompression functions -CMakeLists.txt # build description file -LICENSE.md # license statement -README.MD # software description +git clone -b v0.22 https://github.com/zeux/meshoptimizer.git ``` +Alternatively you can [download the .zip archive from GitHub](https://github.com/zeux/meshoptimizer/archive/v0.22.zip). -## The adaptation of meshoptimizer for OpenHarmony -OpenHarmony only compiles the source code and header files under the src/ directory in the meshoptimizer repository. meshoptimizer is introduced into the thirdparty directory of OpenHarmony and compiled using the method of dependent components in OpenHarmony. +The library is also available as a Linux package in several distributions ([ArchLinux](https://aur.archlinux.org/packages/meshoptimizer/), [Debian](https://packages.debian.org/libmeshoptimizer), [FreeBSD](https://www.freshports.org/misc/meshoptimizer/), [Nix](https://mynixos.com/nixpkgs/package/meshoptimizer), [Ubuntu](https://packages.ubuntu.com/libmeshoptimizer)), as well as a [Vcpkg port](https://github.com/microsoft/vcpkg/tree/master/ports/meshoptimizer) (see [installation instructions](https://learn.microsoft.com/en-us/vcpkg/get_started/get-started)) and a [Conan package](https://conan.io/center/recipes/meshoptimizer). -## Main Code Download -``` -repo init -u https://gitee.com/openharmony/manifest.git -b master --no-repo-verify -repo sync -c -repo forall -c 'git lfs pull' +[gltfpack](./gltf/README.md) is available as a pre-built binary on [Releases page](https://github.com/zeux/meshoptimizer/releases) or via [npm package](https://www.npmjs.com/package/gltfpack). Native binaries are recommended since they are more efficient and support texture compression. + +## Building + +meshoptimizer is distributed as a set of C++ source files. To include it into your project, you can use one of the two options: + +* Use CMake to build the library (either as a standalone project or as part of your project) +* Add source files to your project's build system + +The source files are organized in such a way that you don't need to change your build-system settings, and you only need to add the source files for the algorithms you use. They should build without warnings or special compilation options on all major compilers. + +## Pipeline + +When optimizing a mesh, you should typically feed it through a set of optimizations (the order is important!): + +1. Indexing +2. (optional, discussed last) Simplification +3. Vertex cache optimization +4. Overdraw optimization +5. Vertex fetch optimization +6. Vertex quantization +7. Shadow indexing +8. (optional) Vertex/index buffer compression + +## Indexing + +Most algorithms in this library assume that a mesh has a vertex buffer and an index buffer. For algorithms to work well and also for GPU to render your mesh efficiently, the vertex buffer has to have no redundant vertices; you can generate an index buffer from an unindexed vertex buffer or reindex an existing (potentially redundant) index buffer as follows: + +First, generate a remap table from your existing vertex (and, optionally, index) data: + +```c++ +size_t index_count = face_count * 3; +size_t unindexed_vertex_count = face_count * 3; +std::vector remap(index_count); // allocate temporary memory for the remap table +size_t vertex_count = meshopt_generateVertexRemap(&remap[0], NULL, index_count, &unindexed_vertices[0], unindexed_vertex_count, sizeof(Vertex)); ``` +Note that in this case we only have an unindexed vertex buffer; when input mesh has an index buffer, it will need to be passed to `meshopt_generateVertexRemap` instead of `NULL`, along with the correct source vertex count. In either case, the remap table is generated based on binary equivalence of the input vertices, so the resulting mesh will render the same way. Binary equivalence considers all input bytes, including padding which should be zero-initialized if the vertex structure has gaps. -## Depend on the modules in use. -``` -deps = ["//third_party/meshoptimizer:meshoptimizer"] +After generating the remap table, you can allocate space for the target vertex buffer (`vertex_count` elements) and index buffer (`index_count` elements) and generate them: + +```c++ +meshopt_remapIndexBuffer(indices, NULL, index_count, &remap[0]); +meshopt_remapVertexBuffer(vertices, &unindexed_vertices[0], unindexed_vertex_count, sizeof(Vertex), &remap[0]); ``` +You can then further optimize the resulting buffers by calling the other functions on them in-place. -## Preprocessing -``` -./build/prebuilts_download.sh +`meshopt_generateVertexRemap` uses binary equivalence of vertex data, which is generally a reasonable default; however, in some cases some attributes may have floating point drift causing extra vertices to be generated. For such cases, it may be necessary to quantize some attributes (most importantly, normals and tangents) before generating the remap, or use a custom weld algorithm that supports per-attribute tolerance instead. + +## Vertex cache optimization + +When the GPU renders the mesh, it has to run the vertex shader for each vertex; usually GPUs have a built-in fixed size cache that stores the transformed vertices (the result of running the vertex shader), and uses this cache to reduce the number of vertex shader invocations. This cache is usually small, 16-32 vertices, and can have different replacement policies; to use this cache efficiently, you have to reorder your triangles to maximize the locality of reused vertex references like so: + +```c++ +meshopt_optimizeVertexCache(indices, indices, index_count, vertex_count); ``` +## Overdraw optimization -## Compilation -``` -./build.sh --product-name rk3568 --ccache +After transforming the vertices, GPU sends the triangles for rasterization which results in generating pixels that are usually first ran through the depth test, and pixels that pass it get the pixel shader executed to generate the final color. As pixel shaders get more expensive, it becomes more and more important to reduce overdraw. While in general improving overdraw requires view-dependent operations, this library provides an algorithm to reorder triangles to minimize the overdraw from all directions, which you should run after vertex cache optimization like this: + +```c++ +meshopt_optimizeOverdraw(indices, indices, index_count, &vertices[0].x, vertex_count, sizeof(Vertex), 1.05f); ``` +The overdraw optimizer needs to read vertex positions as a float3 from the vertex; the code snippet above assumes that the vertex stores position as `float x, y, z`. -## Relevant References -https://github.com/zeux/meshoptimizer +When performing the overdraw optimization you have to specify a floating-point threshold parameter. The algorithm tries to maintain a balance between vertex cache efficiency and overdraw; the threshold determines how much the algorithm can compromise the vertex cache hit ratio, with 1.05 meaning that the resulting ratio should be at most 5% worse than before the optimization. +## Vertex fetch optimization + +After the final triangle order has been established, we still can optimize the vertex buffer for memory efficiency. Before running the vertex shader GPU has to fetch the vertex attributes from the vertex buffer; the fetch is usually backed by a memory cache, and as such optimizing the data for the locality of memory access is important. You can do this by running this code: + +```c++ +meshopt_optimizeVertexFetch(vertices, indices, index_count, vertices, vertex_count, sizeof(Vertex)); +``` + +This will reorder the vertices in the vertex buffer to try to improve the locality of reference, and rewrite the indices in place to match; if the vertex data is stored using multiple streams, you should use `meshopt_optimizeVertexFetchRemap` instead. This optimization has to be performed on the final index buffer since the optimal vertex order depends on the triangle order. + +Note that the algorithm does not try to model cache replacement precisely and instead just orders vertices in the order of use, which generally produces results that are close to optimal. + +## Vertex quantization + +To optimize memory bandwidth when fetching the vertex data even further, and to reduce the amount of memory required to store the mesh, it is often beneficial to quantize the vertex attributes to smaller types. While this optimization can technically run at any part of the pipeline (and sometimes doing quantization as the first step can improve indexing by merging almost identical vertices), it generally is easier to run this after all other optimizations since some of them require access to float3 positions. + +Quantization is usually domain specific; it's common to quantize normals using 3 8-bit integers but you can use higher-precision quantization (for example using 10 bits per component in a 10_10_10_2 format), or a different encoding to use just 2 components. For positions and texture coordinate data the two most common storage formats are half precision floats, and 16-bit normalized integers that encode the position relative to the AABB of the mesh or the UV bounding rectangle. + +The number of possible combinations here is very large but this library does provide the building blocks, specifically functions to quantize floating point values to normalized integers, as well as half-precision floats. For example, here's how you can quantize a normal: + +```c++ +unsigned int normal = + (meshopt_quantizeUnorm(v.nx, 10) << 20) | + (meshopt_quantizeUnorm(v.ny, 10) << 10) | + meshopt_quantizeUnorm(v.nz, 10); +``` + +and here's how you can quantize a position: + +```c++ +unsigned short px = meshopt_quantizeHalf(v.x); +unsigned short py = meshopt_quantizeHalf(v.y); +unsigned short pz = meshopt_quantizeHalf(v.z); +``` + +Since quantized vertex attributes often need to remain in their compact representations for efficient transfer and storage, they are usually dequantized during vertex processing by configuring the GPU vertex input correctly to expect normalized integers or half precision floats, which often needs no or minimal changes to the shader code. When CPU dequantization is required instead, `meshopt_dequantizeHalf` can be used to convert half precision values back to single precision; for normalized integer formats, the dequantization just requires dividing by 2^N-1 for unorm and 2^(N-1)-1 for snorm variants, for example manually reversing `meshopt_quantizeUnorm(v, 10)` can be done by dividing by 1023. + +## Shadow indexing + +Many rendering pipelines require meshes to be rendered to depth-only targets, such as shadow maps or during a depth pre-pass, in addition to color/G-buffer targets. While using the same geometry data for both cases is possible, reducing the number of unique vertices for depth-only rendering can be beneficial, especially when the source geometry has many attribute seams due to faceted shading or lightmap texture seams. + +To achieve this, this library provides the `meshopt_generateShadowIndexBuffer` algorithm, which generates a second (shadow) index buffer that can be used with the original vertex data: + +```c++ +std::vector shadow_indices(index_count); +// note: this assumes Vertex starts with float3 positions and should be adjusted accordingly for quantized positions +meshopt_generateShadowIndexBuffer(&shadow_indices[0], indices, index_count, &vertices[0].x, vertex_count, sizeof(float) * 3, sizeof(Vertex)); +``` + +Because the vertex data is shared, shadow indexing should be done after other optimizations of the vertex/index data. However, it's possible (and recommended) to optimize the resulting shadow index buffer for vertex cache: + +```c++ +meshopt_optimizeVertexCache(&shadow_indices[0], &shadow_indices[0], index_count, vertex_count); +``` + +In some cases, it may be beneficial to split the vertex positions into a separate buffer to maximize efficiency for depth-only rendering. Note that the example above assumes only positions are relevant for shadow rendering, but more complex materials may require adding texture coordinates (for alpha testing) or skinning data to the vertex portion used as a key. `meshopt_generateShadowIndexBufferMulti` can be useful for these cases if the relevant data is not contiguous. + +## Vertex/index buffer compression + +In case storage size or transmission bandwidth is of importance, you might want to additionally compress vertex and index data. While several mesh compression libraries, like Google Draco, are available, they typically are designed to maximize the compression ratio at the cost of disturbing the vertex/index order (which makes the meshes inefficient to render on GPU) or decompression performance. They also frequently don't support custom game-ready quantized vertex formats and thus require to re-quantize the data after loading it, introducing extra quantization errors and making decoding slower. + +Alternatively you can use general purpose compression libraries like zstd or Oodle to compress vertex/index data - however these compressors aren't designed to exploit redundancies in vertex/index data and as such compression rates can be unsatisfactory. + +To that end, this library provides algorithms to "encode" vertex and index data. The result of the encoding is generally significantly smaller than initial data, and remains compressible with general purpose compressors - so you can either store encoded data directly (for modest compression ratios and maximum decoding performance), or further compress it with zstd/Oodle to maximize compression ratio. + +> Note: this compression scheme is available as a glTF extension [EXT_meshopt_compression](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Vendor/EXT_meshopt_compression/README.md). + +To encode, you need to allocate target buffers (preferably using the worst case bound) and call encoding functions: + +```c++ +std::vector vbuf(meshopt_encodeVertexBufferBound(vertex_count, sizeof(Vertex))); +vbuf.resize(meshopt_encodeVertexBuffer(&vbuf[0], vbuf.size(), vertices, vertex_count, sizeof(Vertex))); + +std::vector ibuf(meshopt_encodeIndexBufferBound(index_count, vertex_count)); +ibuf.resize(meshopt_encodeIndexBuffer(&ibuf[0], ibuf.size(), indices, index_count)); +``` + +You can then either serialize `vbuf`/`ibuf` as is, or compress them further. To decode the data at runtime, call decoding functions: + +```c++ +int resvb = meshopt_decodeVertexBuffer(vertices, vertex_count, sizeof(Vertex), &vbuf[0], vbuf.size()); +int resib = meshopt_decodeIndexBuffer(indices, index_count, &ibuf[0], ibuf.size()); +assert(resvb == 0 && resib == 0); +``` + +Note that vertex encoding assumes that vertex buffer was optimized for vertex fetch, and that vertices are quantized; index encoding assumes that the vertex/index buffers were optimized for vertex cache and vertex fetch. Feeding unoptimized data into the encoders will produce poor compression ratios. Both codecs are lossless - the only lossy step is quantization that happens before encoding. + +Decoding functions are heavily optimized and can directly target write-combined memory; you can expect both decoders to run at 1-3 GB/s on modern desktop CPUs. Compression ratios depend on the data; vertex data compression ratio is typically around 2-4x (compared to already quantized data), index data compression ratio is around 5-6x (compared to raw 16-bit index data). General purpose lossless compressors can further improve on these results. + +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. + +The following guarantees on data compatibility are provided for point releases (*no* guarantees are given for development branch): + +- Data encoded with older versions of the library can always be decoded with newer versions; +- Data encoded with newer versions of the library can be decoded with older versions, provided that encoding versions are set correctly; if binary stability of encoded data is important, use `meshopt_encodeVertexVersion` and `meshopt_encodeIndexVersion` to 'pin' the data versions. + +Due to a very high decoding performance and compatibility with general purpose lossless compressors, the compression is a good fit for the use on the web. To that end, meshoptimizer provides both vertex and index decoders compiled into WebAssembly and wrapped into a module with JavaScript-friendly interface, `js/meshopt_decoder.js`, that you can use to decode meshes that were encoded offline: + +```js +// ready is a Promise that is resolved when (asynchronous) WebAssembly compilation finishes +await MeshoptDecoder.ready; + +// decode from *Data (Uint8Array) into *Buffer (Uint8Array) +MeshoptDecoder.decodeVertexBuffer(vertexBuffer, vertexCount, vertexSize, vertexData); +MeshoptDecoder.decodeIndexBuffer(indexBuffer, indexCount, indexSize, indexData); +``` + +[Usage example](https://meshoptimizer.org/demo/) is available, with source in `demo/index.html`; this example uses .GLB files encoded using `gltfpack`. + +## Point cloud compression + +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. + +To compress point clouds efficiently, it's recommended to first preprocess the points by sorting them using the spatial sort algorithm: + +```c++ +std::vector remap(point_count); +meshopt_spatialSortRemap(&remap[0], positions, point_count, sizeof(vec3)); + +// for each attribute stream +meshopt_remapVertexBuffer(positions, positions, point_count, sizeof(vec3), &remap[0]); +``` + +After this the resulting arrays should be quantized (e.g. using 16-bit fixed point numbers for positions and 8-bit color components), and the result can be compressed using `meshopt_encodeVertexBuffer` as described in the previous section. To decompress, `meshopt_decodeVertexBuffer` will recover the quantized data that can be used directly or converted back to original floating-point data. The compression ratio depends on the nature of source data, for colored points it's typical to get 35-40 bits per point as a result. + +## Advanced compression + +Both vertex and index codecs are designed to be used in a three-stage pipeline: + +- Preparation (quantization, filtering, ordering) +- Encoding (`meshopt_encodeVertexBuffer`/`meshopt_encodeIndexBuffer`) +- Optional compression (LZ4/zlib/zstd/Oodle) + +The preparation stage is crucial for achieving good compression ratios; this section will cover some techniques that can be used to improve the results. + +The index codec targets 1 byte per triangle as a best case; on real-world data, it's typical to achieve 1-1.2 bytes per triangle. To reach this, the data needs to be optimized for vertex cache and vertex fetch. Optimizations that do not disrupt triangle locality (such as overdraw) are safe to use in between. +To reduce the data size further, it's possible to use `meshopt_optimizeVertexCacheStrip` instead of `meshopt_optimizeVertexCache` when optimizing for vertex cache. This trades off some efficiency in vertex transform for smaller vertex and index data. + +When referenced vertex indices are not sequential, the index codec will use around 2 bytes per index. This can happen when the referenced vertices are a sparse subset of the vertex buffer, such as when encoding LODs. General-purpose compression can be especially helpful in this case. + +The vertex codec tries to take advantage of the inherent locality of sequential vertices and identify bit patterns that repeat in consecutive vertices. Typically, vertex cache + vertex fetch provides a reasonably local vertex traversal order; without an index buffer, it is recommended to sort vertices spatially to improve the compression ratio. +It is crucial to correctly specify the stride when encoding vertex data; however, it does not matter whether the vertices are interleaved or deinterleaved, as the codecs perform full byte deinterleaving internally. + +For optimal compression results, the values must be quantized to small integers. It can be valuable to use bit counts that are not multiples of 8. For example, instead of using 16 bits to represent texture coordinates, use 12-bit integers and divide by 4095 in the shader. Alternatively, using half-precision floats can often achieve good results. +For single-precision floating-point data, it's recommended to use `meshopt_quantizeFloat` to remove entropy from the lower bits of the mantissa. Due to current limitations of the codec, the bit count needs to be 15 (23-8) for good results (7 can be used for more extreme compression). +For normal or tangent vectors, using octahedral encoding is recommended over three components as it reduces redundancy. Similarly to other quantized values, consider using 10-12 bits per component instead of 16. + +> Note: vertex codec v0 is limited to taking advantage of redundancy in high bits of each byte. Because of this, packing multiple 10-bit values into 32 bits will reduce compression ratio, and when storing a 12-bit value in 16 bits, high bits should be zeroed out. This limitation may be lifted in future versions of the codec. + +To further leverage the inherent structure of some data, the preparation stage can use filters that encode and decode the data in a lossy manner. This is similar to quantization but can be used without having to change the shader code. After decoding, the filter transformation needs to be reversed. This library provides three filters: + +- Octahedral filter (`meshopt_encodeFilterOct`/`meshopt_decodeFilterOct`) encodes quantized (snorm) normal or tangent vectors using octahedral encoding. Any number of bits <= 16 can be used with 4 bytes or 8 bytes per vector. +- Quaternion filter (`meshopt_encodeFilterQuat`/`meshopt_decodeFilterQuat`) encodes quantized (snorm) quaternion vectors; this can be used to encode rotations or tangent frames. Any number of bits between 4 and 16 can be used with 8 bytes per vector. +- Exponential filter (`meshopt_encodeFilterExp`/`meshopt_decodeFilterExp`) encodes single-precision floating-point vectors; this can be used to encode arbitrary floating-point data more efficiently. In addition to an arbitrary bit count (<= 24), the filter takes a "mode" parameter that allows specifying how the exponent sharing is performed to trade off compression ratio and quality: + + - `meshopt_EncodeExpSeparate` does not share exponents and results in the largest output + - `meshopt_EncodeExpSharedVector` shares exponents between different components of the same vector + - `meshopt_EncodeExpSharedComponent` shares exponents between the same component in different vectors + - `meshopt_EncodeExpClamped` does not share exponents but clamps the exponent range to reduce exponent entropy + +Note that all filters are lossy and require the data to be deinterleaved with one attribute per stream; this faciliates efficient SIMD implementation of filter decoders, allowing the overall decompression speed to be close to that of the raw codec. + +## Triangle strip conversion + +On most hardware, indexed triangle lists are the most efficient way to drive the GPU. However, in some cases triangle strips might prove beneficial: + +- On some older GPUs, triangle strips may be a bit more efficient to render +- On extremely memory constrained systems, index buffers for triangle strips could save a bit of memory + +This library provides an algorithm for converting a vertex cache optimized triangle list to a triangle strip: + +```c++ +std::vector strip(meshopt_stripifyBound(index_count)); +unsigned int restart_index = ~0u; +size_t strip_size = meshopt_stripify(&strip[0], indices, index_count, vertex_count, restart_index); +``` + +Typically you should expect triangle strips to have ~50-60% of indices compared to triangle lists (~1.5-1.8 indices per triangle) and have ~5% worse ACMR. +Note that triangle strips can be stitched with or without restart index support. Using restart indices can result in ~10% smaller index buffers, but on some GPUs restart indices may result in decreased performance. + +To reduce the triangle strip size further, it's recommended to use `meshopt_optimizeVertexCacheStrip` instead of `meshopt_optimizeVertexCache` when optimizing for vertex cache. This trades off some efficiency in vertex transform for smaller index buffers. + +## Deinterleaved geometry + +All of the examples above assume that geometry is represented as a single vertex buffer and a single index buffer. This requires storing all vertex attributes - position, normal, texture coordinate, skinning weights etc. - in a single contiguous struct. However, in some cases using multiple vertex streams may be preferable. In particular, if some passes require only positional data - such as depth pre-pass or shadow map - then it may be beneficial to split it from the rest of the vertex attributes to make sure the bandwidth use during these passes is optimal. On some mobile GPUs a position-only attribute stream also improves efficiency of tiling algorithms. + +Most of the functions in this library either only need the index buffer (such as vertex cache optimization) or only need positional information (such as overdraw optimization). However, several tasks require knowledge about all vertex attributes. + +For indexing, `meshopt_generateVertexRemap` assumes that there's just one vertex stream; when multiple vertex streams are used, it's necessary to use `meshopt_generateVertexRemapMulti` as follows: + +```c++ +meshopt_Stream streams[] = { + {&unindexed_pos[0], sizeof(float) * 3, sizeof(float) * 3}, + {&unindexed_nrm[0], sizeof(float) * 3, sizeof(float) * 3}, + {&unindexed_uv[0], sizeof(float) * 2, sizeof(float) * 2}, +}; + +std::vector remap(index_count); +size_t vertex_count = meshopt_generateVertexRemapMulti(&remap[0], NULL, index_count, index_count, streams, sizeof(streams) / sizeof(streams[0])); +``` + +After this `meshopt_remapVertexBuffer` needs to be called once for each vertex stream to produce the correctly reindexed stream. For shadow indexing, similarly `meshopt_generateShadowIndexBufferMulti` is available as a replacement. + +Instead of calling `meshopt_optimizeVertexFetch` for reordering vertices in a single vertex buffer for efficiency, calling `meshopt_optimizeVertexFetchRemap` and then calling `meshopt_remapVertexBuffer` for each stream again is recommended. + +Finally, when compressing vertex data, `meshopt_encodeVertexBuffer` should be used on each vertex stream separately - this allows the encoder to best utilize corellation between attribute values for different vertices. + +## Simplification + +All algorithms presented so far don't affect visual appearance at all, with the exception of quantization that has minimal controlled impact. However, fundamentally the most effective way at reducing the rendering or transmission cost of a mesh is to make the mesh simpler. + +This library provides two simplification algorithms that reduce the number of triangles in the mesh. Given a vertex and an index buffer, they generate a second index buffer that uses existing vertices in the vertex buffer. This index buffer can be used directly for rendering with the original vertex buffer (preferably after vertex cache optimization), or a new compact vertex/index buffer can be generated using `meshopt_optimizeVertexFetch` that uses the optimal number and order of vertices. + +The first simplification algorithm, `meshopt_simplify`, follows the topology of the original mesh in an attempt to preserve attribute seams, borders and overall appearance. For meshes with inconsistent topology or many seams, such as faceted meshes, it can result in simplifier getting "stuck" and not being able to simplify the mesh fully. Therefore it's critical that identical vertices are "welded" together, that is, the input vertex buffer does not contain duplicates. Additionally, it may be worthwhile to weld the vertices without taking into account vertex attributes that aren't critical and can be rebuilt later. + +```c++ +float threshold = 0.2f; +size_t target_index_count = size_t(index_count * threshold); +float target_error = 1e-2f; + +std::vector lod(index_count); +float lod_error = 0.f; +lod.resize(meshopt_simplify(&lod[0], indices, index_count, &vertices[0].x, vertex_count, sizeof(Vertex), + target_index_count, target_error, /* options= */ 0, &lod_error)); +``` + +Target error is an approximate measure of the deviation from the original mesh using distance normalized to `[0..1]` range (e.g. `1e-2f` means that simplifier will try to maintain the error to be below 1% of the mesh extents). Note that the simplifier attempts to produce the requested number of indices at minimal error, but because of topological restrictions and error limit it is not guaranteed to reach the target index count and can stop earlier. + +To disable the error limit, `target_error` can be set to `FLT_MAX`. This makes it more likely that the simplifier will reach the target index count, but it may produce a mesh that looks significantly different from the original, so using the resulting error to control viewing distance would be required. Conversely, setting `target_index_count` to 0 will simplify the input mesh as much as possible within the specified error limit; this can be useful for generating LODs that should look good at a given viewing distance. + +The second simplification algorithm, `meshopt_simplifySloppy`, doesn't follow the topology of the original mesh. This means that it doesn't preserve attribute seams or borders, but it can collapse internal details that are too small to matter better because it can merge mesh features that are topologically disjoint but spatially close. + +```c++ +float threshold = 0.2f; +size_t target_index_count = size_t(index_count * threshold); +float target_error = 1e-1f; + +std::vector lod(index_count); +float lod_error = 0.f; +lod.resize(meshopt_simplifySloppy(&lod[0], indices, index_count, &vertices[0].x, vertex_count, sizeof(Vertex), + target_index_count, target_error, &lod_error)); +``` + +This algorithm will not stop early due to topology restrictions but can still do so if target index count can't be reached without introducing an error larger than target. It is 5-6x faster than `meshopt_simplify` when simplification ratio is large, and is able to reach ~20M triangles/sec on a desktop CPU (`meshopt_simplify` works at ~3M triangles/sec). + +Both algorithms can also return the resulting normalized deviation that can be used to choose the correct level of detail based on screen size or solid angle; the error can be converted to object space by multiplying by the scaling factor returned by `meshopt_simplifyScale`. For example, given a mesh with a precomputed LOD and a prescaled error, the screen-space normalized error can be computed and used for LOD selection: + +```c++ +// lod_factor can be 1 or can be adjusted for more or less aggressive LOD selection +float d = max(0, distance(camera_position, mesh_center) - mesh_radius); +float e = d * (tan(camera_fovy / 2) * 2 / screen_height); // 1px in mesh space +bool lod_ok = e * lod_factor >= lod_error; +``` + +When a sequence of LOD meshes is generated that all use the original vertex buffer, care must be taken to order vertices optimally to not penalize mobile GPU architectures that are only capable of transforming a sequential vertex buffer range. It's recommended in this case to first optimize each LOD for vertex cache, then assemble all LODs in one large index buffer starting from the coarsest LOD (the one with fewest triangles), and call `meshopt_optimizeVertexFetch` on the final large index buffer. This will make sure that coarser LODs require a smaller vertex range and are efficient wrt vertex fetch and transform. + +## Advanced simplification + +The main simplification algorithm, `meshopt_simplify`, exposes additional options and functions that can be used to control the simplification process in more detail. + +For basic customization, a number of options can be passed via `options` bitmask that adjust the behavior of the simplifier: + +- `meshopt_SimplifyLockBorder` restricts the simplifier from collapsing edges that are on the border of the mesh. This can be useful for simplifying mesh subsets independently, so that the LODs can be combined without introducing cracks. +- `meshopt_SimplifyErrorAbsolute` changes the error metric from relative to absolute both for the input error limit as well as for the resulting error. This can be used instead of `meshopt_simplifyScale`. +- `meshopt_SimplifySparse` improves simplification performance assuming input indices are a sparse subset of the mesh. This can be useful when simplifying small mesh subsets independently, and is intended to be used for meshlet simplification. For consistency, it is recommended to use absolute errors when sparse simplification is desired, as this flag changes the meaning of the relative errors. +- `meshopt_SimplifyPrune` allows the simplifier to remove isolated components regardless of the topological restrictions inside the component. This is generally recommended for full-mesh simplification as it can improve quality and reduce triangle count; note that with this option, triangles connected to locked vertices may be removed as part of their component. + +While `meshopt_simplify` is aware of attribute discontinuities by default (and infers them through the supplied index buffer) and tries to preserve them, it can be useful to provide information about attribute values. This allows the simplifier to take attribute error into account which can improve shading (by using vertex normals), texture deformation (by using texture coordinates), and may be necessary to preserve vertex colors when textures are not used in the first place. This can be done by using a variant of the simplification function that takes attribute values and weight factors, `meshopt_simplifyWithAttributes`: + +```c++ +const float nrm_weight = 0.5f; +const float attr_weights[3] = {nrm_weight, nrm_weight, nrm_weight}; + +std::vector lod(index_count); +float lod_error = 0.f; +lod.resize(meshopt_simplifyWithAttributes(&lod[0], indices, index_count, &vertices[0].x, vertex_count, sizeof(Vertex), + &vertices[0].nx, sizeof(Vertex), attr_weights, 3, /* vertex_lock= */ NULL, + target_index_count, target_error, /* options= */ 0, &lod_error)); +``` + +The attributes are passed as a separate buffer (in the example above it's a subset of the same vertex buffer) and should be stored as consecutive floats; attribute weights are used to control the importance of each attribute in the simplification process. For normalized attributes like normals and vertex colors, a weight around 1.0 is usually appropriate; internally, a change of `1/weight` in attribute value over a distance `d` is approximately equivalent to a change of `d` in position. Using higher weights may be appropriate to preserve attribute quality at the cost of position quality. If the attribute has a different scale (e.g. unnormalized vertex colors in [0..255] range), the weight should be divided by the scaling factor (1/255 in this example). + +Both the target error and the resulting error combine positional error and attribute error, so the error can be used to control the LOD while taking attribute quality into account, assuming carefully chosen weights. + +When using `meshopt_simplifyWithAttributes`, it is also possible to lock certain vertices by providing a `vertex_lock` array that contains a boolean value for each vertex in the mesh. This can be useful to preserve certain vertices, such as the boundary of the mesh, with more control than `meshopt_SimplifyLockBorder` option provides. + +Simplification currently assumes that the input mesh is using the same material for all triangles. If the mesh uses multiple materials, it is possible to split the mesh into subsets based on the material and simplify each subset independently, using `meshopt_SimplifyLockBorder` or `vertex_lock` to preserve material boundaries; however, this limits the collapses and as a result may reduce the resulting quality. An alternative approach is to encode information about the material into the vertex buffer, ensuring that all three vertices referencing the same triangle have the same material ID; this may require duplicating vertices on the boundary between materials. After this, simplification can be performed as usual, and after simplification per-triangle material information can be computed from the vertex material IDs. There is no need to inform the simplifier of the value of the material ID: the implicit boundaries created by duplicating vertices with conflicting material IDs will be preserved automatically. + +## Point cloud simplification + +In addition to triangle mesh simplification, this library provides a function to simplify point clouds. The algorithm reduces the point cloud to a specified number of points while preserving the overall appearance, and can optionally take per-point colors into account: + +```c++ +const float color_weight = 1; +std::vector indices(target_count); +indices.resize(meshopt_simplifyPoints(&indices[0], &points[0].x, points.size(), sizeof(Point), + &points[0].r, sizeof(Point), color_weight, target_count)); +``` + +The resulting indices can be used to render the simplified point cloud; to reduce the memory footprint, the point cloud can be reindexed to create an array of points from the indices. + +## Mesh shading + +Modern GPUs are beginning to deviate from the traditional rasterization model. NVidia GPUs starting from Turing and AMD GPUs starting from RDNA2 provide a new programmable geometry pipeline that, instead of being built around index buffers and vertex shaders, is built around mesh shaders - a new shader type that allows to provide a batch of work to the rasterizer. + +Using mesh shaders in context of traditional mesh rendering provides an opportunity to use a variety of optimization techniques, starting from more efficient vertex reuse, using various forms of culling (e.g. cluster frustum or occlusion culling) and in-memory compression to maximize the utilization of GPU hardware. Beyond traditional rendering mesh shaders provide a richer programming model that can synthesize new geometry more efficiently than common alternatives such as geometry shaders. Mesh shading can be accessed via Vulkan or Direct3D 12 APIs; please refer to [Introduction to Turing Mesh Shaders](https://developer.nvidia.com/blog/introduction-turing-mesh-shaders/) and [Mesh Shaders and Amplification Shaders: Reinventing the Geometry Pipeline](https://devblogs.microsoft.com/directx/coming-to-directx-12-mesh-shaders-and-amplification-shaders-reinventing-the-geometry-pipeline/) for additional information. + +To use mesh shaders for conventional rendering efficiently, geometry needs to be converted into a series of meshlets; each meshlet represents a small subset of the original mesh and comes with a small set of vertices and a separate micro-index buffer that references vertices in the meshlet. This information can be directly fed to the rasterizer from the mesh shader. This library provides algorithms to create meshlet data for a mesh, and - assuming geometry is static - can compute bounding information that can be used to perform cluster culling, a technique that can reject a meshlet if it's invisible on screen. + +To generate meshlet data, this library provides `meshopt_buildMeshlets` algorithm, which tries to balance topological efficiency (by maximizing vertex reuse inside meshlets) with culling efficiency (by minimizing meshlet radius and triangle direction divergence) and produces GPU-friendly data. As an alternative (that can be useful for load-time processing), `meshopt_buildMeshletsScan` can create the meshlet data using a vertex cache-optimized index buffer as a starting point by greedily aggregating consecutive triangles until they go over the meshlet limits. `meshopt_buildMeshlets` is recommended for offline data processing even if cone culling is not used. + +```c++ +const size_t max_vertices = 64; +const size_t max_triangles = 124; +const float cone_weight = 0.0f; + +size_t max_meshlets = meshopt_buildMeshletsBound(indices.size(), max_vertices, max_triangles); +std::vector meshlets(max_meshlets); +std::vector meshlet_vertices(max_meshlets * max_vertices); +std::vector meshlet_triangles(max_meshlets * max_triangles * 3); + +size_t meshlet_count = meshopt_buildMeshlets(meshlets.data(), meshlet_vertices.data(), meshlet_triangles.data(), indices.data(), + indices.size(), &vertices[0].x, vertices.size(), sizeof(Vertex), max_vertices, max_triangles, cone_weight); +``` + +To generate the meshlet data, `max_vertices` and `max_triangles` need to be set within limits supported by the hardware; for NVidia the values of 64 and 124 are recommended (`max_triangles` must be divisible by 4 so 124 is the value closest to official NVidia's recommended 126). `cone_weight` should be left as 0 if cluster cone culling is not used, and set to a value between 0 and 1 to balance cone culling efficiency with other forms of culling like frustum or occlusion culling. + +Each resulting meshlet refers to a portion of `meshlet_vertices` and `meshlet_triangles` arrays; the arrays are overallocated for the worst case so it's recommended to trim them before saving them as an asset / uploading them to the GPU: + +```c++ +const meshopt_Meshlet& last = meshlets[meshlet_count - 1]; + +meshlet_vertices.resize(last.vertex_offset + last.vertex_count); +meshlet_triangles.resize(last.triangle_offset + ((last.triangle_count * 3 + 3) & ~3)); +meshlets.resize(meshlet_count); +``` + +However depending on the application other strategies of storing the data can be useful; for example, `meshlet_vertices` serves as indices into the original vertex buffer but it might be worthwhile to generate a mini vertex buffer for each meshlet to remove the extra indirection when accessing vertex data, or it might be desirable to compress vertex data as vertices in each meshlet are likely to be very spatially coherent. + +For optimal performance, it is recommended to further optimize each meshlet in isolation for better triangle and vertex locality by calling `meshopt_optimizeMeshlet` on vertex and index data like so: + +```c++ +meshopt_optimizeMeshlet(&meshlet_vertices[m.vertex_offset], &meshlet_triangles[m.triangle_offset], m.triangle_count, m.vertex_count); +``` + +Different applications will choose different strategies for rendering meshlets; on a GPU capable of mesh shading, meshlets can be rendered directly; for example, a basic GLSL shader for `VK_EXT_mesh_shader` extension could look like this (parts omitted for brevity): + +```glsl +layout(binding = 0) readonly buffer Meshlets { Meshlet meshlets[]; }; +layout(binding = 1) readonly buffer MeshletVertices { uint meshlet_vertices[]; }; +layout(binding = 2) readonly buffer MeshletTriangles { uint8_t meshlet_triangles[]; }; + +void main() { + Meshlet meshlet = meshlets[gl_WorkGroupID.x]; + SetMeshOutputsEXT(meshlet.vertex_count, meshlet.triangle_count); + + for (uint i = gl_LocalInvocationIndex; i < meshlet.vertex_count; i += gl_WorkGroupSize.x) { + uint index = meshlet_vertices[meshlet.vertex_offset + i]; + gl_MeshVerticesEXT[i].gl_Position = world_view_projection * vec4(vertex_positions[index], 1); + } + + for (uint i = gl_LocalInvocationIndex; i < meshlet.triangle_count; i += gl_WorkGroupSize.x) { + uint offset = meshlet.triangle_offset + i * 3; + gl_PrimitiveTriangleIndicesEXT[i] = uvec3( + meshlet_triangles[offset], meshlet_triangles[offset + 1], meshlet_triangles[offset + 2]); + } +} +``` + +After generating the meshlet data, it's also possible to generate extra data for each meshlet that can be saved and used at runtime to perform cluster culling, where each meshlet can be discarded if it's guaranteed to be invisible. To generate the data, `meshlet_computeMeshletBounds` can be used: + +```c++ +meshopt_Bounds bounds = meshopt_computeMeshletBounds(&meshlet_vertices[m.vertex_offset], &meshlet_triangles[m.triangle_offset], + m.triangle_count, &vertices[0].x, vertices.size(), sizeof(Vertex)); +``` + +The resulting `bounds` values can be used to perform frustum or occlusion culling using the bounding sphere, or cone culling using the cone axis/angle (which will reject the entire meshlet if all triangles are guaranteed to be back-facing from the camera point of view): + +```c++ +if (dot(normalize(cone_apex - camera_position), cone_axis) >= cone_cutoff) reject(); +``` + +## Efficiency analyzers + +While the only way to get precise performance data is to measure performance on the target GPU, it can be valuable to measure the impact of these optimization in a GPU-independent manner. To this end, the library provides analyzers for all three major optimization routines. For each optimization there is a corresponding analyze function, like `meshopt_analyzeOverdraw`, that returns a struct with statistics. + +`meshopt_analyzeVertexCache` returns vertex cache statistics. The common metric to use is ACMR - average cache miss ratio, which is the ratio of the total number of vertex invocations to the triangle count. The worst-case ACMR is 3 (GPU has to process 3 vertices for each triangle); on regular grids the optimal ACMR approaches 0.5. On real meshes it usually is in [0.5..1.5] range depending on the amount of vertex splits. One other useful metric is ATVR - average transformed vertex ratio - which represents the ratio of vertex shader invocations to the total vertices, and has the best case of 1.0 regardless of mesh topology (each vertex is transformed once). + +`meshopt_analyzeVertexFetch` returns vertex fetch statistics. The main metric it uses is overfetch - the ratio between the number of bytes read from the vertex buffer to the total number of bytes in the vertex buffer. Assuming non-redundant vertex buffers, the best case is 1.0 - each byte is fetched once. + +`meshopt_analyzeOverdraw` returns overdraw statistics. The main metric it uses is overdraw - the ratio between the number of pixel shader invocations to the total number of covered pixels, as measured from several different orthographic cameras. The best case for overdraw is 1.0 - each pixel is shaded once. + +Note that all analyzers use approximate models for the relevant GPU units, so the numbers you will get as the result are only a rough approximation of the actual performance. + +## Specialized processing + +In addition to the core optimization techniques, the library provides several specialized algorithms for specific rendering techniques and pipeline optimizations that require a particular configuration of vertex and index data. + +### Geometry shader adjacency + +For algorithms that use geometry shaders and require adjacency information, this library can generate an index buffer with adjacency data: + +```c++ +std::vector adjacency(indices.size() * 2); +meshopt_generateAdjacencyIndexBuffer(&adjacency[0], &indices[0], indices.size(), &vertices[0].x, vertices.size(), sizeof(Vertex)); +``` + +This creates an index buffer suitable for rendering with triangle-with-adjacency topology, providing 3 extra vertices per triangle that represent vertices opposite to each triangle's edge. This data can be used to compute silhouettes and perform other types of local geometric processing in geometry shaders. To render the mesh with adjacency data, the index buffer should be used with `D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST_ADJ`/`VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY`/`GL_TRIANGLES_ADJACENCY` topology. + +Note that the use of geometry shaders may have a performance impact on some GPUs; in some cases alternative implementation strategies may be more efficient. + +### Tessellation with displacement mapping + +For hardware tessellation with crack-free displacement mapping, this library can generate a special index buffer that supports PN-AEN tessellation: + +```c++ +std::vector tess(indices.size() * 4); +meshopt_generateTessellationIndexBuffer(&tess[0], &indices[0], indices.size(), &vertices[0].x, vertices.size(), sizeof(Vertex)); +``` + +This generates a 12-vertex patch for each input triangle with the following layout: + +- 0, 1, 2: original triangle vertices +- 3, 4: opposing edge for edge 0, 1 +- 5, 6: opposing edge for edge 1, 2 +- 7, 8: opposing edge for edge 2, 0 +- 9, 10, 11: dominant vertices for corners 0, 1, 2 + +This allows the use of hardware tessellation to implement PN-AEN and/or displacement mapping without cracks along UV seams or normal discontinuities. To render the mesh, the index buffer should be used with `D3D_PRIMITIVE_TOPOLOGY_12_CONTROL_POINT_PATCHLIST`/`VK_PRIMITIVE_TOPOLOGY_PATCH_LIST` (`patchControlPoints=12`) topology. For more details please refer to the following papers: [Crack-Free Point-Normal Triangles using Adjacent Edge Normals](https://developer.download.nvidia.com/whitepapers/2010/PN-AEN-Triangles-Whitepaper.pdf), [Tessellation on Any Budget](https://www.nvidia.com/content/pdf/gdc2011/john_mcdonald.pdf) and [My Tessellation Has Cracks!](https://developer.download.nvidia.com/assets/gamedev/files/gdc12/GDC12_DUDASH_MyTessellationHasCracks.pdf). + +### Visibility buffers + +To render geometry into visibility buffers, access to primitive index in fragment shader is required. While it is possible to use `SV_PrimitiveID`/`gl_PrimitiveID` in the fragment shader, this can result in suboptimal performance on some GPUs (notably, AMD RDNA1 and all NVidia GPUs), and may not be supported on mobile or console hardware. Using mesh shaders to generate primitive IDs is efficient but requires hardware support that is not universally available. To work around these limitations, this library provides a way to generate a special index buffer that uses provoking vertex to encode primitive IDs: + +```c++ +std::vector provoke(indices.size()); +std::vector reorder(vertices.size() + indices.size() / 3); +reorder.resize(meshopt_generateProvokingIndexBuffer(&provoke[0], &reorder[0], &indices[0], indices.size(), vertices.size())); +``` + +This generates a special index buffer along with a reorder table that satisfies two constraints: + +- `provoke[3 * tri] == tri` +- `reorder[provoke[x]]` refers to the original triangle vertices + +To render the mesh with provoking vertex data, the application should use `provoke` as an index buffer and a vertex shader that passes vertex index (`SV_VertexID`/`gl_VertexIndex`) via a `flat`/`nointerpolation` attribute to the fragment shader as a primitive index, and loads vertex data manually by computing the real vertex index based on `reorder` table (`reorder[gl_VertexIndex]`). For more details please refer to [Variable Rate Shading with Visibility Buffer Rendering](https://advances.realtimerendering.com/s2024/content/Hable/Advances_SIGGRAPH_2024_VisibilityVRS-SIGGRAPH_Advances_2024.pptx); naturally, this technique does not require VRS. + +Note: This assumes the provoking vertex is the first vertex of a triangle, which is true for all graphics APIs except OpenGL/WebGL. For OpenGL/WebGL, you may need to rotate each triangle (abc -> bca) in the resulting index buffer, or use the `glProvokingVertex` function (OpenGL 3.2+) or `WEBGL_provoking_vertex` extension (WebGL2) to change the provoking vertex convention. For WebGL2, this is highly recommended to avoid a variety of emulation slowdowns that happen by default if `flat` attributes are used, such as an implicit use of geometry shaders. + +## Memory management + +Many algorithms allocate temporary memory to store intermediate results or accelerate processing. The amount of memory allocated is a function of various input parameters such as vertex count and index count. By default memory is allocated using `operator new` and `operator delete`; if these operators are overloaded by the application, the overloads will be used instead. Alternatively it's possible to specify custom allocation/deallocation functions using `meshopt_setAllocator`, e.g. + +```c++ +meshopt_setAllocator(malloc, free); +``` + +> Note that the library expects the allocation function to either throw in case of out-of-memory (in which case the exception will propagate to the caller) or abort, so technically the use of `malloc` above isn't safe. If you want to handle out-of-memory errors without using C++ exceptions, you can use `setjmp`/`longjmp` instead. + +Vertex and index decoders (`meshopt_decodeVertexBuffer`, `meshopt_decodeIndexBuffer`, `meshopt_decodeIndexSequence`) do not allocate memory and work completely within the buffer space provided via arguments. + +All functions have bounded stack usage that does not exceed 32 KB for any algorithms. ## License -This project is subject to the license described in LICENCE. -## Related warehouse -meshoptimizer - -##Usage in OpenHarmony -###Object oriented -Developers of system applications and 3D graphics engines. -###Guidance and reference -``` -deps = ["//third_party/meshoptimizer:meshoptimizer"] -``` -Introduce this third-party library. -``` -include "meshoptimizer.h" -meshopt_decodeVertexBuffer(); -meshopt_decodeFilterOct(); -meshopt_decodeFilterQuat(); -meshopt_decodeFilterExp(); -meshopt_decodeIndexBuffer(); -``` -Introduce the meshoptimizer. h header file and use the corresponding functions. For specific instructions on the functions, please refer to: https://github.com/zeux/meshoptimizer \ No newline at end of file +This library is available to anybody free of charge, under the terms of MIT License (see LICENSE.md). diff --git a/README_en.md b/README_en.md new file mode 100644 index 00000000..3bf2a52c --- /dev/null +++ b/README_en.md @@ -0,0 +1,76 @@ +# meshoptimizer +Mesh optimizer is an efficient mesh optimization library that reduces the storage size of 3D mesh data and improves rendering performance through various techniques. It is mainly used for optimizing 3D models (such as pixel silver optimization, vertex caching optimization, over drawing optimization, vertex acquisition optimization, vertex quantization, shadow indexing), especially when dealing with a large number of polygon 3D models, which can significantly reduce rendering burden and improve running efficiency. + +## Use Cases of meshoptimizer +Gltf (Graphics Library Transmission Format) is a royalty free specification aimed at improving the efficiency of application transmission and loading of 3D scenes and models. +The introduction of meshoptimizer on OpenHarmony is mainly for decompressing the Ext_meshopt_compress extension format in gltf. + +## Directory Structure +``` +demo # demo file +extern # third-party source code +gltf # gltfpack is a tool that can automatically optimize gltf files, reducing download size and improving loading and rendering speed +js # this folder contains JavaScript/WebAssembly modules that can be used to access some functions of the meshoptimizer library +src # C++ source code directory of meshoptimizer containing decompression functions +CMakeLists.txt # build description file +LICENSE.md # license statement +README.MD # software description +``` + + +## The adaptation of meshoptimizer for OpenHarmony +OpenHarmony only compiles the source code and header files under the src/ directory in the meshoptimizer repository. meshoptimizer is introduced into the thirdparty directory of OpenHarmony and compiled using the method of dependent components in OpenHarmony. + +## Main Code Download +``` +repo init -u https://gitee.com/openharmony/manifest.git -b master --no-repo-verify +repo sync -c +repo forall -c 'git lfs pull' +``` + + +## Depend on the modules in use. +``` +deps = ["//third_party/meshoptimizer:meshoptimizer"] +``` + + +## Preprocessing +``` +./build/prebuilts_download.sh +``` + + +## Compilation +``` +./build.sh --product-name rk3568 --ccache +``` + + +## Relevant References +https://github.com/zeux/meshoptimizer + + +## License +This project is subject to the license described in LICENCE. + +## Related warehouse +meshoptimizer + +##Usage in OpenHarmony +###Object oriented +Developers of system applications and 3D graphics engines. +###Guidance and reference +``` +deps = ["//third_party/meshoptimizer:meshoptimizer"] +``` +Introduce this third-party library. +``` +include "meshoptimizer.h" +meshopt_decodeVertexBuffer(); +meshopt_decodeFilterOct(); +meshopt_decodeFilterQuat(); +meshopt_decodeFilterExp(); +meshopt_decodeIndexBuffer(); +``` +Introduce the meshoptimizer. h header file and use the corresponding functions. For specific instructions on the functions, please refer to: https://github.com/zeux/meshoptimizer \ No newline at end of file diff --git a/README_meshoptimizer.md b/README_meshoptimizer.md deleted file mode 100644 index 07a6791f..00000000 --- a/README_meshoptimizer.md +++ /dev/null @@ -1,558 +0,0 @@ -# 🐇 meshoptimizer [![Actions Status](https://github.com/zeux/meshoptimizer/workflows/build/badge.svg)](https://github.com/zeux/meshoptimizer/actions) [![codecov.io](https://codecov.io/github/zeux/meshoptimizer/coverage.svg?branch=master)](https://codecov.io/github/zeux/meshoptimizer?branch=master) ![MIT](https://img.shields.io/badge/license-MIT-blue.svg) [![GitHub](https://img.shields.io/badge/repo-github-green.svg)](https://github.com/zeux/meshoptimizer) - -## Purpose - -When a GPU renders triangle meshes, various stages of the GPU pipeline have to process vertex and index data. The efficiency of these stages depends on the data you feed to them; this library provides algorithms to help optimize meshes for these stages, as well as algorithms to reduce the mesh complexity and storage overhead. - -The library provides a C and C++ interface for all algorithms; you can use it from C/C++ or from other languages via FFI (such as P/Invoke). If you want to use this library from Rust, you should use [meshopt crate](https://crates.io/crates/meshopt). JavaScript interface for some algorithms is available through [meshoptimizer.js](https://www.npmjs.com/package/meshoptimizer). - -[gltfpack](./gltf/README.md), which is a tool that can automatically optimize glTF files, is developed and distributed alongside the library. - -## Installing - -meshoptimizer is hosted on GitHub; you can download the latest release using git: - -``` -git clone -b v0.22 https://github.com/zeux/meshoptimizer.git -``` - -Alternatively you can [download the .zip archive from GitHub](https://github.com/zeux/meshoptimizer/archive/v0.22.zip). - -The library is also available as a Linux package in several distributions ([ArchLinux](https://aur.archlinux.org/packages/meshoptimizer/), [Debian](https://packages.debian.org/libmeshoptimizer), [FreeBSD](https://www.freshports.org/misc/meshoptimizer/), [Nix](https://mynixos.com/nixpkgs/package/meshoptimizer), [Ubuntu](https://packages.ubuntu.com/libmeshoptimizer)), as well as a [Vcpkg port](https://github.com/microsoft/vcpkg/tree/master/ports/meshoptimizer) (see [installation instructions](https://learn.microsoft.com/en-us/vcpkg/get_started/get-started)) and a [Conan package](https://conan.io/center/recipes/meshoptimizer). - -[gltfpack](./gltf/README.md) is available as a pre-built binary on [Releases page](https://github.com/zeux/meshoptimizer/releases) or via [npm package](https://www.npmjs.com/package/gltfpack). Native binaries are recommended since they are more efficient and support texture compression. - -## Building - -meshoptimizer is distributed as a set of C++ source files. To include it into your project, you can use one of the two options: - -* Use CMake to build the library (either as a standalone project or as part of your project) -* Add source files to your project's build system - -The source files are organized in such a way that you don't need to change your build-system settings, and you only need to add the source files for the algorithms you use. They should build without warnings or special compilation options on all major compilers. - -## Pipeline - -When optimizing a mesh, you should typically feed it through a set of optimizations (the order is important!): - -1. Indexing -2. (optional, discussed last) Simplification -3. Vertex cache optimization -4. Overdraw optimization -5. Vertex fetch optimization -6. Vertex quantization -7. Shadow indexing -8. (optional) Vertex/index buffer compression - -## Indexing - -Most algorithms in this library assume that a mesh has a vertex buffer and an index buffer. For algorithms to work well and also for GPU to render your mesh efficiently, the vertex buffer has to have no redundant vertices; you can generate an index buffer from an unindexed vertex buffer or reindex an existing (potentially redundant) index buffer as follows: - -First, generate a remap table from your existing vertex (and, optionally, index) data: - -```c++ -size_t index_count = face_count * 3; -size_t unindexed_vertex_count = face_count * 3; -std::vector remap(index_count); // allocate temporary memory for the remap table -size_t vertex_count = meshopt_generateVertexRemap(&remap[0], NULL, index_count, &unindexed_vertices[0], unindexed_vertex_count, sizeof(Vertex)); -``` - -Note that in this case we only have an unindexed vertex buffer; when input mesh has an index buffer, it will need to be passed to `meshopt_generateVertexRemap` instead of `NULL`, along with the correct source vertex count. In either case, the remap table is generated based on binary equivalence of the input vertices, so the resulting mesh will render the same way. Binary equivalence considers all input bytes, including padding which should be zero-initialized if the vertex structure has gaps. - -After generating the remap table, you can allocate space for the target vertex buffer (`vertex_count` elements) and index buffer (`index_count` elements) and generate them: - -```c++ -meshopt_remapIndexBuffer(indices, NULL, index_count, &remap[0]); -meshopt_remapVertexBuffer(vertices, &unindexed_vertices[0], unindexed_vertex_count, sizeof(Vertex), &remap[0]); -``` - -You can then further optimize the resulting buffers by calling the other functions on them in-place. - -`meshopt_generateVertexRemap` uses binary equivalence of vertex data, which is generally a reasonable default; however, in some cases some attributes may have floating point drift causing extra vertices to be generated. For such cases, it may be necessary to quantize some attributes (most importantly, normals and tangents) before generating the remap, or use a custom weld algorithm that supports per-attribute tolerance instead. - -## Vertex cache optimization - -When the GPU renders the mesh, it has to run the vertex shader for each vertex; usually GPUs have a built-in fixed size cache that stores the transformed vertices (the result of running the vertex shader), and uses this cache to reduce the number of vertex shader invocations. This cache is usually small, 16-32 vertices, and can have different replacement policies; to use this cache efficiently, you have to reorder your triangles to maximize the locality of reused vertex references like so: - -```c++ -meshopt_optimizeVertexCache(indices, indices, index_count, vertex_count); -``` - -## Overdraw optimization - -After transforming the vertices, GPU sends the triangles for rasterization which results in generating pixels that are usually first ran through the depth test, and pixels that pass it get the pixel shader executed to generate the final color. As pixel shaders get more expensive, it becomes more and more important to reduce overdraw. While in general improving overdraw requires view-dependent operations, this library provides an algorithm to reorder triangles to minimize the overdraw from all directions, which you should run after vertex cache optimization like this: - -```c++ -meshopt_optimizeOverdraw(indices, indices, index_count, &vertices[0].x, vertex_count, sizeof(Vertex), 1.05f); -``` - -The overdraw optimizer needs to read vertex positions as a float3 from the vertex; the code snippet above assumes that the vertex stores position as `float x, y, z`. - -When performing the overdraw optimization you have to specify a floating-point threshold parameter. The algorithm tries to maintain a balance between vertex cache efficiency and overdraw; the threshold determines how much the algorithm can compromise the vertex cache hit ratio, with 1.05 meaning that the resulting ratio should be at most 5% worse than before the optimization. - -## Vertex fetch optimization - -After the final triangle order has been established, we still can optimize the vertex buffer for memory efficiency. Before running the vertex shader GPU has to fetch the vertex attributes from the vertex buffer; the fetch is usually backed by a memory cache, and as such optimizing the data for the locality of memory access is important. You can do this by running this code: - -```c++ -meshopt_optimizeVertexFetch(vertices, indices, index_count, vertices, vertex_count, sizeof(Vertex)); -``` - -This will reorder the vertices in the vertex buffer to try to improve the locality of reference, and rewrite the indices in place to match; if the vertex data is stored using multiple streams, you should use `meshopt_optimizeVertexFetchRemap` instead. This optimization has to be performed on the final index buffer since the optimal vertex order depends on the triangle order. - -Note that the algorithm does not try to model cache replacement precisely and instead just orders vertices in the order of use, which generally produces results that are close to optimal. - -## Vertex quantization - -To optimize memory bandwidth when fetching the vertex data even further, and to reduce the amount of memory required to store the mesh, it is often beneficial to quantize the vertex attributes to smaller types. While this optimization can technically run at any part of the pipeline (and sometimes doing quantization as the first step can improve indexing by merging almost identical vertices), it generally is easier to run this after all other optimizations since some of them require access to float3 positions. - -Quantization is usually domain specific; it's common to quantize normals using 3 8-bit integers but you can use higher-precision quantization (for example using 10 bits per component in a 10_10_10_2 format), or a different encoding to use just 2 components. For positions and texture coordinate data the two most common storage formats are half precision floats, and 16-bit normalized integers that encode the position relative to the AABB of the mesh or the UV bounding rectangle. - -The number of possible combinations here is very large but this library does provide the building blocks, specifically functions to quantize floating point values to normalized integers, as well as half-precision floats. For example, here's how you can quantize a normal: - -```c++ -unsigned int normal = - (meshopt_quantizeUnorm(v.nx, 10) << 20) | - (meshopt_quantizeUnorm(v.ny, 10) << 10) | - meshopt_quantizeUnorm(v.nz, 10); -``` - -and here's how you can quantize a position: - -```c++ -unsigned short px = meshopt_quantizeHalf(v.x); -unsigned short py = meshopt_quantizeHalf(v.y); -unsigned short pz = meshopt_quantizeHalf(v.z); -``` - -Since quantized vertex attributes often need to remain in their compact representations for efficient transfer and storage, they are usually dequantized during vertex processing by configuring the GPU vertex input correctly to expect normalized integers or half precision floats, which often needs no or minimal changes to the shader code. When CPU dequantization is required instead, `meshopt_dequantizeHalf` can be used to convert half precision values back to single precision; for normalized integer formats, the dequantization just requires dividing by 2^N-1 for unorm and 2^(N-1)-1 for snorm variants, for example manually reversing `meshopt_quantizeUnorm(v, 10)` can be done by dividing by 1023. - -## Shadow indexing - -Many rendering pipelines require meshes to be rendered to depth-only targets, such as shadow maps or during a depth pre-pass, in addition to color/G-buffer targets. While using the same geometry data for both cases is possible, reducing the number of unique vertices for depth-only rendering can be beneficial, especially when the source geometry has many attribute seams due to faceted shading or lightmap texture seams. - -To achieve this, this library provides the `meshopt_generateShadowIndexBuffer` algorithm, which generates a second (shadow) index buffer that can be used with the original vertex data: - -```c++ -std::vector shadow_indices(index_count); -// note: this assumes Vertex starts with float3 positions and should be adjusted accordingly for quantized positions -meshopt_generateShadowIndexBuffer(&shadow_indices[0], indices, index_count, &vertices[0].x, vertex_count, sizeof(float) * 3, sizeof(Vertex)); -``` - -Because the vertex data is shared, shadow indexing should be done after other optimizations of the vertex/index data. However, it's possible (and recommended) to optimize the resulting shadow index buffer for vertex cache: - -```c++ -meshopt_optimizeVertexCache(&shadow_indices[0], &shadow_indices[0], index_count, vertex_count); -``` - -In some cases, it may be beneficial to split the vertex positions into a separate buffer to maximize efficiency for depth-only rendering. Note that the example above assumes only positions are relevant for shadow rendering, but more complex materials may require adding texture coordinates (for alpha testing) or skinning data to the vertex portion used as a key. `meshopt_generateShadowIndexBufferMulti` can be useful for these cases if the relevant data is not contiguous. - -## Vertex/index buffer compression - -In case storage size or transmission bandwidth is of importance, you might want to additionally compress vertex and index data. While several mesh compression libraries, like Google Draco, are available, they typically are designed to maximize the compression ratio at the cost of disturbing the vertex/index order (which makes the meshes inefficient to render on GPU) or decompression performance. They also frequently don't support custom game-ready quantized vertex formats and thus require to re-quantize the data after loading it, introducing extra quantization errors and making decoding slower. - -Alternatively you can use general purpose compression libraries like zstd or Oodle to compress vertex/index data - however these compressors aren't designed to exploit redundancies in vertex/index data and as such compression rates can be unsatisfactory. - -To that end, this library provides algorithms to "encode" vertex and index data. The result of the encoding is generally significantly smaller than initial data, and remains compressible with general purpose compressors - so you can either store encoded data directly (for modest compression ratios and maximum decoding performance), or further compress it with zstd/Oodle to maximize compression ratio. - -> Note: this compression scheme is available as a glTF extension [EXT_meshopt_compression](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Vendor/EXT_meshopt_compression/README.md). - -To encode, you need to allocate target buffers (preferably using the worst case bound) and call encoding functions: - -```c++ -std::vector vbuf(meshopt_encodeVertexBufferBound(vertex_count, sizeof(Vertex))); -vbuf.resize(meshopt_encodeVertexBuffer(&vbuf[0], vbuf.size(), vertices, vertex_count, sizeof(Vertex))); - -std::vector ibuf(meshopt_encodeIndexBufferBound(index_count, vertex_count)); -ibuf.resize(meshopt_encodeIndexBuffer(&ibuf[0], ibuf.size(), indices, index_count)); -``` - -You can then either serialize `vbuf`/`ibuf` as is, or compress them further. To decode the data at runtime, call decoding functions: - -```c++ -int resvb = meshopt_decodeVertexBuffer(vertices, vertex_count, sizeof(Vertex), &vbuf[0], vbuf.size()); -int resib = meshopt_decodeIndexBuffer(indices, index_count, &ibuf[0], ibuf.size()); -assert(resvb == 0 && resib == 0); -``` - -Note that vertex encoding assumes that vertex buffer was optimized for vertex fetch, and that vertices are quantized; index encoding assumes that the vertex/index buffers were optimized for vertex cache and vertex fetch. Feeding unoptimized data into the encoders will produce poor compression ratios. Both codecs are lossless - the only lossy step is quantization that happens before encoding. - -Decoding functions are heavily optimized and can directly target write-combined memory; you can expect both decoders to run at 1-3 GB/s on modern desktop CPUs. Compression ratios depend on the data; vertex data compression ratio is typically around 2-4x (compared to already quantized data), index data compression ratio is around 5-6x (compared to raw 16-bit index data). General purpose lossless compressors can further improve on these results. - -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. - -The following guarantees on data compatibility are provided for point releases (*no* guarantees are given for development branch): - -- Data encoded with older versions of the library can always be decoded with newer versions; -- Data encoded with newer versions of the library can be decoded with older versions, provided that encoding versions are set correctly; if binary stability of encoded data is important, use `meshopt_encodeVertexVersion` and `meshopt_encodeIndexVersion` to 'pin' the data versions. - -Due to a very high decoding performance and compatibility with general purpose lossless compressors, the compression is a good fit for the use on the web. To that end, meshoptimizer provides both vertex and index decoders compiled into WebAssembly and wrapped into a module with JavaScript-friendly interface, `js/meshopt_decoder.js`, that you can use to decode meshes that were encoded offline: - -```js -// ready is a Promise that is resolved when (asynchronous) WebAssembly compilation finishes -await MeshoptDecoder.ready; - -// decode from *Data (Uint8Array) into *Buffer (Uint8Array) -MeshoptDecoder.decodeVertexBuffer(vertexBuffer, vertexCount, vertexSize, vertexData); -MeshoptDecoder.decodeIndexBuffer(indexBuffer, indexCount, indexSize, indexData); -``` - -[Usage example](https://meshoptimizer.org/demo/) is available, with source in `demo/index.html`; this example uses .GLB files encoded using `gltfpack`. - -## Point cloud compression - -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. - -To compress point clouds efficiently, it's recommended to first preprocess the points by sorting them using the spatial sort algorithm: - -```c++ -std::vector remap(point_count); -meshopt_spatialSortRemap(&remap[0], positions, point_count, sizeof(vec3)); - -// for each attribute stream -meshopt_remapVertexBuffer(positions, positions, point_count, sizeof(vec3), &remap[0]); -``` - -After this the resulting arrays should be quantized (e.g. using 16-bit fixed point numbers for positions and 8-bit color components), and the result can be compressed using `meshopt_encodeVertexBuffer` as described in the previous section. To decompress, `meshopt_decodeVertexBuffer` will recover the quantized data that can be used directly or converted back to original floating-point data. The compression ratio depends on the nature of source data, for colored points it's typical to get 35-40 bits per point as a result. - -## Advanced compression - -Both vertex and index codecs are designed to be used in a three-stage pipeline: - -- Preparation (quantization, filtering, ordering) -- Encoding (`meshopt_encodeVertexBuffer`/`meshopt_encodeIndexBuffer`) -- Optional compression (LZ4/zlib/zstd/Oodle) - -The preparation stage is crucial for achieving good compression ratios; this section will cover some techniques that can be used to improve the results. - -The index codec targets 1 byte per triangle as a best case; on real-world data, it's typical to achieve 1-1.2 bytes per triangle. To reach this, the data needs to be optimized for vertex cache and vertex fetch. Optimizations that do not disrupt triangle locality (such as overdraw) are safe to use in between. -To reduce the data size further, it's possible to use `meshopt_optimizeVertexCacheStrip` instead of `meshopt_optimizeVertexCache` when optimizing for vertex cache. This trades off some efficiency in vertex transform for smaller vertex and index data. - -When referenced vertex indices are not sequential, the index codec will use around 2 bytes per index. This can happen when the referenced vertices are a sparse subset of the vertex buffer, such as when encoding LODs. General-purpose compression can be especially helpful in this case. - -The vertex codec tries to take advantage of the inherent locality of sequential vertices and identify bit patterns that repeat in consecutive vertices. Typically, vertex cache + vertex fetch provides a reasonably local vertex traversal order; without an index buffer, it is recommended to sort vertices spatially to improve the compression ratio. -It is crucial to correctly specify the stride when encoding vertex data; however, it does not matter whether the vertices are interleaved or deinterleaved, as the codecs perform full byte deinterleaving internally. - -For optimal compression results, the values must be quantized to small integers. It can be valuable to use bit counts that are not multiples of 8. For example, instead of using 16 bits to represent texture coordinates, use 12-bit integers and divide by 4095 in the shader. Alternatively, using half-precision floats can often achieve good results. -For single-precision floating-point data, it's recommended to use `meshopt_quantizeFloat` to remove entropy from the lower bits of the mantissa. Due to current limitations of the codec, the bit count needs to be 15 (23-8) for good results (7 can be used for more extreme compression). -For normal or tangent vectors, using octahedral encoding is recommended over three components as it reduces redundancy. Similarly to other quantized values, consider using 10-12 bits per component instead of 16. - -> Note: vertex codec v0 is limited to taking advantage of redundancy in high bits of each byte. Because of this, packing multiple 10-bit values into 32 bits will reduce compression ratio, and when storing a 12-bit value in 16 bits, high bits should be zeroed out. This limitation may be lifted in future versions of the codec. - -To further leverage the inherent structure of some data, the preparation stage can use filters that encode and decode the data in a lossy manner. This is similar to quantization but can be used without having to change the shader code. After decoding, the filter transformation needs to be reversed. This library provides three filters: - -- Octahedral filter (`meshopt_encodeFilterOct`/`meshopt_decodeFilterOct`) encodes quantized (snorm) normal or tangent vectors using octahedral encoding. Any number of bits <= 16 can be used with 4 bytes or 8 bytes per vector. -- Quaternion filter (`meshopt_encodeFilterQuat`/`meshopt_decodeFilterQuat`) encodes quantized (snorm) quaternion vectors; this can be used to encode rotations or tangent frames. Any number of bits between 4 and 16 can be used with 8 bytes per vector. -- Exponential filter (`meshopt_encodeFilterExp`/`meshopt_decodeFilterExp`) encodes single-precision floating-point vectors; this can be used to encode arbitrary floating-point data more efficiently. In addition to an arbitrary bit count (<= 24), the filter takes a "mode" parameter that allows specifying how the exponent sharing is performed to trade off compression ratio and quality: - - - `meshopt_EncodeExpSeparate` does not share exponents and results in the largest output - - `meshopt_EncodeExpSharedVector` shares exponents between different components of the same vector - - `meshopt_EncodeExpSharedComponent` shares exponents between the same component in different vectors - - `meshopt_EncodeExpClamped` does not share exponents but clamps the exponent range to reduce exponent entropy - -Note that all filters are lossy and require the data to be deinterleaved with one attribute per stream; this faciliates efficient SIMD implementation of filter decoders, allowing the overall decompression speed to be close to that of the raw codec. - -## Triangle strip conversion - -On most hardware, indexed triangle lists are the most efficient way to drive the GPU. However, in some cases triangle strips might prove beneficial: - -- On some older GPUs, triangle strips may be a bit more efficient to render -- On extremely memory constrained systems, index buffers for triangle strips could save a bit of memory - -This library provides an algorithm for converting a vertex cache optimized triangle list to a triangle strip: - -```c++ -std::vector strip(meshopt_stripifyBound(index_count)); -unsigned int restart_index = ~0u; -size_t strip_size = meshopt_stripify(&strip[0], indices, index_count, vertex_count, restart_index); -``` - -Typically you should expect triangle strips to have ~50-60% of indices compared to triangle lists (~1.5-1.8 indices per triangle) and have ~5% worse ACMR. -Note that triangle strips can be stitched with or without restart index support. Using restart indices can result in ~10% smaller index buffers, but on some GPUs restart indices may result in decreased performance. - -To reduce the triangle strip size further, it's recommended to use `meshopt_optimizeVertexCacheStrip` instead of `meshopt_optimizeVertexCache` when optimizing for vertex cache. This trades off some efficiency in vertex transform for smaller index buffers. - -## Deinterleaved geometry - -All of the examples above assume that geometry is represented as a single vertex buffer and a single index buffer. This requires storing all vertex attributes - position, normal, texture coordinate, skinning weights etc. - in a single contiguous struct. However, in some cases using multiple vertex streams may be preferable. In particular, if some passes require only positional data - such as depth pre-pass or shadow map - then it may be beneficial to split it from the rest of the vertex attributes to make sure the bandwidth use during these passes is optimal. On some mobile GPUs a position-only attribute stream also improves efficiency of tiling algorithms. - -Most of the functions in this library either only need the index buffer (such as vertex cache optimization) or only need positional information (such as overdraw optimization). However, several tasks require knowledge about all vertex attributes. - -For indexing, `meshopt_generateVertexRemap` assumes that there's just one vertex stream; when multiple vertex streams are used, it's necessary to use `meshopt_generateVertexRemapMulti` as follows: - -```c++ -meshopt_Stream streams[] = { - {&unindexed_pos[0], sizeof(float) * 3, sizeof(float) * 3}, - {&unindexed_nrm[0], sizeof(float) * 3, sizeof(float) * 3}, - {&unindexed_uv[0], sizeof(float) * 2, sizeof(float) * 2}, -}; - -std::vector remap(index_count); -size_t vertex_count = meshopt_generateVertexRemapMulti(&remap[0], NULL, index_count, index_count, streams, sizeof(streams) / sizeof(streams[0])); -``` - -After this `meshopt_remapVertexBuffer` needs to be called once for each vertex stream to produce the correctly reindexed stream. For shadow indexing, similarly `meshopt_generateShadowIndexBufferMulti` is available as a replacement. - -Instead of calling `meshopt_optimizeVertexFetch` for reordering vertices in a single vertex buffer for efficiency, calling `meshopt_optimizeVertexFetchRemap` and then calling `meshopt_remapVertexBuffer` for each stream again is recommended. - -Finally, when compressing vertex data, `meshopt_encodeVertexBuffer` should be used on each vertex stream separately - this allows the encoder to best utilize corellation between attribute values for different vertices. - -## Simplification - -All algorithms presented so far don't affect visual appearance at all, with the exception of quantization that has minimal controlled impact. However, fundamentally the most effective way at reducing the rendering or transmission cost of a mesh is to make the mesh simpler. - -This library provides two simplification algorithms that reduce the number of triangles in the mesh. Given a vertex and an index buffer, they generate a second index buffer that uses existing vertices in the vertex buffer. This index buffer can be used directly for rendering with the original vertex buffer (preferably after vertex cache optimization), or a new compact vertex/index buffer can be generated using `meshopt_optimizeVertexFetch` that uses the optimal number and order of vertices. - -The first simplification algorithm, `meshopt_simplify`, follows the topology of the original mesh in an attempt to preserve attribute seams, borders and overall appearance. For meshes with inconsistent topology or many seams, such as faceted meshes, it can result in simplifier getting "stuck" and not being able to simplify the mesh fully. Therefore it's critical that identical vertices are "welded" together, that is, the input vertex buffer does not contain duplicates. Additionally, it may be worthwhile to weld the vertices without taking into account vertex attributes that aren't critical and can be rebuilt later. - -```c++ -float threshold = 0.2f; -size_t target_index_count = size_t(index_count * threshold); -float target_error = 1e-2f; - -std::vector lod(index_count); -float lod_error = 0.f; -lod.resize(meshopt_simplify(&lod[0], indices, index_count, &vertices[0].x, vertex_count, sizeof(Vertex), - target_index_count, target_error, /* options= */ 0, &lod_error)); -``` - -Target error is an approximate measure of the deviation from the original mesh using distance normalized to `[0..1]` range (e.g. `1e-2f` means that simplifier will try to maintain the error to be below 1% of the mesh extents). Note that the simplifier attempts to produce the requested number of indices at minimal error, but because of topological restrictions and error limit it is not guaranteed to reach the target index count and can stop earlier. - -To disable the error limit, `target_error` can be set to `FLT_MAX`. This makes it more likely that the simplifier will reach the target index count, but it may produce a mesh that looks significantly different from the original, so using the resulting error to control viewing distance would be required. Conversely, setting `target_index_count` to 0 will simplify the input mesh as much as possible within the specified error limit; this can be useful for generating LODs that should look good at a given viewing distance. - -The second simplification algorithm, `meshopt_simplifySloppy`, doesn't follow the topology of the original mesh. This means that it doesn't preserve attribute seams or borders, but it can collapse internal details that are too small to matter better because it can merge mesh features that are topologically disjoint but spatially close. - -```c++ -float threshold = 0.2f; -size_t target_index_count = size_t(index_count * threshold); -float target_error = 1e-1f; - -std::vector lod(index_count); -float lod_error = 0.f; -lod.resize(meshopt_simplifySloppy(&lod[0], indices, index_count, &vertices[0].x, vertex_count, sizeof(Vertex), - target_index_count, target_error, &lod_error)); -``` - -This algorithm will not stop early due to topology restrictions but can still do so if target index count can't be reached without introducing an error larger than target. It is 5-6x faster than `meshopt_simplify` when simplification ratio is large, and is able to reach ~20M triangles/sec on a desktop CPU (`meshopt_simplify` works at ~3M triangles/sec). - -Both algorithms can also return the resulting normalized deviation that can be used to choose the correct level of detail based on screen size or solid angle; the error can be converted to object space by multiplying by the scaling factor returned by `meshopt_simplifyScale`. For example, given a mesh with a precomputed LOD and a prescaled error, the screen-space normalized error can be computed and used for LOD selection: - -```c++ -// lod_factor can be 1 or can be adjusted for more or less aggressive LOD selection -float d = max(0, distance(camera_position, mesh_center) - mesh_radius); -float e = d * (tan(camera_fovy / 2) * 2 / screen_height); // 1px in mesh space -bool lod_ok = e * lod_factor >= lod_error; -``` - -When a sequence of LOD meshes is generated that all use the original vertex buffer, care must be taken to order vertices optimally to not penalize mobile GPU architectures that are only capable of transforming a sequential vertex buffer range. It's recommended in this case to first optimize each LOD for vertex cache, then assemble all LODs in one large index buffer starting from the coarsest LOD (the one with fewest triangles), and call `meshopt_optimizeVertexFetch` on the final large index buffer. This will make sure that coarser LODs require a smaller vertex range and are efficient wrt vertex fetch and transform. - -## Advanced simplification - -The main simplification algorithm, `meshopt_simplify`, exposes additional options and functions that can be used to control the simplification process in more detail. - -For basic customization, a number of options can be passed via `options` bitmask that adjust the behavior of the simplifier: - -- `meshopt_SimplifyLockBorder` restricts the simplifier from collapsing edges that are on the border of the mesh. This can be useful for simplifying mesh subsets independently, so that the LODs can be combined without introducing cracks. -- `meshopt_SimplifyErrorAbsolute` changes the error metric from relative to absolute both for the input error limit as well as for the resulting error. This can be used instead of `meshopt_simplifyScale`. -- `meshopt_SimplifySparse` improves simplification performance assuming input indices are a sparse subset of the mesh. This can be useful when simplifying small mesh subsets independently, and is intended to be used for meshlet simplification. For consistency, it is recommended to use absolute errors when sparse simplification is desired, as this flag changes the meaning of the relative errors. -- `meshopt_SimplifyPrune` allows the simplifier to remove isolated components regardless of the topological restrictions inside the component. This is generally recommended for full-mesh simplification as it can improve quality and reduce triangle count; note that with this option, triangles connected to locked vertices may be removed as part of their component. - -While `meshopt_simplify` is aware of attribute discontinuities by default (and infers them through the supplied index buffer) and tries to preserve them, it can be useful to provide information about attribute values. This allows the simplifier to take attribute error into account which can improve shading (by using vertex normals), texture deformation (by using texture coordinates), and may be necessary to preserve vertex colors when textures are not used in the first place. This can be done by using a variant of the simplification function that takes attribute values and weight factors, `meshopt_simplifyWithAttributes`: - -```c++ -const float nrm_weight = 0.5f; -const float attr_weights[3] = {nrm_weight, nrm_weight, nrm_weight}; - -std::vector lod(index_count); -float lod_error = 0.f; -lod.resize(meshopt_simplifyWithAttributes(&lod[0], indices, index_count, &vertices[0].x, vertex_count, sizeof(Vertex), - &vertices[0].nx, sizeof(Vertex), attr_weights, 3, /* vertex_lock= */ NULL, - target_index_count, target_error, /* options= */ 0, &lod_error)); -``` - -The attributes are passed as a separate buffer (in the example above it's a subset of the same vertex buffer) and should be stored as consecutive floats; attribute weights are used to control the importance of each attribute in the simplification process. For normalized attributes like normals and vertex colors, a weight around 1.0 is usually appropriate; internally, a change of `1/weight` in attribute value over a distance `d` is approximately equivalent to a change of `d` in position. Using higher weights may be appropriate to preserve attribute quality at the cost of position quality. If the attribute has a different scale (e.g. unnormalized vertex colors in [0..255] range), the weight should be divided by the scaling factor (1/255 in this example). - -Both the target error and the resulting error combine positional error and attribute error, so the error can be used to control the LOD while taking attribute quality into account, assuming carefully chosen weights. - -When using `meshopt_simplifyWithAttributes`, it is also possible to lock certain vertices by providing a `vertex_lock` array that contains a boolean value for each vertex in the mesh. This can be useful to preserve certain vertices, such as the boundary of the mesh, with more control than `meshopt_SimplifyLockBorder` option provides. - -Simplification currently assumes that the input mesh is using the same material for all triangles. If the mesh uses multiple materials, it is possible to split the mesh into subsets based on the material and simplify each subset independently, using `meshopt_SimplifyLockBorder` or `vertex_lock` to preserve material boundaries; however, this limits the collapses and as a result may reduce the resulting quality. An alternative approach is to encode information about the material into the vertex buffer, ensuring that all three vertices referencing the same triangle have the same material ID; this may require duplicating vertices on the boundary between materials. After this, simplification can be performed as usual, and after simplification per-triangle material information can be computed from the vertex material IDs. There is no need to inform the simplifier of the value of the material ID: the implicit boundaries created by duplicating vertices with conflicting material IDs will be preserved automatically. - -## Point cloud simplification - -In addition to triangle mesh simplification, this library provides a function to simplify point clouds. The algorithm reduces the point cloud to a specified number of points while preserving the overall appearance, and can optionally take per-point colors into account: - -```c++ -const float color_weight = 1; -std::vector indices(target_count); -indices.resize(meshopt_simplifyPoints(&indices[0], &points[0].x, points.size(), sizeof(Point), - &points[0].r, sizeof(Point), color_weight, target_count)); -``` - -The resulting indices can be used to render the simplified point cloud; to reduce the memory footprint, the point cloud can be reindexed to create an array of points from the indices. - -## Mesh shading - -Modern GPUs are beginning to deviate from the traditional rasterization model. NVidia GPUs starting from Turing and AMD GPUs starting from RDNA2 provide a new programmable geometry pipeline that, instead of being built around index buffers and vertex shaders, is built around mesh shaders - a new shader type that allows to provide a batch of work to the rasterizer. - -Using mesh shaders in context of traditional mesh rendering provides an opportunity to use a variety of optimization techniques, starting from more efficient vertex reuse, using various forms of culling (e.g. cluster frustum or occlusion culling) and in-memory compression to maximize the utilization of GPU hardware. Beyond traditional rendering mesh shaders provide a richer programming model that can synthesize new geometry more efficiently than common alternatives such as geometry shaders. Mesh shading can be accessed via Vulkan or Direct3D 12 APIs; please refer to [Introduction to Turing Mesh Shaders](https://developer.nvidia.com/blog/introduction-turing-mesh-shaders/) and [Mesh Shaders and Amplification Shaders: Reinventing the Geometry Pipeline](https://devblogs.microsoft.com/directx/coming-to-directx-12-mesh-shaders-and-amplification-shaders-reinventing-the-geometry-pipeline/) for additional information. - -To use mesh shaders for conventional rendering efficiently, geometry needs to be converted into a series of meshlets; each meshlet represents a small subset of the original mesh and comes with a small set of vertices and a separate micro-index buffer that references vertices in the meshlet. This information can be directly fed to the rasterizer from the mesh shader. This library provides algorithms to create meshlet data for a mesh, and - assuming geometry is static - can compute bounding information that can be used to perform cluster culling, a technique that can reject a meshlet if it's invisible on screen. - -To generate meshlet data, this library provides `meshopt_buildMeshlets` algorithm, which tries to balance topological efficiency (by maximizing vertex reuse inside meshlets) with culling efficiency (by minimizing meshlet radius and triangle direction divergence) and produces GPU-friendly data. As an alternative (that can be useful for load-time processing), `meshopt_buildMeshletsScan` can create the meshlet data using a vertex cache-optimized index buffer as a starting point by greedily aggregating consecutive triangles until they go over the meshlet limits. `meshopt_buildMeshlets` is recommended for offline data processing even if cone culling is not used. - -```c++ -const size_t max_vertices = 64; -const size_t max_triangles = 124; -const float cone_weight = 0.0f; - -size_t max_meshlets = meshopt_buildMeshletsBound(indices.size(), max_vertices, max_triangles); -std::vector meshlets(max_meshlets); -std::vector meshlet_vertices(max_meshlets * max_vertices); -std::vector meshlet_triangles(max_meshlets * max_triangles * 3); - -size_t meshlet_count = meshopt_buildMeshlets(meshlets.data(), meshlet_vertices.data(), meshlet_triangles.data(), indices.data(), - indices.size(), &vertices[0].x, vertices.size(), sizeof(Vertex), max_vertices, max_triangles, cone_weight); -``` - -To generate the meshlet data, `max_vertices` and `max_triangles` need to be set within limits supported by the hardware; for NVidia the values of 64 and 124 are recommended (`max_triangles` must be divisible by 4 so 124 is the value closest to official NVidia's recommended 126). `cone_weight` should be left as 0 if cluster cone culling is not used, and set to a value between 0 and 1 to balance cone culling efficiency with other forms of culling like frustum or occlusion culling. - -Each resulting meshlet refers to a portion of `meshlet_vertices` and `meshlet_triangles` arrays; the arrays are overallocated for the worst case so it's recommended to trim them before saving them as an asset / uploading them to the GPU: - -```c++ -const meshopt_Meshlet& last = meshlets[meshlet_count - 1]; - -meshlet_vertices.resize(last.vertex_offset + last.vertex_count); -meshlet_triangles.resize(last.triangle_offset + ((last.triangle_count * 3 + 3) & ~3)); -meshlets.resize(meshlet_count); -``` - -However depending on the application other strategies of storing the data can be useful; for example, `meshlet_vertices` serves as indices into the original vertex buffer but it might be worthwhile to generate a mini vertex buffer for each meshlet to remove the extra indirection when accessing vertex data, or it might be desirable to compress vertex data as vertices in each meshlet are likely to be very spatially coherent. - -For optimal performance, it is recommended to further optimize each meshlet in isolation for better triangle and vertex locality by calling `meshopt_optimizeMeshlet` on vertex and index data like so: - -```c++ -meshopt_optimizeMeshlet(&meshlet_vertices[m.vertex_offset], &meshlet_triangles[m.triangle_offset], m.triangle_count, m.vertex_count); -``` - -Different applications will choose different strategies for rendering meshlets; on a GPU capable of mesh shading, meshlets can be rendered directly; for example, a basic GLSL shader for `VK_EXT_mesh_shader` extension could look like this (parts omitted for brevity): - -```glsl -layout(binding = 0) readonly buffer Meshlets { Meshlet meshlets[]; }; -layout(binding = 1) readonly buffer MeshletVertices { uint meshlet_vertices[]; }; -layout(binding = 2) readonly buffer MeshletTriangles { uint8_t meshlet_triangles[]; }; - -void main() { - Meshlet meshlet = meshlets[gl_WorkGroupID.x]; - SetMeshOutputsEXT(meshlet.vertex_count, meshlet.triangle_count); - - for (uint i = gl_LocalInvocationIndex; i < meshlet.vertex_count; i += gl_WorkGroupSize.x) { - uint index = meshlet_vertices[meshlet.vertex_offset + i]; - gl_MeshVerticesEXT[i].gl_Position = world_view_projection * vec4(vertex_positions[index], 1); - } - - for (uint i = gl_LocalInvocationIndex; i < meshlet.triangle_count; i += gl_WorkGroupSize.x) { - uint offset = meshlet.triangle_offset + i * 3; - gl_PrimitiveTriangleIndicesEXT[i] = uvec3( - meshlet_triangles[offset], meshlet_triangles[offset + 1], meshlet_triangles[offset + 2]); - } -} -``` - -After generating the meshlet data, it's also possible to generate extra data for each meshlet that can be saved and used at runtime to perform cluster culling, where each meshlet can be discarded if it's guaranteed to be invisible. To generate the data, `meshlet_computeMeshletBounds` can be used: - -```c++ -meshopt_Bounds bounds = meshopt_computeMeshletBounds(&meshlet_vertices[m.vertex_offset], &meshlet_triangles[m.triangle_offset], - m.triangle_count, &vertices[0].x, vertices.size(), sizeof(Vertex)); -``` - -The resulting `bounds` values can be used to perform frustum or occlusion culling using the bounding sphere, or cone culling using the cone axis/angle (which will reject the entire meshlet if all triangles are guaranteed to be back-facing from the camera point of view): - -```c++ -if (dot(normalize(cone_apex - camera_position), cone_axis) >= cone_cutoff) reject(); -``` - -## Efficiency analyzers - -While the only way to get precise performance data is to measure performance on the target GPU, it can be valuable to measure the impact of these optimization in a GPU-independent manner. To this end, the library provides analyzers for all three major optimization routines. For each optimization there is a corresponding analyze function, like `meshopt_analyzeOverdraw`, that returns a struct with statistics. - -`meshopt_analyzeVertexCache` returns vertex cache statistics. The common metric to use is ACMR - average cache miss ratio, which is the ratio of the total number of vertex invocations to the triangle count. The worst-case ACMR is 3 (GPU has to process 3 vertices for each triangle); on regular grids the optimal ACMR approaches 0.5. On real meshes it usually is in [0.5..1.5] range depending on the amount of vertex splits. One other useful metric is ATVR - average transformed vertex ratio - which represents the ratio of vertex shader invocations to the total vertices, and has the best case of 1.0 regardless of mesh topology (each vertex is transformed once). - -`meshopt_analyzeVertexFetch` returns vertex fetch statistics. The main metric it uses is overfetch - the ratio between the number of bytes read from the vertex buffer to the total number of bytes in the vertex buffer. Assuming non-redundant vertex buffers, the best case is 1.0 - each byte is fetched once. - -`meshopt_analyzeOverdraw` returns overdraw statistics. The main metric it uses is overdraw - the ratio between the number of pixel shader invocations to the total number of covered pixels, as measured from several different orthographic cameras. The best case for overdraw is 1.0 - each pixel is shaded once. - -Note that all analyzers use approximate models for the relevant GPU units, so the numbers you will get as the result are only a rough approximation of the actual performance. - -## Specialized processing - -In addition to the core optimization techniques, the library provides several specialized algorithms for specific rendering techniques and pipeline optimizations that require a particular configuration of vertex and index data. - -### Geometry shader adjacency - -For algorithms that use geometry shaders and require adjacency information, this library can generate an index buffer with adjacency data: - -```c++ -std::vector adjacency(indices.size() * 2); -meshopt_generateAdjacencyIndexBuffer(&adjacency[0], &indices[0], indices.size(), &vertices[0].x, vertices.size(), sizeof(Vertex)); -``` - -This creates an index buffer suitable for rendering with triangle-with-adjacency topology, providing 3 extra vertices per triangle that represent vertices opposite to each triangle's edge. This data can be used to compute silhouettes and perform other types of local geometric processing in geometry shaders. To render the mesh with adjacency data, the index buffer should be used with `D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST_ADJ`/`VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY`/`GL_TRIANGLES_ADJACENCY` topology. - -Note that the use of geometry shaders may have a performance impact on some GPUs; in some cases alternative implementation strategies may be more efficient. - -### Tessellation with displacement mapping - -For hardware tessellation with crack-free displacement mapping, this library can generate a special index buffer that supports PN-AEN tessellation: - -```c++ -std::vector tess(indices.size() * 4); -meshopt_generateTessellationIndexBuffer(&tess[0], &indices[0], indices.size(), &vertices[0].x, vertices.size(), sizeof(Vertex)); -``` - -This generates a 12-vertex patch for each input triangle with the following layout: - -- 0, 1, 2: original triangle vertices -- 3, 4: opposing edge for edge 0, 1 -- 5, 6: opposing edge for edge 1, 2 -- 7, 8: opposing edge for edge 2, 0 -- 9, 10, 11: dominant vertices for corners 0, 1, 2 - -This allows the use of hardware tessellation to implement PN-AEN and/or displacement mapping without cracks along UV seams or normal discontinuities. To render the mesh, the index buffer should be used with `D3D_PRIMITIVE_TOPOLOGY_12_CONTROL_POINT_PATCHLIST`/`VK_PRIMITIVE_TOPOLOGY_PATCH_LIST` (`patchControlPoints=12`) topology. For more details please refer to the following papers: [Crack-Free Point-Normal Triangles using Adjacent Edge Normals](https://developer.download.nvidia.com/whitepapers/2010/PN-AEN-Triangles-Whitepaper.pdf), [Tessellation on Any Budget](https://www.nvidia.com/content/pdf/gdc2011/john_mcdonald.pdf) and [My Tessellation Has Cracks!](https://developer.download.nvidia.com/assets/gamedev/files/gdc12/GDC12_DUDASH_MyTessellationHasCracks.pdf). - -### Visibility buffers - -To render geometry into visibility buffers, access to primitive index in fragment shader is required. While it is possible to use `SV_PrimitiveID`/`gl_PrimitiveID` in the fragment shader, this can result in suboptimal performance on some GPUs (notably, AMD RDNA1 and all NVidia GPUs), and may not be supported on mobile or console hardware. Using mesh shaders to generate primitive IDs is efficient but requires hardware support that is not universally available. To work around these limitations, this library provides a way to generate a special index buffer that uses provoking vertex to encode primitive IDs: - -```c++ -std::vector provoke(indices.size()); -std::vector reorder(vertices.size() + indices.size() / 3); -reorder.resize(meshopt_generateProvokingIndexBuffer(&provoke[0], &reorder[0], &indices[0], indices.size(), vertices.size())); -``` - -This generates a special index buffer along with a reorder table that satisfies two constraints: - -- `provoke[3 * tri] == tri` -- `reorder[provoke[x]]` refers to the original triangle vertices - -To render the mesh with provoking vertex data, the application should use `provoke` as an index buffer and a vertex shader that passes vertex index (`SV_VertexID`/`gl_VertexIndex`) via a `flat`/`nointerpolation` attribute to the fragment shader as a primitive index, and loads vertex data manually by computing the real vertex index based on `reorder` table (`reorder[gl_VertexIndex]`). For more details please refer to [Variable Rate Shading with Visibility Buffer Rendering](https://advances.realtimerendering.com/s2024/content/Hable/Advances_SIGGRAPH_2024_VisibilityVRS-SIGGRAPH_Advances_2024.pptx); naturally, this technique does not require VRS. - -Note: This assumes the provoking vertex is the first vertex of a triangle, which is true for all graphics APIs except OpenGL/WebGL. For OpenGL/WebGL, you may need to rotate each triangle (abc -> bca) in the resulting index buffer, or use the `glProvokingVertex` function (OpenGL 3.2+) or `WEBGL_provoking_vertex` extension (WebGL2) to change the provoking vertex convention. For WebGL2, this is highly recommended to avoid a variety of emulation slowdowns that happen by default if `flat` attributes are used, such as an implicit use of geometry shaders. - -## Memory management - -Many algorithms allocate temporary memory to store intermediate results or accelerate processing. The amount of memory allocated is a function of various input parameters such as vertex count and index count. By default memory is allocated using `operator new` and `operator delete`; if these operators are overloaded by the application, the overloads will be used instead. Alternatively it's possible to specify custom allocation/deallocation functions using `meshopt_setAllocator`, e.g. - -```c++ -meshopt_setAllocator(malloc, free); -``` - -> Note that the library expects the allocation function to either throw in case of out-of-memory (in which case the exception will propagate to the caller) or abort, so technically the use of `malloc` above isn't safe. If you want to handle out-of-memory errors without using C++ exceptions, you can use `setjmp`/`longjmp` instead. - -Vertex and index decoders (`meshopt_decodeVertexBuffer`, `meshopt_decodeIndexBuffer`, `meshopt_decodeIndexSequence`) do not allocate memory and work completely within the buffer space provided via arguments. - -All functions have bounded stack usage that does not exceed 32 KB for any algorithms. - -## License - -This library is available to anybody free of charge, under the terms of MIT License (see LICENSE.md). diff --git a/src/meshoptimizer.h b/src/meshoptimizer.h index cb548250..77be5371 100644 --- a/src/meshoptimizer.h +++ b/src/meshoptimizer.h @@ -1,7 +1,7 @@ /** * meshoptimizer - version 0.22 * - * Copyright (C) 2016-2024 Arseny Kapoulkine (arseny.kapoulkine@gmail.com) + * Copyright (C) 2016-2024, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) * Report bugs and download new versions at https://github.com/zeux/meshoptimizer * * This library is distributed under the MIT License. See notice at the end of this file. From 1a81bef36c9773ee86d466f8124d33cd4e73aece Mon Sep 17 00:00:00 2001 From: wangshilin Date: Wed, 24 Sep 2025 11:53:05 +0800 Subject: [PATCH 4/6] =?UTF-8?q?=E5=A2=9E=E5=8A=A0bundel.json,=20=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B=E3=80=82Signed-of?= =?UTF-8?q?f-by:=20wangshilin=20?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bundel.json | 46 +++++++++ testcase/decode_meshopt_test.cpp | 168 ------------------------------- 2 files changed, 46 insertions(+), 168 deletions(-) create mode 100644 bundel.json delete mode 100644 testcase/decode_meshopt_test.cpp diff --git a/bundel.json b/bundel.json new file mode 100644 index 00000000..9240f873 --- /dev/null +++ b/bundel.json @@ -0,0 +1,46 @@ +{ + "name": "@ohos/meshoptimizer", + "description": "meshoptimizer", + "version": "6.0", + "license": "meshoptimizer license", + "publishAs": "code-segment", + "segment": { + "desPath": "third_party/meshoptimizer" + }, + "dirs": {}, + "scripts": {}, + "licensePath": "LICENSES", + "readmePath": { + "en": "README_en" + }, + "component": { + "name": "meshoptimizer", + "subsystem": "thirdparty", + "syscap": [], + "features": [], + "adapted_system_type": [ + "small", + "mini", + "standard" + ], + "rom": "", + "ram": "", + "deps": { + "components": [], + "third_party": [] + }, + "build": { + "sub_component": [], + "inner_kits": [ + { + "type": "so", + "name": "//third_party/meshoptimizer:meshoptimizer", + "header": { + "header_files": [], + "header_base": "//third_party/meshoptimizer" + } + }], + "test": [] + } + } +} \ No newline at end of file diff --git a/testcase/decode_meshopt_test.cpp b/testcase/decode_meshopt_test.cpp deleted file mode 100644 index 22457b0e..00000000 --- a/testcase/decode_meshopt_test.cpp +++ /dev/null @@ -1,168 +0,0 @@ -// Copyright (c) 2021 Huawei Device Co., Ltd. - -// MIT LISENCE - -#include "../src/meshoptimizer" -#include -#include -#include -#include -#include - -// Testbench for meshopt_decodeVertexBuffer -void test_meshopt_decodeVertexBuffer() { - unsigned char buffer[12]; - int result; - - // Invalid buffer size - unsigned char vertex_data[12]; - result = meshopt_decodeVertexBuffer(vertex_data, 10, 4, buffer, 0); - assert(result == -2); - - // Invalid data_header - buffer[0] = 0x00; - result = meshopt_decodeVertexBuffer(vertex_data, 10, 4, buffer, sizeof(buffer)); - assert(result == -1); - - // Unsupported version - buffer[0] = 0x1F; - result = meshopt_decodeVertexBuffer(vertex_data, 10, 4, buffer, sizeof(buffer)); - assert(result == -1); - - // Insufficient buffer size for tail - buffer[0] = 0xa1; - result = meshopt_decodeVertexBuffer(vertex_data, 10, 4, buffer, 1); - assert(result == -2); - - // Successful decoding - buffer[0] = 0xa0; - size_t buffer_size = 37; - result = meshopt_decodeVertexBuffer(vertex_data, 1, 4, buffer, buffer_size); - assert(result == 0); -} - -void test_meshopt_decodeFilterOct() { - // test the case where the stride is 4 - signed char buffer[4] = { - 0, 64, 64, 64, - }; - meshopt_decodeFilterOct(buffer, 1, 4); - assert(buffer[0] == 0); - assert(buffer[1] == 127); - assert(buffer[2] == 0); - assert(buffer[3] == 64); - - short buffer_8[8] = { - 0, 64, 64, 64, - 0, 64, 64, 64, - }; - // test the case where the stride is 8 - meshopt_decodeFilterOct(buffer_8, 1, 8); - assert(buffer[0] == 0); - assert(buffer[1] == 32767); - assert(buffer[2] == 0); - assert(buffer[3] == 64); - assert(buffer[4] == 0); - assert(buffer[5] == 64); - assert(buffer[6] == 64); - assert(buffer[7] == 64); -} - -void test_meshopt_decodeFilterQuat() { - short buffer_8[8] = { - 0, 64, 64, 64, - 0, 64, 64, 64, - }; - - // test the case where the stride is 8 - meshopt_decodeFilterQuat(buffer_8, 1, 8) - assert(buffer[0] == 9695); - assert(buffer[1] == 0); - assert(buffer[2] == 22132); - assert(buffer[3] == 22132); - assert(buffer[4] == 0); - assert(buffer[5] == 64); - assert(buffer[6] == 64); - assert(buffer[7] == 64); -} - -void test_meshopt_decodeFilterExp() { - // test the case where the stride is 4 - unsigned int buffer[4] = { - 0, 64, 64, 64, - }; - meshopt_decodeFilterExp(buffer, 1, 4); - assert(buffer[0] == 0); - assert(buffer[1] == 64); - assert(buffer[2] == 64); - assert(buffer[3] == 64); - - unsigned int buffer[8] = { - 0, 64, 64, 64, - 0, 64, 64, 64, - }; - - // test the case where the stride is 8 - meshopt_decodeFilterExp(buffer_8, 1, 8); - assert(buffer[0] == 0); - assert(buffer[1] == 1115684864); - assert(buffer[2] == 64); - assert(buffer[3] == 64); - assert(buffer[4] == 0); - assert(buffer[5] == 64); - assert(buffer[6] == 64); - assert(buffer[7] == 64); -} - -void test_meshopt_decodeIndexBuffer() { - const size_t index_count = 6; - const size_t index_size = 2; - const size_t buffer_size = 1 + index_count / 3 + 16; - const size_t buffer_size_small = 1 + index_count / 3 + 15; - - unsigned char buffer[buffer_size]; - memset(buffer, 0); - - buffer[0] = 0xe0; - buffer[1] = 0x01; - - // target buffer for testing - uint16_t destination[index_count]; - memset(destination, 0, sizeof(destination)); - - int result = meshopt_decodeIndexBuffer(destination, index_count, index_size, buffer, buffer_size); - assert(result == 0); - - // verify the contents of the target buffer - assert(destination[0] == 65535); - assert(destination[0] == 65535); - assert(destination[0] == 65535); - assert(destination[0] == 65535); - assert(destination[0] == 65535); - assert(destination[0] == 0); - - // test different input conditions - buffer[0] = 0x20; - result = meshopt_decodeIndexBuffer(destination, index_count, index_size, buffer, buffer_size); - assert(result == -1); - - buffer[0] = 0xe0; - buffer[1] = 0xFF; - result = meshopt_decodeIndexBuffer(destination, index_count, index_size, buffer, buffer_size); - assert(result == -2); - - buffer[0] = 0xe0; - buffer[1] = 0x01; - result = meshopt_decodeIndexBuffer(destination, index_count, index_size, buffer, buffer_size); - assert(result == -2); -} - -int main() { - test_meshopt_decodeVertexBuffer(); - test_meshopt_decodeFilterOct(); - test_meshopt_decodeFilterQuat(); - test_meshopt_decodeFilterExp(); - test_meshopt_decodeIndexBuffer(); - std::count << "All test cases passed!" << std::endl; - return 0; -} \ No newline at end of file From 1f791a09663818017e907f44454d833f247d1d9d Mon Sep 17 00:00:00 2001 From: wangshilin Date: Wed, 24 Sep 2025 14:24:36 +0800 Subject: [PATCH 5/6] =?UTF-8?q?=E4=BF=AE=E6=94=B9OAT.xml=20Signed-off-by:?= =?UTF-8?q?=20wangshilin=20?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OAT.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/OAT.xml b/OAT.xml index 14a7238b..b591831a 100644 --- a/OAT.xml +++ b/OAT.xml @@ -18,6 +18,7 @@ MIT LISENSE + From 16e56c3b017e556f56efad3f876a597b744ef78c Mon Sep 17 00:00:00 2001 From: wangshilin Date: Sun, 28 Sep 2025 15:21:29 +0800 Subject: [PATCH 6/6] =?UTF-8?q?=E5=A2=9E=E5=8A=A0ROM=20Signed-off-by:=20wa?= =?UTF-8?q?ngshilin=20?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bundel.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundel.json b/bundel.json index 9240f873..536201af 100644 --- a/bundel.json +++ b/bundel.json @@ -23,7 +23,7 @@ "mini", "standard" ], - "rom": "", + "rom": "125KB", "ram": "", "deps": { "components": [],