├── .github └── workflows │ └── rust-check.yml ├── .gitignore ├── .gitmodules ├── .vscode └── launch.json ├── CHANGES.md ├── CODE_OF_CONDUCT.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── build.rs ├── examples ├── cube.mtl ├── cube.obj ├── cube.optmesh ├── demo.rs ├── encoder.rs ├── multi.obj ├── multi.optmesh ├── pirate.obj ├── pirate.optmesh ├── pirate_opt.obj ├── pirate_opt.optmesh ├── plane.obj ├── plane.optmesh ├── plane_opt.obj └── plane_opt.optmesh ├── gen └── bindings.rs ├── include_wasm32 ├── assert.h ├── limits.h ├── math.h └── string.h └── src ├── analyze.rs ├── clusterize.rs ├── encoding.rs ├── error.rs ├── ffi.rs ├── lib.rs ├── optimize.rs ├── packing.rs ├── remap.rs ├── shadow.rs ├── simplify.rs ├── stripify.rs └── utilities.rs /.github/workflows/rust-check.yml: -------------------------------------------------------------------------------- 1 | name: rust-build 2 | 3 | on: 4 | push: 5 | branches: 6 | - '*' 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | build: 13 | runs-on: ${{ matrix.os }} 14 | strategy: 15 | matrix: 16 | os: [ubuntu-latest, macos-latest, windows-latest] 17 | 18 | steps: 19 | - name: Checkout repo 20 | uses: actions/checkout@v2 21 | with: 22 | submodules: true 23 | 24 | - name: Install Rust 25 | uses: hecrj/setup-rust-action@v2 26 | with: 27 | rust-version: '1.68.0' 28 | 29 | - name: Rustfmt 30 | run: cargo fmt --verbose --all --check 31 | 32 | - name: Clippy 33 | run: cargo clippy --verbose --tests --examples 34 | 35 | - name: Tests 36 | run: cargo test --verbose -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vendor"] 2 | path = vendor 3 | url = https://github.com/zeux/meshoptimizer.git 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "(lldb) Launch", 9 | "type": "cppdbg", 10 | "request": "launch", 11 | "program": "${workspaceFolder}/target/debug/examples/demo", 12 | "args": [], 13 | "stopAtEntry": false, 14 | "cwd": "${workspaceFolder}", 15 | "environment": [], 16 | "externalConsole": true, 17 | "MIMode": "lldb" 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # Changes 2 | 3 | ## 0.5.0 (2025-04-??) 4 | 5 | * Upgraded meshoptimizer library to 0.23 (hash 3e9d1ff3135794f519f3237515277c8d9a3fd3f2) 6 | * Added `build_meshlets_flex` API function 7 | * Added `dequantize_half` utility 8 | 9 | ## 0.4.1 (2024-12-22) 10 | 11 | * Truncate vertices and triangles when building meshlets 12 | 13 | ## 0.4.0 (2024-10-25) 14 | 15 | * Upgraded meshoptimizer library to 0.22 (hash 4affad044571506a5724c9a6f15424f43e86f731) 16 | * Added `simplify_with_attributes_and_locks` API function 17 | 18 | ## 0.3.0 (2024-06-26) 19 | 20 | * Upgraded meshoptimizer library to 0.21 (hash 47aafa533b439a78b53cd2854c177db61be7e666) 21 | * Added `SimplifyOptions::Sparse` and `SimplifyOptions::ErrorAbsolute` options 22 | * Improved `build_meshlets` to automatically optimize meshlet triangle order for HW efficiency 23 | 24 | ## 0.2.1 (2024-04-03) 25 | 26 | * Updated dependencies 27 | * Added `simplify_scale` and `simplify_scale_decoder` API functions 28 | * Added `simplify_with_locks` and `simplify_with_locks_decoder` API functions 29 | 30 | ## 0.2.0 (2024-01-23) 31 | 32 | * Updated dependencies. 33 | * **Breaking change**: New parameters to simplify API. 34 | 35 | ## 0.1.9 (2019-11-02) 36 | 37 | * Updated dependencies. 38 | * Added `dyn` to `Fail::cause()` to fix warning. 39 | * Added missing `allocator.cpp` to source_files in `build.rs` and in `Cargo.toml` package include list. 40 | * Made the crate buildable on WebAssembly. 41 | * Fixed build under toolchain 'windows-gnu'. 42 | * Updated vendoring of meshoptimizer to commit hash `7cf4a53ece15fa7526410a6d4cae059bd5593178`. 43 | 44 | ## 0.1.8 (2019-07-14) 45 | 46 | * Updated vendoring of meshoptimizer to commit hash `212a35ea9d32ea5e0223105566b3b7deeb06071f`. 47 | * Updated dependencies. 48 | * Updated demo stripify code for restart index. 49 | 50 | ## 0.1.7 (2019-05-19) 51 | 52 | * Implemented `VertexDataAdapter` and modified a number of methods to remove a heavy allocation and slow decode. `DecodePosition` is supported through new `*_decoder` methods. 53 | * Updated vendoring of meshoptimizer to commit hash `7bf6e425fa158794c3da75684e8f8c7040b97cfa`. 54 | 55 | ## 0.1.6 (2019-03-29) 56 | 57 | * Fixed usage of VertexStream and adjust data representation. 58 | * Upgraded meshoptimizer library to 0.11.0. 59 | * Upgraded crate dependencies. 60 | * Added `simplify_sloppy` wrapper 61 | 62 | ## 0.1.5 (2019-01-14) 63 | 64 | * Fixed demo example. 65 | 66 | ## 0.1.4 (2019-01-12) 67 | 68 | * Upgraded meshoptimizer library to 0.10.0. 69 | * Upgraded crate dependencies. 70 | * Added proper error handling and removed asserts/unwraps. 71 | * Derived and implemented debug in generated bindings (where possible). 72 | * Implemented mesh encoder command line tool (matches format for meshoptimizer's wasm viewer/loader). 73 | * Implemented support for multiple vertex attribute streams. 74 | * Implemented generate_shadow_indices_multi 75 | * Implemented generate_vertex_remap_multi 76 | * Passed in vertex count to remap_vertex_buffer (needed for correctly resizing result). 77 | * Added more documentation (and some fixes) 78 | 79 | ## 0.1.3 (2018-12-07) 80 | 81 | * Rust 2018 Edition. 82 | 83 | ## 0.1.2 (2018-12-04) 84 | 85 | * Upgraded meshoptimizer library. 86 | * Added support for generating shadow indices. 87 | * Added support for meshlet generation. 88 | 89 | ## 0.1.1 (2018-10-19) 90 | 91 | * Support remapping meshes with a pre-existing index buffer, instead of purely unindexed data. 92 | 93 | ## 0.1.0 (2018-10-19) 94 | 95 | * First release. 96 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at graham@wihlidal.ca. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "meshopt" 3 | version = "0.5.0" 4 | authors = ["Graham Wihlidal "] 5 | description = "Rust ffi bindings and idiomatic wrapper for mesh optimizer" 6 | homepage = "https://github.com/gwihlidal/meshopt-rs" 7 | repository = "https://github.com/gwihlidal/meshopt-rs" 8 | documentation = "https://docs.rs/meshopt" 9 | readme = "README.md" 10 | keywords = ["mesh", "optimize", "cache", "3d", "rendering"] 11 | categories = ["rendering", "rendering::engine"] 12 | license = "MIT OR Apache-2.0" 13 | build = "build.rs" 14 | include = [ 15 | "src/*.rs", 16 | "gen/bindings.rs", 17 | "build.rs", 18 | "Cargo.toml", 19 | "vendor/src/meshoptimizer.h", 20 | "vendor/src/allocator.cpp", 21 | "vendor/src/clusterizer.cpp", 22 | "vendor/src/indexcodec.cpp", 23 | "vendor/src/indexgenerator.cpp", 24 | "vendor/src/overdrawanalyzer.cpp", 25 | "vendor/src/overdrawoptimizer.cpp", 26 | "vendor/src/partition.cpp", 27 | "vendor/src/quantization.cpp", 28 | "vendor/src/simplifier.cpp", 29 | "vendor/src/spatialorder.cpp", 30 | "vendor/src/stripifier.cpp", 31 | "vendor/src/vcacheanalyzer.cpp", 32 | "vendor/src/vcacheoptimizer.cpp", 33 | "vendor/src/vertexcodec.cpp", 34 | "vendor/src/vertexfilter.cpp", 35 | "vendor/src/vfetchanalyzer.cpp", 36 | "vendor/src/vfetchoptimizer.cpp", 37 | "include_wasm32/*.h", 38 | ] 39 | edition = "2021" 40 | 41 | [badges] 42 | maintenance = { status = "actively-developed" } 43 | 44 | [dependencies] 45 | float-cmp = "0.10" 46 | thiserror = "2.0" 47 | bitflags = "2.4" 48 | 49 | [build-dependencies] 50 | cc = { version = "1.0" } 51 | 52 | [build-dependencies.bindgen] 53 | version = "0.70" 54 | optional = true 55 | 56 | [dev-dependencies] 57 | tobj = "4.0" 58 | miniz_oxide = "0.8" 59 | rand = "0.9" 60 | libc = "0.2" 61 | structopt = "0.3" 62 | memoffset = "0.9" 63 | 64 | [profile.release] 65 | lto = true 66 | opt-level = 3 67 | codegen-units = 1 68 | 69 | [features] 70 | generate_bindings = ["bindgen"] 71 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Graham Wihlidal 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | meshopt 2 | ======== 3 | 4 | [![Latest version](https://img.shields.io/crates/v/meshopt.svg)](https://crates.io/crates/meshopt) 5 | [![Documentation](https://docs.rs/meshopt/badge.svg)](https://docs.rs/meshopt) 6 | [![](https://tokei.rs/b1/github/gwihlidal/meshopt-rs)](https://github.com/gwihlidal/meshopt-rs) 7 | ![MIT](https://img.shields.io/badge/license-MIT-blue.svg) 8 | ![APACHE2](https://img.shields.io/badge/license-APACHE2-blue.svg) 9 | 10 | This crate provides an FFI layer and idiomatic rust wrappers for the excellent [meshoptimizer](https://github.com/zeux/meshoptimizer) C/C++ library. 11 | 12 | - [Documentation](https://docs.rs/meshopt) 13 | - [Release Notes](https://github.com/gwihlidal/meshopt-rs/releases) 14 | 15 | ## Purpose 16 | 17 | When 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. 18 | 19 | ## Usage 20 | 21 | Add this to your `Cargo.toml`: 22 | 23 | ```toml 24 | [dependencies] 25 | meshopt = "0.5" 26 | ``` 27 | 28 | ## Example 29 | 30 | ### demo 31 | 32 | This is a single monolithic `demo` example, which runs nearly the entire feature matrix. In `demo`, the `opt_complete` routine is the approach to get 100% optimal GPU performance. Further CPU improvements can be chosen through the various packing and encoding routines. 33 | 34 | ```shell 35 | cargo run --release --example demo 36 | ``` 37 | 38 | ### encoder 39 | 40 | The `encoder` example shows the minimal calls to perform mesh optimization in a typical game engine pipeline, and serializes the mesh into a format that is compatible with the WebAssembly loader and THREE.js viewer experiment that exists in the `meshoptimizer` repository. 41 | 42 | * https://github.com/zeux/meshoptimizer/blob/master/tools/OptMeshLoader.js 43 | * https://github.com/zeux/meshoptimizer/blob/master/demo/index.html 44 | 45 | ```shell 46 | cargo run --release --example encoder -- --input pirate.obj --output pirate.optmesh 47 | ``` 48 | 49 | ## Pipeline 50 | 51 | When optimizing a mesh, you should typically feed it through a set of optimizations (the order is important!): 52 | 53 | 1. Indexing 54 | 2. Vertex cache optimization 55 | 3. Overdraw optimization 56 | 4. Vertex fetch optimization 57 | 5. Vertex quantization 58 | 6. (optional) Vertex/index buffer compression 59 | 60 | ## Indexing 61 | 62 | 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 using `generate_vertex_remap`. 63 | 64 | After generating the remap table, you can perform remapping with `remap_index_buffer` and `remap_vertex_buffer`. 65 | 66 | You can then further optimize the resulting buffers by calling the other functions on them in-place. 67 | 68 | ## Vertex cache optimization 69 | 70 | 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; this reordering can be done with `optimize_vertex_cache`. 71 | 72 | ## Overdraw optimization 73 | 74 | 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; the routine for this is `optimize_overdraw`. 75 | 76 | 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. 77 | 78 | ## Vertex fetch optimization 79 | 80 | 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: 81 | 82 | To optimize the index/vertex buffers for vertex fetch efficiency, call `optimize_vertex_fetch`. 83 | 84 | 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 `optimize_vertex_fetch_remap` instead. This optimization has to be performed on the final index buffer since the optimal vertex order depends on the triangle order. 85 | 86 | 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. 87 | 88 | ## Vertex quantization 89 | 90 | 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. 91 | 92 | 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. 93 | 94 | 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. 95 | 96 | Relevant routines: 97 | 98 | - `quantize_unorm` 99 | - `quantize_snorm` 100 | - `quantize_half` 101 | - `quantize_float` 102 | 103 | ## Vertex/index buffer compression 104 | 105 | After all of the above optimizations, the geometry data is optimal for GPU to consume - however, you don't have to store the data as is. In case storage size or transmission bandwidth is of importance, you might want to 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 preserving the vertex/index order (which makes the meshes inefficient to render on GPU) or decompression performance. Additionally they 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. 106 | 107 | 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. 108 | 109 | 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 et al, to maximize compression rate. 110 | 111 | To encode, the `encode_vertex_buffer` and `encode_index_buffer` routines can be used. The encoded data can be serialized as is, or compressed further. Decoding at runtime can be performed with the `decode_vertex_buffer` and `decode_index_buffer` routines. 112 | 113 | 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 rates. Both codecs are lossless - the only lossy step is quantization that happens before encoding. 114 | 115 | Decoding functions are heavily optimized and can directly target write-combined memory; you can expect both decoders to run at 1-2 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. 116 | 117 | ## Triangle strip conversion 118 | 119 | On most hardware, indexed triangle lists are the most efficient way to drive the GPU. However, in some cases triangle strips might prove beneficial: 120 | 121 | - On some older GPUs, triangle strips may be a bit more efficient to render 122 | - On extremely memory constrained systems, index buffers for triangle strips could save a bit of memory 123 | 124 | This library provides the `stripify` routine for converting a vertex cache optimized triangle list to a triangle strip; the inverse can be performed with the `unstripify` routine. 125 | 126 | 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 require restart index support for rendering; using degenerate triangles to connect strips is not supported. 127 | 128 | ## Efficiency analyzers 129 | 130 | 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 `analyze_overdraw`, that returns a struct with statistics. 131 | 132 | `analyze_vertex_cache` 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). 133 | 134 | `analyze_vertex_fetch` 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. 135 | 136 | `analyze_overdraw` 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. 137 | 138 | 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. 139 | 140 | ## License 141 | 142 | Licensed under either of 143 | 144 | * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 145 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 146 | 147 | at your option. 148 | 149 | ## Credits and Special Thanks 150 | 151 | - [Arseny Kapoulkine](https://github.com/zeux) (Author of C/C++ library) 152 | - [Daniel Collin](https://github.com/emoon) (Code review) 153 | - [Jake Shadle](https://github.com/Jake-Shadle) (Code review) 154 | - [Thomas Herzog](https://github.com/karroffel) (Contributions) 155 | - [Alexandru Ene](https://github.com/AlexEne) (Contributions) 156 | - [Ralf Jung](https://github.com/RalfJung) (Contributions) 157 | - [Maxime Rouyrre](https://github.com/blaze33) (Contributions) 158 | - [Shiwei Wang](https://github.com/wsw0108) (Contributions) 159 | - Simon Chopin (Contributions) 160 | 161 | ## Contribution 162 | 163 | Unless you explicitly state otherwise, any contribution intentionally submitted 164 | for inclusion in this crate by you, as defined in the Apache-2.0 license, shall 165 | be dual licensed as above, without any additional terms or conditions. 166 | 167 | Contributions are always welcome; please look at the [issue tracker](https://github.com/gwihlidal/meshopt-rs/issues) to see what 168 | known improvements are documented. 169 | 170 | ## Code of Conduct 171 | 172 | Contribution to the meshopt crate is organized under the terms of the 173 | Contributor Covenant, the maintainer of meshopt, @gwihlidal, promises to 174 | intervene to uphold that code of conduct. 175 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | fn main() { 4 | let mut build = cc::Build::new(); 5 | 6 | build.include("src"); 7 | 8 | // Add the files we build 9 | let source_files = [ 10 | "vendor/src/allocator.cpp", 11 | "vendor/src/clusterizer.cpp", 12 | "vendor/src/indexcodec.cpp", 13 | "vendor/src/indexgenerator.cpp", 14 | "vendor/src/overdrawanalyzer.cpp", 15 | "vendor/src/overdrawoptimizer.cpp", 16 | "vendor/src/partition.cpp", 17 | "vendor/src/quantization.cpp", 18 | "vendor/src/simplifier.cpp", 19 | "vendor/src/spatialorder.cpp", 20 | "vendor/src/stripifier.cpp", 21 | "vendor/src/vcacheanalyzer.cpp", 22 | "vendor/src/vcacheoptimizer.cpp", 23 | "vendor/src/vertexcodec.cpp", 24 | "vendor/src/vertexfilter.cpp", 25 | "vendor/src/vfetchanalyzer.cpp", 26 | "vendor/src/vfetchoptimizer.cpp", 27 | ]; 28 | 29 | for source_file in &source_files { 30 | build.file(source_file); 31 | } 32 | 33 | let target = env::var("TARGET").unwrap(); 34 | if target.contains("darwin") { 35 | build 36 | .flag("-std=c++11") 37 | .cpp_link_stdlib("c++") 38 | .cpp_set_stdlib("c++") 39 | .cpp(true); 40 | } else if target.contains("linux") || target.contains("windows-gnu") { 41 | build.flag("-std=c++11").cpp_link_stdlib("stdc++").cpp(true); 42 | } 43 | 44 | if target.starts_with("wasm32") { 45 | // In webassembly there's no stdlib, so we use 46 | // our own stripped down headers to provide the few 47 | // functions needed via LLVM intrinsics. 48 | build.flag("-isystem").flag("include_wasm32"); 49 | // The Wasm backend needs a compatible ar 50 | // which will most likely be available under 51 | // this name on Windows, via manual LLVM install 52 | let host = env::var("HOST").unwrap(); 53 | if host.contains("windows") { 54 | build.archiver("llvm-ar"); 55 | } 56 | } 57 | 58 | build.compile("meshopt_cpp"); 59 | 60 | generate_bindings("gen/bindings.rs"); 61 | } 62 | 63 | #[cfg(feature = "generate_bindings")] 64 | fn generate_bindings(output_file: &str) { 65 | let bindings = bindgen::Builder::default() 66 | .header("vendor/src/meshoptimizer.h") 67 | .derive_debug(true) 68 | .impl_debug(true) 69 | .blocklist_type("__darwin_.*") 70 | .allowlist_function("meshopt.*") 71 | .trust_clang_mangling(false) 72 | .layout_tests(false) 73 | .size_t_is_usize(true) 74 | .generate() 75 | .expect("Unable to generate bindings!"); 76 | 77 | bindings 78 | .write_to_file(std::path::Path::new(output_file)) 79 | .expect("Unable to write bindings!"); 80 | } 81 | 82 | #[cfg(not(feature = "generate_bindings"))] 83 | fn generate_bindings(_: &str) {} 84 | -------------------------------------------------------------------------------- /examples/cube.mtl: -------------------------------------------------------------------------------- 1 | newmtl white 2 | Ka 0 0 0 3 | Kd 1 1 1 4 | Ks 0 0 0 5 | 6 | newmtl red 7 | Ka 0 0 0 8 | Kd 1 0 0 9 | Ks 0 0 0 10 | 11 | newmtl green 12 | Ka 0 0 0 13 | Kd 0 1 0 14 | Ks 0 0 0 15 | 16 | newmtl blue 17 | Ka 0 0 0 18 | Kd 0 0 1 19 | Ks 0 0 0 20 | 21 | newmtl light 22 | Ka 20 20 20 23 | Kd 1 1 1 24 | Ks 0 0 0 -------------------------------------------------------------------------------- /examples/cube.obj: -------------------------------------------------------------------------------- 1 | mtllib cube.mtl 2 | 3 | v 0.000000 2.000000 2.000000 4 | v 0.000000 0.000000 2.000000 5 | v 2.000000 0.000000 2.000000 6 | v 2.000000 2.000000 2.000000 7 | v 0.000000 2.000000 0.000000 8 | v 0.000000 0.000000 0.000000 9 | v 2.000000 0.000000 0.000000 10 | v 2.000000 2.000000 0.000000 11 | # 8 vertices 12 | 13 | g front cube 14 | usemtl white 15 | f 1 2 3 4 16 | # two white spaces between 'back' and 'cube' 17 | g back cube 18 | # expects white material 19 | f 8 7 6 5 20 | g right cube 21 | usemtl red 22 | f 4 3 7 8 23 | g top cube 24 | usemtl white 25 | f 5 1 4 8 26 | g left cube 27 | usemtl green 28 | f 5 6 2 1 29 | g bottom cube 30 | usemtl white 31 | f 2 6 7 3 32 | # 6 elements -------------------------------------------------------------------------------- /examples/cube.optmesh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwihlidal/meshopt-rs/c2165927e09c557e717f6fcb6b7690bee65f6c90/examples/cube.optmesh -------------------------------------------------------------------------------- /examples/demo.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::identity_op)] 2 | 3 | use memoffset::offset_of; 4 | use meshopt::*; 5 | use rand::seq::SliceRandom; 6 | use std::{ 7 | fmt, 8 | fs::File, 9 | io::Write, 10 | mem, 11 | path::{Path, PathBuf}, 12 | time::{Duration, Instant}, 13 | }; 14 | 15 | const CACHE_SIZE: usize = 16; 16 | 17 | fn elapsed_to_ms(elapsed: Duration) -> f32 { 18 | elapsed.subsec_nanos() as f32 / 1_000_000.0 + elapsed.as_secs() as f32 * 1_000.0 19 | } 20 | 21 | #[derive(Default, Debug, Copy, Clone)] 22 | #[repr(C)] 23 | struct Triangle { 24 | v: [Vertex; 3], 25 | } 26 | 27 | impl Triangle { 28 | fn rotate(&mut self) -> bool { 29 | if self.v[1] < self.v[2] && self.v[0] > self.v[1] { 30 | // 1 is minimum, rotate 012 => 120 31 | let tv = self.v[0]; 32 | self.v[0] = self.v[1]; 33 | self.v[1] = self.v[2]; 34 | self.v[2] = tv; 35 | } else if self.v[0] > self.v[2] && self.v[1] > self.v[2] { 36 | // 2 is minimum, rotate 012 => 201 37 | let tv = self.v[2]; 38 | self.v[2] = self.v[1]; 39 | self.v[1] = self.v[0]; 40 | self.v[0] = tv; 41 | } 42 | self.v[0] != self.v[1] && self.v[0] != self.v[2] && self.v[1] != self.v[2] 43 | } 44 | } 45 | 46 | impl Ord for Triangle { 47 | fn cmp(&self, other: &Triangle) -> std::cmp::Ordering { 48 | let lhs = meshopt::utilities::any_as_u8_slice(self); 49 | let rhs = meshopt::utilities::any_as_u8_slice(other); 50 | lhs.cmp(rhs) 51 | } 52 | } 53 | 54 | impl PartialOrd for Triangle { 55 | fn partial_cmp(&self, other: &Self) -> Option { 56 | Some(self.cmp(other)) 57 | } 58 | } 59 | 60 | impl PartialEq for Triangle { 61 | fn eq(&self, other: &Self) -> bool { 62 | self.cmp(other) == std::cmp::Ordering::Equal 63 | } 64 | } 65 | 66 | impl Eq for Triangle {} 67 | 68 | #[derive(Default, Clone)] 69 | struct Mesh { 70 | vertices: Vec, 71 | indices: Vec, 72 | } 73 | 74 | impl PartialEq for Mesh { 75 | fn eq(&self, other: &Mesh) -> bool { 76 | let mut lt = self.deindex(); 77 | let mut rt = other.deindex(); 78 | lt.sort(); 79 | rt.sort(); 80 | lt == rt 81 | } 82 | } 83 | 84 | impl Eq for Mesh {} 85 | 86 | impl fmt::Debug for Mesh { 87 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 88 | write!( 89 | f, 90 | "vertices: {}, indices: {}", 91 | self.vertices.len(), 92 | self.indices.len() 93 | ) 94 | } 95 | } 96 | 97 | impl Mesh { 98 | fn is_valid(&self) -> bool { 99 | if self.indices.len() % 3 != 0 { 100 | return false; 101 | } else { 102 | for i in 0..self.indices.len() { 103 | if self.indices[i] as usize >= self.vertices.len() { 104 | return false; 105 | } 106 | } 107 | } 108 | true 109 | } 110 | 111 | fn load_obj(path: &Path) -> Self { 112 | let obj = tobj::load_obj( 113 | path, 114 | &tobj::LoadOptions { 115 | triangulate: true, 116 | single_index: true, 117 | ..Default::default() 118 | }, 119 | ); 120 | assert!(obj.is_ok()); 121 | let (models, _materials) = obj.unwrap(); 122 | 123 | assert!(models.len() == 1); 124 | 125 | let mut merged_vertices: Vec = Vec::new(); 126 | 127 | let mut total_indices = 0; 128 | 129 | for m in models.iter() { 130 | let mut vertices: Vec = Vec::new(); 131 | let mesh = &m.mesh; 132 | total_indices += mesh.indices.len(); 133 | 134 | for i in 0..mesh.indices.len() { 135 | let index = mesh.indices[i] as usize; 136 | 137 | // pos = [x, y, z] 138 | let p = [ 139 | mesh.positions[index * 3], 140 | mesh.positions[index * 3 + 1], 141 | mesh.positions[index * 3 + 2], 142 | ]; 143 | 144 | let n = if !mesh.normals.is_empty() { 145 | // normal = [x, y, z] 146 | [ 147 | mesh.normals[index * 3], 148 | mesh.normals[index * 3 + 1], 149 | mesh.normals[index * 3 + 2], 150 | ] 151 | } else { 152 | [0f32, 0f32, 0f32] 153 | }; 154 | 155 | let t = if !mesh.texcoords.is_empty() { 156 | // tex coord = [u, v]; 157 | [mesh.texcoords[index * 2], mesh.texcoords[index * 2 + 1]] 158 | } else { 159 | [0f32, 0f32] 160 | }; 161 | 162 | vertices.push(Vertex { p, n, t }); 163 | } 164 | 165 | merged_vertices.append(&mut vertices); 166 | } 167 | 168 | let (total_vertices, vertex_remap) = meshopt::generate_vertex_remap(&merged_vertices, None); 169 | 170 | let mut mesh = Self::default(); 171 | 172 | mesh.indices.resize(total_indices, 0u32); 173 | unsafe { 174 | meshopt::ffi::meshopt_remapIndexBuffer( 175 | mesh.indices.as_ptr() as *mut ::std::os::raw::c_uint, 176 | ::std::ptr::null(), 177 | total_indices, 178 | vertex_remap.as_ptr() as *const ::std::os::raw::c_uint, 179 | ); 180 | } 181 | 182 | mesh.vertices.resize(total_vertices, Vertex::default()); 183 | unsafe { 184 | meshopt::ffi::meshopt_remapVertexBuffer( 185 | mesh.vertices.as_ptr() as *mut ::std::os::raw::c_void, 186 | merged_vertices.as_ptr() as *const ::std::os::raw::c_void, 187 | total_indices, 188 | mem::size_of::(), 189 | vertex_remap.as_ptr() as *const ::std::os::raw::c_uint, 190 | ); 191 | } 192 | 193 | println!( 194 | "# {:?}: {} vertices, {} triangles", 195 | path, 196 | mesh.vertices.len(), 197 | mesh.indices.len() / 3 198 | ); 199 | 200 | mesh 201 | } 202 | 203 | #[allow(dead_code)] 204 | fn save_obj(&self, path: &Path) -> std::io::Result<()> { 205 | let mut buffer = File::create(path)?; 206 | 207 | for i in 0..self.vertices.len() { 208 | writeln!( 209 | buffer, 210 | "v {} {} {}", 211 | self.vertices[i].p[0], self.vertices[i].p[1], self.vertices[i].p[2] 212 | )?; 213 | writeln!( 214 | buffer, 215 | "vn {} {} {}", 216 | self.vertices[i].n[0], self.vertices[i].n[1], self.vertices[i].n[2] 217 | )?; 218 | writeln!( 219 | buffer, 220 | "vt {} {} {}", 221 | self.vertices[i].t[0], self.vertices[i].t[1], 0f32 222 | )?; 223 | } 224 | 225 | for i in (0..self.indices.len()).step_by(3) { 226 | let i0 = self.indices[i + 0] + 1; 227 | let i1 = self.indices[i + 1] + 1; 228 | let i2 = self.indices[i + 2] + 1; 229 | writeln!( 230 | buffer, 231 | "f {}/{}/{} {}/{}/{} {}/{}/{}", 232 | i0, i0, i0, i1, i1, i1, i2, i2, i2 233 | )?; 234 | } 235 | 236 | Ok(()) 237 | } 238 | 239 | fn create_plane(size: u32) -> Self { 240 | let mut mesh = Self { 241 | vertices: Vec::with_capacity((size as usize + 1) * (size as usize + 1)), 242 | indices: Vec::with_capacity(size as usize * size as usize * 6), 243 | }; 244 | 245 | for y in 0..(size + 1) { 246 | for x in 0..(size + 1) { 247 | mesh.vertices.push(Vertex { 248 | p: [x as f32, y as f32, 0f32], 249 | n: [0f32, 0f32, 1f32], 250 | t: [x as f32 / size as f32, y as f32 / size as f32], 251 | }); 252 | } 253 | } 254 | 255 | for y in 0..size { 256 | for x in 0..size { 257 | mesh.indices.push((y + 0) * (size + 1) + (x + 0)); 258 | mesh.indices.push((y + 0) * (size + 1) + (x + 1)); 259 | mesh.indices.push((y + 1) * (size + 1) + (x + 0)); 260 | 261 | mesh.indices.push((y + 1) * (size + 1) + (x + 0)); 262 | mesh.indices.push((y + 0) * (size + 1) + (x + 1)); 263 | mesh.indices.push((y + 1) * (size + 1) + (x + 1)); 264 | } 265 | } 266 | 267 | println!( 268 | "# tessellated plane: {} vertices, {} triangles", 269 | mesh.vertices.len(), 270 | mesh.indices.len() / 3 271 | ); 272 | mesh 273 | } 274 | 275 | fn deindex(&self) -> Vec { 276 | let tri_count = self.indices.len() / 3; 277 | let mut result = Vec::with_capacity(tri_count); 278 | 279 | for i in 0..tri_count { 280 | let i0 = self.indices[i * 3 + 0]; 281 | let i1 = self.indices[i * 3 + 1]; 282 | let i2 = self.indices[i * 3 + 2]; 283 | let mut tri = Triangle { 284 | v: [ 285 | self.vertices[i0 as usize], 286 | self.vertices[i1 as usize], 287 | self.vertices[i2 as usize], 288 | ], 289 | }; 290 | 291 | // skip degenerate triangles since some algorithms don't preserve them 292 | if tri.rotate() { 293 | result.push(tri); 294 | } 295 | } 296 | 297 | result 298 | } 299 | 300 | fn split(&mut self) -> (VertexDataAdapter, &mut [u32]) { 301 | let position_offset = offset_of!(Vertex, p); 302 | let vertex_stride = std::mem::size_of::(); 303 | let vertex_data = typed_to_bytes(&self.vertices); 304 | ( 305 | VertexDataAdapter::new(vertex_data, vertex_stride, position_offset) 306 | .expect("failed to create vertex data reader"), 307 | &mut self.indices, 308 | ) 309 | } 310 | 311 | fn vertex_adapter(&self) -> VertexDataAdapter { 312 | let position_offset = offset_of!(Vertex, p); 313 | let vertex_stride = std::mem::size_of::(); 314 | let vertex_data = typed_to_bytes(&self.vertices); 315 | 316 | VertexDataAdapter::new(vertex_data, vertex_stride, position_offset) 317 | .expect("failed to create vertex data reader") 318 | } 319 | } 320 | 321 | fn optimize_mesh(mesh: &Mesh, name: &str, opt: fn(mesh: &mut Mesh)) { 322 | let mut copy = mesh.clone(); 323 | 324 | assert_eq!(mesh, ©); 325 | assert!(copy.is_valid()); 326 | 327 | let optimize_start = Instant::now(); 328 | opt(&mut copy); 329 | let optimize_elapsed = optimize_start.elapsed(); 330 | 331 | let vertex_adapter = copy.vertex_adapter(); 332 | 333 | let vcs = 334 | meshopt::analyze_vertex_cache(©.indices, copy.vertices.len(), CACHE_SIZE as u32, 0, 0); 335 | 336 | let vfs = 337 | meshopt::analyze_vertex_fetch(©.indices, copy.vertices.len(), mem::size_of::()); 338 | 339 | let os = meshopt::analyze_overdraw(©.indices, &vertex_adapter); 340 | 341 | let vcs_nv = meshopt::analyze_vertex_cache(©.indices, copy.vertices.len(), 32, 32, 32); 342 | 343 | let vcs_amd = meshopt::analyze_vertex_cache(©.indices, copy.vertices.len(), 14, 64, 128); 344 | 345 | let vcs_intel = meshopt::analyze_vertex_cache(©.indices, copy.vertices.len(), 128, 0, 0); 346 | 347 | println!( 348 | "{:9}: ACMR {:.6} ATVR {:.6} (NV {:.6} AMD {:.6} Intel {:.6}) Overfetch {:.6} Overdraw {:.6} in {:.2} msec", 349 | name, 350 | vcs.acmr, 351 | vcs.atvr, 352 | vcs_nv.atvr, 353 | vcs_amd.atvr, 354 | vcs_intel.atvr, 355 | vfs.overfetch, 356 | os.overdraw, 357 | elapsed_to_ms(optimize_elapsed), 358 | ); 359 | } 360 | 361 | fn opt_none(_: &mut Mesh) { 362 | // no-op 363 | } 364 | 365 | fn opt_random_shuffle(mesh: &mut Mesh) { 366 | let face_count = mesh.indices.len() / 3; 367 | let mut faces: Vec = (0..face_count).collect(); 368 | let mut rng = rand::rng(); 369 | faces.shuffle(&mut rng); 370 | 371 | let mut result: Vec = Vec::with_capacity(mesh.indices.len()); 372 | faces.iter().for_each(|face| { 373 | result.push(mesh.indices[faces[*face] * 3 + 0]); 374 | result.push(mesh.indices[faces[*face] * 3 + 1]); 375 | result.push(mesh.indices[faces[*face] * 3 + 2]); 376 | }); 377 | 378 | mesh.indices = result; 379 | } 380 | 381 | fn opt_cache(mesh: &mut Mesh) { 382 | meshopt::optimize_vertex_cache_in_place(&mut mesh.indices, mesh.vertices.len()); 383 | } 384 | 385 | fn opt_cache_fifo(mesh: &mut Mesh) { 386 | meshopt::optimize_vertex_cache_fifo_in_place( 387 | &mut mesh.indices, 388 | mesh.vertices.len(), 389 | CACHE_SIZE as u32, 390 | ); 391 | } 392 | 393 | fn opt_overdraw(mesh: &mut Mesh) { 394 | let (vertex_adapter, indices) = mesh.split(); 395 | 396 | // use worst-case ACMR threshold so that overdraw optimizer can sort *all* triangles 397 | // warning: this significantly deteriorates the vertex cache efficiency so it is not advised; look at `opt_complete` for the recommended method 398 | let threshold = 3f32; 399 | meshopt::optimize_overdraw_in_place(indices, &vertex_adapter, threshold); 400 | } 401 | 402 | fn opt_fetch(mesh: &mut Mesh) { 403 | meshopt::optimize_vertex_fetch_in_place(&mut mesh.indices, &mut mesh.vertices); 404 | } 405 | 406 | fn opt_fetch_remap(mesh: &mut Mesh) { 407 | let remap = meshopt::optimize_vertex_fetch_remap(&mesh.indices, mesh.vertices.len()); 408 | mesh.indices = meshopt::remap_index_buffer(Some(&mesh.indices), mesh.indices.len(), &remap); 409 | mesh.vertices = meshopt::remap_vertex_buffer(&mesh.vertices, mesh.vertices.len(), &remap); 410 | } 411 | 412 | fn opt_complete(mesh: &mut Mesh) { 413 | { 414 | let (vertex_adapter, indices) = mesh.split(); 415 | 416 | // vertex cache optimization should go first as it provides starting order for overdraw 417 | meshopt::optimize_vertex_cache_in_place(indices, vertex_adapter.vertex_count); 418 | 419 | // reorder indices for overdraw, balancing overdraw and vertex cache efficiency 420 | let threshold = 1.05f32; // allow up to 5% worse ACMR to get more reordering opportunities for overdraw 421 | meshopt::optimize_overdraw_in_place(indices, &vertex_adapter, threshold); 422 | } 423 | 424 | // vertex fetch optimization should go last as it depends on the final index order 425 | let final_size = meshopt::optimize_vertex_fetch_in_place(&mut mesh.indices, &mut mesh.vertices); 426 | mesh.vertices.resize(final_size, Default::default()); 427 | } 428 | 429 | fn stripify(mesh: &Mesh, use_restart: bool) { 430 | let restart_index = if use_restart { 0xffffffff } else { 0x00000000 }; 431 | 432 | let process_start = Instant::now(); 433 | let strip = meshopt::stripify(&mesh.indices, mesh.vertices.len(), restart_index).unwrap(); 434 | let process_elapsed = process_start.elapsed(); 435 | 436 | let mut copy = mesh.clone(); 437 | copy.indices = meshopt::unstripify(&strip, restart_index).unwrap(); 438 | 439 | assert!(copy.is_valid()); 440 | assert_eq!(mesh, ©); 441 | 442 | let vcs = 443 | meshopt::analyze_vertex_cache(©.indices, copy.vertices.len(), CACHE_SIZE as u32, 0, 0); 444 | let vcs_nv = meshopt::analyze_vertex_cache(©.indices, copy.vertices.len(), 32, 32, 32); 445 | let vcs_amd = meshopt::analyze_vertex_cache(©.indices, copy.vertices.len(), 14, 64, 128); 446 | let vcs_intel = meshopt::analyze_vertex_cache(©.indices, copy.vertices.len(), 128, 0, 0); 447 | 448 | println!("Stripify{}: ACMR {:.6} ATVR {:.6} (NV {:.6} AMD {:.6} Intel {:.6}); {} strip indices ({:.1}%) in {:.2} msec", 449 | if use_restart { "R" } else { " " }, 450 | vcs.acmr, 451 | vcs.atvr, 452 | vcs_nv.atvr, 453 | vcs_amd.atvr, 454 | vcs_intel.atvr, 455 | strip.len() as i32, 456 | strip.len() as f64 / mesh.indices.len() as f64 * 100f64, 457 | elapsed_to_ms(process_elapsed), 458 | ); 459 | } 460 | 461 | fn shadow(mesh: &Mesh) { 462 | let process_start = Instant::now(); 463 | let vertex_adapter = mesh.vertex_adapter(); 464 | let mut shadow_indices = meshopt::generate_shadow_indices(&mesh.indices, &vertex_adapter); 465 | let process_elapsed = process_start.elapsed(); 466 | 467 | // While you can't optimize the vertex data after shadow IB was constructed, you can and should optimize 468 | // the shadow IB for vertex cache. This is valuable even if the original indices array was optimized for 469 | // vertex cache! 470 | meshopt::optimize_vertex_cache_in_place(&mut shadow_indices, mesh.vertices.len()); 471 | 472 | let vcs = 473 | meshopt::analyze_vertex_cache(&mesh.indices, mesh.vertices.len(), CACHE_SIZE as u32, 0, 0); 474 | let vcss = meshopt::analyze_vertex_cache( 475 | &shadow_indices, 476 | mesh.vertices.len(), 477 | CACHE_SIZE as u32, 478 | 0, 479 | 0, 480 | ); 481 | 482 | let mut shadow_flags: Vec = vec![0; mesh.vertices.len()]; 483 | let mut shadow_vertices: usize = 0; 484 | for shadow_index in shadow_indices { 485 | shadow_vertices += 1 - shadow_flags[shadow_index as usize]; 486 | shadow_flags[shadow_index as usize] = 1; 487 | } 488 | 489 | println!("ShadowIB : ACMR {:.6} ({:.2}x improvement); {} shadow vertices ({:.2}x improvement) in {:.2} msec", 490 | vcss.acmr, 491 | vcs.vertices_transformed as f64 / vcss.vertices_transformed as f64, 492 | shadow_vertices, 493 | mesh.vertices.len() as f64 / shadow_vertices as f64, 494 | elapsed_to_ms(process_elapsed) 495 | ); 496 | } 497 | 498 | fn meshlets(mesh: &Mesh) { 499 | let max_vertices = 64; 500 | let max_triangles = 124; 501 | 502 | let vertex_adapter = mesh.vertex_adapter(); 503 | 504 | let process_start = Instant::now(); 505 | let meshlets = meshopt::build_meshlets( 506 | &mesh.indices, 507 | &vertex_adapter, 508 | max_vertices, 509 | max_triangles, 510 | 0.5, // cone weight 511 | ); 512 | let process_elapsed = process_start.elapsed(); 513 | 514 | let mut avg_vertices = 0f64; 515 | let mut avg_triangles = 0f64; 516 | let mut not_full = 0usize; 517 | 518 | for meshlet in &meshlets.meshlets { 519 | avg_vertices += meshlet.vertex_count as f64; 520 | avg_triangles += meshlet.triangle_count as f64; 521 | not_full += if (meshlet.vertex_count as usize) < max_vertices { 522 | 1 523 | } else { 524 | 0 525 | }; 526 | } 527 | 528 | avg_vertices /= meshlets.len() as f64; 529 | avg_triangles /= meshlets.len() as f64; 530 | 531 | println!("Meshlets : {} meshlets (avg vertices {:.1}, avg triangles {:.1}, not full {}) in {:.2} msec", 532 | meshlets.len(), 533 | avg_vertices, 534 | avg_triangles, 535 | not_full, 536 | elapsed_to_ms(process_elapsed)); 537 | 538 | let camera: [f32; 3] = [100.0, 100.0, 100.0]; 539 | 540 | let mut rejected = 0; 541 | let mut rejected_s8 = 0; 542 | let mut rejected_alt = 0; 543 | let mut rejected_alt_s8 = 0; 544 | let mut accepted = 0; 545 | let mut accepted_s8 = 0; 546 | 547 | let test_start = Instant::now(); 548 | for meshlet in meshlets.iter() { 549 | let bounds = meshopt::compute_meshlet_bounds(meshlet, &vertex_adapter); 550 | 551 | // trivial accept: we can't ever backface cull this meshlet 552 | if bounds.cone_cutoff >= 1f32 { 553 | accepted += 1; 554 | } 555 | 556 | if bounds.cone_cutoff_s8 == 127 { 557 | accepted_s8 += 1; 558 | } 559 | 560 | // perspective projection: dot(normalize(cone_apex - camera_position), cone_axis) > cone_cutoff 561 | let mview: [f32; 3] = [ 562 | bounds.cone_apex[0] - camera[0], 563 | bounds.cone_apex[1] - camera[1], 564 | bounds.cone_apex[2] - camera[2], 565 | ]; 566 | 567 | let mviewlength = (mview[0] * mview[0] + mview[1] * mview[1] + mview[2] * mview[2]).sqrt(); 568 | 569 | if mview[0] * bounds.cone_axis[0] 570 | + mview[1] * bounds.cone_axis[1] 571 | + mview[2] * bounds.cone_axis[2] 572 | >= bounds.cone_cutoff * mviewlength 573 | { 574 | rejected += 1; 575 | } 576 | 577 | if mview[0] * (bounds.cone_axis_s8[0] as f32 / 127.0) 578 | + mview[1] * (bounds.cone_axis_s8[1] as f32 / 127.0) 579 | + mview[2] * (bounds.cone_axis_s8[2] as f32 / 127.0) 580 | >= (bounds.cone_cutoff_s8 as f32 / 127.0) * mviewlength 581 | { 582 | rejected_s8 += 1; 583 | } 584 | 585 | // alternative formulation for perspective projection that doesn't use apex (and uses cluster bounding sphere instead): 586 | // dot(normalize(center - camera_position), cone_axis) > cone_cutoff + radius / length(center - camera_position) 587 | let cview: [f32; 3] = [ 588 | bounds.center[0] - camera[0], 589 | bounds.center[1] - camera[1], 590 | bounds.center[2] - camera[2], 591 | ]; 592 | 593 | let cviewlength = (cview[0] * cview[0] + cview[1] * cview[1] + cview[2] * cview[2]).sqrt(); 594 | 595 | if cview[0] * bounds.cone_axis[0] 596 | + cview[1] * bounds.cone_axis[1] 597 | + cview[2] * bounds.cone_axis[2] 598 | >= bounds.cone_cutoff * cviewlength + bounds.radius 599 | { 600 | rejected_alt += 1; 601 | } 602 | 603 | if cview[0] * (bounds.cone_axis_s8[0] as f32 / 127.0) 604 | + cview[1] * (bounds.cone_axis_s8[1] as f32 / 127.0) 605 | + cview[2] * (bounds.cone_axis_s8[2] as f32 / 127.0) 606 | >= (bounds.cone_cutoff_s8 as f32 / 127.0) * cviewlength + bounds.radius 607 | { 608 | rejected_alt_s8 += 1; 609 | } 610 | } 611 | let test_elapsed = test_start.elapsed(); 612 | 613 | println!("ConeCull : rejected apex {} ({:.1}%) / center {} ({:.1}%), trivially accepted {} ({:.1}%) in {:.2} msec", 614 | rejected, 615 | rejected as f64 / (meshlets.len() as f64) * 100.0, 616 | rejected_alt, 617 | rejected_alt as f64 / (meshlets.len() as f64) * 100.0, 618 | accepted, 619 | accepted as f64 / (meshlets.len() as f64) * 100.0, 620 | elapsed_to_ms(test_elapsed)); 621 | 622 | println!("ConeCull8: rejected apex {} ({:.1}%) / center {} ({:.1}%), trivially accepted {} ({:.1}%) in {:.2} msec", 623 | rejected_s8, 624 | rejected_s8 as f64 / (meshlets.len() as f64) * 100.0, 625 | rejected_alt_s8, 626 | rejected_alt_s8 as f64 / (meshlets.len() as f64) * 100.0, 627 | accepted_s8, 628 | accepted_s8 as f64 / (meshlets.len() as f64) * 100.0, 629 | elapsed_to_ms(test_elapsed)); 630 | } 631 | 632 | fn simplify(mesh: &Mesh) { 633 | let lod_count = 5; 634 | 635 | let process_start = Instant::now(); 636 | 637 | let vertex_adapter = mesh.vertex_adapter(); 638 | 639 | // generate 4 LOD levels (1-4), with each subsequent LOD using 70% triangles 640 | // note that each LOD uses the same (shared) vertex buffer 641 | let mut lods: Vec> = Vec::with_capacity(lod_count); 642 | lods.push(mesh.indices.clone()); 643 | 644 | for i in 1..lod_count { 645 | let threshold = 0.7f32.powf(i as f32); 646 | let target_index_count = (mesh.indices.len() as f32 * threshold) as usize / 3 * 3; 647 | let target_error = 1e-3f32; 648 | let lod: Vec; 649 | { 650 | // we can simplify all the way from base level or from the last result 651 | // simplifying from the base level sometimes produces better results, but simplifying from last level is faster 652 | let src = &lods[lods.len() - 1]; 653 | lod = meshopt::simplify( 654 | src, 655 | &vertex_adapter, 656 | ::std::cmp::min(src.len(), target_index_count), 657 | target_error, 658 | SimplifyOptions::None, 659 | None, 660 | ); 661 | } 662 | lods.push(lod); 663 | } 664 | 665 | let process_elapsed = process_start.elapsed(); 666 | let optimize_start = Instant::now(); 667 | 668 | // optimize each individual LOD for vertex cache & overdraw 669 | for lod in &mut lods { 670 | meshopt::optimize_vertex_cache_in_place(lod, vertex_adapter.vertex_count); 671 | meshopt::optimize_overdraw_in_place(lod, &vertex_adapter, 1f32); 672 | } 673 | 674 | // concatenate all LODs into one IB 675 | // note: the order of concatenation is important - since we optimize the entire IB for vertex fetch, 676 | // putting coarse LODs first makes sure that the vertex range referenced by them is as small as possible 677 | // some GPUs process the entire range referenced by the index buffer region so doing this optimizes the vertex transform 678 | // cost for coarse LODs 679 | // this order also produces much better vertex fetch cache coherency for coarse LODs (since they're essentially optimized first) 680 | // somewhat surprisingly, the vertex fetch cache coherency for fine LODs doesn't seem to suffer that much. 681 | let mut lod_offsets: Vec = vec![0; lod_count]; 682 | let mut lod_counts: Vec = vec![0; lod_count]; 683 | 684 | let mut total_index_count: usize = 0; 685 | for i in (0..lod_count).rev() { 686 | lod_offsets[i] = total_index_count; 687 | lod_counts[i] = lods[i].len(); 688 | total_index_count += lod_counts[i]; 689 | } 690 | 691 | let mut indices: Vec = vec![0; total_index_count]; 692 | for i in 0..lod_count { 693 | let lod = &lods[i]; 694 | let offset = lod_offsets[i]; 695 | indices.splice(offset..(offset + lod.len()), lod.iter().cloned()); 696 | } 697 | 698 | // vertex fetch optimization should go last as it depends on the final index order 699 | // note that the order of LODs above affects vertex fetch results 700 | let mut vertices = mesh.vertices.clone(); 701 | let next_vertex = meshopt::optimize_vertex_fetch_in_place(&mut indices, &mut vertices); 702 | vertices.resize(next_vertex, Default::default()); 703 | 704 | let optimize_elapsed = optimize_start.elapsed(); 705 | 706 | println!( 707 | "{:9}: {} triangles => {} LOD levels down to {} triangles in {:.2} msec, optimized in {:.2} msec", 708 | "Simplify", 709 | lod_counts[0] / 3, 710 | lod_count, 711 | lod_counts[lod_count - 1] / 3, 712 | elapsed_to_ms(process_elapsed), 713 | elapsed_to_ms(optimize_elapsed), 714 | ); 715 | 716 | // for using LOD data at runtime, in addition to vertices and indices you have to save lod_index_offsets/lod_index_counts. 717 | let offset_n = lod_count - 1; 718 | 719 | let vcs_0 = meshopt::analyze_vertex_cache( 720 | &indices[lod_offsets[0]..(lod_offsets[0] + lod_counts[0])], 721 | vertices.len(), 722 | CACHE_SIZE as u32, 723 | 0, 724 | 0, 725 | ); 726 | 727 | let vfs_0 = meshopt::analyze_vertex_fetch( 728 | &indices[lod_offsets[0]..(lod_offsets[0] + lod_counts[0])], 729 | vertices.len(), 730 | mem::size_of::(), 731 | ); 732 | 733 | let vcs_n = meshopt::analyze_vertex_cache( 734 | &indices[lod_offsets[offset_n]..(lod_offsets[offset_n] + lod_counts[offset_n])], 735 | vertices.len(), 736 | CACHE_SIZE as u32, 737 | 0, 738 | 0, 739 | ); 740 | 741 | let vfs_n = meshopt::analyze_vertex_fetch( 742 | &indices[lod_offsets[offset_n]..(lod_offsets[offset_n] + lod_counts[offset_n])], 743 | vertices.len(), 744 | mem::size_of::(), 745 | ); 746 | 747 | let packed = pack_vertices::(&vertices); 748 | let encoded_vertices = meshopt::encode_vertex_buffer(&packed).unwrap(); 749 | let encoded_indices = meshopt::encode_index_buffer(&indices, vertices.len()).unwrap(); 750 | 751 | println!("{:9} ACMR {:.6}...{:.6} Overfetch {:.6}..{:.6} Codec VB {:.1} bits/vertex IB {:.1} bits/triangle", 752 | "", 753 | vcs_0.acmr, 754 | vcs_n.acmr, 755 | vfs_0.overfetch, 756 | vfs_n.overfetch, 757 | encoded_vertices.len() as f64 / vertices.len() as f64 * 8f64, 758 | encoded_indices.len() as f64 / (indices.len() as f64 / 3f64) * 8f64 759 | ); 760 | } 761 | 762 | fn encode_index(mesh: &Mesh) { 763 | let encode_start = Instant::now(); 764 | let encoded = meshopt::encode_index_buffer(&mesh.indices, mesh.vertices.len()).unwrap(); 765 | let encode_elapsed = encode_start.elapsed(); 766 | 767 | let decode_start = Instant::now(); 768 | let decoded = meshopt::decode_index_buffer::(&encoded, mesh.indices.len()).unwrap(); 769 | let decode_elapsed = decode_start.elapsed(); 770 | 771 | let compressed = compress(&encoded); 772 | for i in (0..mesh.indices.len()).step_by(3) { 773 | assert!( 774 | (decoded[i + 0] == mesh.indices[i + 0] 775 | && decoded[i + 1] == mesh.indices[i + 1] 776 | && decoded[i + 2] == mesh.indices[i + 2]) 777 | || (decoded[i + 1] == mesh.indices[i + 0] 778 | && decoded[i + 2] == mesh.indices[i + 1] 779 | && decoded[i + 0] == mesh.indices[i + 2]) 780 | || (decoded[i + 2] == mesh.indices[i + 0] 781 | && decoded[i + 0] == mesh.indices[i + 1] 782 | && decoded[i + 1] == mesh.indices[i + 2]) 783 | ); 784 | } 785 | 786 | if mesh.vertices.len() <= 65536 { 787 | let decoded2 = meshopt::decode_index_buffer::(&encoded, mesh.indices.len()).unwrap(); 788 | for i in (0..mesh.indices.len()).step_by(3) { 789 | assert!( 790 | decoded[i + 0] == decoded2[i + 0] as u32 791 | && decoded[i + 1] == decoded2[i + 1] as u32 792 | && decoded[i + 2] == decoded2[i + 2] as u32 793 | ); 794 | } 795 | } 796 | 797 | println!( 798 | "IdxCodec : {:.1} bits/triangle (post-deflate {:.1} bits/triangle); encode {:.2} msec, decode {:.2} msec ({:.2} GB/s)", 799 | (encoded.len() * 8) as f64 / (mesh.indices.len() / 3) as f64, 800 | (compressed.len() * 8) as f64 / (mesh.indices.len() / 3) as f64, 801 | elapsed_to_ms(encode_elapsed), 802 | elapsed_to_ms(decode_elapsed), 803 | ((decoded.len() * 4) as f64 / (1 << 30) as f64) / (elapsed_to_ms(decode_elapsed) as f64 / 1000.0), 804 | ); 805 | } 806 | 807 | fn encode_vertex(mesh: &Mesh, name: &str) { 808 | let packed = pack_vertices::(&mesh.vertices); 809 | 810 | let encode_start = Instant::now(); 811 | let encoded = meshopt::encode_vertex_buffer(&packed).unwrap(); 812 | let encode_elapsed = encode_start.elapsed(); 813 | 814 | let decode_start = Instant::now(); 815 | let decoded = meshopt::decode_vertex_buffer(&encoded, mesh.vertices.len()).unwrap(); 816 | let decode_elapsed = decode_start.elapsed(); 817 | 818 | assert!(packed == decoded); 819 | 820 | let compressed = compress(&encoded); 821 | 822 | println!( 823 | "VtxCodec{:1}: {:.1} bits/vertex (post-deflate {:.1} bits/vertex); encode {:.2} msec, decode {:.2} msec ({:.2} GB/s)", 824 | name, 825 | (encoded.len() * 8) as f64 / (mesh.vertices.len()) as f64, 826 | (compressed.len() * 8) as f64 / (mesh.vertices.len()) as f64, 827 | elapsed_to_ms(encode_elapsed), 828 | elapsed_to_ms(decode_elapsed), 829 | ((decoded.len() * 4) as f64 / (1 << 30) as f64) / (elapsed_to_ms(decode_elapsed) as f64 / 1000.0), 830 | ); 831 | } 832 | 833 | fn pack_mesh(mesh: &Mesh, name: &str) { 834 | let vertices = pack_vertices::(&mesh.vertices); 835 | let compressed = compress(&vertices); 836 | 837 | println!( 838 | "VtxPack{} : {:.1} bits/vertex (post-deflate {:.1} bits/vertices)", 839 | name, 840 | (vertices.len() * mem::size_of::() * 8) as f64 / mesh.vertices.len() as f64, 841 | (compressed.len() * 8) as f64 / mesh.vertices.len() as f64 842 | ); 843 | } 844 | 845 | fn compress(data: &[T]) -> Vec { 846 | use miniz_oxide::deflate::compress_to_vec; 847 | let bytes: &[u8] = typed_to_bytes(data); 848 | compress_to_vec(bytes, 6 /* 0-10 compression level */) 849 | } 850 | 851 | fn process(path: Option, export: bool) { 852 | let mesh = match &path { 853 | Some(path) => Mesh::load_obj(path), 854 | None => { 855 | let mesh = Mesh::create_plane(200); 856 | if export { 857 | mesh.save_obj(Path::new("examples/plane.obj")).unwrap(); 858 | } 859 | mesh 860 | } 861 | }; 862 | 863 | optimize_mesh(&mesh, "Original", opt_none); 864 | optimize_mesh(&mesh, "Random", opt_random_shuffle); 865 | optimize_mesh(&mesh, "Cache", opt_cache); 866 | optimize_mesh(&mesh, "CacheFifo", opt_cache_fifo); 867 | optimize_mesh(&mesh, "Overdraw", opt_overdraw); 868 | optimize_mesh(&mesh, "Fetch", opt_fetch); 869 | optimize_mesh(&mesh, "FetchMap", opt_fetch_remap); 870 | optimize_mesh(&mesh, "Complete", opt_complete); 871 | 872 | let mut copy = mesh.clone(); 873 | meshopt::optimize_vertex_cache_in_place(&mut copy.indices, copy.vertices.len()); 874 | meshopt::optimize_vertex_fetch_in_place(&mut copy.indices, &mut copy.vertices); 875 | 876 | if export { 877 | match path { 878 | Some(ref path) => { 879 | let stem = path.file_stem().unwrap().to_str().unwrap(); 880 | let new_path = format!("examples/{}_opt.obj", stem); 881 | copy.save_obj(Path::new(&new_path)).unwrap(); 882 | } 883 | None => { 884 | copy.save_obj(Path::new("examples/plane_opt.obj")).unwrap(); 885 | } 886 | } 887 | } 888 | 889 | stripify(©, false); 890 | stripify(©, true); 891 | 892 | meshlets(©); 893 | shadow(©); 894 | 895 | encode_index(©); 896 | pack_mesh::(©, ""); 897 | encode_vertex::(©, ""); 898 | encode_vertex::(©, "0"); 899 | 900 | simplify(&mesh); 901 | } 902 | 903 | fn main() { 904 | let export = false; 905 | process(None, export); 906 | process(Some(Path::new("examples/pirate.obj").to_path_buf()), export); 907 | } 908 | -------------------------------------------------------------------------------- /examples/encoder.rs: -------------------------------------------------------------------------------- 1 | use meshopt::{ 2 | any_as_u8_slice, quantize_snorm, quantize_unorm, rcp_safe, EncodeHeader, EncodeObject, 3 | PackedVertex, Vertex, 4 | }; 5 | 6 | use std::{fs::File, io::Write, path::PathBuf}; 7 | use structopt::StructOpt; 8 | 9 | #[derive(StructOpt, Debug)] 10 | #[structopt(name = "meshencoder")] 11 | struct Options { 12 | /// Input file 13 | #[structopt(short = "i", long = "input", parse(from_os_str))] 14 | input: PathBuf, 15 | 16 | /// Output file 17 | #[structopt(short = "o", long = "output", parse(from_os_str))] 18 | output: PathBuf, 19 | 20 | /// No optimization (just encoding) 21 | #[structopt(short = "u", long = "unoptimized")] 22 | unoptimized: bool, 23 | } 24 | 25 | #[derive(Debug)] 26 | struct Object { 27 | material: String, 28 | index_offset: usize, 29 | index_count: usize, 30 | } 31 | 32 | #[allow(clippy::identity_op)] 33 | fn main() { 34 | let options = Options::from_args(); 35 | 36 | if options.unoptimized { 37 | println!("Encoding [unoptimized] {:?}", &options.input); 38 | } else { 39 | println!("Encoding {:?}", &options.input); 40 | } 41 | 42 | let obj_file = tobj::load_obj( 43 | &options.input, 44 | &tobj::LoadOptions { 45 | triangulate: true, 46 | single_index: true, 47 | ..Default::default() 48 | }, 49 | ); 50 | let (models, materials) = obj_file.unwrap(); 51 | let materials = materials.unwrap(); 52 | 53 | let mut merged_positions: Vec = Vec::new(); 54 | let mut merged_coords: Vec = Vec::new(); 55 | let mut merged_vertices: Vec = Vec::new(); 56 | let mut merged_indices: Vec = Vec::new(); 57 | 58 | let mut objects: Vec = Vec::new(); 59 | 60 | for m in models.iter() { 61 | let mesh = &m.mesh; 62 | 63 | let material = match mesh.material_id { 64 | Some(id) => materials[id].name.to_owned(), 65 | None => String::new(), 66 | }; 67 | 68 | let mut vertices: Vec = Vec::new(); 69 | let vertex_start = merged_vertices.len(); 70 | let index_start = merged_indices.len(); 71 | 72 | for i in 0..mesh.indices.len() { 73 | let index = mesh.indices[i] as usize; 74 | 75 | // pos = [x, y, z] 76 | let p = [ 77 | mesh.positions[index * 3 + 0], 78 | mesh.positions[index * 3 + 1], 79 | mesh.positions[index * 3 + 2], 80 | ]; 81 | 82 | merged_positions.push(p[0]); 83 | merged_positions.push(p[1]); 84 | merged_positions.push(p[2]); 85 | 86 | // normal = [x, y, z] 87 | let n = if !mesh.normals.is_empty() { 88 | [ 89 | mesh.normals[index * 3 + 0], 90 | mesh.normals[index * 3 + 1], 91 | mesh.normals[index * 3 + 2], 92 | ] 93 | } else { 94 | [0f32, 0f32, 0f32] 95 | }; 96 | 97 | // tex coord = [u, v]; 98 | let t = if !mesh.texcoords.is_empty() { 99 | [mesh.texcoords[index * 2], mesh.texcoords[index * 2 + 1]] 100 | } else { 101 | [0f32, 0f32] 102 | }; 103 | 104 | merged_coords.push(t[0]); 105 | merged_coords.push(t[1]); 106 | 107 | vertices.push(Vertex { p, n, t }); 108 | merged_indices.push((vertex_start + index) as u32); 109 | } 110 | 111 | merged_vertices.append(&mut vertices); 112 | 113 | objects.push(Object { 114 | material, 115 | index_offset: index_start, 116 | index_count: mesh.indices.len(), 117 | }); 118 | } 119 | 120 | let pos_bits = 14; 121 | let uv_bits = 12; 122 | 123 | let (pos_offset, pos_scale) = meshopt::calc_pos_offset_and_scale(&merged_positions); 124 | let (uv_offset, uv_scale) = meshopt::calc_uv_offset_and_scale(&merged_coords); 125 | 126 | let pos_scale_inv = rcp_safe(pos_scale); 127 | let uv_scale_inv = [rcp_safe(uv_scale[0]), rcp_safe(uv_scale[1])]; 128 | 129 | let quantized_vertices: Vec = merged_vertices 130 | .iter() 131 | .map(|v| { 132 | let p_0 = quantize_unorm((v.p[0] - pos_offset[0]) * pos_scale_inv, pos_bits) as u16; 133 | let p_1 = quantize_unorm((v.p[1] - pos_offset[1]) * pos_scale_inv, pos_bits) as u16; 134 | let p_2 = quantize_unorm((v.p[2] - pos_offset[2]) * pos_scale_inv, pos_bits) as u16; 135 | 136 | let n_0 = quantize_snorm(v.n[0], 8) as i8; 137 | let n_1 = quantize_snorm(v.n[1], 8) as i8; 138 | let n_2 = quantize_snorm(v.n[2], 8) as i8; 139 | 140 | let t_0 = quantize_unorm((v.t[0] - uv_offset[0]) * uv_scale_inv[0], uv_bits) as u16; 141 | let t_1 = quantize_unorm((v.t[1] - uv_offset[1]) * uv_scale_inv[1], uv_bits) as u16; 142 | 143 | PackedVertex { 144 | p: [p_0, p_1, p_2, 0], 145 | n: [n_0, n_1, n_2, 0], 146 | t: [t_0, t_1], 147 | } 148 | }) 149 | .collect(); 150 | 151 | let (vertex_count, vertex_remap) = meshopt::generate_vertex_remap(&quantized_vertices, None); 152 | 153 | let mut remapped_indices = 154 | meshopt::remap_index_buffer(None, merged_indices.len(), &vertex_remap); 155 | 156 | let mut remapped_vertices = 157 | meshopt::remap_vertex_buffer(&quantized_vertices, vertex_count, &vertex_remap); 158 | 159 | if !options.unoptimized { 160 | for object in &objects { 161 | meshopt::optimize_vertex_cache_in_place( 162 | &mut remapped_indices 163 | [object.index_offset..(object.index_offset + object.index_count)], 164 | remapped_vertices.len(), 165 | ); 166 | } 167 | 168 | meshopt::optimize_vertex_fetch_in_place(&mut remapped_indices, &mut remapped_vertices); 169 | } 170 | 171 | let encoded_vertices = meshopt::encode_vertex_buffer(&remapped_vertices).unwrap(); 172 | let encoded_indices = 173 | meshopt::encode_index_buffer(&remapped_indices, remapped_vertices.len()).unwrap(); 174 | 175 | let header = EncodeHeader { 176 | magic: *b"OPTM", 177 | group_count: objects.len() as u32, 178 | vertex_count: vertex_count as u32, 179 | index_count: merged_indices.len() as u32, 180 | vertex_data_size: encoded_vertices.len() as u32, 181 | index_data_size: encoded_indices.len() as u32, 182 | pos_offset, 183 | pos_scale: pos_scale / ((1 << pos_bits) - 1) as f32, 184 | uv_offset, 185 | uv_scale: [ 186 | uv_scale[0] / ((1 << uv_bits) - 1) as f32, 187 | uv_scale[1] / ((1 << uv_bits) - 1) as f32, 188 | ], 189 | reserved: [0, 0], 190 | }; 191 | 192 | let mut output = File::create(&options.output).unwrap(); 193 | 194 | output.write_all(any_as_u8_slice(&header)).unwrap(); 195 | 196 | for object in &objects { 197 | let object = EncodeObject { 198 | index_offset: object.index_offset as u32, 199 | index_count: object.index_count as u32, 200 | material_length: object.material.len() as u32, 201 | reserved: 0, 202 | }; 203 | output.write_all(any_as_u8_slice(&object)).unwrap(); 204 | } 205 | 206 | for object in &objects { 207 | output.write_all(object.material.as_bytes()).unwrap(); 208 | } 209 | 210 | output.write_all(&encoded_vertices).unwrap(); 211 | output.write_all(&encoded_indices).unwrap(); 212 | 213 | println!(" Serialized encoded mesh to {:?}", &options.output); 214 | } 215 | -------------------------------------------------------------------------------- /examples/multi.obj: -------------------------------------------------------------------------------- 1 | # Blender3D v249 OBJ File: 2 | # www.blender3d.org 3 | o Triangle_Plane.001 4 | v -2.210575 -0.000000 -0.986309 5 | v -2.210575 0.000000 1.013691 6 | v -4.210576 0.000000 1.013691 7 | s off 8 | f 1 3 2 9 | o Square_Plane 10 | v 1.000000 -0.000000 -1.000000 11 | v 1.000000 0.000000 1.000000 12 | v -1.000000 0.000000 1.000000 13 | v -1.000000 -0.000000 -1.000000 14 | s off 15 | f 4 7 6 5 -------------------------------------------------------------------------------- /examples/multi.optmesh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwihlidal/meshopt-rs/c2165927e09c557e717f6fcb6b7690bee65f6c90/examples/multi.optmesh -------------------------------------------------------------------------------- /examples/pirate.optmesh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwihlidal/meshopt-rs/c2165927e09c557e717f6fcb6b7690bee65f6c90/examples/pirate.optmesh -------------------------------------------------------------------------------- /examples/pirate_opt.optmesh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwihlidal/meshopt-rs/c2165927e09c557e717f6fcb6b7690bee65f6c90/examples/pirate_opt.optmesh -------------------------------------------------------------------------------- /examples/plane.optmesh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwihlidal/meshopt-rs/c2165927e09c557e717f6fcb6b7690bee65f6c90/examples/plane.optmesh -------------------------------------------------------------------------------- /examples/plane_opt.optmesh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwihlidal/meshopt-rs/c2165927e09c557e717f6fcb6b7690bee65f6c90/examples/plane_opt.optmesh -------------------------------------------------------------------------------- /gen/bindings.rs: -------------------------------------------------------------------------------- 1 | /* automatically generated by rust-bindgen 0.70.1 */ 2 | 3 | #[doc = " Vertex attribute stream\n Each element takes size bytes, beginning at data, with stride controlling the spacing between successive elements (stride >= size)."] 4 | #[repr(C)] 5 | #[derive(Debug, Copy, Clone)] 6 | pub struct meshopt_Stream { 7 | pub data: *const ::std::os::raw::c_void, 8 | pub size: usize, 9 | pub stride: usize, 10 | } 11 | extern "C" { 12 | #[doc = " Generates a vertex remap table from the vertex buffer and an optional index buffer and returns number of unique vertices\n As a result, all vertices that are binary equivalent map to the same (new) location, with no gaps in the resulting sequence.\n Resulting remap table maps old vertices to new vertices and can be used in meshopt_remapVertexBuffer/meshopt_remapIndexBuffer.\n Note that binary equivalence considers all vertex_size bytes, including padding which should be zero-initialized.\n\n destination must contain enough space for the resulting remap table (vertex_count elements)\n indices can be NULL if the input is unindexed"] 13 | pub fn meshopt_generateVertexRemap( 14 | destination: *mut ::std::os::raw::c_uint, 15 | indices: *const ::std::os::raw::c_uint, 16 | index_count: usize, 17 | vertices: *const ::std::os::raw::c_void, 18 | vertex_count: usize, 19 | vertex_size: usize, 20 | ) -> usize; 21 | } 22 | extern "C" { 23 | #[doc = " Generates a vertex remap table from multiple vertex streams and an optional index buffer and returns number of unique vertices\n As a result, all vertices that are binary equivalent map to the same (new) location, with no gaps in the resulting sequence.\n Resulting remap table maps old vertices to new vertices and can be used in meshopt_remapVertexBuffer/meshopt_remapIndexBuffer.\n To remap vertex buffers, you will need to call meshopt_remapVertexBuffer for each vertex stream.\n Note that binary equivalence considers all size bytes in each stream, including padding which should be zero-initialized.\n\n destination must contain enough space for the resulting remap table (vertex_count elements)\n indices can be NULL if the input is unindexed\n stream_count must be <= 16"] 24 | pub fn meshopt_generateVertexRemapMulti( 25 | destination: *mut ::std::os::raw::c_uint, 26 | indices: *const ::std::os::raw::c_uint, 27 | index_count: usize, 28 | vertex_count: usize, 29 | streams: *const meshopt_Stream, 30 | stream_count: usize, 31 | ) -> usize; 32 | } 33 | extern "C" { 34 | #[doc = " Generates vertex buffer from the source vertex buffer and remap table generated by meshopt_generateVertexRemap\n\n destination must contain enough space for the resulting vertex buffer (unique_vertex_count elements, returned by meshopt_generateVertexRemap)\n vertex_count should be the initial vertex count and not the value returned by meshopt_generateVertexRemap"] 35 | pub fn meshopt_remapVertexBuffer( 36 | destination: *mut ::std::os::raw::c_void, 37 | vertices: *const ::std::os::raw::c_void, 38 | vertex_count: usize, 39 | vertex_size: usize, 40 | remap: *const ::std::os::raw::c_uint, 41 | ); 42 | } 43 | extern "C" { 44 | #[doc = " Generate index buffer from the source index buffer and remap table generated by meshopt_generateVertexRemap\n\n destination must contain enough space for the resulting index buffer (index_count elements)\n indices can be NULL if the input is unindexed"] 45 | pub fn meshopt_remapIndexBuffer( 46 | destination: *mut ::std::os::raw::c_uint, 47 | indices: *const ::std::os::raw::c_uint, 48 | index_count: usize, 49 | remap: *const ::std::os::raw::c_uint, 50 | ); 51 | } 52 | extern "C" { 53 | #[doc = " Generate index buffer that can be used for more efficient rendering when only a subset of the vertex attributes is necessary\n All vertices that are binary equivalent (wrt first vertex_size bytes) map to the first vertex in the original vertex buffer.\n This makes it possible to use the index buffer for Z pre-pass or shadowmap rendering, while using the original index buffer for regular rendering.\n Note that binary equivalence considers all vertex_size bytes, including padding which should be zero-initialized.\n\n destination must contain enough space for the resulting index buffer (index_count elements)"] 54 | pub fn meshopt_generateShadowIndexBuffer( 55 | destination: *mut ::std::os::raw::c_uint, 56 | indices: *const ::std::os::raw::c_uint, 57 | index_count: usize, 58 | vertices: *const ::std::os::raw::c_void, 59 | vertex_count: usize, 60 | vertex_size: usize, 61 | vertex_stride: usize, 62 | ); 63 | } 64 | extern "C" { 65 | #[doc = " Generate index buffer that can be used for more efficient rendering when only a subset of the vertex attributes is necessary\n All vertices that are binary equivalent (wrt specified streams) map to the first vertex in the original vertex buffer.\n This makes it possible to use the index buffer for Z pre-pass or shadowmap rendering, while using the original index buffer for regular rendering.\n Note that binary equivalence considers all size bytes in each stream, including padding which should be zero-initialized.\n\n destination must contain enough space for the resulting index buffer (index_count elements)\n stream_count must be <= 16"] 66 | pub fn meshopt_generateShadowIndexBufferMulti( 67 | destination: *mut ::std::os::raw::c_uint, 68 | indices: *const ::std::os::raw::c_uint, 69 | index_count: usize, 70 | vertex_count: usize, 71 | streams: *const meshopt_Stream, 72 | stream_count: usize, 73 | ); 74 | } 75 | extern "C" { 76 | #[doc = " Generate index buffer that can be used as a geometry shader input with triangle adjacency topology\n Each triangle is converted into a 6-vertex patch with the following layout:\n - 0, 2, 4: original triangle vertices\n - 1, 3, 5: vertices adjacent to edges 02, 24 and 40\n The resulting patch can be rendered with geometry shaders using e.g. VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY.\n This can be used to implement algorithms like silhouette detection/expansion and other forms of GS-driven rendering.\n\n destination must contain enough space for the resulting index buffer (index_count*2 elements)\n vertex_positions should have float3 position in the first 12 bytes of each vertex"] 77 | pub fn meshopt_generateAdjacencyIndexBuffer( 78 | destination: *mut ::std::os::raw::c_uint, 79 | indices: *const ::std::os::raw::c_uint, 80 | index_count: usize, 81 | vertex_positions: *const f32, 82 | vertex_count: usize, 83 | vertex_positions_stride: usize, 84 | ); 85 | } 86 | extern "C" { 87 | #[doc = " Generate index buffer that can be used for PN-AEN tessellation with crack-free displacement\n Each triangle is converted into a 12-vertex patch with the following layout:\n - 0, 1, 2: original triangle vertices\n - 3, 4: opposing edge for edge 0, 1\n - 5, 6: opposing edge for edge 1, 2\n - 7, 8: opposing edge for edge 2, 0\n - 9, 10, 11: dominant vertices for corners 0, 1, 2\n The resulting patch can be rendered with hardware tessellation using PN-AEN and displacement mapping.\n See \"Tessellation on Any Budget\" (John McDonald, GDC 2011) for implementation details.\n\n destination must contain enough space for the resulting index buffer (index_count*4 elements)\n vertex_positions should have float3 position in the first 12 bytes of each vertex"] 88 | pub fn meshopt_generateTessellationIndexBuffer( 89 | destination: *mut ::std::os::raw::c_uint, 90 | indices: *const ::std::os::raw::c_uint, 91 | index_count: usize, 92 | vertex_positions: *const f32, 93 | vertex_count: usize, 94 | vertex_positions_stride: usize, 95 | ); 96 | } 97 | extern "C" { 98 | #[doc = " Experimental: Generate index buffer that can be used for visibility buffer rendering and returns the size of the reorder table\n Each triangle's provoking vertex index is equal to primitive id; this allows passing it to the fragment shader using nointerpolate attribute.\n This is important for performance on hardware where primitive id can't be accessed efficiently in fragment shader.\n The reorder table stores the original vertex id for each vertex in the new index buffer, and should be used in the vertex shader to load vertex data.\n The provoking vertex is assumed to be the first vertex in the triangle; if this is not the case (OpenGL), rotate each triangle (abc -> bca) before rendering.\n For maximum efficiency the input index buffer should be optimized for vertex cache first.\n\n destination must contain enough space for the resulting index buffer (index_count elements)\n reorder must contain enough space for the worst case reorder table (vertex_count + index_count/3 elements)"] 99 | pub fn meshopt_generateProvokingIndexBuffer( 100 | destination: *mut ::std::os::raw::c_uint, 101 | reorder: *mut ::std::os::raw::c_uint, 102 | indices: *const ::std::os::raw::c_uint, 103 | index_count: usize, 104 | vertex_count: usize, 105 | ) -> usize; 106 | } 107 | extern "C" { 108 | #[doc = " Vertex transform cache optimizer\n Reorders indices to reduce the number of GPU vertex shader invocations\n If index buffer contains multiple ranges for multiple draw calls, this functions needs to be called on each range individually.\n\n destination must contain enough space for the resulting index buffer (index_count elements)"] 109 | pub fn meshopt_optimizeVertexCache( 110 | destination: *mut ::std::os::raw::c_uint, 111 | indices: *const ::std::os::raw::c_uint, 112 | index_count: usize, 113 | vertex_count: usize, 114 | ); 115 | } 116 | extern "C" { 117 | #[doc = " Vertex transform cache optimizer for strip-like caches\n Produces inferior results to meshopt_optimizeVertexCache from the GPU vertex cache perspective\n However, the resulting index order is more optimal if the goal is to reduce the triangle strip length or improve compression efficiency\n\n destination must contain enough space for the resulting index buffer (index_count elements)"] 118 | pub fn meshopt_optimizeVertexCacheStrip( 119 | destination: *mut ::std::os::raw::c_uint, 120 | indices: *const ::std::os::raw::c_uint, 121 | index_count: usize, 122 | vertex_count: usize, 123 | ); 124 | } 125 | extern "C" { 126 | #[doc = " Vertex transform cache optimizer for FIFO caches\n Reorders indices to reduce the number of GPU vertex shader invocations\n Generally takes ~3x less time to optimize meshes but produces inferior results compared to meshopt_optimizeVertexCache\n If index buffer contains multiple ranges for multiple draw calls, this functions needs to be called on each range individually.\n\n destination must contain enough space for the resulting index buffer (index_count elements)\n cache_size should be less than the actual GPU cache size to avoid cache thrashing"] 127 | pub fn meshopt_optimizeVertexCacheFifo( 128 | destination: *mut ::std::os::raw::c_uint, 129 | indices: *const ::std::os::raw::c_uint, 130 | index_count: usize, 131 | vertex_count: usize, 132 | cache_size: ::std::os::raw::c_uint, 133 | ); 134 | } 135 | extern "C" { 136 | #[doc = " Overdraw optimizer\n Reorders indices to reduce the number of GPU vertex shader invocations and the pixel overdraw\n If index buffer contains multiple ranges for multiple draw calls, this functions needs to be called on each range individually.\n\n destination must contain enough space for the resulting index buffer (index_count elements)\n indices must contain index data that is the result of meshopt_optimizeVertexCache (*not* the original mesh indices!)\n vertex_positions should have float3 position in the first 12 bytes of each vertex\n threshold indicates how much the overdraw optimizer can degrade vertex cache efficiency (1.05 = up to 5%) to reduce overdraw more efficiently"] 137 | pub fn meshopt_optimizeOverdraw( 138 | destination: *mut ::std::os::raw::c_uint, 139 | indices: *const ::std::os::raw::c_uint, 140 | index_count: usize, 141 | vertex_positions: *const f32, 142 | vertex_count: usize, 143 | vertex_positions_stride: usize, 144 | threshold: f32, 145 | ); 146 | } 147 | extern "C" { 148 | #[doc = " Vertex fetch cache optimizer\n Reorders vertices and changes indices to reduce the amount of GPU memory fetches during vertex processing\n Returns the number of unique vertices, which is the same as input vertex count unless some vertices are unused\n This functions works for a single vertex stream; for multiple vertex streams, use meshopt_optimizeVertexFetchRemap + meshopt_remapVertexBuffer for each stream.\n\n destination must contain enough space for the resulting vertex buffer (vertex_count elements)\n indices is used both as an input and as an output index buffer"] 149 | pub fn meshopt_optimizeVertexFetch( 150 | destination: *mut ::std::os::raw::c_void, 151 | indices: *mut ::std::os::raw::c_uint, 152 | index_count: usize, 153 | vertices: *const ::std::os::raw::c_void, 154 | vertex_count: usize, 155 | vertex_size: usize, 156 | ) -> usize; 157 | } 158 | extern "C" { 159 | #[doc = " Vertex fetch cache optimizer\n Generates vertex remap to reduce the amount of GPU memory fetches during vertex processing\n Returns the number of unique vertices, which is the same as input vertex count unless some vertices are unused\n The resulting remap table should be used to reorder vertex/index buffers using meshopt_remapVertexBuffer/meshopt_remapIndexBuffer\n\n destination must contain enough space for the resulting remap table (vertex_count elements)"] 160 | pub fn meshopt_optimizeVertexFetchRemap( 161 | destination: *mut ::std::os::raw::c_uint, 162 | indices: *const ::std::os::raw::c_uint, 163 | index_count: usize, 164 | vertex_count: usize, 165 | ) -> usize; 166 | } 167 | extern "C" { 168 | #[doc = " Index buffer encoder\n Encodes index data into an array of bytes that is generally much smaller (<1.5 bytes/triangle) and compresses better (<1 bytes/triangle) compared to original.\n Input index buffer must represent a triangle list.\n Returns encoded data size on success, 0 on error; the only error condition is if buffer doesn't have enough space\n For maximum efficiency the index buffer being encoded has to be optimized for vertex cache and vertex fetch first.\n\n buffer must contain enough space for the encoded index buffer (use meshopt_encodeIndexBufferBound to compute worst case size)"] 169 | pub fn meshopt_encodeIndexBuffer( 170 | buffer: *mut ::std::os::raw::c_uchar, 171 | buffer_size: usize, 172 | indices: *const ::std::os::raw::c_uint, 173 | index_count: usize, 174 | ) -> usize; 175 | } 176 | extern "C" { 177 | pub fn meshopt_encodeIndexBufferBound(index_count: usize, vertex_count: usize) -> usize; 178 | } 179 | extern "C" { 180 | #[doc = " Set index encoder format version\n version must specify the data format version to encode; valid values are 0 (decodable by all library versions) and 1 (decodable by 0.14+)"] 181 | pub fn meshopt_encodeIndexVersion(version: ::std::os::raw::c_int); 182 | } 183 | extern "C" { 184 | #[doc = " Index buffer decoder\n Decodes index data from an array of bytes generated by meshopt_encodeIndexBuffer\n Returns 0 if decoding was successful, and an error code otherwise\n The decoder is safe to use for untrusted input, but it may produce garbage data (e.g. out of range indices).\n\n destination must contain enough space for the resulting index buffer (index_count elements)"] 185 | pub fn meshopt_decodeIndexBuffer( 186 | destination: *mut ::std::os::raw::c_void, 187 | index_count: usize, 188 | index_size: usize, 189 | buffer: *const ::std::os::raw::c_uchar, 190 | buffer_size: usize, 191 | ) -> ::std::os::raw::c_int; 192 | } 193 | extern "C" { 194 | #[doc = " Get encoded index format version\n Returns format version of the encoded index buffer/sequence, or -1 if the buffer header is invalid\n Note that a non-negative value doesn't guarantee that the buffer will be decoded correctly if the input is malformed."] 195 | pub fn meshopt_decodeIndexVersion( 196 | buffer: *const ::std::os::raw::c_uchar, 197 | buffer_size: usize, 198 | ) -> ::std::os::raw::c_int; 199 | } 200 | extern "C" { 201 | #[doc = " Index sequence encoder\n Encodes index sequence into an array of bytes that is generally smaller and compresses better compared to original.\n Input index sequence can represent arbitrary topology; for triangle lists meshopt_encodeIndexBuffer is likely to be better.\n Returns encoded data size on success, 0 on error; the only error condition is if buffer doesn't have enough space\n\n buffer must contain enough space for the encoded index sequence (use meshopt_encodeIndexSequenceBound to compute worst case size)"] 202 | pub fn meshopt_encodeIndexSequence( 203 | buffer: *mut ::std::os::raw::c_uchar, 204 | buffer_size: usize, 205 | indices: *const ::std::os::raw::c_uint, 206 | index_count: usize, 207 | ) -> usize; 208 | } 209 | extern "C" { 210 | pub fn meshopt_encodeIndexSequenceBound(index_count: usize, vertex_count: usize) -> usize; 211 | } 212 | extern "C" { 213 | #[doc = " Index sequence decoder\n Decodes index data from an array of bytes generated by meshopt_encodeIndexSequence\n Returns 0 if decoding was successful, and an error code otherwise\n The decoder is safe to use for untrusted input, but it may produce garbage data (e.g. out of range indices).\n\n destination must contain enough space for the resulting index sequence (index_count elements)"] 214 | pub fn meshopt_decodeIndexSequence( 215 | destination: *mut ::std::os::raw::c_void, 216 | index_count: usize, 217 | index_size: usize, 218 | buffer: *const ::std::os::raw::c_uchar, 219 | buffer_size: usize, 220 | ) -> ::std::os::raw::c_int; 221 | } 222 | extern "C" { 223 | #[doc = " Vertex buffer encoder\n Encodes vertex data into an array of bytes that is generally smaller and compresses better compared to original.\n Returns encoded data size on success, 0 on error; the only error condition is if buffer doesn't have enough space\n This function works for a single vertex stream; for multiple vertex streams, call meshopt_encodeVertexBuffer for each stream.\n Note that all vertex_size bytes of each vertex are encoded verbatim, including padding which should be zero-initialized.\n For maximum efficiency the vertex buffer being encoded has to be quantized and optimized for locality of reference (cache/fetch) first.\n\n buffer must contain enough space for the encoded vertex buffer (use meshopt_encodeVertexBufferBound to compute worst case size)"] 224 | pub fn meshopt_encodeVertexBuffer( 225 | buffer: *mut ::std::os::raw::c_uchar, 226 | buffer_size: usize, 227 | vertices: *const ::std::os::raw::c_void, 228 | vertex_count: usize, 229 | vertex_size: usize, 230 | ) -> usize; 231 | } 232 | extern "C" { 233 | pub fn meshopt_encodeVertexBufferBound(vertex_count: usize, vertex_size: usize) -> usize; 234 | } 235 | extern "C" { 236 | #[doc = " Experimental: Vertex buffer encoder\n Encodes vertex data just like meshopt_encodeVertexBuffer, but allows to override compression level.\n For compression level to take effect, the vertex encoding version must be set to 1 via meshopt_encodeVertexVersion.\n The default compression level implied by meshopt_encodeVertexBuffer is 2.\n\n level should be in the range [0, 3] with 0 being the fastest and 3 being the slowest and producing the best compression ratio."] 237 | pub fn meshopt_encodeVertexBufferLevel( 238 | buffer: *mut ::std::os::raw::c_uchar, 239 | buffer_size: usize, 240 | vertices: *const ::std::os::raw::c_void, 241 | vertex_count: usize, 242 | vertex_size: usize, 243 | level: ::std::os::raw::c_int, 244 | ) -> usize; 245 | } 246 | extern "C" { 247 | #[doc = " Set vertex encoder format version\n version must specify the data format version to encode; valid values are 0 (decodable by all library versions) and 1 (decodable by 0.23+)"] 248 | pub fn meshopt_encodeVertexVersion(version: ::std::os::raw::c_int); 249 | } 250 | extern "C" { 251 | #[doc = " Vertex buffer decoder\n Decodes vertex data from an array of bytes generated by meshopt_encodeVertexBuffer\n Returns 0 if decoding was successful, and an error code otherwise\n The decoder is safe to use for untrusted input, but it may produce garbage data.\n\n destination must contain enough space for the resulting vertex buffer (vertex_count * vertex_size bytes)"] 252 | pub fn meshopt_decodeVertexBuffer( 253 | destination: *mut ::std::os::raw::c_void, 254 | vertex_count: usize, 255 | vertex_size: usize, 256 | buffer: *const ::std::os::raw::c_uchar, 257 | buffer_size: usize, 258 | ) -> ::std::os::raw::c_int; 259 | } 260 | extern "C" { 261 | #[doc = " Get encoded vertex format version\n Returns format version of the encoded vertex buffer, or -1 if the buffer header is invalid\n Note that a non-negative value doesn't guarantee that the buffer will be decoded correctly if the input is malformed."] 262 | pub fn meshopt_decodeVertexVersion( 263 | buffer: *const ::std::os::raw::c_uchar, 264 | buffer_size: usize, 265 | ) -> ::std::os::raw::c_int; 266 | } 267 | extern "C" { 268 | #[doc = " Vertex buffer filters\n These functions can be used to filter output of meshopt_decodeVertexBuffer in-place.\n\n meshopt_decodeFilterOct decodes octahedral encoding of a unit vector with K-bit (K <= 16) signed X/Y as an input; Z must store 1.0f.\n Each component is stored as an 8-bit or 16-bit normalized integer; stride must be equal to 4 or 8. W is preserved as is.\n\n meshopt_decodeFilterQuat decodes 3-component quaternion encoding with K-bit (4 <= K <= 16) component encoding and a 2-bit component index indicating which component to reconstruct.\n Each component is stored as an 16-bit integer; stride must be equal to 8.\n\n meshopt_decodeFilterExp decodes exponential encoding of floating-point data with 8-bit exponent and 24-bit integer mantissa as 2^E*M.\n Each 32-bit component is decoded in isolation; stride must be divisible by 4."] 269 | pub fn meshopt_decodeFilterOct( 270 | buffer: *mut ::std::os::raw::c_void, 271 | count: usize, 272 | stride: usize, 273 | ); 274 | } 275 | extern "C" { 276 | pub fn meshopt_decodeFilterQuat( 277 | buffer: *mut ::std::os::raw::c_void, 278 | count: usize, 279 | stride: usize, 280 | ); 281 | } 282 | extern "C" { 283 | pub fn meshopt_decodeFilterExp( 284 | buffer: *mut ::std::os::raw::c_void, 285 | count: usize, 286 | stride: usize, 287 | ); 288 | } 289 | pub const meshopt_EncodeExpMode_meshopt_EncodeExpSeparate: meshopt_EncodeExpMode = 0; 290 | pub const meshopt_EncodeExpMode_meshopt_EncodeExpSharedVector: meshopt_EncodeExpMode = 1; 291 | pub const meshopt_EncodeExpMode_meshopt_EncodeExpSharedComponent: meshopt_EncodeExpMode = 2; 292 | pub const meshopt_EncodeExpMode_meshopt_EncodeExpClamped: meshopt_EncodeExpMode = 3; 293 | #[doc = " Vertex buffer filter encoders\n These functions can be used to encode data in a format that meshopt_decodeFilter can decode\n\n meshopt_encodeFilterOct encodes unit vectors with K-bit (K <= 16) signed X/Y as an output.\n Each component is stored as an 8-bit or 16-bit normalized integer; stride must be equal to 4 or 8. W is preserved as is.\n Input data must contain 4 floats for every vector (count*4 total).\n\n meshopt_encodeFilterQuat encodes unit quaternions with K-bit (4 <= K <= 16) component encoding.\n Each component is stored as an 16-bit integer; stride must be equal to 8.\n Input data must contain 4 floats for every quaternion (count*4 total).\n\n meshopt_encodeFilterExp encodes arbitrary (finite) floating-point data with 8-bit exponent and K-bit integer mantissa (1 <= K <= 24).\n Exponent can be shared between all components of a given vector as defined by stride or all values of a given component; stride must be divisible by 4.\n Input data must contain stride/4 floats for every vector (count*stride/4 total)."] 294 | pub type meshopt_EncodeExpMode = ::std::os::raw::c_int; 295 | extern "C" { 296 | pub fn meshopt_encodeFilterOct( 297 | destination: *mut ::std::os::raw::c_void, 298 | count: usize, 299 | stride: usize, 300 | bits: ::std::os::raw::c_int, 301 | data: *const f32, 302 | ); 303 | } 304 | extern "C" { 305 | pub fn meshopt_encodeFilterQuat( 306 | destination: *mut ::std::os::raw::c_void, 307 | count: usize, 308 | stride: usize, 309 | bits: ::std::os::raw::c_int, 310 | data: *const f32, 311 | ); 312 | } 313 | extern "C" { 314 | pub fn meshopt_encodeFilterExp( 315 | destination: *mut ::std::os::raw::c_void, 316 | count: usize, 317 | stride: usize, 318 | bits: ::std::os::raw::c_int, 319 | data: *const f32, 320 | mode: meshopt_EncodeExpMode, 321 | ); 322 | } 323 | extern "C" { 324 | #[doc = " Mesh simplifier\n Reduces the number of triangles in the mesh, attempting to preserve mesh appearance as much as possible\n The algorithm tries to preserve mesh topology and can stop short of the target goal based on topology constraints or target error.\n If not all attributes from the input mesh are required, it's recommended to reindex the mesh without them prior to simplification.\n Returns the number of indices after simplification, with destination containing new index data\n The resulting index buffer references vertices from the original vertex buffer.\n If the original vertex data isn't required, creating a compact vertex buffer using meshopt_optimizeVertexFetch is recommended.\n\n destination must contain enough space for the target index buffer, worst case is index_count elements (*not* target_index_count)!\n vertex_positions should have float3 position in the first 12 bytes of each vertex\n target_error represents the error relative to mesh extents that can be tolerated, e.g. 0.01 = 1% deformation; value range [0..1]\n options must be a bitmask composed of meshopt_SimplifyX options; 0 is a safe default\n result_error can be NULL; when it's not NULL, it will contain the resulting (relative) error after simplification"] 325 | pub fn meshopt_simplify( 326 | destination: *mut ::std::os::raw::c_uint, 327 | indices: *const ::std::os::raw::c_uint, 328 | index_count: usize, 329 | vertex_positions: *const f32, 330 | vertex_count: usize, 331 | vertex_positions_stride: usize, 332 | target_index_count: usize, 333 | target_error: f32, 334 | options: ::std::os::raw::c_uint, 335 | result_error: *mut f32, 336 | ) -> usize; 337 | } 338 | extern "C" { 339 | #[doc = " Mesh simplifier with attribute metric\n The algorithm enhances meshopt_simplify by incorporating attribute values into the error metric used to prioritize simplification order; see meshopt_simplify documentation for details.\n Note that the number of attributes affects memory requirements and running time; this algorithm requires ~1.5x more memory and time compared to meshopt_simplify when using 4 scalar attributes.\n\n vertex_attributes should have attribute_count floats for each vertex\n attribute_weights should have attribute_count floats in total; the weights determine relative priority of attributes between each other and wrt position\n attribute_count must be <= 32\n vertex_lock can be NULL; when it's not NULL, it should have a value for each vertex; 1 denotes vertices that can't be moved"] 340 | pub fn meshopt_simplifyWithAttributes( 341 | destination: *mut ::std::os::raw::c_uint, 342 | indices: *const ::std::os::raw::c_uint, 343 | index_count: usize, 344 | vertex_positions: *const f32, 345 | vertex_count: usize, 346 | vertex_positions_stride: usize, 347 | vertex_attributes: *const f32, 348 | vertex_attributes_stride: usize, 349 | attribute_weights: *const f32, 350 | attribute_count: usize, 351 | vertex_lock: *const ::std::os::raw::c_uchar, 352 | target_index_count: usize, 353 | target_error: f32, 354 | options: ::std::os::raw::c_uint, 355 | result_error: *mut f32, 356 | ) -> usize; 357 | } 358 | extern "C" { 359 | #[doc = " Experimental: Mesh simplifier (sloppy)\n Reduces the number of triangles in the mesh, sacrificing mesh appearance for simplification performance\n The algorithm doesn't preserve mesh topology but can stop short of the target goal based on target error.\n Returns the number of indices after simplification, with destination containing new index data\n The resulting index buffer references vertices from the original vertex buffer.\n If the original vertex data isn't required, creating a compact vertex buffer using meshopt_optimizeVertexFetch is recommended.\n\n destination must contain enough space for the target index buffer, worst case is index_count elements (*not* target_index_count)!\n vertex_positions should have float3 position in the first 12 bytes of each vertex\n target_error represents the error relative to mesh extents that can be tolerated, e.g. 0.01 = 1% deformation; value range [0..1]\n result_error can be NULL; when it's not NULL, it will contain the resulting (relative) error after simplification"] 360 | pub fn meshopt_simplifySloppy( 361 | destination: *mut ::std::os::raw::c_uint, 362 | indices: *const ::std::os::raw::c_uint, 363 | index_count: usize, 364 | vertex_positions: *const f32, 365 | vertex_count: usize, 366 | vertex_positions_stride: usize, 367 | target_index_count: usize, 368 | target_error: f32, 369 | result_error: *mut f32, 370 | ) -> usize; 371 | } 372 | extern "C" { 373 | #[doc = " Point cloud simplifier\n Reduces the number of points in the cloud to reach the given target\n Returns the number of points after simplification, with destination containing new index data\n The resulting index buffer references vertices from the original vertex buffer.\n If the original vertex data isn't required, creating a compact vertex buffer using meshopt_optimizeVertexFetch is recommended.\n\n destination must contain enough space for the target index buffer (target_vertex_count elements)\n vertex_positions should have float3 position in the first 12 bytes of each vertex\n vertex_colors can be NULL; when it's not NULL, it should have float3 color in the first 12 bytes of each vertex\n color_weight determines relative priority of color wrt position; 1.0 is a safe default"] 374 | pub fn meshopt_simplifyPoints( 375 | destination: *mut ::std::os::raw::c_uint, 376 | vertex_positions: *const f32, 377 | vertex_count: usize, 378 | vertex_positions_stride: usize, 379 | vertex_colors: *const f32, 380 | vertex_colors_stride: usize, 381 | color_weight: f32, 382 | target_vertex_count: usize, 383 | ) -> usize; 384 | } 385 | extern "C" { 386 | #[doc = " Returns the error scaling factor used by the simplifier to convert between absolute and relative extents\n\n Absolute error must be *divided* by the scaling factor before passing it to meshopt_simplify as target_error\n Relative error returned by meshopt_simplify via result_error must be *multiplied* by the scaling factor to get absolute error."] 387 | pub fn meshopt_simplifyScale( 388 | vertex_positions: *const f32, 389 | vertex_count: usize, 390 | vertex_positions_stride: usize, 391 | ) -> f32; 392 | } 393 | extern "C" { 394 | #[doc = " Mesh stripifier\n Converts a previously vertex cache optimized triangle list to triangle strip, stitching strips using restart index or degenerate triangles\n Returns the number of indices in the resulting strip, with destination containing new index data\n For maximum efficiency the index buffer being converted has to be optimized for vertex cache first.\n Using restart indices can result in ~10% smaller index buffers, but on some GPUs restart indices may result in decreased performance.\n\n destination must contain enough space for the target index buffer, worst case can be computed with meshopt_stripifyBound\n restart_index should be 0xffff or 0xffffffff depending on index size, or 0 to use degenerate triangles"] 395 | pub fn meshopt_stripify( 396 | destination: *mut ::std::os::raw::c_uint, 397 | indices: *const ::std::os::raw::c_uint, 398 | index_count: usize, 399 | vertex_count: usize, 400 | restart_index: ::std::os::raw::c_uint, 401 | ) -> usize; 402 | } 403 | extern "C" { 404 | pub fn meshopt_stripifyBound(index_count: usize) -> usize; 405 | } 406 | extern "C" { 407 | #[doc = " Mesh unstripifier\n Converts a triangle strip to a triangle list\n Returns the number of indices in the resulting list, with destination containing new index data\n\n destination must contain enough space for the target index buffer, worst case can be computed with meshopt_unstripifyBound"] 408 | pub fn meshopt_unstripify( 409 | destination: *mut ::std::os::raw::c_uint, 410 | indices: *const ::std::os::raw::c_uint, 411 | index_count: usize, 412 | restart_index: ::std::os::raw::c_uint, 413 | ) -> usize; 414 | } 415 | extern "C" { 416 | pub fn meshopt_unstripifyBound(index_count: usize) -> usize; 417 | } 418 | #[repr(C)] 419 | #[derive(Debug, Copy, Clone)] 420 | pub struct meshopt_VertexCacheStatistics { 421 | pub vertices_transformed: ::std::os::raw::c_uint, 422 | pub warps_executed: ::std::os::raw::c_uint, 423 | pub acmr: f32, 424 | pub atvr: f32, 425 | } 426 | extern "C" { 427 | #[doc = " Vertex transform cache analyzer\n Returns cache hit statistics using a simplified FIFO model\n Results may not match actual GPU performance"] 428 | pub fn meshopt_analyzeVertexCache( 429 | indices: *const ::std::os::raw::c_uint, 430 | index_count: usize, 431 | vertex_count: usize, 432 | cache_size: ::std::os::raw::c_uint, 433 | warp_size: ::std::os::raw::c_uint, 434 | primgroup_size: ::std::os::raw::c_uint, 435 | ) -> meshopt_VertexCacheStatistics; 436 | } 437 | #[repr(C)] 438 | #[derive(Debug, Copy, Clone)] 439 | pub struct meshopt_OverdrawStatistics { 440 | pub pixels_covered: ::std::os::raw::c_uint, 441 | pub pixels_shaded: ::std::os::raw::c_uint, 442 | pub overdraw: f32, 443 | } 444 | extern "C" { 445 | #[doc = " Overdraw analyzer\n Returns overdraw statistics using a software rasterizer\n Results may not match actual GPU performance\n\n vertex_positions should have float3 position in the first 12 bytes of each vertex"] 446 | pub fn meshopt_analyzeOverdraw( 447 | indices: *const ::std::os::raw::c_uint, 448 | index_count: usize, 449 | vertex_positions: *const f32, 450 | vertex_count: usize, 451 | vertex_positions_stride: usize, 452 | ) -> meshopt_OverdrawStatistics; 453 | } 454 | #[repr(C)] 455 | #[derive(Debug, Copy, Clone)] 456 | pub struct meshopt_VertexFetchStatistics { 457 | pub bytes_fetched: ::std::os::raw::c_uint, 458 | pub overfetch: f32, 459 | } 460 | extern "C" { 461 | #[doc = " Vertex fetch cache analyzer\n Returns cache hit statistics using a simplified direct mapped model\n Results may not match actual GPU performance"] 462 | pub fn meshopt_analyzeVertexFetch( 463 | indices: *const ::std::os::raw::c_uint, 464 | index_count: usize, 465 | vertex_count: usize, 466 | vertex_size: usize, 467 | ) -> meshopt_VertexFetchStatistics; 468 | } 469 | #[doc = " Meshlet is a small mesh cluster (subset) that consists of:\n - triangles, an 8-bit micro triangle (index) buffer, that for each triangle specifies three local vertices to use;\n - vertices, a 32-bit vertex indirection buffer, that for each local vertex specifies which mesh vertex to fetch vertex attributes from.\n\n For efficiency, meshlet triangles and vertices are packed into two large arrays; this structure contains offsets and counts to access the data."] 470 | #[repr(C)] 471 | #[derive(Debug, Copy, Clone)] 472 | pub struct meshopt_Meshlet { 473 | pub vertex_offset: ::std::os::raw::c_uint, 474 | pub triangle_offset: ::std::os::raw::c_uint, 475 | pub vertex_count: ::std::os::raw::c_uint, 476 | pub triangle_count: ::std::os::raw::c_uint, 477 | } 478 | extern "C" { 479 | #[doc = " Meshlet builder\n Splits the mesh into a set of meshlets where each meshlet has a micro index buffer indexing into meshlet vertices that refer to the original vertex buffer\n The resulting data can be used to render meshes using NVidia programmable mesh shading pipeline, or in other cluster-based renderers.\n When targeting mesh shading hardware, for maximum efficiency meshlets should be further optimized using meshopt_optimizeMeshlet.\n When using buildMeshlets, vertex positions need to be provided to minimize the size of the resulting clusters.\n When using buildMeshletsScan, for maximum efficiency the index buffer being converted has to be optimized for vertex cache first.\n\n meshlets must contain enough space for all meshlets, worst case size can be computed with meshopt_buildMeshletsBound\n meshlet_vertices must contain enough space for all meshlets, worst case size is equal to max_meshlets * max_vertices\n meshlet_triangles must contain enough space for all meshlets, worst case size is equal to max_meshlets * max_triangles * 3\n vertex_positions should have float3 position in the first 12 bytes of each vertex\n max_vertices and max_triangles must not exceed implementation limits (max_vertices <= 256, max_triangles <= 512; max_triangles must be divisible by 4)\n cone_weight should be set to 0 when cone culling is not used, and a value between 0 and 1 otherwise to balance between cluster size and cone culling efficiency"] 480 | pub fn meshopt_buildMeshlets( 481 | meshlets: *mut meshopt_Meshlet, 482 | meshlet_vertices: *mut ::std::os::raw::c_uint, 483 | meshlet_triangles: *mut ::std::os::raw::c_uchar, 484 | indices: *const ::std::os::raw::c_uint, 485 | index_count: usize, 486 | vertex_positions: *const f32, 487 | vertex_count: usize, 488 | vertex_positions_stride: usize, 489 | max_vertices: usize, 490 | max_triangles: usize, 491 | cone_weight: f32, 492 | ) -> usize; 493 | } 494 | extern "C" { 495 | pub fn meshopt_buildMeshletsScan( 496 | meshlets: *mut meshopt_Meshlet, 497 | meshlet_vertices: *mut ::std::os::raw::c_uint, 498 | meshlet_triangles: *mut ::std::os::raw::c_uchar, 499 | indices: *const ::std::os::raw::c_uint, 500 | index_count: usize, 501 | vertex_count: usize, 502 | max_vertices: usize, 503 | max_triangles: usize, 504 | ) -> usize; 505 | } 506 | extern "C" { 507 | pub fn meshopt_buildMeshletsBound( 508 | index_count: usize, 509 | max_vertices: usize, 510 | max_triangles: usize, 511 | ) -> usize; 512 | } 513 | extern "C" { 514 | #[doc = " Experimental: Meshlet builder with flexible cluster sizes\n Splits the mesh into a set of meshlets, similarly to meshopt_buildMeshlets, but allows to specify minimum and maximum number of triangles per meshlet.\n Clusters between min and max triangle counts are split when the cluster size would have exceeded the expected cluster size by more than split_factor.\n Additionally, allows to switch to axis aligned clusters by setting cone_weight to a negative value.\n\n meshlets must contain enough space for all meshlets, worst case size can be computed with meshopt_buildMeshletsBound using min_triangles (not max!)\n meshlet_vertices must contain enough space for all meshlets, worst case size is equal to max_meshlets * max_vertices\n meshlet_triangles must contain enough space for all meshlets, worst case size is equal to max_meshlets * max_triangles * 3\n vertex_positions should have float3 position in the first 12 bytes of each vertex\n max_vertices, min_triangles and max_triangles must not exceed implementation limits (max_vertices <= 256, max_triangles <= 512; min_triangles <= max_triangles; both min_triangles and max_triangles must be divisible by 4)\n cone_weight should be set to 0 when cone culling is not used, and a value between 0 and 1 otherwise to balance between cluster size and cone culling efficiency; additionally, cone_weight can be set to a negative value to prioritize axis aligned clusters (for raytracing) instead\n split_factor should be set to a non-negative value; when greater than 0, clusters that have large bounds may be split unless they are under the min_triangles threshold"] 515 | pub fn meshopt_buildMeshletsFlex( 516 | meshlets: *mut meshopt_Meshlet, 517 | meshlet_vertices: *mut ::std::os::raw::c_uint, 518 | meshlet_triangles: *mut ::std::os::raw::c_uchar, 519 | indices: *const ::std::os::raw::c_uint, 520 | index_count: usize, 521 | vertex_positions: *const f32, 522 | vertex_count: usize, 523 | vertex_positions_stride: usize, 524 | max_vertices: usize, 525 | min_triangles: usize, 526 | max_triangles: usize, 527 | cone_weight: f32, 528 | split_factor: f32, 529 | ) -> usize; 530 | } 531 | extern "C" { 532 | #[doc = " Meshlet optimizer\n Reorders meshlet vertices and triangles to maximize locality to improve rasterizer throughput\n\n meshlet_triangles and meshlet_vertices must refer to meshlet triangle and vertex index data; when buildMeshlets* is used, these\n need to be computed from meshlet's vertex_offset and triangle_offset\n triangle_count and vertex_count must not exceed implementation limits (vertex_count <= 256, triangle_count <= 512)"] 533 | pub fn meshopt_optimizeMeshlet( 534 | meshlet_vertices: *mut ::std::os::raw::c_uint, 535 | meshlet_triangles: *mut ::std::os::raw::c_uchar, 536 | triangle_count: usize, 537 | vertex_count: usize, 538 | ); 539 | } 540 | #[repr(C)] 541 | #[derive(Debug, Copy, Clone)] 542 | pub struct meshopt_Bounds { 543 | pub center: [f32; 3usize], 544 | pub radius: f32, 545 | pub cone_apex: [f32; 3usize], 546 | pub cone_axis: [f32; 3usize], 547 | pub cone_cutoff: f32, 548 | pub cone_axis_s8: [::std::os::raw::c_schar; 3usize], 549 | pub cone_cutoff_s8: ::std::os::raw::c_schar, 550 | } 551 | extern "C" { 552 | #[doc = " Cluster bounds generator\n Creates bounding volumes that can be used for frustum, backface and occlusion culling.\n\n For backface culling with orthographic projection, use the following formula to reject backfacing clusters:\n dot(view, cone_axis) >= cone_cutoff\n\n For perspective projection, you can use the formula that needs cone apex in addition to axis & cutoff:\n dot(normalize(cone_apex - camera_position), cone_axis) >= cone_cutoff\n\n Alternatively, you can use the formula that doesn't need cone apex and uses bounding sphere instead:\n dot(normalize(center - camera_position), cone_axis) >= cone_cutoff + radius / length(center - camera_position)\n or an equivalent formula that doesn't have a singularity at center = camera_position:\n dot(center - camera_position, cone_axis) >= cone_cutoff * length(center - camera_position) + radius\n\n The formula that uses the apex is slightly more accurate but needs the apex; if you are already using bounding sphere\n to do frustum/occlusion culling, the formula that doesn't use the apex may be preferable (for derivation see\n Real-Time Rendering 4th Edition, section 19.3).\n\n vertex_positions should have float3 position in the first 12 bytes of each vertex\n vertex_count should specify the number of vertices in the entire mesh, not cluster or meshlet\n index_count/3 and triangle_count must not exceed implementation limits (<= 512)"] 553 | pub fn meshopt_computeClusterBounds( 554 | indices: *const ::std::os::raw::c_uint, 555 | index_count: usize, 556 | vertex_positions: *const f32, 557 | vertex_count: usize, 558 | vertex_positions_stride: usize, 559 | ) -> meshopt_Bounds; 560 | } 561 | extern "C" { 562 | pub fn meshopt_computeMeshletBounds( 563 | meshlet_vertices: *const ::std::os::raw::c_uint, 564 | meshlet_triangles: *const ::std::os::raw::c_uchar, 565 | triangle_count: usize, 566 | vertex_positions: *const f32, 567 | vertex_count: usize, 568 | vertex_positions_stride: usize, 569 | ) -> meshopt_Bounds; 570 | } 571 | extern "C" { 572 | #[doc = " Experimental: Sphere bounds generator\n Creates bounding sphere around a set of points or a set of spheres; returns the center and radius of the sphere, with other fields of the result set to 0.\n\n positions should have float3 position in the first 12 bytes of each element\n radii can be NULL; when it's not NULL, it should have a non-negative float radius in the first 4 bytes of each element"] 573 | pub fn meshopt_computeSphereBounds( 574 | positions: *const f32, 575 | count: usize, 576 | positions_stride: usize, 577 | radii: *const f32, 578 | radii_stride: usize, 579 | ) -> meshopt_Bounds; 580 | } 581 | extern "C" { 582 | #[doc = " Experimental: Cluster partitioner\n Partitions clusters into groups of similar size, prioritizing grouping clusters that share vertices.\n\n destination must contain enough space for the resulting partiotion data (cluster_count elements)\n destination[i] will contain the partition id for cluster i, with the total number of partitions returned by the function\n cluster_indices should have the vertex indices referenced by each cluster, stored sequentially\n cluster_index_counts should have the number of indices in each cluster; sum of all cluster_index_counts must be equal to total_index_count\n target_partition_size is a target size for each partition, in clusters; the resulting partitions may be smaller or larger"] 583 | pub fn meshopt_partitionClusters( 584 | destination: *mut ::std::os::raw::c_uint, 585 | cluster_indices: *const ::std::os::raw::c_uint, 586 | total_index_count: usize, 587 | cluster_index_counts: *const ::std::os::raw::c_uint, 588 | cluster_count: usize, 589 | vertex_count: usize, 590 | target_partition_size: usize, 591 | ) -> usize; 592 | } 593 | extern "C" { 594 | #[doc = " Spatial sorter\n Generates a remap table that can be used to reorder points for spatial locality.\n Resulting remap table maps old vertices to new vertices and can be used in meshopt_remapVertexBuffer.\n\n destination must contain enough space for the resulting remap table (vertex_count elements)\n vertex_positions should have float3 position in the first 12 bytes of each vertex"] 595 | pub fn meshopt_spatialSortRemap( 596 | destination: *mut ::std::os::raw::c_uint, 597 | vertex_positions: *const f32, 598 | vertex_count: usize, 599 | vertex_positions_stride: usize, 600 | ); 601 | } 602 | extern "C" { 603 | #[doc = " Experimental: Spatial sorter\n Reorders triangles for spatial locality, and generates a new index buffer. The resulting index buffer can be used with other functions like optimizeVertexCache.\n\n destination must contain enough space for the resulting index buffer (index_count elements)\n vertex_positions should have float3 position in the first 12 bytes of each vertex"] 604 | pub fn meshopt_spatialSortTriangles( 605 | destination: *mut ::std::os::raw::c_uint, 606 | indices: *const ::std::os::raw::c_uint, 607 | index_count: usize, 608 | vertex_positions: *const f32, 609 | vertex_count: usize, 610 | vertex_positions_stride: usize, 611 | ); 612 | } 613 | extern "C" { 614 | #[doc = " Quantize a float into half-precision (as defined by IEEE-754 fp16) floating point value\n Generates +-inf for overflow, preserves NaN, flushes denormals to zero, rounds to nearest\n Representable magnitude range: [6e-5; 65504]\n Maximum relative reconstruction error: 5e-4"] 615 | pub fn meshopt_quantizeHalf(v: f32) -> ::std::os::raw::c_ushort; 616 | } 617 | extern "C" { 618 | #[doc = " Quantize a float into a floating point value with a limited number of significant mantissa bits, preserving the IEEE-754 fp32 binary representation\n Generates +-inf for overflow, preserves NaN, flushes denormals to zero, rounds to nearest\n Assumes N is in a valid mantissa precision range, which is 1..23"] 619 | pub fn meshopt_quantizeFloat(v: f32, N: ::std::os::raw::c_int) -> f32; 620 | } 621 | extern "C" { 622 | #[doc = " Reverse quantization of a half-precision (as defined by IEEE-754 fp16) floating point value\n Preserves Inf/NaN, flushes denormals to zero"] 623 | pub fn meshopt_dequantizeHalf(h: ::std::os::raw::c_ushort) -> f32; 624 | } 625 | extern "C" { 626 | #[doc = " Set allocation callbacks\n These callbacks will be used instead of the default operator new/operator delete for all temporary allocations in the library.\n Note that all algorithms only allocate memory for temporary use.\n allocate/deallocate are always called in a stack-like order - last pointer to be allocated is deallocated first."] 627 | pub fn meshopt_setAllocator( 628 | allocate: ::std::option::Option< 629 | unsafe extern "C" fn(arg1: usize) -> *mut ::std::os::raw::c_void, 630 | >, 631 | deallocate: ::std::option::Option, 632 | ); 633 | } 634 | -------------------------------------------------------------------------------- /include_wasm32/assert.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #define assert(a) 3 | -------------------------------------------------------------------------------- /include_wasm32/limits.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define INT_MAX ((int)~(1u << (8*sizeof(unsigned)-1))) 4 | -------------------------------------------------------------------------------- /include_wasm32/math.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define sqrtf(f) __builtin_sqrtf(f) 4 | #define fabsf(f) __builtin_fabsf(f) 5 | -------------------------------------------------------------------------------- /include_wasm32/string.h: -------------------------------------------------------------------------------- 1 | 2 | #define memcpy(a, b, c) __builtin_memcpy(a, b, c) 3 | #define memset(a, b, c) __builtin_memset(a, b, c) 4 | #define memcmp(a, b, c) __builtin_memcmp(a, b, c) 5 | #define memmove(a, b, c) __builtin_memmove(a, b, c) 6 | -------------------------------------------------------------------------------- /src/analyze.rs: -------------------------------------------------------------------------------- 1 | use crate::{ffi, DecodePosition, VertexDataAdapter}; 2 | use std::mem; 3 | 4 | pub type VertexCacheStatistics = ffi::meshopt_VertexCacheStatistics; 5 | pub type VertexFetchStatistics = ffi::meshopt_VertexFetchStatistics; 6 | pub type OverdrawStatistics = ffi::meshopt_OverdrawStatistics; 7 | 8 | /// Returns cache hit statistics using a simplified FIFO model. 9 | /// Results may not match actual GPU performance. 10 | pub fn analyze_vertex_cache( 11 | indices: &[u32], 12 | vertex_count: usize, 13 | cache_size: u32, 14 | warp_size: u32, 15 | prim_group_size: u32, 16 | ) -> VertexCacheStatistics { 17 | unsafe { 18 | ffi::meshopt_analyzeVertexCache( 19 | indices.as_ptr(), 20 | indices.len(), 21 | vertex_count, 22 | cache_size, 23 | warp_size, 24 | prim_group_size, 25 | ) 26 | } 27 | } 28 | 29 | /// Returns cache hit statistics using a simplified direct mapped model. 30 | /// Results may not match actual GPU performance. 31 | pub fn analyze_vertex_fetch( 32 | indices: &[u32], 33 | vertex_count: usize, 34 | vertex_size: usize, 35 | ) -> VertexFetchStatistics { 36 | unsafe { 37 | ffi::meshopt_analyzeVertexFetch(indices.as_ptr(), indices.len(), vertex_count, vertex_size) 38 | } 39 | } 40 | 41 | /// Returns overdraw statistics using a software rasterizer. 42 | /// Results may not match actual GPU performance. 43 | pub fn analyze_overdraw_decoder( 44 | indices: &[u32], 45 | vertices: &[T], 46 | ) -> OverdrawStatistics { 47 | let positions = vertices 48 | .iter() 49 | .map(|vertex| vertex.decode_position()) 50 | .collect::>(); 51 | unsafe { 52 | ffi::meshopt_analyzeOverdraw( 53 | indices.as_ptr(), 54 | indices.len(), 55 | positions.as_ptr().cast(), 56 | positions.len(), 57 | mem::size_of::() * 3, 58 | ) 59 | } 60 | } 61 | 62 | /// Returns overdraw statistics using a software rasterizer. 63 | /// Results may not match actual GPU performance. 64 | pub fn analyze_overdraw(indices: &[u32], vertices: &VertexDataAdapter<'_>) -> OverdrawStatistics { 65 | unsafe { 66 | ffi::meshopt_analyzeOverdraw( 67 | indices.as_ptr(), 68 | indices.len(), 69 | vertices.pos_ptr(), 70 | vertices.vertex_count, 71 | vertices.vertex_stride, 72 | ) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/clusterize.rs: -------------------------------------------------------------------------------- 1 | use crate::ffi; 2 | use crate::{DecodePosition, VertexDataAdapter}; 3 | 4 | pub type Bounds = ffi::meshopt_Bounds; 5 | 6 | #[derive(Copy, Clone)] 7 | pub struct Meshlet<'data> { 8 | pub vertices: &'data [u32], 9 | pub triangles: &'data [u8], 10 | } 11 | 12 | pub struct Meshlets { 13 | pub meshlets: Vec, 14 | pub vertices: Vec, 15 | pub triangles: Vec, 16 | } 17 | 18 | impl Meshlets { 19 | #[inline] 20 | pub fn len(&self) -> usize { 21 | self.meshlets.len() 22 | } 23 | #[inline] 24 | pub fn is_empty(&self) -> bool { 25 | self.meshlets.is_empty() 26 | } 27 | 28 | fn meshlet_from_ffi(&self, meshlet: &ffi::meshopt_Meshlet) -> Meshlet<'_> { 29 | Meshlet { 30 | vertices: &self.vertices[meshlet.vertex_offset as usize 31 | ..meshlet.vertex_offset as usize + meshlet.vertex_count as usize], 32 | triangles: &self.triangles[meshlet.triangle_offset as usize 33 | ..meshlet.triangle_offset as usize + meshlet.triangle_count as usize * 3], 34 | } 35 | } 36 | 37 | #[inline] 38 | pub fn get(&self, idx: usize) -> Meshlet<'_> { 39 | self.meshlet_from_ffi(&self.meshlets[idx]) 40 | } 41 | 42 | pub fn iter(&self) -> impl Iterator> { 43 | self.meshlets 44 | .iter() 45 | .map(|meshlet| self.meshlet_from_ffi(meshlet)) 46 | } 47 | } 48 | 49 | /// Splits the mesh into a set of meshlets where each meshlet has a micro index buffer 50 | /// indexing into meshlet vertices that refer to the original vertex buffer. 51 | /// 52 | /// The resulting data can be used to render meshes using `NVidia programmable mesh shading` 53 | /// pipeline, or in other cluster-based renderers. 54 | /// 55 | /// Note: `max_vertices` must be <= 256 and `max_triangles` must be <= 512 and divisible by 4. 56 | pub fn build_meshlets( 57 | indices: &[u32], 58 | vertices: &VertexDataAdapter<'_>, 59 | max_vertices: usize, 60 | max_triangles: usize, 61 | cone_weight: f32, 62 | ) -> Meshlets { 63 | let meshlet_count = 64 | unsafe { ffi::meshopt_buildMeshletsBound(indices.len(), max_vertices, max_triangles) }; 65 | let mut meshlets: Vec = 66 | vec![unsafe { ::std::mem::zeroed() }; meshlet_count]; 67 | 68 | let mut meshlet_verts: Vec = vec![0; meshlet_count * max_vertices]; 69 | let mut meshlet_tris: Vec = vec![0; meshlet_count * max_triangles * 3]; 70 | 71 | let count = unsafe { 72 | ffi::meshopt_buildMeshlets( 73 | meshlets.as_mut_ptr(), 74 | meshlet_verts.as_mut_ptr(), 75 | meshlet_tris.as_mut_ptr(), 76 | indices.as_ptr(), 77 | indices.len(), 78 | vertices.pos_ptr(), 79 | vertices.vertex_count, 80 | vertices.vertex_stride, 81 | max_vertices, 82 | max_triangles, 83 | cone_weight, 84 | ) 85 | }; 86 | 87 | let last_meshlet = meshlets[count - 1]; 88 | meshlet_verts 89 | .truncate(last_meshlet.vertex_offset as usize + last_meshlet.vertex_count as usize); 90 | meshlet_tris.truncate( 91 | last_meshlet.triangle_offset as usize 92 | + ((last_meshlet.triangle_count as usize * 3 + 3) & !3), 93 | ); 94 | meshlets.truncate(count); 95 | 96 | for meshlet in meshlets.iter_mut().take(count) { 97 | unsafe { 98 | ffi::meshopt_optimizeMeshlet( 99 | &mut meshlet_verts[meshlet.vertex_offset as usize], 100 | &mut meshlet_tris[meshlet.triangle_offset as usize], 101 | meshlet.triangle_count as usize, 102 | meshlet.vertex_count as usize, 103 | ); 104 | }; 105 | } 106 | 107 | Meshlets { 108 | meshlets, 109 | vertices: meshlet_verts, 110 | triangles: meshlet_tris, 111 | } 112 | } 113 | 114 | /// Experimental: Meshlet builder with flexible cluster sizes. 115 | /// 116 | /// Splits the mesh into a set of meshlets, similarly to `build_meshlets`, but allows to specify minimum and maximum number of triangles per meshlet. 117 | /// Clusters between min and max triangle counts are split when the cluster size would have exceeded the expected cluster size by more than `split_factor`. 118 | /// Additionally, allows to switch to axis aligned clusters by setting `cone_weight` to a negative value. 119 | /// 120 | /// * `max_vertices`, `min_triangles` and `max_triangles` must not exceed implementation limits (`max_vertices` <= 256, `max_triangles` <= 512; `min_triangles` <= `max_triangles`; both `min_triangles` and `max_triangles` must be divisible by 4) 121 | /// * `cone_weight` should be set to 0 when cone culling is not used, and a value between 0 and 1 otherwise to balance between cluster size and cone culling efficiency; additionally, `cone_weight` can be set to a negative value to prioritize axis aligned clusters (for raytracing) instead 122 | /// * `split_factor` should be set to a non-negative value; when greater than 0, clusters that have large bounds may be split unless they are under the `min_triangles` threshold 123 | pub fn build_meshlets_flex( 124 | indices: &[u32], 125 | vertices: &VertexDataAdapter<'_>, 126 | max_vertices: usize, 127 | min_triangles: usize, 128 | max_triangles: usize, 129 | cone_weight: f32, 130 | split_factor: f32, 131 | ) -> Meshlets { 132 | let meshlet_count = 133 | unsafe { ffi::meshopt_buildMeshletsBound(indices.len(), max_vertices, max_triangles) }; 134 | let mut meshlets: Vec = 135 | vec![unsafe { ::std::mem::zeroed() }; meshlet_count]; 136 | 137 | let mut meshlet_verts: Vec = vec![0; meshlet_count * max_vertices]; 138 | let mut meshlet_tris: Vec = vec![0; meshlet_count * max_triangles * 3]; 139 | 140 | let count = unsafe { 141 | ffi::meshopt_buildMeshletsFlex( 142 | meshlets.as_mut_ptr(), 143 | meshlet_verts.as_mut_ptr(), 144 | meshlet_tris.as_mut_ptr(), 145 | indices.as_ptr(), 146 | indices.len(), 147 | vertices.pos_ptr(), 148 | vertices.vertex_count, 149 | vertices.vertex_stride, 150 | max_vertices, 151 | min_triangles, 152 | max_triangles, 153 | cone_weight, 154 | split_factor, 155 | ) 156 | }; 157 | 158 | let last_meshlet = meshlets[count - 1]; 159 | meshlet_verts 160 | .truncate(last_meshlet.vertex_offset as usize + last_meshlet.vertex_count as usize); 161 | meshlet_tris.truncate( 162 | last_meshlet.triangle_offset as usize 163 | + ((last_meshlet.triangle_count as usize * 3 + 3) & !3), 164 | ); 165 | meshlets.truncate(count); 166 | 167 | for meshlet in meshlets.iter_mut().take(count) { 168 | unsafe { 169 | ffi::meshopt_optimizeMeshlet( 170 | &mut meshlet_verts[meshlet.vertex_offset as usize], 171 | &mut meshlet_tris[meshlet.triangle_offset as usize], 172 | meshlet.triangle_count as usize, 173 | meshlet.vertex_count as usize, 174 | ); 175 | }; 176 | } 177 | 178 | Meshlets { 179 | meshlets, 180 | vertices: meshlet_verts, 181 | triangles: meshlet_tris, 182 | } 183 | } 184 | 185 | /// Cluster partitioner 186 | /// Partitions clusters into groups of similar size, prioritizing grouping clusters that share vertices. 187 | /// 188 | /// `destination` must contain enough space for the resulting partition data (`cluster_index_counts.len()` elements) 189 | /// `destination[i]` will contain the partition id for cluster i, with the total number of partitions returned by the function. 190 | /// `cluster_indices` should have the vertex indices referenced by each cluster, stored sequentially 191 | /// `cluster_index_counts` should have the number of indices in each cluster; sum of all `cluster_index_counts` must be equal to `cluster_indices.len()` 192 | /// `target_partition_size` is a target size for each partition, in clusters; the resulting partitions may be smaller or larger 193 | /// 194 | /// The returned value is the number of partitions created. (`destination` can be sliced to the size of the returned value) 195 | pub fn partition_clusters( 196 | destination: &mut [u32], 197 | cluster_indices: &[u32], 198 | cluster_index_counts: &[u32], 199 | vertex_count: usize, 200 | target_partition_size: usize, 201 | ) -> usize { 202 | assert_eq!(destination.len(), cluster_index_counts.len()); 203 | debug_assert_eq!( 204 | cluster_indices.len(), 205 | cluster_index_counts.iter().sum::() as usize 206 | ); 207 | unsafe { 208 | ffi::meshopt_partitionClusters( 209 | destination.as_mut_ptr(), 210 | cluster_indices.as_ptr(), 211 | cluster_indices.len(), 212 | cluster_index_counts.as_ptr(), 213 | cluster_index_counts.len(), 214 | vertex_count, 215 | target_partition_size, 216 | ) 217 | } 218 | } 219 | 220 | /// Creates bounding volumes that can be used for frustum, backface and occlusion culling. 221 | /// 222 | /// For backface culling with orthographic projection, use the following formula to reject backfacing clusters: 223 | /// `dot(view, cone_axis) >= cone_cutoff` 224 | /// 225 | /// For perspective projection, use the following formula that needs cone apex in addition to axis & cutoff: 226 | /// `dot(normalize(cone_apex - camera_position), cone_axis) >= cone_cutoff` 227 | /// 228 | /// Alternatively, you can use the formula that doesn't need cone apex and uses bounding sphere instead: 229 | /// `dot(normalize(center - camera_position), cone_axis) >= cone_cutoff + radius / length(center - camera_position)` 230 | /// 231 | /// or an equivalent formula that doesn't have a singularity at `center = camera_position`: 232 | /// `dot(center - camera_position, cone_axis) >= cone_cutoff * length(center - camera_position) + radius` 233 | /// 234 | /// The formula that uses the apex is slightly more accurate but needs the apex; if you are already using bounding sphere 235 | /// to do frustum/occlusion culling, the formula that doesn't use the apex may be preferable. 236 | /// 237 | /// `index_count` should be <= 256*3 (the function assumes clusters of limited size) 238 | pub fn compute_cluster_bounds(indices: &[u32], vertices: &VertexDataAdapter<'_>) -> Bounds { 239 | unsafe { 240 | ffi::meshopt_computeClusterBounds( 241 | indices.as_ptr(), 242 | indices.len(), 243 | vertices.pos_ptr(), 244 | vertices.vertex_count, 245 | vertices.vertex_stride, 246 | ) 247 | } 248 | } 249 | 250 | /// Creates a view into data containing position-like information. 251 | pub struct PositionDataAdapter<'a> { 252 | /// The data buffer containing position information. 253 | /// This should be a pointer to a buffer of size at least `position_count * position_stride`. 254 | pub data: &'a [u8], 255 | /// The number of positions in the buffer. 256 | pub position_count: usize, 257 | /// The size of each element in the buffer. can be as big as you want if indexing in some other struct 258 | pub position_stride: usize, 259 | /// The offset in bytes from the start of each element to the position data. it must contain 3xf32 of information. 260 | pub position_offset: usize, 261 | } 262 | 263 | /// Creates a view into data containing radius-like information. radius must be a non-negative f32. 264 | /// You may use the same data for both position and radius, but the stride must be the same. 265 | pub struct RadiusDataAdapter<'a> { 266 | /// The data buffer containing radius information. 267 | /// This should be a pointer to a buffer of size at least `radius_count * radius_stride`. 268 | pub data: &'a [u8], 269 | /// The number of radii in the buffer. 270 | pub radius_count: usize, 271 | /// The size of each element in the buffer. can be as big as you want if indexing in some other struct 272 | pub radius_stride: usize, 273 | /// The offset in bytes from the start of each element to the radius data. 274 | pub radius_offset: usize, 275 | } 276 | 277 | pub struct Sphere { 278 | pub center: [f32; 3], 279 | pub radius: f32, 280 | } 281 | 282 | /// Sphere bounds generator 283 | /// Creates bounding sphere around a set of points or a set of spheres; 284 | pub fn compute_sphere_bounds( 285 | positions: PositionDataAdapter<'_>, 286 | radius: Option>, 287 | ) -> Sphere { 288 | if let Some(ref r) = radius { 289 | assert_eq!(positions.position_count, r.radius_count); 290 | } 291 | assert!(positions.data.len() >= positions.position_count * positions.position_stride); 292 | unsafe { 293 | let (radius_ptr, radius_stride) = match radius { 294 | Some(r) => (r.data.as_ptr().add(r.radius_offset).cast(), r.radius_stride), 295 | None => (std::ptr::null(), 0), 296 | }; 297 | let bounds = ffi::meshopt_computeSphereBounds( 298 | positions 299 | .data 300 | .as_ptr() 301 | .add(positions.position_offset) 302 | .cast(), 303 | positions.position_count, 304 | positions.position_stride, 305 | radius_ptr, 306 | radius_stride, 307 | ); 308 | 309 | Sphere { 310 | center: [bounds.center[0], bounds.center[1], bounds.center[2]], 311 | radius: bounds.radius, 312 | } 313 | } 314 | } 315 | 316 | /// Creates bounding volumes that can be used for frustum, backface and occlusion culling. 317 | /// 318 | /// For backface culling with orthographic projection, use the following formula to reject backfacing clusters: 319 | /// `dot(view, cone_axis) >= cone_cutoff` 320 | /// 321 | /// For perspective projection, use the following formula that needs cone apex in addition to axis & cutoff: 322 | /// `dot(normalize(cone_apex - camera_position), cone_axis) >= cone_cutoff` 323 | /// 324 | /// Alternatively, you can use the formula that doesn't need cone apex and uses bounding sphere instead: 325 | /// `dot(normalize(center - camera_position), cone_axis) >= cone_cutoff + radius / length(center - camera_position)` 326 | /// 327 | /// or an equivalent formula that doesn't have a singularity at `center = camera_position`: 328 | /// `dot(center - camera_position, cone_axis) >= cone_cutoff * length(center - camera_position) + radius` 329 | /// 330 | /// The formula that uses the apex is slightly more accurate but needs the apex; if you are already using bounding sphere 331 | /// to do frustum/occlusion culling, the formula that doesn't use the apex may be preferable. 332 | /// 333 | /// `index_count` should be <= 256*3 (the function assumes clusters of limited size) 334 | pub fn compute_cluster_bounds_decoder( 335 | indices: &[u32], 336 | vertices: &[T], 337 | ) -> Bounds { 338 | let vertices = vertices 339 | .iter() 340 | .map(|vertex| vertex.decode_position()) 341 | .collect::>(); 342 | unsafe { 343 | ffi::meshopt_computeClusterBounds( 344 | indices.as_ptr(), 345 | indices.len(), 346 | vertices.as_ptr().cast(), 347 | vertices.len() * 3, 348 | ::std::mem::size_of::() * 3, 349 | ) 350 | } 351 | } 352 | 353 | pub fn compute_meshlet_bounds(meshlet: Meshlet<'_>, vertices: &VertexDataAdapter<'_>) -> Bounds { 354 | unsafe { 355 | ffi::meshopt_computeMeshletBounds( 356 | meshlet.vertices.as_ptr(), 357 | meshlet.triangles.as_ptr(), 358 | meshlet.triangles.len() / 3, 359 | vertices.pos_ptr(), 360 | vertices.vertex_count, 361 | vertices.vertex_stride, 362 | ) 363 | } 364 | } 365 | 366 | pub fn compute_meshlet_bounds_decoder( 367 | meshlet: Meshlet<'_>, 368 | vertices: &[T], 369 | ) -> Bounds { 370 | let vertices = vertices 371 | .iter() 372 | .map(|vertex| vertex.decode_position()) 373 | .collect::>(); 374 | unsafe { 375 | ffi::meshopt_computeMeshletBounds( 376 | meshlet.vertices.as_ptr(), 377 | meshlet.triangles.as_ptr(), 378 | meshlet.triangles.len() / 3, 379 | vertices.as_ptr().cast(), 380 | vertices.len() * 3, 381 | std::mem::size_of::() * 3, 382 | ) 383 | } 384 | } 385 | 386 | #[cfg(test)] 387 | mod tests { 388 | use super::*; 389 | use crate::typed_to_bytes; 390 | #[test] 391 | fn test_cluster_sphere_bounds() { 392 | let vbr: &[f32] = &[ 393 | 0.0, 0.0, 0.0, 0.0, // 394 | 0.0, 1.0, 0.0, 1.0, // 395 | 0.0, 0.0, 1.0, 2.0, // 396 | 1.0, 0.0, 1.0, 3.0, 397 | ]; 398 | 399 | let bounds = compute_sphere_bounds( 400 | PositionDataAdapter { 401 | data: typed_to_bytes(vbr), 402 | position_count: 4, 403 | position_stride: 4 * ::std::mem::size_of::(), 404 | position_offset: 0, 405 | }, 406 | None, 407 | ); 408 | 409 | assert!(bounds.radius < 0.97); 410 | 411 | let eps: &[f32] = &[1e-3, 2e-3, 3e-3, 4e-3]; 412 | 413 | let bounds = compute_sphere_bounds( 414 | PositionDataAdapter { 415 | data: typed_to_bytes(vbr), 416 | position_count: 4, 417 | position_stride: 4 * ::std::mem::size_of::(), 418 | position_offset: 0, 419 | }, 420 | Some(RadiusDataAdapter { 421 | data: typed_to_bytes(eps), 422 | radius_count: 4, 423 | radius_stride: ::std::mem::size_of::(), 424 | radius_offset: 0, 425 | }), 426 | ); 427 | 428 | assert!(bounds.radius < 0.87); 429 | assert!((bounds.center[0] - 0.5).abs() < 1e-2); 430 | assert!((bounds.center[1] - 0.5).abs() < 1e-2); 431 | assert!((bounds.center[2] - 0.5).abs() < 1e-2); 432 | 433 | let bounds = compute_sphere_bounds( 434 | PositionDataAdapter { 435 | data: typed_to_bytes(vbr), 436 | position_count: 4, 437 | position_stride: 4 * ::std::mem::size_of::(), 438 | position_offset: 0, 439 | }, 440 | Some(RadiusDataAdapter { 441 | data: typed_to_bytes(vbr), 442 | radius_count: 4, 443 | radius_stride: 4 * ::std::mem::size_of::(), 444 | radius_offset: 3 * ::std::mem::size_of::(), 445 | }), 446 | ); 447 | 448 | assert!((bounds.radius - 3.0).abs() < 1e-2); 449 | assert!((bounds.center[0] - 1.0).abs() < 1e-2); 450 | assert!((bounds.center[1] - 0.0).abs() < 1e-2); 451 | assert!((bounds.center[2] - 1.0).abs() < 1e-2); 452 | } 453 | 454 | #[test] 455 | fn test_partition_basic() { 456 | // 0 1 2 457 | // 3 458 | // 4 5 6 7 8 459 | // 9 460 | // 10 11 12 461 | let cluster_indices: &[u32] = &[ 462 | 0, 1, 3, 4, 5, 6, // 463 | 1, 2, 3, 6, 7, 8, // 464 | 4, 5, 6, 9, 10, 11, // 465 | 6, 7, 8, 9, 11, 12, // 466 | ]; 467 | 468 | let cluster_index_counts: &[u32] = &[6, 6, 6, 6]; 469 | let mut partitions = vec![0u32; cluster_index_counts.len()]; 470 | 471 | assert_eq!( 472 | partition_clusters( 473 | &mut partitions, 474 | cluster_indices, 475 | cluster_index_counts, 476 | 13, 477 | 1 478 | ), 479 | 4 480 | ); 481 | assert_eq!(partitions, [0, 1, 2, 3]); 482 | 483 | assert_eq!( 484 | partition_clusters( 485 | &mut partitions, 486 | cluster_indices, 487 | cluster_index_counts, 488 | 13, 489 | 2 490 | ), 491 | 2 492 | ); 493 | assert_eq!(partitions, [0, 0, 1, 1]); 494 | 495 | assert_eq!( 496 | partition_clusters( 497 | &mut partitions, 498 | cluster_indices, 499 | cluster_index_counts, 500 | 13, 501 | 4 502 | ), 503 | 1 504 | ); 505 | assert_eq!(partitions, [0, 0, 0, 0]); 506 | } 507 | 508 | #[test] 509 | fn test_meshlets_flex() { 510 | // Two tetrahedrons far apart 511 | let vb: &[f32] = &[ 512 | 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, // 513 | 10.0, 0.0, 0.0, 11.0, 0.0, 0.0, 10.0, 1.0, 0.0, 10.0, 0.0, 1.0, 514 | ]; 515 | 516 | let ib: &[u32] = &[ 517 | 0, 1, 2, 0, 2, 3, 0, 3, 1, 1, 3, 2, // 518 | 4, 5, 6, 4, 6, 7, 4, 7, 5, 5, 7, 6, 519 | ]; 520 | 521 | let vertices = 522 | VertexDataAdapter::new(typed_to_bytes(vb), std::mem::size_of::<[f32; 3]>(), 0).unwrap(); 523 | 524 | // Up to 2 meshlets with min_triangles = 4 525 | assert_eq!( 526 | unsafe { ffi::meshopt_buildMeshletsBound(ib.len(), 16, 4) }, 527 | 2 528 | ); 529 | 530 | let meshlets = build_meshlets_flex(ib, &vertices, 16, 4, 8, 0.0, 0.0); 531 | assert_eq!(meshlets.len(), 1); 532 | assert_eq!(meshlets.meshlets[0].triangle_count, 8); 533 | assert_eq!(meshlets.meshlets[0].vertex_count, 8); 534 | 535 | let meshlets = build_meshlets_flex(ib, &vertices, 16, 4, 8, 0.0, 10.0); 536 | assert_eq!(meshlets.len(), 1); 537 | assert_eq!(meshlets.meshlets[0].triangle_count, 8); 538 | assert_eq!(meshlets.meshlets[0].vertex_count, 8); 539 | 540 | let meshlets = build_meshlets_flex(ib, &vertices, 16, 4, 8, 0.0, 1.0); 541 | assert_eq!(meshlets.len(), 2); 542 | assert_eq!(meshlets.meshlets[0].triangle_count, 4); 543 | assert_eq!(meshlets.meshlets[0].vertex_count, 4); 544 | assert_eq!(meshlets.meshlets[1].triangle_count, 4); 545 | assert_eq!(meshlets.meshlets[1].vertex_count, 4); 546 | 547 | let meshlets = build_meshlets_flex(ib, &vertices, 16, 4, 8, -1.0, 10.0); 548 | assert_eq!(meshlets.len(), 1); 549 | assert_eq!(meshlets.meshlets[0].triangle_count, 8); 550 | assert_eq!(meshlets.meshlets[0].vertex_count, 8); 551 | 552 | let meshlets = build_meshlets_flex(ib, &vertices, 16, 4, 8, -1.0, 1.0); 553 | assert_eq!(meshlets.len(), 2); 554 | assert_eq!(meshlets.meshlets[0].triangle_count, 4); 555 | assert_eq!(meshlets.meshlets[0].vertex_count, 4); 556 | assert_eq!(meshlets.meshlets[1].triangle_count, 4); 557 | assert_eq!(meshlets.meshlets[1].vertex_count, 4); 558 | } 559 | } 560 | -------------------------------------------------------------------------------- /src/encoding.rs: -------------------------------------------------------------------------------- 1 | use crate::{error_or, ffi, utilities::rcp_safe, Result}; 2 | use std::mem; 3 | 4 | /// Encodes index data into an array of bytes that is generally much smaller (<1.5 bytes/triangle) 5 | /// and compresses better (<1 bytes/triangle) compared to original. 6 | /// 7 | /// For maximum efficiency the index buffer being encoded has to be optimized for vertex cache and 8 | /// vertex fetch first. 9 | pub fn encode_index_buffer(indices: &[u32], vertex_count: usize) -> Result> { 10 | let bounds = unsafe { ffi::meshopt_encodeIndexBufferBound(indices.len(), vertex_count) }; 11 | let mut result: Vec = vec![0; bounds]; 12 | let size = unsafe { 13 | ffi::meshopt_encodeIndexBuffer( 14 | result.as_mut_ptr(), 15 | result.len(), 16 | indices.as_ptr(), 17 | indices.len(), 18 | ) 19 | }; 20 | result.resize(size, 0u8); 21 | Ok(result) 22 | } 23 | 24 | /// Decodes index data from an array of bytes generated by `encode_index_buffer`. 25 | /// The decoder is safe to use for untrusted input, but it may produce garbage 26 | /// data (e.g. out of range indices). 27 | pub fn decode_index_buffer( 28 | encoded: &[u8], 29 | index_count: usize, 30 | ) -> Result> { 31 | const fn assert_valid_size() { 32 | assert!( 33 | mem::size_of::() == 2 || mem::size_of::() == 4, 34 | "size of result type must be 2 or 4 bytes wide" 35 | ); 36 | } 37 | 38 | assert_valid_size::(); 39 | 40 | let mut result: Vec = vec![Default::default(); index_count]; 41 | let result_code = unsafe { 42 | ffi::meshopt_decodeIndexBuffer( 43 | result.as_mut_ptr().cast(), 44 | index_count, 45 | mem::size_of::(), 46 | encoded.as_ptr(), 47 | encoded.len(), 48 | ) 49 | }; 50 | 51 | error_or(result_code, result) 52 | } 53 | 54 | /// Encodes vertex data into an array of bytes that is generally smaller and compresses better 55 | /// compared to original. 56 | /// 57 | /// This function works for a single vertex stream; for multiple vertex streams, 58 | /// call `encode_vertex_buffer` for each stream. 59 | pub fn encode_vertex_buffer(vertices: &[T]) -> Result> { 60 | let bounds = 61 | unsafe { ffi::meshopt_encodeVertexBufferBound(vertices.len(), mem::size_of::()) }; 62 | let mut result: Vec = vec![0; bounds]; 63 | let size = unsafe { 64 | ffi::meshopt_encodeVertexBuffer( 65 | result.as_mut_ptr(), 66 | result.len(), 67 | vertices.as_ptr().cast(), 68 | vertices.len(), 69 | mem::size_of::(), 70 | ) 71 | }; 72 | result.resize(size, 0u8); 73 | Ok(result) 74 | } 75 | 76 | /// Decodes vertex data from an array of bytes generated by `encode_vertex_buffer`. 77 | /// The decoder is safe to use for untrusted input, but it may produce garbage data. 78 | pub fn decode_vertex_buffer( 79 | encoded: &[u8], 80 | vertex_count: usize, 81 | ) -> Result> { 82 | let mut result: Vec = vec![Default::default(); vertex_count]; 83 | let result_code = unsafe { 84 | ffi::meshopt_decodeVertexBuffer( 85 | result.as_mut_ptr().cast(), 86 | vertex_count, 87 | mem::size_of::(), 88 | encoded.as_ptr(), 89 | encoded.len(), 90 | ) 91 | }; 92 | 93 | error_or(result_code, result) 94 | } 95 | 96 | #[repr(C)] 97 | #[derive(Debug, Copy, Clone)] 98 | pub struct EncodeHeader { 99 | pub magic: [u8; 4], // OPTM 100 | 101 | pub group_count: u32, 102 | pub vertex_count: u32, 103 | pub index_count: u32, 104 | pub vertex_data_size: u32, 105 | pub index_data_size: u32, 106 | 107 | pub pos_offset: [f32; 3], 108 | pub pos_scale: f32, 109 | pub uv_offset: [f32; 2], 110 | pub uv_scale: [f32; 2], 111 | 112 | pub reserved: [u32; 2], 113 | } 114 | 115 | #[repr(C)] 116 | #[derive(Debug, Copy, Clone)] 117 | pub struct EncodeObject { 118 | pub index_offset: u32, 119 | pub index_count: u32, 120 | pub material_length: u32, 121 | pub reserved: u32, 122 | } 123 | 124 | pub fn calc_pos_offset_and_scale(positions: &[f32]) -> ([f32; 3], f32) { 125 | const MAX: f32 = f32::MAX; 126 | let pos_offset = positions 127 | .chunks(3) 128 | .fold([MAX, MAX, MAX], |result, position| { 129 | [ 130 | result[0].min(position[0]), 131 | result[1].min(position[1]), 132 | result[2].min(position[2]), 133 | ] 134 | }); 135 | 136 | let pos_scale = positions.chunks(3).fold(0f32, |result, position| { 137 | result 138 | .max(position[0] - pos_offset[0]) 139 | .max(position[1] - pos_offset[1]) 140 | .max(position[2] - pos_offset[2]) 141 | }); 142 | 143 | (pos_offset, pos_scale) 144 | } 145 | 146 | pub fn calc_pos_offset_and_scale_inverse(positions: &[f32]) -> ([f32; 3], f32) { 147 | let (pos_offset, pos_scale) = calc_pos_offset_and_scale(positions); 148 | let pos_scale_inverse = rcp_safe(pos_scale); 149 | (pos_offset, pos_scale_inverse) 150 | } 151 | 152 | pub fn calc_uv_offset_and_scale(coords: &[f32]) -> ([f32; 2], [f32; 2]) { 153 | const MAX: f32 = f32::MAX; 154 | 155 | let uv_offset = coords.chunks(2).fold([MAX, MAX], |result, coord| { 156 | [result[0].min(coord[0]), result[1].min(coord[1])] 157 | }); 158 | 159 | let uv_scale = coords.chunks(2).fold([MAX, MAX], |result, coord| { 160 | [ 161 | result[0].max(coord[0] - uv_offset[0]), 162 | result[1].max(coord[1] - uv_offset[1]), 163 | ] 164 | }); 165 | 166 | (uv_offset, uv_scale) 167 | } 168 | 169 | pub fn calc_uv_offset_and_scale_inverse(coords: &[f32]) -> ([f32; 2], [f32; 2]) { 170 | let (uv_offset, uv_scale) = calc_uv_offset_and_scale(coords); 171 | let uv_scale_inverse = [rcp_safe(uv_scale[0]), rcp_safe(uv_scale[1])]; 172 | (uv_offset, uv_scale_inverse) 173 | } 174 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | /// A type alias for handling errors throughout meshopt 2 | pub type Result = std::result::Result; 3 | 4 | /// An error that can occur 5 | #[derive(Debug, thiserror::Error)] 6 | #[non_exhaustive] 7 | pub enum Error { 8 | /// An error that occurred interfacing with native code through FFI. 9 | #[error("native error: {0}")] 10 | Native(i32), 11 | 12 | /// An error that occurred while accessing or allocating memory 13 | #[error("memory error: {0}")] 14 | Memory(std::borrow::Cow<'static, str>), 15 | 16 | /// An error that occurred while parsing a data source 17 | #[error("parse error: {0}")] 18 | Parse(String), 19 | 20 | /// An error that occurred while working with a file path. 21 | #[error("path error: {0}")] 22 | Path(std::path::PathBuf), 23 | 24 | /// Generally, these errors correspond to bugs in this library. 25 | #[error("BUG: Please report this bug with a backtrace to https://github.com/gwihlidal/meshopt-rs\n{0}")] 26 | Bug(String), 27 | 28 | /// An error occurred while reading/writing a configuration 29 | #[error("config error: {0}")] 30 | Config(String), 31 | 32 | /// An unexpected I/O error occurred. 33 | #[error(transparent)] 34 | Io(#[from] std::io::Error), 35 | // An error occurred while parsing a number in a free-form query. 36 | //Number, 37 | } 38 | 39 | impl Error { 40 | #[inline] 41 | pub(crate) fn memory(msg: &'static str) -> Self { 42 | Self::Memory(std::borrow::Cow::Borrowed(msg)) 43 | } 44 | 45 | #[inline] 46 | pub(crate) fn memory_dynamic(msg: String) -> Self { 47 | Self::Memory(std::borrow::Cow::Owned(msg)) 48 | } 49 | } 50 | 51 | #[inline] 52 | pub(crate) fn error_or(code: i32, ok: T) -> Result { 53 | if code == 0 { 54 | Ok(ok) 55 | } else { 56 | Err(Error::Native(code)) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/ffi.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_upper_case_globals)] 2 | #![allow(non_camel_case_types)] 3 | #![allow(non_snake_case)] 4 | #![allow(clippy::unreadable_literal)] 5 | 6 | include!("../gen/bindings.rs"); 7 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // BEGIN - Embark standard lints v5 for Rust 1.55+ 2 | // do not change or add/remove here, but one can add exceptions after this section 3 | // for more info see: 4 | #![deny(unsafe_code)] 5 | #![warn( 6 | clippy::all, 7 | clippy::await_holding_lock, 8 | clippy::char_lit_as_u8, 9 | clippy::checked_conversions, 10 | clippy::dbg_macro, 11 | clippy::debug_assert_with_mut_call, 12 | clippy::disallowed_methods, 13 | clippy::disallowed_types, 14 | clippy::doc_markdown, 15 | clippy::empty_enum, 16 | clippy::enum_glob_use, 17 | clippy::exit, 18 | clippy::expl_impl_clone_on_copy, 19 | clippy::explicit_deref_methods, 20 | clippy::explicit_into_iter_loop, 21 | clippy::fallible_impl_from, 22 | clippy::filter_map_next, 23 | clippy::flat_map_option, 24 | clippy::float_cmp_const, 25 | clippy::fn_params_excessive_bools, 26 | clippy::from_iter_instead_of_collect, 27 | clippy::if_let_mutex, 28 | clippy::implicit_clone, 29 | clippy::imprecise_flops, 30 | clippy::inefficient_to_string, 31 | clippy::invalid_upcast_comparisons, 32 | clippy::large_digit_groups, 33 | clippy::large_stack_arrays, 34 | clippy::large_types_passed_by_value, 35 | clippy::let_unit_value, 36 | clippy::linkedlist, 37 | clippy::lossy_float_literal, 38 | clippy::macro_use_imports, 39 | clippy::manual_ok_or, 40 | clippy::map_err_ignore, 41 | clippy::map_flatten, 42 | clippy::map_unwrap_or, 43 | clippy::match_on_vec_items, 44 | clippy::match_same_arms, 45 | clippy::match_wild_err_arm, 46 | clippy::match_wildcard_for_single_variants, 47 | clippy::mem_forget, 48 | clippy::missing_enforced_import_renames, 49 | clippy::mut_mut, 50 | clippy::mutex_integer, 51 | clippy::needless_borrow, 52 | clippy::needless_continue, 53 | clippy::needless_for_each, 54 | clippy::option_option, 55 | clippy::path_buf_push_overwrite, 56 | clippy::ptr_as_ptr, 57 | clippy::rc_mutex, 58 | clippy::ref_option_ref, 59 | clippy::rest_pat_in_fully_bound_structs, 60 | clippy::same_functions_in_if_condition, 61 | clippy::semicolon_if_nothing_returned, 62 | clippy::single_match_else, 63 | clippy::string_add_assign, 64 | clippy::string_add, 65 | clippy::string_lit_as_bytes, 66 | clippy::string_to_string, 67 | clippy::todo, 68 | clippy::trait_duplication_in_bounds, 69 | clippy::unimplemented, 70 | clippy::unnested_or_patterns, 71 | clippy::unused_self, 72 | clippy::useless_transmute, 73 | clippy::verbose_file_reads, 74 | clippy::zero_sized_map_values, 75 | future_incompatible, 76 | nonstandard_style, 77 | rust_2018_idioms 78 | )] 79 | // END - Embark standard lints v0.5 for Rust 1.55+ 80 | // crate-specific exceptions: 81 | // This crate is doing a lot of FFI and byte munging 82 | #![allow(unsafe_code)] 83 | 84 | pub mod analyze; 85 | pub mod clusterize; 86 | pub mod encoding; 87 | pub mod error; 88 | pub mod ffi; 89 | pub mod optimize; 90 | pub mod packing; 91 | pub mod remap; 92 | pub mod shadow; 93 | pub mod simplify; 94 | pub mod stripify; 95 | pub mod utilities; 96 | 97 | pub use crate::{ 98 | analyze::*, clusterize::*, encoding::*, error::*, optimize::*, packing::*, remap::*, shadow::*, 99 | simplify::*, stripify::*, utilities::*, 100 | }; 101 | use std::marker::PhantomData; 102 | 103 | /// Vertex attribute stream, similar to `glVertexPointer` 104 | /// 105 | /// Each element takes size bytes, with stride controlling 106 | /// the spacing between successive elements. 107 | #[derive(Debug, Copy, Clone)] 108 | pub struct VertexStream<'a> { 109 | /// Pointer to buffer which contains vertex data. 110 | pub data: *const u8, 111 | /// Space between vertices inside the buffer (in bytes). 112 | pub stride: usize, 113 | /// The size in bytes of the vertex attribute this Stream is representing. 114 | pub size: usize, 115 | 116 | _marker: PhantomData<&'a ()>, 117 | } 118 | 119 | impl<'a> VertexStream<'a> { 120 | /// Create a `VertexStream` for a buffer consisting only of elements of type `T`. 121 | pub fn new(ptr: *const T) -> VertexStream<'a> { 122 | Self::new_with_stride::(ptr, std::mem::size_of::()) 123 | } 124 | 125 | /// Create a `VertexStream` for a buffer that contains elements of type `VertexType`. 126 | /// 127 | /// The buffer pointed to by `ptr` starts with one value of `T`, the next value of T 128 | /// is `*(ptr + stride)`. 129 | /// 130 | /// (The `VertexType` does not need to be a concrete type, 131 | /// it is only used here to avoid casts on the caller side). 132 | pub fn new_with_stride( 133 | ptr: *const VertexType, 134 | stride: usize, 135 | ) -> VertexStream<'a> { 136 | VertexStream { 137 | data: ptr.cast(), 138 | stride, 139 | size: std::mem::size_of::(), 140 | 141 | _marker: PhantomData, 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/optimize.rs: -------------------------------------------------------------------------------- 1 | use crate::{ffi, DecodePosition, VertexDataAdapter}; 2 | use std::mem; 3 | 4 | /// Reorders indices to reduce the number of GPU vertex shader invocations. 5 | /// 6 | /// If index buffer contains multiple ranges for multiple draw calls, 7 | /// this function needs to be called on each range individually. 8 | pub fn optimize_vertex_cache(indices: &[u32], vertex_count: usize) -> Vec { 9 | let mut optimized: Vec = vec![0; indices.len()]; 10 | unsafe { 11 | ffi::meshopt_optimizeVertexCache( 12 | optimized.as_mut_ptr(), 13 | indices.as_ptr(), 14 | indices.len(), 15 | vertex_count, 16 | ); 17 | } 18 | optimized 19 | } 20 | 21 | /// Reorders indices to reduce the number of GPU vertex shader invocations. 22 | /// 23 | /// If index buffer contains multiple ranges for multiple draw calls, 24 | /// this function needs to be called on each range individually. 25 | pub fn optimize_vertex_cache_in_place(indices: &mut [u32], vertex_count: usize) { 26 | unsafe { 27 | ffi::meshopt_optimizeVertexCache( 28 | indices.as_mut_ptr(), 29 | indices.as_ptr(), 30 | indices.len(), 31 | vertex_count, 32 | ); 33 | } 34 | } 35 | 36 | /// Vertex transform cache optimizer for FIFO caches. 37 | /// 38 | /// Reorders indices to reduce the number of GPU vertex shader invocations. 39 | /// 40 | /// Generally takes ~3x less time to optimize meshes but produces inferior 41 | /// results compared to `optimize_vertex_cache`. 42 | /// 43 | /// If index buffer contains multiple ranges for multiple draw calls, 44 | /// this function needs to be called on each range individually. 45 | pub fn optimize_vertex_cache_fifo( 46 | indices: &[u32], 47 | vertex_count: usize, 48 | cache_size: u32, 49 | ) -> Vec { 50 | let mut optimized: Vec = vec![0; indices.len()]; 51 | unsafe { 52 | ffi::meshopt_optimizeVertexCacheFifo( 53 | optimized.as_mut_ptr(), 54 | indices.as_ptr(), 55 | indices.len(), 56 | vertex_count, 57 | cache_size, 58 | ); 59 | } 60 | optimized 61 | } 62 | 63 | /// Vertex transform cache optimizer for FIFO caches (in place). 64 | /// 65 | /// Reorders indices to reduce the number of GPU vertex shader invocations. 66 | /// 67 | /// Generally takes ~3x less time to optimize meshes but produces inferior 68 | /// results compared to `optimize_vertex_cache_fifo_in_place`. 69 | /// 70 | /// If index buffer contains multiple ranges for multiple draw calls, 71 | /// this function needs to be called on each range individually. 72 | pub fn optimize_vertex_cache_fifo_in_place( 73 | indices: &mut [u32], 74 | vertex_count: usize, 75 | cache_size: u32, 76 | ) { 77 | unsafe { 78 | ffi::meshopt_optimizeVertexCacheFifo( 79 | indices.as_mut_ptr(), 80 | indices.as_ptr(), 81 | indices.len(), 82 | vertex_count, 83 | cache_size, 84 | ); 85 | } 86 | } 87 | 88 | /// Reorders vertices and changes indices to reduce the amount of GPU 89 | /// memory fetches during vertex processing. 90 | /// 91 | /// This functions works for a single vertex stream; for multiple vertex streams, 92 | /// use `optimize_vertex_fetch_remap` + `remap_vertex_buffer` for each stream. 93 | /// 94 | /// `indices` is used both as an input and as an output index buffer. 95 | pub fn optimize_vertex_fetch(indices: &mut [u32], vertices: &[T]) -> Vec { 96 | let mut result: Vec = vec![T::default(); vertices.len()]; 97 | let next_vertex = unsafe { 98 | ffi::meshopt_optimizeVertexFetch( 99 | result.as_mut_ptr().cast(), 100 | indices.as_mut_ptr(), 101 | indices.len(), 102 | vertices.as_ptr().cast(), 103 | vertices.len(), 104 | mem::size_of::(), 105 | ) 106 | }; 107 | result.resize(next_vertex, T::default()); 108 | result 109 | } 110 | 111 | /// Vertex fetch cache optimizer (modifies in place) 112 | /// Reorders vertices and changes indices to reduce the amount of GPU 113 | /// memory fetches during vertex processing. 114 | /// 115 | /// This functions works for a single vertex stream; for multiple vertex streams, 116 | /// use `optimize_vertex_fetch_remap` + `remap_vertex_buffer` for each stream. 117 | /// 118 | /// `indices` and `vertices` are used both as an input and as an output buffer. 119 | pub fn optimize_vertex_fetch_in_place(indices: &mut [u32], vertices: &mut [T]) -> usize { 120 | unsafe { 121 | ffi::meshopt_optimizeVertexFetch( 122 | vertices.as_mut_ptr().cast(), 123 | indices.as_mut_ptr(), 124 | indices.len(), 125 | vertices.as_ptr().cast(), 126 | vertices.len(), 127 | mem::size_of::(), 128 | ) 129 | } 130 | } 131 | 132 | /// Generates vertex remap to reduce the amount of GPU memory fetches during 133 | /// vertex processing. 134 | /// 135 | /// The resulting remap table should be used to reorder vertex/index buffers 136 | /// using `optimize_remap_vertex_buffer`/`optimize_remap_index_buffer`. 137 | pub fn optimize_vertex_fetch_remap(indices: &[u32], vertex_count: usize) -> Vec { 138 | let mut result: Vec = vec![0; vertex_count]; 139 | let next_vertex = unsafe { 140 | ffi::meshopt_optimizeVertexFetchRemap( 141 | result.as_mut_ptr(), 142 | indices.as_ptr(), 143 | indices.len(), 144 | vertex_count, 145 | ) 146 | }; 147 | result.resize(next_vertex, 0u32); 148 | result 149 | } 150 | 151 | /// Reorders indices to reduce the number of GPU vertex shader invocations 152 | /// and the pixel overdraw. 153 | /// 154 | /// `indices` must contain index data that is the result of `optimize_vertex_cache` 155 | /// (*not* the original mesh indices!) 156 | /// 157 | /// `threshold` indicates how much the overdraw optimizer can degrade vertex cache 158 | /// efficiency (1.05 = up to 5%) to reduce overdraw more efficiently. 159 | pub fn optimize_overdraw_in_place( 160 | indices: &mut [u32], 161 | vertices: &VertexDataAdapter<'_>, 162 | threshold: f32, 163 | ) { 164 | let vertex_data = vertices.reader.get_ref(); 165 | let vertex_data = vertex_data.as_ptr().cast::(); 166 | let positions = unsafe { vertex_data.add(vertices.position_offset) }; 167 | unsafe { 168 | ffi::meshopt_optimizeOverdraw( 169 | indices.as_mut_ptr(), 170 | indices.as_ptr(), 171 | indices.len(), 172 | positions.cast(), 173 | vertices.vertex_count, 174 | vertices.vertex_stride, 175 | threshold, 176 | ); 177 | } 178 | } 179 | 180 | /// Reorders indices to reduce the number of GPU vertex shader invocations 181 | /// and the pixel overdraw. 182 | /// 183 | /// `indices` must contain index data that is the result of `optimize_vertex_cache` 184 | /// (*not* the original mesh indices!) 185 | /// 186 | /// `threshold` indicates how much the overdraw optimizer can degrade vertex cache 187 | /// efficiency (1.05 = up to 5%) to reduce overdraw more efficiently. 188 | pub fn optimize_overdraw_in_place_decoder( 189 | indices: &mut [u32], 190 | vertices: &[T], 191 | threshold: f32, 192 | ) { 193 | let positions = vertices 194 | .iter() 195 | .map(|vertex| vertex.decode_position()) 196 | .collect::>(); 197 | unsafe { 198 | ffi::meshopt_optimizeOverdraw( 199 | indices.as_mut_ptr(), 200 | indices.as_ptr(), 201 | indices.len(), 202 | positions.as_ptr().cast(), 203 | positions.len(), 204 | mem::size_of::() * 3, 205 | threshold, 206 | ); 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /src/packing.rs: -------------------------------------------------------------------------------- 1 | use crate::{quantize_half, quantize_snorm}; 2 | use float_cmp::ApproxEqUlps; 3 | 4 | pub trait DecodePosition { 5 | fn decode_position(&self) -> [f32; 3]; 6 | } 7 | 8 | impl DecodePosition for [f32; 3] { 9 | fn decode_position(&self) -> [f32; 3] { 10 | *self 11 | } 12 | } 13 | 14 | pub trait FromVertex { 15 | fn fill_from_vertex(&mut self, vertex: &Vertex); 16 | } 17 | 18 | #[derive(Default, Debug, Copy, Clone, Eq, PartialEq)] 19 | #[repr(C)] 20 | pub struct PackedVertex { 21 | /// Unsigned 16-bit value, use `pos_offset/pos_scale` to unpack 22 | pub p: [u16; 4], 23 | 24 | /// Normalized signed 8-bit value 25 | pub n: [i8; 4], 26 | 27 | /// Unsigned 16-bit value, use `uv_offset/uv_scale` to unpack 28 | pub t: [u16; 2], 29 | } 30 | 31 | impl FromVertex for PackedVertex { 32 | fn fill_from_vertex(&mut self, vertex: &Vertex) { 33 | self.p[0] = quantize_half(vertex.p[0]); 34 | self.p[1] = quantize_half(vertex.p[1]); 35 | self.p[2] = quantize_half(vertex.p[2]); 36 | self.p[3] = 0u16; 37 | 38 | self.n[0] = quantize_snorm(vertex.n[0], 8) as i8; 39 | self.n[1] = quantize_snorm(vertex.n[1], 8) as i8; 40 | self.n[2] = quantize_snorm(vertex.n[2], 8) as i8; 41 | self.n[3] = 0i8; 42 | 43 | self.t[0] = quantize_half(vertex.t[0]); 44 | self.t[1] = quantize_half(vertex.t[1]); 45 | } 46 | } 47 | 48 | #[derive(Default, Debug, Copy, Clone, Eq, PartialEq)] 49 | #[repr(C)] 50 | pub struct PackedVertexOct { 51 | pub p: [u16; 3], 52 | pub n: [u8; 2], // octahedron encoded normal, aliases .pw 53 | pub t: [u16; 2], 54 | } 55 | 56 | impl FromVertex for PackedVertexOct { 57 | fn fill_from_vertex(&mut self, vertex: &Vertex) { 58 | self.p[0] = quantize_half(vertex.p[0]); 59 | self.p[1] = quantize_half(vertex.p[1]); 60 | self.p[2] = quantize_half(vertex.p[2]); 61 | 62 | let nsum = vertex.n[0].abs() + vertex.n[1].abs() + vertex.n[2].abs(); 63 | let nx = vertex.n[0] / nsum; 64 | let ny = vertex.n[1] / nsum; 65 | let nz = vertex.n[2]; 66 | 67 | let nu = if nz >= 0f32 { 68 | nx 69 | } else { 70 | (1f32 - ny.abs()) * if nx >= 0f32 { 1f32 } else { -1f32 } 71 | }; 72 | 73 | let nv = if nz >= 0f32 { 74 | ny 75 | } else { 76 | (1f32 - nx.abs()) * if ny >= 0f32 { 1f32 } else { -1f32 } 77 | }; 78 | 79 | self.n[0] = quantize_snorm(nu, 8) as u8; 80 | self.n[1] = quantize_snorm(nv, 8) as u8; 81 | 82 | self.t[0] = quantize_half(vertex.t[0]); 83 | self.t[1] = quantize_half(vertex.t[1]); 84 | } 85 | } 86 | 87 | #[derive(Default, Debug, Copy, Clone, PartialOrd)] 88 | #[repr(C)] 89 | /// A basic Vertex type that can be used with most mesh processing functions. 90 | /// 91 | /// You don't _need_ to use this type, you can use your own type by implementing 92 | /// the `DecodePosition` trait and making a [`VertexDataAdapter`] from slices of it. 93 | /// 94 | /// [`VertexDataAdapter`]: crate::VertexDataAdapter 95 | pub struct Vertex { 96 | pub p: [f32; 3], 97 | pub n: [f32; 3], 98 | pub t: [f32; 2], 99 | } 100 | 101 | impl PartialEq for Vertex { 102 | fn eq(&self, other: &Vertex) -> bool { 103 | self.p[0].approx_eq_ulps(&other.p[0], 2) 104 | && self.p[1].approx_eq_ulps(&other.p[1], 2) 105 | && self.p[2].approx_eq_ulps(&other.p[2], 2) 106 | && self.n[0].approx_eq_ulps(&other.n[0], 2) 107 | && self.n[1].approx_eq_ulps(&other.n[1], 2) 108 | && self.n[2].approx_eq_ulps(&other.n[2], 2) 109 | && self.t[0].approx_eq_ulps(&other.t[0], 2) 110 | && self.t[1].approx_eq_ulps(&other.t[1], 2) 111 | } 112 | } 113 | 114 | impl Eq for Vertex {} 115 | 116 | impl Vertex {} 117 | 118 | impl DecodePosition for Vertex { 119 | fn decode_position(&self) -> [f32; 3] { 120 | self.p 121 | } 122 | } 123 | 124 | pub fn pack_vertices(input: &[Vertex]) -> Vec { 125 | let mut vertices: Vec = vec![T::default(); input.len()]; 126 | for i in 0..input.len() { 127 | vertices[i].fill_from_vertex(&input[i]); 128 | } 129 | vertices 130 | } 131 | -------------------------------------------------------------------------------- /src/remap.rs: -------------------------------------------------------------------------------- 1 | use crate::{ffi, VertexStream}; 2 | use std::mem; 3 | 4 | /// Generates a vertex remap table from the vertex buffer and an optional index buffer and returns number of unique vertices. 5 | /// 6 | /// As a result, all vertices that are binary equivalent map to the same (new) location, with no gaps in the resulting sequence. 7 | /// Resulting remap table maps old vertices to new vertices and can be used in `remap_vertex_buffer`/`remap_index_buffer`. 8 | /// 9 | /// The `indices` can be `None` if the input is unindexed. 10 | pub fn generate_vertex_remap(vertices: &[T], indices: Option<&[u32]>) -> (usize, Vec) { 11 | let mut remap: Vec = vec![0; vertices.len()]; 12 | let vertex_count = unsafe { 13 | match indices { 14 | Some(indices) => ffi::meshopt_generateVertexRemap( 15 | remap.as_mut_ptr().cast(), 16 | indices.as_ptr().cast(), 17 | indices.len(), 18 | vertices.as_ptr().cast(), 19 | vertices.len(), 20 | mem::size_of::(), 21 | ), 22 | None => ffi::meshopt_generateVertexRemap( 23 | remap.as_mut_ptr(), 24 | std::ptr::null(), 25 | vertices.len(), 26 | vertices.as_ptr().cast(), 27 | vertices.len(), 28 | mem::size_of::(), 29 | ), 30 | } 31 | }; 32 | (vertex_count, remap) 33 | } 34 | 35 | /// Generates a vertex remap table from multiple vertex streams and an optional index buffer and returns number of unique vertices. 36 | /// 37 | /// As a result, all vertices that are binary equivalent map to the same (new) location, with no gaps in the resulting sequence. 38 | /// Resulting remap table maps old vertices to new vertices and can be used in `remap_vertex_buffer`/`remap_index_buffer`. 39 | /// 40 | /// To remap vertex buffers, you will need to call `remap_vertex_buffer` for each vertex stream. 41 | /// 42 | /// The `indices` can be `None` if the input is unindexed. 43 | pub fn generate_vertex_remap_multi( 44 | vertex_count: usize, 45 | streams: &[VertexStream<'_>], 46 | indices: Option<&[u32]>, 47 | ) -> (usize, Vec) { 48 | let streams: Vec = streams 49 | .iter() 50 | .map(|stream| ffi::meshopt_Stream { 51 | data: stream.data.cast(), 52 | size: stream.size, 53 | stride: stream.stride, 54 | }) 55 | .collect(); 56 | let mut remap: Vec = vec![0; vertex_count]; 57 | let vertex_count = unsafe { 58 | match indices { 59 | Some(indices) => ffi::meshopt_generateVertexRemapMulti( 60 | remap.as_mut_ptr(), 61 | indices.as_ptr(), 62 | indices.len(), 63 | vertex_count, 64 | streams.as_ptr(), 65 | streams.len(), 66 | ), 67 | None => ffi::meshopt_generateVertexRemapMulti( 68 | remap.as_mut_ptr(), 69 | std::ptr::null(), 70 | vertex_count, 71 | vertex_count, 72 | streams.as_ptr(), 73 | streams.len(), 74 | ), 75 | } 76 | }; 77 | (vertex_count, remap) 78 | } 79 | 80 | /// Generate index buffer from the source index buffer and remap table generated by `generate_vertex_remap`. 81 | /// 82 | /// `indices` can be `None` if the input is unindexed. 83 | pub fn remap_index_buffer(indices: Option<&[u32]>, vertex_count: usize, remap: &[u32]) -> Vec { 84 | let mut result: Vec = Vec::new(); 85 | if let Some(indices) = indices { 86 | result.resize(indices.len(), 0u32); 87 | unsafe { 88 | ffi::meshopt_remapIndexBuffer( 89 | result.as_mut_ptr(), 90 | indices.as_ptr(), 91 | indices.len(), 92 | remap.as_ptr(), 93 | ); 94 | } 95 | } else { 96 | result.resize(vertex_count, 0u32); 97 | unsafe { 98 | ffi::meshopt_remapIndexBuffer( 99 | result.as_mut_ptr(), 100 | std::ptr::null(), 101 | vertex_count, 102 | remap.as_ptr(), 103 | ); 104 | } 105 | } 106 | 107 | result 108 | } 109 | 110 | /// Generates vertex buffer from the source vertex buffer and remap table generated by `generate_vertex_remap`. 111 | pub fn remap_vertex_buffer( 112 | vertices: &[T], 113 | vertex_count: usize, 114 | remap: &[u32], 115 | ) -> Vec { 116 | let mut result: Vec = vec![T::default(); vertex_count]; 117 | unsafe { 118 | ffi::meshopt_remapVertexBuffer( 119 | result.as_mut_ptr().cast(), 120 | vertices.as_ptr().cast(), 121 | vertices.len(), 122 | mem::size_of::(), 123 | remap.as_ptr(), 124 | ); 125 | } 126 | result 127 | } 128 | -------------------------------------------------------------------------------- /src/shadow.rs: -------------------------------------------------------------------------------- 1 | use crate::{ffi, DecodePosition, VertexDataAdapter, VertexStream}; 2 | 3 | /// Generate index buffer that can be used for more efficient rendering when only a subset of the vertex 4 | /// attributes is necessary. 5 | /// 6 | /// All vertices that are binary equivalent (wrt first `vertex_size` bytes) map to 7 | /// the first vertex in the original vertex buffer. 8 | /// 9 | /// This makes it possible to use the index buffer for Z pre-pass or shadowmap rendering, while using 10 | /// the original index buffer for regular rendering. 11 | pub fn generate_shadow_indices(indices: &[u32], vertices: &VertexDataAdapter<'_>) -> Vec { 12 | let vertex_data = vertices.reader.get_ref(); 13 | let vertex_data = vertex_data.as_ptr().cast::(); 14 | let positions = unsafe { vertex_data.add(vertices.position_offset) }; 15 | let mut shadow_indices: Vec = vec![0; indices.len()]; 16 | unsafe { 17 | ffi::meshopt_generateShadowIndexBuffer( 18 | shadow_indices.as_mut_ptr(), 19 | indices.as_ptr(), 20 | indices.len(), 21 | positions.cast(), 22 | vertices.vertex_count, 23 | std::mem::size_of::() * 3, 24 | vertices.vertex_stride, 25 | ); 26 | } 27 | shadow_indices 28 | } 29 | 30 | /// Generate index buffer that can be used for more efficient rendering when only a subset of the vertex 31 | /// attributes is necessary. 32 | /// 33 | /// All vertices that are binary equivalent (wrt first `vertex_size` bytes) map to 34 | /// the first vertex in the original vertex buffer. 35 | /// 36 | /// This makes it possible to use the index buffer for Z pre-pass or shadowmap rendering, while using 37 | /// the original index buffer for regular rendering. 38 | pub fn generate_shadow_indices_decoder( 39 | indices: &[u32], 40 | vertices: &[T], 41 | ) -> Vec { 42 | let vertices = vertices 43 | .iter() 44 | .map(|vertex| vertex.decode_position()) 45 | .collect::>(); 46 | let positions = vertices.as_ptr().cast(); 47 | let mut shadow_indices: Vec = vec![0; indices.len()]; 48 | unsafe { 49 | ffi::meshopt_generateShadowIndexBuffer( 50 | shadow_indices.as_mut_ptr(), 51 | indices.as_ptr(), 52 | indices.len(), 53 | positions, 54 | vertices.len() * 3, 55 | std::mem::size_of::() * 3, 56 | std::mem::size_of::() * 3, 57 | ); 58 | } 59 | shadow_indices 60 | } 61 | 62 | /// Generate index buffer that can be used for more efficient rendering when only a subset of the vertex 63 | /// attributes is necessary. 64 | /// 65 | /// All vertices that are binary equivalent (wrt specified streams) map to the 66 | /// first vertex in the original vertex buffer. 67 | /// 68 | /// This makes it possible to use the index buffer for Z pre-pass or shadowmap rendering, while using 69 | /// the original index buffer for regular rendering. 70 | pub fn generate_shadow_indices_multi( 71 | indices: &[u32], 72 | vertex_count: usize, 73 | streams: &[VertexStream<'_>], 74 | ) -> Vec { 75 | let streams: Vec = streams 76 | .iter() 77 | .map(|stream| ffi::meshopt_Stream { 78 | data: stream.data.cast(), 79 | size: stream.size, 80 | stride: stream.stride, 81 | }) 82 | .collect(); 83 | let mut shadow_indices: Vec = vec![0; indices.len()]; 84 | unsafe { 85 | ffi::meshopt_generateShadowIndexBufferMulti( 86 | shadow_indices.as_mut_ptr(), 87 | indices.as_ptr(), 88 | indices.len(), 89 | vertex_count, 90 | streams.as_ptr(), 91 | streams.len(), 92 | ); 93 | } 94 | shadow_indices 95 | } 96 | -------------------------------------------------------------------------------- /src/simplify.rs: -------------------------------------------------------------------------------- 1 | use crate::{ffi, DecodePosition, VertexDataAdapter}; 2 | use bitflags::bitflags; 3 | use std::mem; 4 | 5 | bitflags! { 6 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 7 | pub struct SimplifyOptions : u32 { 8 | const None = 0; 9 | /// Locks the vertices that lie on the topological border of the mesh in place such that 10 | /// they don't move during simplification. 11 | /// This can be valuable to simplify independent chunks of a mesh, for example terrain, 12 | /// to ensure that individual levels of detail can be stitched together later without gaps. 13 | const LockBorder = 1; 14 | /// Improve simplification performance assuming input indices are a sparse subset of the mesh. 15 | /// Note that error becomes relative to subset extents. 16 | const Sparse = 2; 17 | /// Treat error limit and resulting error as absolute instead of relative to mesh extents. 18 | const ErrorAbsolute = 4; 19 | } 20 | } 21 | 22 | /// Reduces the number of triangles in the mesh, attempting to preserve mesh 23 | /// appearance as much as possible. 24 | /// 25 | /// The resulting index buffer references vertices from the original vertex buffer. 26 | /// 27 | /// If the original vertex data isn't required, creating a compact vertex buffer 28 | /// using `optimize_vertex_fetch` is recommended. 29 | pub fn simplify( 30 | indices: &[u32], 31 | vertices: &VertexDataAdapter<'_>, 32 | target_count: usize, 33 | target_error: f32, 34 | options: SimplifyOptions, 35 | result_error: Option<&mut f32>, 36 | ) -> Vec { 37 | let vertex_data = vertices.reader.get_ref(); 38 | let vertex_data = vertex_data.as_ptr().cast::(); 39 | let positions = unsafe { vertex_data.add(vertices.position_offset) }; 40 | let mut result: Vec = vec![0; indices.len()]; 41 | let index_count = unsafe { 42 | ffi::meshopt_simplify( 43 | result.as_mut_ptr().cast(), 44 | indices.as_ptr().cast(), 45 | indices.len(), 46 | positions.cast::(), 47 | vertices.vertex_count, 48 | vertices.vertex_stride, 49 | target_count, 50 | target_error, 51 | options.bits(), 52 | result_error.map_or_else(std::ptr::null_mut, |v| v as *mut _), 53 | ) 54 | }; 55 | result.resize(index_count, 0u32); 56 | result 57 | } 58 | 59 | /// Reduces the number of triangles in the mesh, attempting to preserve mesh 60 | /// appearance as much as possible. 61 | /// 62 | /// The resulting index buffer references vertices from the original vertex buffer. 63 | /// 64 | /// If the original vertex data isn't required, creating a compact vertex buffer 65 | /// using `optimize_vertex_fetch` is recommended. 66 | pub fn simplify_decoder( 67 | indices: &[u32], 68 | vertices: &[T], 69 | target_count: usize, 70 | target_error: f32, 71 | options: SimplifyOptions, 72 | result_error: Option<&mut f32>, 73 | ) -> Vec { 74 | let positions = vertices 75 | .iter() 76 | .map(|vertex| vertex.decode_position()) 77 | .collect::>(); 78 | let mut result: Vec = vec![0; indices.len()]; 79 | let index_count = unsafe { 80 | ffi::meshopt_simplify( 81 | result.as_mut_ptr().cast(), 82 | indices.as_ptr().cast(), 83 | indices.len(), 84 | positions.as_ptr().cast(), 85 | positions.len(), 86 | mem::size_of::() * 3, 87 | target_count, 88 | target_error, 89 | options.bits(), 90 | result_error.map_or_else(std::ptr::null_mut, |v| v as *mut _), 91 | ) 92 | }; 93 | result.resize(index_count, 0u32); 94 | result 95 | } 96 | 97 | /// Reduces the number of triangles in the mesh, attempting to preserve mesh 98 | /// appearance as much as possible, while respecting the given vertex locks 99 | /// 100 | /// The resulting index buffer references vertices from the original vertex buffer. 101 | /// 102 | /// If the original vertex data isn't required, creating a compact vertex buffer 103 | /// using `optimize_vertex_fetch` is recommended. 104 | pub fn simplify_with_locks( 105 | indices: &[u32], 106 | vertices: &VertexDataAdapter<'_>, 107 | vertex_lock: &[bool], 108 | target_count: usize, 109 | target_error: f32, 110 | options: SimplifyOptions, 111 | result_error: Option<&mut f32>, 112 | ) -> Vec { 113 | let vertex_data = vertices.reader.get_ref(); 114 | let vertex_data = vertex_data.as_ptr().cast::(); 115 | let positions = unsafe { vertex_data.add(vertices.position_offset) }; 116 | let mut result: Vec = vec![0; indices.len()]; 117 | let index_count = unsafe { 118 | ffi::meshopt_simplifyWithAttributes( 119 | result.as_mut_ptr().cast(), 120 | indices.as_ptr().cast(), 121 | indices.len(), 122 | positions.cast::(), 123 | vertices.vertex_count, 124 | vertices.vertex_stride, 125 | std::ptr::null(), 126 | 0, 127 | std::ptr::null(), 128 | 0, 129 | vertex_lock.as_ptr().cast(), 130 | target_count, 131 | target_error, 132 | options.bits(), 133 | result_error.map_or_else(std::ptr::null_mut, |v| v as *mut _), 134 | ) 135 | }; 136 | result.resize(index_count, 0u32); 137 | result 138 | } 139 | 140 | /// Reduces the number of triangles in the mesh, attempting to preserve mesh 141 | /// appearance as much as possible, while respecting the given vertex locks 142 | /// 143 | /// The resulting index buffer references vertices from the original vertex buffer. 144 | /// 145 | /// If the original vertex data isn't required, creating a compact vertex buffer 146 | /// using `optimize_vertex_fetch` is recommended. 147 | pub fn simplify_with_locks_decoder( 148 | indices: &[u32], 149 | vertices: &[T], 150 | vertex_lock: &[bool], 151 | target_count: usize, 152 | target_error: f32, 153 | options: SimplifyOptions, 154 | result_error: Option<&mut f32>, 155 | ) -> Vec { 156 | let positions = vertices 157 | .iter() 158 | .map(|vertex| vertex.decode_position()) 159 | .collect::>(); 160 | let mut result: Vec = vec![0; indices.len()]; 161 | let index_count = unsafe { 162 | ffi::meshopt_simplifyWithAttributes( 163 | result.as_mut_ptr().cast(), 164 | indices.as_ptr().cast(), 165 | indices.len(), 166 | positions.as_ptr().cast(), 167 | positions.len(), 168 | mem::size_of::() * 3, 169 | std::ptr::null(), 170 | 0, 171 | std::ptr::null(), 172 | 0, 173 | vertex_lock.as_ptr().cast(), 174 | target_count, 175 | target_error, 176 | options.bits(), 177 | result_error.map_or_else(std::ptr::null_mut, |v| v as *mut _), 178 | ) 179 | }; 180 | result.resize(index_count, 0u32); 181 | result 182 | } 183 | 184 | /// Reduces the number of triangles in the mesh, attempting to preserve mesh 185 | /// appearance as much as possible, weighing vertex attributes by the supplied weights, 186 | /// while respecting the given vertex locks 187 | /// 188 | /// The resulting index buffer references vertices from the original vertex buffer. 189 | /// 190 | /// If the original vertex data isn't required, creating a compact vertex buffer 191 | /// using `optimize_vertex_fetch` is recommended. 192 | #[allow(clippy::too_many_arguments)] 193 | pub fn simplify_with_attributes_and_locks( 194 | indices: &[u32], 195 | vertices: &VertexDataAdapter<'_>, 196 | vertex_attributes: &[f32], 197 | vertex_attribute_weights: &[f32], 198 | vertex_attributes_stride: usize, 199 | vertex_lock: &[bool], 200 | target_count: usize, 201 | target_error: f32, 202 | options: SimplifyOptions, 203 | result_error: Option<&mut f32>, 204 | ) -> Vec { 205 | let vertex_data = vertices.reader.get_ref(); 206 | let vertex_data = vertex_data.as_ptr().cast::(); 207 | let positions = unsafe { vertex_data.add(vertices.position_offset) }; 208 | let mut result: Vec = vec![0; indices.len()]; 209 | let index_count = unsafe { 210 | ffi::meshopt_simplifyWithAttributes( 211 | result.as_mut_ptr().cast(), 212 | indices.as_ptr().cast(), 213 | indices.len(), 214 | positions.cast::(), 215 | vertices.vertex_count, 216 | vertices.vertex_stride, 217 | vertex_attributes.as_ptr(), 218 | vertex_attributes_stride, 219 | vertex_attribute_weights.as_ptr(), 220 | vertex_attribute_weights.len(), 221 | vertex_lock.as_ptr().cast(), 222 | target_count, 223 | target_error, 224 | options.bits(), 225 | result_error.map_or_else(std::ptr::null_mut, |v| v as *mut _), 226 | ) 227 | }; 228 | result.resize(index_count, 0u32); 229 | result 230 | } 231 | 232 | /// Reduces the number of triangles in the mesh, attempting to preserve mesh 233 | /// appearance as much as possible, weighing vertex attributes by the supplied weights, 234 | /// while respecting the given vertex locks 235 | /// 236 | /// The resulting index buffer references vertices from the original vertex buffer. 237 | /// 238 | /// If the original vertex data isn't required, creating a compact vertex buffer 239 | /// using `optimize_vertex_fetch` is recommended. 240 | #[allow(clippy::too_many_arguments)] 241 | pub fn simplify_with_attributes_and_locks_decoder( 242 | indices: &[u32], 243 | vertices: &[T], 244 | vertex_attributes: &[f32], 245 | vertex_attribute_weights: &[f32], 246 | vertex_attributes_stride: usize, 247 | vertex_lock: &[bool], 248 | target_count: usize, 249 | target_error: f32, 250 | options: SimplifyOptions, 251 | result_error: Option<&mut f32>, 252 | ) -> Vec { 253 | let positions = vertices 254 | .iter() 255 | .map(|vertex| vertex.decode_position()) 256 | .collect::>(); 257 | let mut result: Vec = vec![0; indices.len()]; 258 | let index_count = unsafe { 259 | ffi::meshopt_simplifyWithAttributes( 260 | result.as_mut_ptr().cast(), 261 | indices.as_ptr().cast(), 262 | indices.len(), 263 | positions.as_ptr().cast(), 264 | positions.len(), 265 | mem::size_of::() * 3, 266 | vertex_attributes.as_ptr(), 267 | vertex_attributes_stride, 268 | vertex_attribute_weights.as_ptr(), 269 | vertex_attribute_weights.len(), 270 | vertex_lock.as_ptr().cast(), 271 | target_count, 272 | target_error, 273 | options.bits(), 274 | result_error.map_or_else(std::ptr::null_mut, |v| v as *mut _), 275 | ) 276 | }; 277 | result.resize(index_count, 0u32); 278 | result 279 | } 280 | 281 | /// Reduces the number of triangles in the mesh, sacrificing mesh appearance for simplification performance. 282 | /// 283 | /// The algorithm doesn't preserve mesh topology but is always able to reach target triangle count. 284 | /// 285 | /// The resulting index buffer references vertices from the original vertex buffer. 286 | /// 287 | /// If the original vertex data isn't required, creating a compact vertex buffer using `optimize_vertex_fetch` 288 | /// is recommended. 289 | pub fn simplify_sloppy( 290 | indices: &[u32], 291 | vertices: &VertexDataAdapter<'_>, 292 | target_count: usize, 293 | target_error: f32, 294 | result_error: Option<&mut f32>, 295 | ) -> Vec { 296 | let vertex_data = vertices.reader.get_ref(); 297 | let vertex_data = vertex_data.as_ptr().cast::(); 298 | let positions = unsafe { vertex_data.add(vertices.position_offset) }; 299 | let mut result: Vec = vec![0; indices.len()]; 300 | let index_count = unsafe { 301 | ffi::meshopt_simplifySloppy( 302 | result.as_mut_ptr().cast(), 303 | indices.as_ptr().cast(), 304 | indices.len(), 305 | positions.cast(), 306 | vertices.vertex_count, 307 | vertices.vertex_stride, 308 | target_count, 309 | target_error, 310 | result_error.map_or_else(std::ptr::null_mut, |v| v as *mut _), 311 | ) 312 | }; 313 | result.resize(index_count, 0u32); 314 | result 315 | } 316 | 317 | /// Reduces the number of triangles in the mesh, sacrificing mesh appearance for simplification performance. 318 | /// 319 | /// The algorithm doesn't preserve mesh topology but is always able to reach target triangle count. 320 | /// 321 | /// The resulting index buffer references vertices from the original vertex buffer. 322 | /// 323 | /// If the original vertex data isn't required, creating a compact vertex buffer using `optimize_vertex_fetch` 324 | /// is recommended. 325 | pub fn simplify_sloppy_decoder( 326 | indices: &[u32], 327 | vertices: &[T], 328 | target_count: usize, 329 | target_error: f32, 330 | result_error: Option<&mut f32>, 331 | ) -> Vec { 332 | let positions = vertices 333 | .iter() 334 | .map(|vertex| vertex.decode_position()) 335 | .collect::>(); 336 | let mut result: Vec = vec![0; indices.len()]; 337 | let index_count = unsafe { 338 | ffi::meshopt_simplifySloppy( 339 | result.as_mut_ptr().cast(), 340 | indices.as_ptr().cast(), 341 | indices.len(), 342 | positions.as_ptr().cast(), 343 | positions.len(), 344 | mem::size_of::() * 3, 345 | target_count, 346 | target_error, 347 | result_error.map_or_else(std::ptr::null_mut, |v| v as *mut _), 348 | ) 349 | }; 350 | result.resize(index_count, 0u32); 351 | result 352 | } 353 | 354 | /// Returns the error scaling factor used by the simplifier to convert between absolute and relative extents 355 | /// 356 | /// Absolute error must be *divided* by the scaling factor before passing it to `simplify` as `target_error` 357 | /// Relative error returned by `simplify` via `result_error` must be *multiplied* by the scaling factor to get absolute error. 358 | pub fn simplify_scale(vertices: &VertexDataAdapter<'_>) -> f32 { 359 | unsafe { 360 | ffi::meshopt_simplifyScale( 361 | vertices.pos_ptr(), 362 | vertices.vertex_count, 363 | vertices.vertex_stride, 364 | ) 365 | } 366 | } 367 | 368 | /// Returns the error scaling factor used by the simplifier to convert between absolute and relative extents 369 | /// 370 | /// Absolute error must be *divided* by the scaling factor before passing it to `simplify` as `target_error` 371 | /// Relative error returned by `simplify` via `result_error` must be *multiplied* by the scaling factor to get absolute error. 372 | pub fn simplify_scale_decoder(vertices: &[T]) -> f32 { 373 | let positions = vertices 374 | .iter() 375 | .map(|vertex| vertex.decode_position()) 376 | .collect::>(); 377 | 378 | unsafe { 379 | ffi::meshopt_simplifyScale( 380 | positions.as_ptr().cast(), 381 | positions.len(), 382 | mem::size_of::() * 3, 383 | ) 384 | } 385 | } 386 | -------------------------------------------------------------------------------- /src/stripify.rs: -------------------------------------------------------------------------------- 1 | use crate::{ffi, Error, Result}; 2 | 3 | /// Converts a previously vertex cache optimized triangle list to triangle 4 | /// strip, stitching strips using restart index. 5 | /// 6 | /// For maximum efficiency the index buffer being converted has to be 7 | /// optimized for vertex cache first. 8 | /// 9 | /// The `restart_index` should be 0xffff or 0xffffffff depending on index size, 10 | /// or 0 to use degenerate triangles. 11 | pub fn stripify(indices: &[u32], vertex_count: usize, restart_index: u32) -> Result> { 12 | let mut result: Vec = vec![0; indices.len() / 3 * 4]; 13 | let index_count = unsafe { 14 | ffi::meshopt_stripify( 15 | result.as_mut_ptr().cast(), 16 | indices.as_ptr().cast(), 17 | indices.len(), 18 | vertex_count, 19 | restart_index, 20 | ) 21 | }; 22 | if index_count <= result.len() { 23 | result.resize(index_count, 0u32); 24 | Ok(result) 25 | } else { 26 | Err(Error::memory("index count is larger than result")) 27 | } 28 | } 29 | 30 | /// Converts a triangle strip to a triangle list 31 | pub fn unstripify(indices: &[u32], restart_index: u32) -> Result> { 32 | let mut result: Vec = vec![0; (indices.len() - 2) * 3]; 33 | let index_count = unsafe { 34 | ffi::meshopt_unstripify( 35 | result.as_mut_ptr().cast(), 36 | indices.as_ptr().cast(), 37 | indices.len(), 38 | restart_index, 39 | ) 40 | }; 41 | if index_count <= result.len() { 42 | result.resize(index_count, 0u32); 43 | Ok(result) 44 | } else { 45 | Err(Error::memory("index count is larger than result")) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/utilities.rs: -------------------------------------------------------------------------------- 1 | use crate::{Error, Result}; 2 | use std::io::{Cursor, Read}; 3 | 4 | #[inline(always)] 5 | pub fn any_as_u8_slice(p: &T) -> &[u8] { 6 | typed_to_bytes(std::slice::from_ref(p)) 7 | } 8 | 9 | #[inline(always)] 10 | pub fn typed_to_bytes(typed: &[T]) -> &[u8] { 11 | unsafe { std::slice::from_raw_parts(typed.as_ptr().cast(), std::mem::size_of_val(typed)) } 12 | } 13 | 14 | pub fn convert_indices_32_to_16(indices: &[u32]) -> Result> { 15 | let mut result: Vec = Vec::with_capacity(indices.len()); 16 | for index in indices { 17 | if *index > 65536 { 18 | return Err(Error::memory( 19 | "index value must be <= 65536 when converting to 16-bit", 20 | )); 21 | } 22 | result.push(*index as u16); 23 | } 24 | Ok(result) 25 | } 26 | 27 | pub fn convert_indices_16_to_32(indices: &[u16]) -> Result> { 28 | let mut result: Vec = Vec::with_capacity(indices.len()); 29 | for index in indices { 30 | result.push(u32::from(*index)); 31 | } 32 | Ok(result) 33 | } 34 | 35 | /// Quantize a float in [0..1] range into an N-bit fixed point unorm value. 36 | /// 37 | /// Assumes reconstruction function (q / (2^N-1)), which is the case for 38 | /// fixed-function normalized fixed point conversion. 39 | /// 40 | /// Maximum reconstruction error: 1/2^(N+1). 41 | #[inline(always)] 42 | pub fn quantize_unorm(v: f32, n: i32) -> i32 { 43 | let scale = ((1i32 << n) - 1i32) as f32; 44 | let v = if v >= 0f32 { v } else { 0f32 }; 45 | let v = if v <= 1f32 { v } else { 1f32 }; 46 | (v * scale + 0.5f32) as i32 47 | } 48 | 49 | /// Quantize a float in [-1..1] range into an N-bit fixed point snorm value. 50 | /// 51 | /// Assumes reconstruction function (q / (2^(N-1)-1)), which is the case for 52 | /// fixed-function normalized fixed point conversion (except early OpenGL versions). 53 | /// 54 | /// Maximum reconstruction error: 1/2^N. 55 | #[inline(always)] 56 | pub fn quantize_snorm(v: f32, n: u32) -> i32 { 57 | let scale = ((1 << (n - 1)) - 1) as f32; 58 | let round = if v >= 0f32 { 0.5f32 } else { -0.5f32 }; 59 | let v = if v >= -1f32 { v } else { -1f32 }; 60 | let v = if v <= 1f32 { v } else { 1f32 }; 61 | (v * scale + round) as i32 62 | } 63 | 64 | /// Quantize a float into half-precision floating point value. 65 | /// 66 | /// Generates +-inf for overflow, preserves NaN, flushes denormals to zero, rounds to nearest. 67 | /// Representable magnitude range: [6e-5; 65504]. 68 | /// Maximum relative reconstruction error: 5e-4. 69 | #[inline(always)] 70 | pub fn quantize_half(v: f32) -> u16 { 71 | let ui = f32::to_bits(v); 72 | let s = ((ui >> 16) & 0x8000) as i32; 73 | let em = (ui & 0x7fff_ffff) as i32; 74 | 75 | // bias exponent and round to nearest; 112 is relative exponent bias (127-15) 76 | let mut h = (em - (112 << 23) + (1 << 12)) >> 13; 77 | 78 | // underflow: flush to zero; 113 encodes exponent -14 79 | h = if em < (113 << 23) { 0 } else { h }; 80 | 81 | // overflow: infinity; 143 encodes exponent 16 82 | h = if em >= (143 << 23) { 0x7c00 } else { h }; 83 | 84 | // NaN; note that we convert all types of NaN to qNaN 85 | h = if em > (255 << 23) { 0x7e00 } else { h }; 86 | 87 | (s | h) as u16 88 | } 89 | 90 | /// Quantize a float into a floating point value with a limited number of significant mantissa bits. 91 | /// 92 | /// Generates +-inf for overflow, preserves NaN, flushes denormals to zero, rounds to nearest. 93 | /// Assumes N is in a valid mantissa precision range, which is 1..23 94 | #[inline(always)] 95 | pub fn quantize_float(v: f32, n: i32) -> f32 { 96 | let mut ui = f32::to_bits(v); 97 | 98 | let mask = (1 << (23 - n)) - 1; 99 | let round = (1 << (23 - n)) >> 1; 100 | 101 | let e = (ui & 0x7f80_0000) as i32; 102 | let rui: u32 = ((ui as i32 + round) & !mask) as u32; 103 | 104 | // round all numbers except inf/nan; this is important to make 105 | // sure nan doesn't overflow into -0 106 | ui = if e == 0x7f80_0000 { ui } else { rui }; 107 | 108 | // flush denormals to zero 109 | ui = if e == 0 { 0 } else { ui }; 110 | 111 | f32::from_bits(ui) 112 | } 113 | 114 | /// Reverse quantization of a half-precision (as defined by IEEE-754 fp16) floating point value 115 | /// 116 | /// Preserves Inf/NaN, flushes denormals to zero 117 | #[inline(always)] 118 | pub fn dequantize_half(h: u16) -> f32 { 119 | let s = ((h & 0x8000) as u32) << 16; 120 | let em = (h & 0x7fff) as u32; 121 | 122 | // bias exponent and pad mantissa with 0; 112 is relative exponent bias (127-15) 123 | let mut r = (em + (112 << 10)) << 13; 124 | 125 | // denormal: flush to zero 126 | if em < (1 << 10) { 127 | r = 0; 128 | } 129 | 130 | // infinity/NaN; note that we preserve NaN payload as a byproduct of unifying inf/nan cases 131 | // 112 is an exponent bias fixup; since we already applied it once, applying it twice converts 31 to 255 132 | if em >= (31 << 10) { 133 | r += 112 << 23; 134 | } 135 | 136 | let bits = s | r; 137 | f32::from_bits(bits) 138 | } 139 | 140 | #[inline(always)] 141 | pub fn rcp_safe(v: f32) -> f32 { 142 | if v.abs() as u32 == 0 { 143 | 0f32 144 | } else { 145 | 1f32 / v 146 | } 147 | } 148 | 149 | pub struct VertexDataAdapter<'a> { 150 | pub reader: Cursor<&'a [u8]>, 151 | pub vertex_count: usize, 152 | pub vertex_stride: usize, 153 | pub position_offset: usize, 154 | } 155 | 156 | impl<'a> VertexDataAdapter<'a> { 157 | pub fn new( 158 | data: &'a [u8], 159 | vertex_stride: usize, 160 | position_offset: usize, 161 | ) -> Result> { 162 | let vertex_count = data.len() / vertex_stride; 163 | if data.len() % vertex_stride != 0 { 164 | Err(Error::memory_dynamic(format!( 165 | "vertex data length ({}) must be evenly divisible by vertex_stride ({})", 166 | data.len(), 167 | vertex_stride 168 | ))) 169 | } else if position_offset >= vertex_stride { 170 | Err(Error::memory_dynamic(format!( 171 | "position_offset ({}) must be smaller than vertex_stride ({})", 172 | position_offset, vertex_stride 173 | ))) 174 | } else { 175 | Ok(VertexDataAdapter { 176 | reader: Cursor::new(data), 177 | vertex_count, 178 | vertex_stride, 179 | position_offset, 180 | }) 181 | } 182 | } 183 | 184 | pub fn xyz_f32_at(&mut self, vertex: usize) -> Result<[f32; 3]> { 185 | if vertex >= self.vertex_count { 186 | return Err(Error::memory_dynamic(format!( 187 | "vertex index ({}) must be less than total vertex count ({})", 188 | vertex, self.vertex_count 189 | ))); 190 | } 191 | let reader_pos = self.reader.position(); 192 | let vertex_offset = vertex * self.vertex_stride; 193 | self.reader 194 | .set_position((vertex_offset + self.position_offset) as u64); 195 | let mut scratch = [0u8; 12]; 196 | self.reader.read_exact(&mut scratch)?; 197 | 198 | let position: [f32; 3] = unsafe { std::mem::transmute(scratch) }; 199 | 200 | self.reader.set_position(reader_pos); 201 | Ok(position) 202 | } 203 | 204 | pub fn pos_ptr(&self) -> *const f32 { 205 | let vertex_data = self.reader.get_ref(); 206 | let vertex_data = vertex_data.as_ptr().cast::(); 207 | let positions = unsafe { vertex_data.add(self.position_offset) }; 208 | positions.cast() 209 | } 210 | } 211 | 212 | impl Read for VertexDataAdapter<'_> { 213 | fn read(&mut self, buf: &mut [u8]) -> std::result::Result { 214 | self.reader.read(buf) 215 | } 216 | } 217 | 218 | #[cfg(test)] 219 | mod tests { 220 | use super::*; 221 | use crate::{typed_to_bytes, Vertex, VertexDataAdapter}; 222 | use memoffset::offset_of; 223 | 224 | #[test] 225 | fn test_xyz_f32_at() { 226 | let vertices = vec![ 227 | Vertex { 228 | p: [1.0, 2.0, 3.0], 229 | n: [0.0; 3], 230 | t: [0.0; 2], 231 | }, 232 | Vertex { 233 | p: [4.0, 5.0, 6.0], 234 | n: [0.0; 3], 235 | t: [0.0; 2], 236 | }, 237 | ]; 238 | 239 | let mut adapter = VertexDataAdapter::new( 240 | typed_to_bytes(&vertices), 241 | ::std::mem::size_of::(), 242 | offset_of!(Vertex, p), 243 | ) 244 | .unwrap(); 245 | 246 | let p = adapter.xyz_f32_at(0).unwrap(); 247 | assert_eq!(p, [1.0, 2.0, 3.0]); 248 | let p = adapter.xyz_f32_at(1).unwrap(); 249 | assert_eq!(p, [4.0, 5.0, 6.0]); 250 | 251 | adapter.xyz_f32_at(2).expect_err("should fail"); 252 | } 253 | 254 | #[test] 255 | fn quantize_roundtrip() { 256 | for i in u16::MIN..u16::MAX { 257 | let f = dequantize_half(i); 258 | let q = quantize_half(f); 259 | // dont care about denormals 260 | if !f.is_normal() { 261 | continue; 262 | } 263 | assert_eq!(i, q, "quantization error for {i}: {f} -> {q}"); 264 | } 265 | } 266 | } 267 | --------------------------------------------------------------------------------