├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .gitmodules ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── NOTICE ├── README.md ├── basis-universal-sys ├── Cargo.toml ├── build.rs ├── generate_bindings.sh ├── src │ ├── encoding_bindings.rs │ ├── lib.rs │ └── transcoding_bindings.rs └── vendor │ ├── encoding_wrapper.cpp │ └── transcoding_wrapper.cpp ├── basis-universal ├── Cargo.toml ├── examples │ ├── benchmark.rs │ └── example.rs ├── src │ ├── encoding │ │ ├── compressor.rs │ │ ├── compressor_image.rs │ │ ├── compressor_params.rs │ │ ├── encoding_tests.rs │ │ └── mod.rs │ ├── lib.rs │ └── transcoding │ │ ├── enums.rs │ │ ├── mod.rs │ │ ├── transcoder.rs │ │ └── transcoding_tests.rs └── test_assets │ ├── rebuild_assets.sh │ ├── rust-logo-256x256.png │ ├── rust-logo-etc.basis │ ├── rust-logo-uastc.basis │ └── rust-logo.png ├── deny.toml └── rustfmt.toml /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | CARGO_TERM_COLOR: always 7 | 8 | jobs: 9 | build: 10 | strategy: 11 | matrix: 12 | toolchain: [stable, beta] 13 | os: [windows-2019, ubuntu-20.04, macos-12] 14 | exclude: 15 | - os: macos-12 16 | toolchain: beta 17 | - os: windows-2019 18 | toolchain: beta 19 | runs-on: ${{ matrix.os }} 20 | needs: clean 21 | steps: 22 | - uses: actions/checkout@v2 23 | with: 24 | submodules: recursive 25 | - uses: seanmiddleditch/gha-setup-ninja@master 26 | - uses: actions-rs/toolchain@v1 27 | with: 28 | toolchain: ${{ matrix.toolchain }} 29 | override: true 30 | 31 | - uses: actions/cache@v2 32 | with: 33 | path: | 34 | target 35 | key: ${{ runner.os }}-cargo-check-test-${{ matrix.toolchain }}-${{ hashFiles('**/Cargo.lock') }} 36 | 37 | - name: Run tests 38 | run: cargo test --workspace 39 | env: 40 | CARGO_INCREMENTAL: 0 41 | RUSTFLAGS: "-C debuginfo=0 -D warnings" 42 | 43 | deny-check: 44 | name: cargo-deny 45 | runs-on: ubuntu-latest 46 | steps: 47 | - uses: actions/checkout@v1 48 | - uses: EmbarkStudios/cargo-deny-action@v1 49 | with: 50 | command: check ${{ matrix.checks }} 51 | 52 | clean: 53 | runs-on: ubuntu-latest 54 | steps: 55 | - uses: actions/checkout@v2 56 | - uses: actions-rs/toolchain@v1 57 | with: 58 | toolchain: stable 59 | components: rustfmt, clippy 60 | override: true 61 | 62 | - name: Check the format 63 | run: cargo fmt --all -- --check 64 | 65 | # TODO: Enable this 66 | # - name: Run clippy 67 | # run: > 68 | # cargo clippy 69 | # --all-targets 70 | # --all-features 71 | # -- 72 | # -D warnings 73 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | .idea 4 | CMakeCache.txt 5 | CMakeFiles 6 | /basis-universal-sys/vendor/bin 7 | /basis-universal-sys/vendor/Makefile 8 | /basis-universal-sys/vendor/cmake_install.cmake 9 | .DS_Store 10 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "basis_universal"] 2 | path = basis-universal-sys/vendor/basis_universal 3 | url = https://github.com/BinomialLLC/basis_universal.git 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.3.1 4 | * Additional bindings to support providing custom mip levels 5 | 6 | ## 0.3.0 7 | * Updated to most recent up-stream (1.16.x) 8 | * `compressor_params_set_global_sel_pal`, `compressor_params_set_auto_global_sel_pal` and 9 | `etc1_global_selector_codebook` were removed, as they no longer exist upstream 10 | * Some of the bindings have been changed to i32 to match upstream C++ behavior 11 | 12 | ## 0.2.0 13 | * Add basis_block_format_is_uncompressed() 14 | * Add LowLevelUastcTranscoder, TranscoderBlockFormat 15 | * Fix typo HIGH_QULITY -> HIGH_QUALITY 16 | * Add files to be excluded when publishing 17 | 18 | ## 0.1.1 19 | * Add set_rdo_uastc() 20 | * Bump upstream code to more recent revision 21 | 22 | ## 0.1.0 23 | * Initial release 24 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "basis-universal", 4 | "basis-universal-sys" 5 | ] 6 | 7 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-2020 Philip Degarmo and other basis-universal-rs contributors 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. -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | basis-universal-rs: Bindings for basis-universal by Binomial LLC. 2 | https://github.com/aclysma/basis-universal-rs 3 | 4 | Copyright (c) 2019-2020 Philip Degarmo and other basis-universal-rs contributors 5 | 6 | This software is available under the MIT License or Apache License 2.0 at your option. 7 | 8 | ---------------------------------------------------------------------------------------- 9 | 10 | basis_universal: Basis Universal Supercompressed GPU Texture Codec 11 | https://github.com/BinomialLLC/basis_universal 12 | 13 | Copyright (C) 2019-2020 Binomial LLC. All Rights Reserved. 14 | Licensed under the Apache License, Version 2.0 (the "License"); 15 | you may not use this file except in compliance with the License. 16 | You may obtain a copy of the License at 17 | 18 | http://www.apache.org/licenses/LICENSE-2.0 19 | 20 | Unless required by applicable law or agreed to in writing, software 21 | distributed under the License is distributed on an "AS IS" BASIS, 22 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 23 | See the License for the specific language governing permissions and 24 | limitations under the License. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # basis-universal-rs 2 | 3 | Bindings for Binomial LLC's [`basis-universal`](https://github.com/BinomialLLC/basis_universal) Supercompressed GPU 4 | Texture Codec 5 | 6 | `basis-universal` functionality can be grouped into two categories: 7 | * Encoding: Compresses and encode textures (optionally combining multiple images and mipmap layers in a single 8 | file/binary blob) 9 | * Transcoding: Unpacks the texture into GPU-friendly compression formats. The final format can be chosen based on what 10 | the available GPU hardware can support. 11 | 12 | Encoding can be done ahead of time using a command line tool in the [upstream repository](https://github.com/BinomialLLC/basis_universal). 13 | 14 | The encoded data can either be stored as a file or a binary blob. This data can include multiple images, and each image 15 | can store multiple levels. This is commonly used for storing cube textures and textures with precomputed mipmaps. This 16 | library also supports generating mipmaps for you. 17 | 18 | Please refer to https://github.com/BinomialLLC/basis_universal for more details. 19 | 20 | ## Status 21 | 22 | These bindings should be production-ready. The API should remain fairly stable (assuming no major upstream changes in 23 | the wrapped `basis-universal` library) 24 | 25 | ## Performance 26 | 27 | The C++ code that is wrapped by these bindings is built at the same optimization level used for building the 28 | `basis-universal-sys` crate. Most users of these bindings will benefit from building this crate with optimizations 29 | enabled, even during development. To do that, place this in your Cargo.toml file. 30 | 31 | ``` 32 | [profile.dev.package."basis-universal-sys"] 33 | opt-level = 3 34 | ``` 35 | 36 | Enable optimizations for ALL upstream crates works too: 37 | 38 | ``` 39 | [profile.dev.package."*"] 40 | opt-level = 3 41 | ``` 42 | 43 | ## License 44 | 45 | The bindings are licensed under either of 46 | 47 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 48 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 49 | 50 | at your option. 51 | 52 | ### Upstream Dependencies 53 | 54 | Some dependencies may be licensed under other terms. These licenses include "ISC", "CC0-1.0", "BSD-2-Clause", 55 | "BSD-3-Clause", and "Zlib". This is validated on a best-effort basis in every CI run using cargo-deny. 56 | 57 | Binomial LLC's `basis-universal` Supercompressed GPU Texture Codec is licensed under Apache License 2.0: 58 | 59 | > Copyright (C) 2019-2020 Binomial LLC. All Rights Reserved. 60 | > Licensed under the Apache License, Version 2.0 (the "License"); 61 | > you may not use this file except in compliance with the License. 62 | > You may obtain a copy of the License at 63 | > 64 | > http://www.apache.org/licenses/LICENSE-2.0 65 | > 66 | > Unless required by applicable law or agreed to in writing, software 67 | > distributed under the License is distributed on an "AS IS" BASIS, 68 | > WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 69 | > See the License for the specific language governing permissions and 70 | > limitations under the License. 71 | 72 | ## Contribution 73 | 74 | Unless you explicitly state otherwise, any contribution intentionally 75 | submitted for inclusion in the work by you, as defined in the Apache-2.0 76 | license, shall be dual licensed as above, without any additional terms or 77 | conditions. 78 | 79 | See [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT). 80 | -------------------------------------------------------------------------------- /basis-universal-sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "basis-universal-sys" 3 | version = "0.3.1" 4 | authors = ["Philip Degarmo "] 5 | edition = "2018" 6 | license = "MIT OR Apache-2.0" 7 | readme = "../README.md" 8 | repository = "https://github.com/aclysma/basis-universal-rs" 9 | homepage = "https://github.com/aclysma/basis-universal-rs" 10 | description = "Bindings for the basis-universal Supercompressed GPU Texture Codec by Binomial" 11 | keywords = ["game", "basis-universal", "texture", "compression", "gpu"] 12 | categories = ["game-development", "graphics", "external-ffi-bindings", "compression", "encoding"] 13 | exclude = ["vendor/basis_universal/test_files", "vendor/basis_universal/webgl", "vendor/basis_universal/webgl_videotest"] 14 | 15 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 16 | 17 | [build-dependencies] 18 | cc = "1.0" 19 | -------------------------------------------------------------------------------- /basis-universal-sys/build.rs: -------------------------------------------------------------------------------- 1 | // args from the basis cmake file 2 | fn build_with_common_settings() -> cc::Build { 3 | let mut build = cc::Build::new(); 4 | build 5 | .flag_if_supported("-fvisibility=hidden") 6 | .flag_if_supported("-fno-strict-aliasing") 7 | .flag_if_supported("-Wall") 8 | .flag_if_supported("-Wextra") 9 | .flag_if_supported("-Wno-unused-local-typedefs") 10 | .flag_if_supported("-Wno-unused-value") 11 | .flag_if_supported("-Wno-unused-parameter") 12 | .flag_if_supported("-Wno-unused-variable"); 13 | 14 | build 15 | } 16 | 17 | fn main() { 18 | build_with_common_settings() 19 | .cpp(true) 20 | .define("BASISD_SUPPORT_KTX2_ZSTD", "0") 21 | //.define("BASISU_SUPPORT_SSE", "1") TODO: expose this in a futher release 22 | .flag_if_supported("--std=c++11") 23 | .file("vendor/basis_universal/encoder/pvpngreader.cpp") 24 | .file("vendor/basis_universal/encoder/jpgd.cpp") 25 | .file("vendor/basis_universal/encoder/basisu_uastc_enc.cpp") 26 | .file("vendor/basis_universal/encoder/basisu_ssim.cpp") 27 | .file("vendor/basis_universal/encoder/basisu_resampler.cpp") 28 | .file("vendor/basis_universal/encoder/basisu_resample_filters.cpp") 29 | .file("vendor/basis_universal/encoder/basisu_pvrtc1_4.cpp") 30 | .file("vendor/basis_universal/encoder/basisu_opencl.cpp") 31 | .file("vendor/basis_universal/encoder/basisu_kernels_sse.cpp") 32 | .file("vendor/basis_universal/encoder/basisu_gpu_texture.cpp") 33 | .file("vendor/basis_universal/encoder/basisu_frontend.cpp") 34 | .file("vendor/basis_universal/encoder/basisu_etc.cpp") 35 | .file("vendor/basis_universal/encoder/basisu_enc.cpp") 36 | .file("vendor/basis_universal/encoder/basisu_comp.cpp") 37 | .file("vendor/basis_universal/encoder/basisu_bc7enc.cpp") 38 | .file("vendor/basis_universal/encoder/basisu_basis_file.cpp") 39 | .file("vendor/basis_universal/encoder/basisu_backend.cpp") 40 | .file("vendor/basis_universal/transcoder/basisu_transcoder.cpp") 41 | .file("vendor/transcoding_wrapper.cpp") 42 | .file("vendor/encoding_wrapper.cpp") 43 | .compile("basisuniversal"); 44 | 45 | // We regenerate binding code and check it in. (See generate_bindings.sh) 46 | } 47 | -------------------------------------------------------------------------------- /basis-universal-sys/generate_bindings.sh: -------------------------------------------------------------------------------- 1 | bindgen vendor/transcoding_wrapper.cpp -o src/transcoding_bindings.rs \ 2 | --allowlist-function basis_get_bytes_per_block_or_pixel \ 3 | --allowlist-function basis_get_format_name \ 4 | --allowlist-function basis_get_block_format_name \ 5 | --allowlist-function basis_transcoder_format_has_alpha \ 6 | --allowlist-function basis_get_basisu_texture_format \ 7 | --allowlist-function basis_get_texture_type_name \ 8 | \ 9 | --allowlist-function basis_transcoder_format_is_uncompressed \ 10 | --allowlist-function basis_block_format_is_uncompressed \ 11 | --allowlist-function basis_get_uncompressed_bytes_per_pixel \ 12 | --allowlist-function basis_get_block_width \ 13 | --allowlist-function basis_get_block_height \ 14 | --allowlist-function basis_is_format_supported \ 15 | --allowlist-function basis_validate_output_buffer_size \ 16 | \ 17 | --allowlist-function low_level_uastc_transcoder_new \ 18 | --allowlist-function low_level_uastc_transcoder_delete \ 19 | \ 20 | --allowlist-function low_level_uastc_transcoder_transcode_slice \ 21 | \ 22 | --allowlist-function transcoder_new \ 23 | --allowlist-function transcoder_delete \ 24 | \ 25 | --allowlist-function transcoder_validate_file_checksums \ 26 | --allowlist-function transcoder_validate_header \ 27 | --allowlist-function transcoder_get_texture_type \ 28 | --allowlist-function transcoder_get_userdata \ 29 | \ 30 | --allowlist-function transcoder_get_total_images \ 31 | --allowlist-function transcoder_get_tex_format \ 32 | --allowlist-function transcoder_get_total_image_levels \ 33 | \ 34 | --allowlist-function transcoder_get_image_level_desc \ 35 | --allowlist-function transcoder_get_image_info \ 36 | --allowlist-function transcoder_get_image_level_info \ 37 | --allowlist-function transcoder_get_file_info \ 38 | \ 39 | --allowlist-function transcoder_start_transcoding \ 40 | --allowlist-function transcoder_stop_transcoding \ 41 | --allowlist-function transcoder_get_ready_to_transcode \ 42 | --allowlist-function transcoder_transcode_image_level \ 43 | \ 44 | --allowlist-function basisu_transcoder_init \ 45 | \ 46 | --opaque-type LowLevelUastcTranscoder \ 47 | --opaque-type basist::block_format \ 48 | \ 49 | --opaque-type Transcoder \ 50 | --opaque-type basist::basisu_transcoder_state \ 51 | \ 52 | -- -x c++ -std=c++14 53 | 54 | bindgen vendor/encoding_wrapper.cpp -o src/encoding_bindings.rs \ 55 | --allowlist-function image_clear \ 56 | --allowlist-function image_resize_with_pitch \ 57 | --allowlist-function image_resize \ 58 | --allowlist-function image_init \ 59 | --allowlist-function image_get_pixel_at_checked \ 60 | --allowlist-function image_get_pixel_at_unchecked \ 61 | --allowlist-function image_get_width \ 62 | --allowlist-function image_get_height \ 63 | --allowlist-function image_get_pitch \ 64 | --allowlist-function image_get_total_pixels \ 65 | --allowlist-function image_get_block_width \ 66 | --allowlist-function image_get_block_height \ 67 | --allowlist-function image_get_total_blocks \ 68 | --allowlist-function image_get_pixel_data \ 69 | \ 70 | --allowlist-function compressor_params_new \ 71 | --allowlist-function compressor_params_delete \ 72 | --allowlist-function compressor_params_clear \ 73 | \ 74 | --allowlist-function compressor_params_get_or_create_source_image \ 75 | --allowlist-function compressor_params_resize_source_image_list \ 76 | --allowlist-function compressor_params_clear_source_image_list \ 77 | \ 78 | --allowlist-function compressor_params_get_or_create_source_mipmap_image \ 79 | --allowlist-function compressor_params_resize_source_mipmap_image_list \ 80 | --allowlist-function compressor_params_clear_source_mipmap_image_list \ 81 | --allowlist-function compressor_params_resize_source_mipmap_image_level_list \ 82 | \ 83 | --allowlist-function compressor_params_set_status_output \ 84 | --allowlist-function compressor_params_set_quality_level \ 85 | --allowlist-function compressor_params_get_pack_uastc_flags \ 86 | --allowlist-function compressor_params_set_pack_uastc_flags \ 87 | --allowlist-function compressor_params_set_uastc \ 88 | --allowlist-function compressor_params_set_perceptual \ 89 | --allowlist-function compressor_params_set_mip_srgb \ 90 | --allowlist-function compressor_params_set_no_selector_rdo \ 91 | --allowlist-function compressor_params_set_no_endpoint_rdo \ 92 | --allowlist-function compressor_params_set_rdo_uastc \ 93 | --allowlist-function compressor_params_set_rdo_uastc_quality_scalar \ 94 | --allowlist-function compressor_params_set_generate_mipmaps \ 95 | --allowlist-function compressor_params_set_mip_smallest_dimension \ 96 | --allowlist-function compressor_params_set_userdata \ 97 | \ 98 | --allowlist-function compressor_new \ 99 | --allowlist-function compressor_delete \ 100 | --allowlist-function compressor_init \ 101 | --allowlist-function compressor_process \ 102 | --allowlist-function compressor_get_output_basis_file \ 103 | \ 104 | --allowlist-function compressor_get_basis_file_size \ 105 | --allowlist-function compressor_get_basis_bits_per_texel \ 106 | --allowlist-function compressor_get_any_source_image_has_alpha \ 107 | \ 108 | --allowlist-function basisu_encoder_init \ 109 | \ 110 | --allowlist-var basisu::BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION \ 111 | --allowlist-var basisu::BASISU_DEFAULT_ENDPOINT_RDO_THRESH \ 112 | --allowlist-var basisu::BASISU_DEFAULT_SELECTOR_RDO_THRESH \ 113 | --allowlist-var basisu::BASISU_DEFAULT_QUALITY \ 114 | --allowlist-var basisu::BASISU_DEFAULT_HYBRID_SEL_CB_QUALITY_THRESH \ 115 | --allowlist-var basisu::BASISU_MAX_IMAGE_DIMENSION \ 116 | --allowlist-var basisu::BASISU_QUALITY_MIN \ 117 | --allowlist-var basisu::BASISU_QUALITY_MAX \ 118 | --allowlist-var basisu::BASISU_MAX_ENDPOINT_CLUSTERS \ 119 | --allowlist-var basisu::BASISU_MAX_SELECTOR_CLUSTERS \ 120 | --allowlist-var basisu::BASISU_MAX_SLICES \ 121 | --allowlist-var basisu::BASISU_RDO_UASTC_DICT_SIZE_DEFAULT \ 122 | --allowlist-var basisu::BASISU_RDO_UASTC_DICT_SIZE_MIN \ 123 | --allowlist-var basisu::BASISU_RDO_UASTC_DICT_SIZE_MAX \ 124 | --allowlist-var basisu::TOTAL_PACK_UASTC_LEVELS \ 125 | \ 126 | --opaque-type CompressorParams \ 127 | --opaque-type Compressor \ 128 | --opaque-type basisu::image \ 129 | \ 130 | -- -x c++ -std=c++14 131 | -------------------------------------------------------------------------------- /basis-universal-sys/src/encoding_bindings.rs: -------------------------------------------------------------------------------- 1 | /* automatically generated by rust-bindgen 0.66.1 */ 2 | 3 | #[repr(C)] 4 | #[derive(Copy, Clone)] 5 | pub struct basisu_color_rgba { 6 | pub __bindgen_anon_1: basisu_color_rgba__bindgen_ty_1, 7 | } 8 | #[repr(C)] 9 | #[derive(Copy, Clone)] 10 | pub union basisu_color_rgba__bindgen_ty_1 { 11 | pub m_comps: [u8; 4usize], 12 | pub __bindgen_anon_1: basisu_color_rgba__bindgen_ty_1__bindgen_ty_1, 13 | } 14 | #[repr(C)] 15 | #[derive(Debug, Copy, Clone)] 16 | pub struct basisu_color_rgba__bindgen_ty_1__bindgen_ty_1 { 17 | pub r: u8, 18 | pub g: u8, 19 | pub b: u8, 20 | pub a: u8, 21 | } 22 | #[test] 23 | fn bindgen_test_layout_basisu_color_rgba__bindgen_ty_1__bindgen_ty_1() { 24 | const UNINIT: ::std::mem::MaybeUninit = 25 | ::std::mem::MaybeUninit::uninit(); 26 | let ptr = UNINIT.as_ptr(); 27 | assert_eq!( 28 | ::std::mem::size_of::(), 29 | 4usize, 30 | concat!( 31 | "Size of: ", 32 | stringify!(basisu_color_rgba__bindgen_ty_1__bindgen_ty_1) 33 | ) 34 | ); 35 | assert_eq!( 36 | ::std::mem::align_of::(), 37 | 1usize, 38 | concat!( 39 | "Alignment of ", 40 | stringify!(basisu_color_rgba__bindgen_ty_1__bindgen_ty_1) 41 | ) 42 | ); 43 | assert_eq!( 44 | unsafe { ::std::ptr::addr_of!((*ptr).r) as usize - ptr as usize }, 45 | 0usize, 46 | concat!( 47 | "Offset of field: ", 48 | stringify!(basisu_color_rgba__bindgen_ty_1__bindgen_ty_1), 49 | "::", 50 | stringify!(r) 51 | ) 52 | ); 53 | assert_eq!( 54 | unsafe { ::std::ptr::addr_of!((*ptr).g) as usize - ptr as usize }, 55 | 1usize, 56 | concat!( 57 | "Offset of field: ", 58 | stringify!(basisu_color_rgba__bindgen_ty_1__bindgen_ty_1), 59 | "::", 60 | stringify!(g) 61 | ) 62 | ); 63 | assert_eq!( 64 | unsafe { ::std::ptr::addr_of!((*ptr).b) as usize - ptr as usize }, 65 | 2usize, 66 | concat!( 67 | "Offset of field: ", 68 | stringify!(basisu_color_rgba__bindgen_ty_1__bindgen_ty_1), 69 | "::", 70 | stringify!(b) 71 | ) 72 | ); 73 | assert_eq!( 74 | unsafe { ::std::ptr::addr_of!((*ptr).a) as usize - ptr as usize }, 75 | 3usize, 76 | concat!( 77 | "Offset of field: ", 78 | stringify!(basisu_color_rgba__bindgen_ty_1__bindgen_ty_1), 79 | "::", 80 | stringify!(a) 81 | ) 82 | ); 83 | } 84 | #[test] 85 | fn bindgen_test_layout_basisu_color_rgba__bindgen_ty_1() { 86 | const UNINIT: ::std::mem::MaybeUninit = 87 | ::std::mem::MaybeUninit::uninit(); 88 | let ptr = UNINIT.as_ptr(); 89 | assert_eq!( 90 | ::std::mem::size_of::(), 91 | 4usize, 92 | concat!("Size of: ", stringify!(basisu_color_rgba__bindgen_ty_1)) 93 | ); 94 | assert_eq!( 95 | ::std::mem::align_of::(), 96 | 1usize, 97 | concat!("Alignment of ", stringify!(basisu_color_rgba__bindgen_ty_1)) 98 | ); 99 | assert_eq!( 100 | unsafe { ::std::ptr::addr_of!((*ptr).m_comps) as usize - ptr as usize }, 101 | 0usize, 102 | concat!( 103 | "Offset of field: ", 104 | stringify!(basisu_color_rgba__bindgen_ty_1), 105 | "::", 106 | stringify!(m_comps) 107 | ) 108 | ); 109 | } 110 | #[test] 111 | fn bindgen_test_layout_basisu_color_rgba() { 112 | assert_eq!( 113 | ::std::mem::size_of::(), 114 | 4usize, 115 | concat!("Size of: ", stringify!(basisu_color_rgba)) 116 | ); 117 | assert_eq!( 118 | ::std::mem::align_of::(), 119 | 1usize, 120 | concat!("Alignment of ", stringify!(basisu_color_rgba)) 121 | ); 122 | } 123 | #[repr(C)] 124 | #[repr(align(8))] 125 | #[derive(Debug, Copy, Clone)] 126 | pub struct basisu_image { 127 | pub _bindgen_opaque_blob: [u64; 4usize], 128 | } 129 | #[test] 130 | fn bindgen_test_layout_basisu_image() { 131 | assert_eq!( 132 | ::std::mem::size_of::(), 133 | 32usize, 134 | concat!("Size of: ", stringify!(basisu_image)) 135 | ); 136 | assert_eq!( 137 | ::std::mem::align_of::(), 138 | 8usize, 139 | concat!("Alignment of ", stringify!(basisu_image)) 140 | ); 141 | } 142 | extern "C" { 143 | #[link_name = "\u{1}?debug_text@image@basisu@@QEAAXIIIIAEBVcolor_rgba@2@PEBV32@_NPEBDZZ"] 144 | pub fn basisu_image_debug_text( 145 | this: *mut basisu_image, 146 | x_ofs: u32, 147 | y_ofs: u32, 148 | x_scale: u32, 149 | y_scale: u32, 150 | fg: *const basisu_color_rgba, 151 | pBG: *const basisu_color_rgba, 152 | alpha_only: bool, 153 | p: *const ::std::os::raw::c_char, 154 | ... 155 | ); 156 | } 157 | pub const basisu_TOTAL_PACK_UASTC_LEVELS: u32 = 5; 158 | pub const basisu_BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION: u32 = 16384; 159 | pub const basisu_BASISU_DEFAULT_ENDPOINT_RDO_THRESH: f32 = 1.5; 160 | pub const basisu_BASISU_DEFAULT_SELECTOR_RDO_THRESH: f32 = 1.25; 161 | pub const basisu_BASISU_DEFAULT_QUALITY: ::std::os::raw::c_int = 128; 162 | pub const basisu_BASISU_DEFAULT_HYBRID_SEL_CB_QUALITY_THRESH: f32 = 2.0; 163 | pub const basisu_BASISU_MAX_IMAGE_DIMENSION: u32 = 16384; 164 | pub const basisu_BASISU_QUALITY_MIN: u32 = 1; 165 | pub const basisu_BASISU_QUALITY_MAX: u32 = 255; 166 | pub const basisu_BASISU_MAX_ENDPOINT_CLUSTERS: u32 = 16128; 167 | pub const basisu_BASISU_MAX_SELECTOR_CLUSTERS: u32 = 16128; 168 | pub const basisu_BASISU_MAX_SLICES: u32 = 16777215; 169 | pub const basisu_BASISU_RDO_UASTC_DICT_SIZE_DEFAULT: ::std::os::raw::c_int = 4096; 170 | pub const basisu_BASISU_RDO_UASTC_DICT_SIZE_MIN: ::std::os::raw::c_int = 64; 171 | pub const basisu_BASISU_RDO_UASTC_DICT_SIZE_MAX: ::std::os::raw::c_int = 65536; 172 | pub const basisu_basis_compressor_error_code_cECSuccess: basisu_basis_compressor_error_code = 0; 173 | pub const basisu_basis_compressor_error_code_cECFailedInitializing: 174 | basisu_basis_compressor_error_code = 1; 175 | pub const basisu_basis_compressor_error_code_cECFailedReadingSourceImages: 176 | basisu_basis_compressor_error_code = 2; 177 | pub const basisu_basis_compressor_error_code_cECFailedValidating: 178 | basisu_basis_compressor_error_code = 3; 179 | pub const basisu_basis_compressor_error_code_cECFailedEncodeUASTC: 180 | basisu_basis_compressor_error_code = 4; 181 | pub const basisu_basis_compressor_error_code_cECFailedFrontEnd: basisu_basis_compressor_error_code = 182 | 5; 183 | pub const basisu_basis_compressor_error_code_cECFailedFontendExtract: 184 | basisu_basis_compressor_error_code = 6; 185 | pub const basisu_basis_compressor_error_code_cECFailedBackend: basisu_basis_compressor_error_code = 186 | 7; 187 | pub const basisu_basis_compressor_error_code_cECFailedCreateBasisFile: 188 | basisu_basis_compressor_error_code = 8; 189 | pub const basisu_basis_compressor_error_code_cECFailedWritingOutput: 190 | basisu_basis_compressor_error_code = 9; 191 | pub const basisu_basis_compressor_error_code_cECFailedUASTCRDOPostProcess: 192 | basisu_basis_compressor_error_code = 10; 193 | pub const basisu_basis_compressor_error_code_cECFailedCreateKTX2File: 194 | basisu_basis_compressor_error_code = 11; 195 | pub type basisu_basis_compressor_error_code = ::std::os::raw::c_int; 196 | #[repr(C)] 197 | #[derive(Copy, Clone)] 198 | pub union ColorU8 { 199 | pub channels: ColorU8_Channels, 200 | pub components: [u8; 4usize], 201 | pub combined: u32, 202 | } 203 | #[repr(C)] 204 | #[derive(Debug, Copy, Clone)] 205 | pub struct ColorU8_Channels { 206 | pub r: u8, 207 | pub g: u8, 208 | pub b: u8, 209 | pub a: u8, 210 | } 211 | #[test] 212 | fn bindgen_test_layout_ColorU8_Channels() { 213 | const UNINIT: ::std::mem::MaybeUninit = ::std::mem::MaybeUninit::uninit(); 214 | let ptr = UNINIT.as_ptr(); 215 | assert_eq!( 216 | ::std::mem::size_of::(), 217 | 4usize, 218 | concat!("Size of: ", stringify!(ColorU8_Channels)) 219 | ); 220 | assert_eq!( 221 | ::std::mem::align_of::(), 222 | 1usize, 223 | concat!("Alignment of ", stringify!(ColorU8_Channels)) 224 | ); 225 | assert_eq!( 226 | unsafe { ::std::ptr::addr_of!((*ptr).r) as usize - ptr as usize }, 227 | 0usize, 228 | concat!( 229 | "Offset of field: ", 230 | stringify!(ColorU8_Channels), 231 | "::", 232 | stringify!(r) 233 | ) 234 | ); 235 | assert_eq!( 236 | unsafe { ::std::ptr::addr_of!((*ptr).g) as usize - ptr as usize }, 237 | 1usize, 238 | concat!( 239 | "Offset of field: ", 240 | stringify!(ColorU8_Channels), 241 | "::", 242 | stringify!(g) 243 | ) 244 | ); 245 | assert_eq!( 246 | unsafe { ::std::ptr::addr_of!((*ptr).b) as usize - ptr as usize }, 247 | 2usize, 248 | concat!( 249 | "Offset of field: ", 250 | stringify!(ColorU8_Channels), 251 | "::", 252 | stringify!(b) 253 | ) 254 | ); 255 | assert_eq!( 256 | unsafe { ::std::ptr::addr_of!((*ptr).a) as usize - ptr as usize }, 257 | 3usize, 258 | concat!( 259 | "Offset of field: ", 260 | stringify!(ColorU8_Channels), 261 | "::", 262 | stringify!(a) 263 | ) 264 | ); 265 | } 266 | #[test] 267 | fn bindgen_test_layout_ColorU8() { 268 | const UNINIT: ::std::mem::MaybeUninit = ::std::mem::MaybeUninit::uninit(); 269 | let ptr = UNINIT.as_ptr(); 270 | assert_eq!( 271 | ::std::mem::size_of::(), 272 | 4usize, 273 | concat!("Size of: ", stringify!(ColorU8)) 274 | ); 275 | assert_eq!( 276 | ::std::mem::align_of::(), 277 | 4usize, 278 | concat!("Alignment of ", stringify!(ColorU8)) 279 | ); 280 | assert_eq!( 281 | unsafe { ::std::ptr::addr_of!((*ptr).channels) as usize - ptr as usize }, 282 | 0usize, 283 | concat!( 284 | "Offset of field: ", 285 | stringify!(ColorU8), 286 | "::", 287 | stringify!(channels) 288 | ) 289 | ); 290 | assert_eq!( 291 | unsafe { ::std::ptr::addr_of!((*ptr).components) as usize - ptr as usize }, 292 | 0usize, 293 | concat!( 294 | "Offset of field: ", 295 | stringify!(ColorU8), 296 | "::", 297 | stringify!(components) 298 | ) 299 | ); 300 | assert_eq!( 301 | unsafe { ::std::ptr::addr_of!((*ptr).combined) as usize - ptr as usize }, 302 | 0usize, 303 | concat!( 304 | "Offset of field: ", 305 | stringify!(ColorU8), 306 | "::", 307 | stringify!(combined) 308 | ) 309 | ); 310 | } 311 | pub const UastcPackFlags_PackUASTCLevelFastest: UastcPackFlags = 0; 312 | pub const UastcPackFlags_PackUASTCLevelFaster: UastcPackFlags = 1; 313 | pub const UastcPackFlags_PackUASTCLevelDefault: UastcPackFlags = 2; 314 | pub const UastcPackFlags_PackUASTCLevelSlower: UastcPackFlags = 3; 315 | pub const UastcPackFlags_PackUASTCLevelVerySlow: UastcPackFlags = 4; 316 | pub const UastcPackFlags_PackUASTCLevelMask: UastcPackFlags = 15; 317 | pub const UastcPackFlags_PackUASTCFavorUASTCError: UastcPackFlags = 8; 318 | pub const UastcPackFlags_PackUASTCFavorBC7Error: UastcPackFlags = 16; 319 | pub const UastcPackFlags_PackUASTCETC1FasterHints: UastcPackFlags = 64; 320 | pub const UastcPackFlags_PackUASTCETC1FastestHints: UastcPackFlags = 128; 321 | pub const UastcPackFlags_PackUASTCETC1DisableFlipAndIndividual: UastcPackFlags = 256; 322 | pub type UastcPackFlags = ::std::os::raw::c_int; 323 | extern "C" { 324 | pub fn image_clear(image: *mut basisu_image); 325 | } 326 | extern "C" { 327 | pub fn image_resize_with_pitch( 328 | image: *mut basisu_image, 329 | w: u32, 330 | h: u32, 331 | p: u32, 332 | ); 333 | } 334 | extern "C" { 335 | pub fn image_resize( 336 | image: *mut basisu_image, 337 | w: u32, 338 | h: u32, 339 | ); 340 | } 341 | extern "C" { 342 | pub fn image_init( 343 | image: *mut basisu_image, 344 | pData: *const u8, 345 | width: u32, 346 | height: u32, 347 | comps: u32, 348 | ); 349 | } 350 | extern "C" { 351 | pub fn image_get_pixel_at_checked( 352 | image: *mut basisu_image, 353 | x: u32, 354 | y: u32, 355 | pOutColor: *mut ColorU8, 356 | ) -> bool; 357 | } 358 | extern "C" { 359 | pub fn image_get_pixel_at_unchecked( 360 | image: *mut basisu_image, 361 | x: u32, 362 | y: u32, 363 | ) -> ColorU8; 364 | } 365 | extern "C" { 366 | pub fn image_get_width(image: *mut basisu_image) -> u32; 367 | } 368 | extern "C" { 369 | pub fn image_get_height(image: *mut basisu_image) -> u32; 370 | } 371 | extern "C" { 372 | pub fn image_get_pitch(image: *mut basisu_image) -> u32; 373 | } 374 | extern "C" { 375 | pub fn image_get_total_pixels(image: *mut basisu_image) -> u32; 376 | } 377 | extern "C" { 378 | pub fn image_get_block_width( 379 | image: *mut basisu_image, 380 | w: u32, 381 | ) -> u32; 382 | } 383 | extern "C" { 384 | pub fn image_get_block_height( 385 | image: *mut basisu_image, 386 | h: u32, 387 | ) -> u32; 388 | } 389 | extern "C" { 390 | pub fn image_get_total_blocks( 391 | image: *mut basisu_image, 392 | w: u32, 393 | h: u32, 394 | ) -> u32; 395 | } 396 | #[repr(C)] 397 | #[derive(Debug, Copy, Clone)] 398 | pub struct PixelData { 399 | pub pData: *mut ColorU8, 400 | pub length: usize, 401 | } 402 | #[test] 403 | fn bindgen_test_layout_PixelData() { 404 | const UNINIT: ::std::mem::MaybeUninit = ::std::mem::MaybeUninit::uninit(); 405 | let ptr = UNINIT.as_ptr(); 406 | assert_eq!( 407 | ::std::mem::size_of::(), 408 | 16usize, 409 | concat!("Size of: ", stringify!(PixelData)) 410 | ); 411 | assert_eq!( 412 | ::std::mem::align_of::(), 413 | 8usize, 414 | concat!("Alignment of ", stringify!(PixelData)) 415 | ); 416 | assert_eq!( 417 | unsafe { ::std::ptr::addr_of!((*ptr).pData) as usize - ptr as usize }, 418 | 0usize, 419 | concat!( 420 | "Offset of field: ", 421 | stringify!(PixelData), 422 | "::", 423 | stringify!(pData) 424 | ) 425 | ); 426 | assert_eq!( 427 | unsafe { ::std::ptr::addr_of!((*ptr).length) as usize - ptr as usize }, 428 | 8usize, 429 | concat!( 430 | "Offset of field: ", 431 | stringify!(PixelData), 432 | "::", 433 | stringify!(length) 434 | ) 435 | ); 436 | } 437 | extern "C" { 438 | pub fn image_get_pixel_data(image: *mut basisu_image) -> PixelData; 439 | } 440 | #[repr(C)] 441 | #[repr(align(8))] 442 | #[derive(Debug, Copy, Clone)] 443 | pub struct CompressorParams { 444 | pub _bindgen_opaque_blob: u64, 445 | } 446 | #[test] 447 | fn bindgen_test_layout_CompressorParams() { 448 | assert_eq!( 449 | ::std::mem::size_of::(), 450 | 8usize, 451 | concat!("Size of: ", stringify!(CompressorParams)) 452 | ); 453 | assert_eq!( 454 | ::std::mem::align_of::(), 455 | 8usize, 456 | concat!("Alignment of ", stringify!(CompressorParams)) 457 | ); 458 | } 459 | extern "C" { 460 | pub fn compressor_params_new() -> *mut CompressorParams; 461 | } 462 | extern "C" { 463 | pub fn compressor_params_delete(params: *mut CompressorParams); 464 | } 465 | extern "C" { 466 | pub fn compressor_params_clear(params: *mut CompressorParams); 467 | } 468 | extern "C" { 469 | pub fn compressor_params_get_or_create_source_image( 470 | params: *mut CompressorParams, 471 | index: u32, 472 | ) -> *mut basisu_image; 473 | } 474 | extern "C" { 475 | pub fn compressor_params_resize_source_image_list( 476 | params: *mut CompressorParams, 477 | size: usize, 478 | ); 479 | } 480 | extern "C" { 481 | pub fn compressor_params_clear_source_image_list(params: *mut CompressorParams); 482 | } 483 | extern "C" { 484 | pub fn compressor_params_get_or_create_source_mipmap_image( 485 | params: *mut CompressorParams, 486 | index: u32, 487 | level: u32, 488 | ) -> *mut basisu_image; 489 | } 490 | extern "C" { 491 | pub fn compressor_params_resize_source_mipmap_image_list( 492 | params: *mut CompressorParams, 493 | size: usize, 494 | ); 495 | } 496 | extern "C" { 497 | pub fn compressor_params_resize_source_mipmap_image_level_list( 498 | params: *mut CompressorParams, 499 | level: usize, 500 | size: usize, 501 | ); 502 | } 503 | extern "C" { 504 | pub fn compressor_params_clear_source_mipmap_image_list(params: *mut CompressorParams); 505 | } 506 | extern "C" { 507 | pub fn compressor_params_set_status_output( 508 | params: *mut CompressorParams, 509 | status_output: bool, 510 | ); 511 | } 512 | extern "C" { 513 | pub fn compressor_params_set_quality_level( 514 | params: *mut CompressorParams, 515 | quality_level: ::std::os::raw::c_int, 516 | ); 517 | } 518 | extern "C" { 519 | pub fn compressor_params_get_pack_uastc_flags(params: *mut CompressorParams) -> UastcPackFlags; 520 | } 521 | extern "C" { 522 | pub fn compressor_params_set_pack_uastc_flags( 523 | params: *mut CompressorParams, 524 | pack_uastc_flags: UastcPackFlags, 525 | ); 526 | } 527 | extern "C" { 528 | pub fn compressor_params_set_uastc( 529 | params: *mut CompressorParams, 530 | is_uastc: bool, 531 | ); 532 | } 533 | extern "C" { 534 | pub fn compressor_params_set_perceptual( 535 | params: *mut CompressorParams, 536 | perceptual: bool, 537 | ); 538 | } 539 | extern "C" { 540 | pub fn compressor_params_set_mip_srgb( 541 | params: *mut CompressorParams, 542 | mip_srgb: bool, 543 | ); 544 | } 545 | extern "C" { 546 | pub fn compressor_params_set_no_selector_rdo( 547 | params: *mut CompressorParams, 548 | no_selector_rdo: bool, 549 | ); 550 | } 551 | extern "C" { 552 | pub fn compressor_params_set_no_endpoint_rdo( 553 | params: *mut CompressorParams, 554 | no_endpoint_rdo: bool, 555 | ); 556 | } 557 | extern "C" { 558 | pub fn compressor_params_set_rdo_uastc( 559 | params: *mut CompressorParams, 560 | rdo_uastc: bool, 561 | ); 562 | } 563 | extern "C" { 564 | pub fn compressor_params_set_rdo_uastc_quality_scalar( 565 | params: *mut CompressorParams, 566 | rdo_uastc_quality_scalar: f32, 567 | ); 568 | } 569 | extern "C" { 570 | pub fn compressor_params_set_generate_mipmaps( 571 | params: *mut CompressorParams, 572 | generate_mipmaps: bool, 573 | ); 574 | } 575 | extern "C" { 576 | pub fn compressor_params_set_mip_smallest_dimension( 577 | params: *mut CompressorParams, 578 | mip_smallest_dimension: ::std::os::raw::c_int, 579 | ); 580 | } 581 | extern "C" { 582 | pub fn compressor_params_set_userdata( 583 | params: *mut CompressorParams, 584 | userdata0: u32, 585 | userdata1: u32, 586 | ); 587 | } 588 | #[repr(C)] 589 | #[repr(align(8))] 590 | #[derive(Debug, Copy, Clone)] 591 | pub struct Compressor { 592 | pub _bindgen_opaque_blob: [u64; 2usize], 593 | } 594 | #[test] 595 | fn bindgen_test_layout_Compressor() { 596 | assert_eq!( 597 | ::std::mem::size_of::(), 598 | 16usize, 599 | concat!("Size of: ", stringify!(Compressor)) 600 | ); 601 | assert_eq!( 602 | ::std::mem::align_of::(), 603 | 8usize, 604 | concat!("Alignment of ", stringify!(Compressor)) 605 | ); 606 | } 607 | extern "C" { 608 | pub fn compressor_new(num_threads: ::std::os::raw::c_int) -> *mut Compressor; 609 | } 610 | extern "C" { 611 | pub fn compressor_delete(compressor: *mut Compressor); 612 | } 613 | extern "C" { 614 | pub fn compressor_init( 615 | compressor: *mut Compressor, 616 | params: *const CompressorParams, 617 | ) -> bool; 618 | } 619 | extern "C" { 620 | pub fn compressor_process(compressor: *mut Compressor) -> basisu_basis_compressor_error_code; 621 | } 622 | #[repr(C)] 623 | #[derive(Debug, Copy, Clone)] 624 | pub struct CompressorBasisFile { 625 | pub pData: *const u8, 626 | pub length: usize, 627 | } 628 | #[test] 629 | fn bindgen_test_layout_CompressorBasisFile() { 630 | const UNINIT: ::std::mem::MaybeUninit = ::std::mem::MaybeUninit::uninit(); 631 | let ptr = UNINIT.as_ptr(); 632 | assert_eq!( 633 | ::std::mem::size_of::(), 634 | 16usize, 635 | concat!("Size of: ", stringify!(CompressorBasisFile)) 636 | ); 637 | assert_eq!( 638 | ::std::mem::align_of::(), 639 | 8usize, 640 | concat!("Alignment of ", stringify!(CompressorBasisFile)) 641 | ); 642 | assert_eq!( 643 | unsafe { ::std::ptr::addr_of!((*ptr).pData) as usize - ptr as usize }, 644 | 0usize, 645 | concat!( 646 | "Offset of field: ", 647 | stringify!(CompressorBasisFile), 648 | "::", 649 | stringify!(pData) 650 | ) 651 | ); 652 | assert_eq!( 653 | unsafe { ::std::ptr::addr_of!((*ptr).length) as usize - ptr as usize }, 654 | 8usize, 655 | concat!( 656 | "Offset of field: ", 657 | stringify!(CompressorBasisFile), 658 | "::", 659 | stringify!(length) 660 | ) 661 | ); 662 | } 663 | extern "C" { 664 | pub fn compressor_get_output_basis_file(compressor: *mut Compressor) -> CompressorBasisFile; 665 | } 666 | extern "C" { 667 | pub fn compressor_get_basis_file_size(compressor: *const Compressor) -> u32; 668 | } 669 | extern "C" { 670 | pub fn compressor_get_basis_bits_per_texel(compressor: *const Compressor) -> f64; 671 | } 672 | extern "C" { 673 | pub fn compressor_get_any_source_image_has_alpha(compressor: *const Compressor) -> bool; 674 | } 675 | extern "C" { 676 | pub fn basisu_encoder_init(); 677 | } 678 | -------------------------------------------------------------------------------- /basis-universal-sys/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[allow(non_upper_case_globals)] 2 | #[allow(non_camel_case_types)] 3 | #[allow(non_snake_case)] 4 | #[allow(deref_nullptr)] 5 | #[rustfmt::skip] 6 | pub mod transcoding_bindings; 7 | pub use transcoding_bindings::*; 8 | 9 | #[allow(non_upper_case_globals)] 10 | #[allow(non_camel_case_types)] 11 | #[allow(non_snake_case)] 12 | #[allow(deref_nullptr)] 13 | #[rustfmt::skip] 14 | pub mod encoding_bindings; 15 | pub use encoding_bindings::*; 16 | -------------------------------------------------------------------------------- /basis-universal-sys/vendor/encoding_wrapper.cpp: -------------------------------------------------------------------------------- 1 | #include "basis_universal/encoder/basisu_comp.h" 2 | #include "basis_universal/encoder/basisu_enc.h" 3 | 4 | //TODO: constants 5 | 6 | extern "C" { 7 | union ColorU8 { 8 | struct Channels { 9 | uint8_t r; 10 | uint8_t g; 11 | uint8_t b; 12 | uint8_t a; 13 | } channels; 14 | 15 | uint8_t components[4]; 16 | uint32_t combined; 17 | }; 18 | 19 | // These are in an anonymous enum, so wrap them 20 | enum UastcPackFlags { 21 | PackUASTCLevelFastest = basisu::cPackUASTCLevelFastest, 22 | PackUASTCLevelFaster = basisu::cPackUASTCLevelFaster, 23 | PackUASTCLevelDefault = basisu::cPackUASTCLevelDefault, 24 | PackUASTCLevelSlower = basisu::cPackUASTCLevelSlower, 25 | PackUASTCLevelVerySlow = basisu::cPackUASTCLevelVerySlow, 26 | PackUASTCLevelMask = basisu::cPackUASTCLevelMask, 27 | PackUASTCFavorUASTCError = basisu::cPackUASTCFavorUASTCError, 28 | PackUASTCFavorBC7Error = basisu::cPackUASTCFavorBC7Error, 29 | PackUASTCETC1FasterHints = basisu::cPackUASTCETC1FasterHints, 30 | PackUASTCETC1FastestHints = basisu::cPackUASTCETC1FastestHints, 31 | PackUASTCETC1DisableFlipAndIndividual = basisu::cPackUASTCETC1DisableFlipAndIndividual, 32 | }; 33 | 34 | void image_clear(basisu::image *image) { 35 | image->clear(); 36 | } 37 | 38 | // 39 | // These mirror the constructors (we don't expose new/delete) 40 | // 41 | void image_resize_with_pitch(basisu::image *image, uint32_t w, uint32_t h, uint32_t p) { 42 | image->resize(w, h, p); 43 | } 44 | 45 | void image_resize(basisu::image *image, uint32_t w, uint32_t h) { 46 | image_resize_with_pitch(image, w, h, UINT32_MAX); 47 | } 48 | 49 | void image_init(basisu::image *image, const uint8_t *pData, uint32_t width, uint32_t height, uint32_t comps) { 50 | image->init(pData, width, height, comps); 51 | } 52 | 53 | bool image_get_pixel_at_checked(basisu::image *image, uint32_t x, uint32_t y, ColorU8 *pOutColor) { 54 | if (x >= image->get_width() || y >= image->get_height()) { 55 | return false; 56 | } 57 | 58 | basisu::color_rgba basis_color = (*image)(x, y); 59 | *pOutColor = *reinterpret_cast(&basis_color); 60 | return true; 61 | } 62 | 63 | ColorU8 image_get_pixel_at_unchecked(basisu::image *image, uint32_t x, uint32_t y) { 64 | basisu::color_rgba basis_color = (*image)(x, y); 65 | return *reinterpret_cast(&basis_color); 66 | } 67 | 68 | uint32_t image_get_width(basisu::image *image) { 69 | return image->get_width(); 70 | } 71 | 72 | uint32_t image_get_height(basisu::image *image) { 73 | return image->get_height(); 74 | } 75 | 76 | uint32_t image_get_pitch(basisu::image *image) { 77 | return image->get_pitch(); 78 | } 79 | 80 | uint32_t image_get_total_pixels(basisu::image *image) { 81 | return image->get_total_pixels(); 82 | } 83 | 84 | uint32_t image_get_block_width(basisu::image *image, uint32_t w) { 85 | return image->get_block_width(w); 86 | } 87 | 88 | uint32_t image_get_block_height(basisu::image *image, uint32_t h) { 89 | return image->get_block_height(h); 90 | } 91 | 92 | uint32_t image_get_total_blocks(basisu::image *image, uint32_t w, uint32_t h) { 93 | return image->get_total_blocks(w, h); 94 | } 95 | 96 | struct PixelData { 97 | ColorU8 *pData; 98 | size_t length; 99 | }; 100 | 101 | PixelData image_get_pixel_data(basisu::image *image) { 102 | PixelData data; 103 | basisu::color_rgba *colorPtr = image->get_pixels().data(); 104 | data.pData = reinterpret_cast(colorPtr); 105 | data.length = image->get_pixels().size(); 106 | return data; 107 | } 108 | 109 | // 110 | // basisu::basis_compressor_params 111 | // 112 | struct CompressorParams { 113 | basisu::basis_compressor_params *pParams; 114 | }; 115 | 116 | CompressorParams *compressor_params_new() { 117 | CompressorParams *params = new CompressorParams; 118 | params->pParams = new basisu::basis_compressor_params(); 119 | return params; 120 | }; 121 | 122 | void compressor_params_delete(CompressorParams *params) { 123 | delete params->pParams; 124 | delete params; 125 | } 126 | 127 | void compressor_params_clear(CompressorParams *params) { 128 | params->pParams->clear(); 129 | } 130 | 131 | // 132 | // These function are used to load image data into the compressor 133 | // 134 | basisu::image *compressor_params_get_or_create_source_image(CompressorParams *params, uint32_t index) { 135 | if (params->pParams->m_source_images.size() < index + 1) { 136 | params->pParams->m_source_images.resize(index + 1); 137 | } 138 | 139 | return ¶ms->pParams->m_source_images[index]; 140 | } 141 | 142 | void compressor_params_resize_source_image_list(CompressorParams *params, size_t size) { 143 | params->pParams->m_source_images.resize(size); 144 | } 145 | 146 | void compressor_params_clear_source_image_list(CompressorParams *params) { 147 | params->pParams->clear(); 148 | } 149 | 150 | // These function are used to load custom mip map image data into the compressor 151 | // 152 | basisu::image *compressor_params_get_or_create_source_mipmap_image(CompressorParams *params, uint32_t index, uint32_t level) { 153 | if (params->pParams->m_source_mipmap_images.size() < index + 1) { 154 | params->pParams->m_source_mipmap_images.resize(index + 1); 155 | } 156 | if (params->pParams->m_source_mipmap_images[index].size() < level + 1) { 157 | params->pParams->m_source_mipmap_images[index].resize(level + 1); 158 | } 159 | 160 | return ¶ms->pParams->m_source_mipmap_images[index][level]; 161 | } 162 | 163 | void compressor_params_resize_source_mipmap_image_list(CompressorParams *params, size_t size) { 164 | params->pParams->m_source_mipmap_images.resize(size); 165 | } 166 | 167 | void compressor_params_resize_source_mipmap_image_level_list(CompressorParams *params, size_t level, size_t size) { 168 | params->pParams->m_source_mipmap_images[level].resize(size); 169 | } 170 | 171 | void compressor_params_clear_source_mipmap_image_list(CompressorParams *params) { 172 | params->pParams->m_source_mipmap_images.clear(); 173 | } 174 | 175 | // 176 | // These set parameters for compression 177 | // 178 | void compressor_params_set_status_output(CompressorParams *params, bool status_output) { 179 | params->pParams->m_status_output = status_output; 180 | } 181 | 182 | // According to CLI --help, this only affects ETC1S 183 | void compressor_params_set_quality_level(CompressorParams *params, int quality_level) { 184 | params->pParams->m_quality_level = quality_level; 185 | } 186 | 187 | UastcPackFlags compressor_params_get_pack_uastc_flags(CompressorParams *params) { 188 | return static_cast(params->pParams->m_pack_uastc_flags); 189 | } 190 | 191 | void compressor_params_set_pack_uastc_flags(CompressorParams *params, UastcPackFlags pack_uastc_flags) { 192 | params->pParams->m_pack_uastc_flags = static_cast(pack_uastc_flags); 193 | } 194 | 195 | void compressor_params_set_uastc(CompressorParams *params, bool is_uastc) { 196 | params->pParams->m_uastc = is_uastc; 197 | } 198 | 199 | void compressor_params_set_perceptual(CompressorParams *params, bool perceptual) { 200 | params->pParams->m_perceptual = perceptual; 201 | } 202 | 203 | void compressor_params_set_mip_srgb(CompressorParams *params, bool mip_srgb) { 204 | params->pParams->m_mip_srgb = mip_srgb; 205 | } 206 | 207 | void compressor_params_set_no_selector_rdo(CompressorParams *params, bool no_selector_rdo) { 208 | params->pParams->m_no_selector_rdo = no_selector_rdo; 209 | } 210 | 211 | void compressor_params_set_no_endpoint_rdo(CompressorParams *params, bool no_endpoint_rdo) { 212 | params->pParams->m_no_endpoint_rdo = no_endpoint_rdo; 213 | } 214 | 215 | void compressor_params_set_rdo_uastc(CompressorParams *params, bool rdo_uastc) { 216 | params->pParams->m_rdo_uastc = rdo_uastc; 217 | } 218 | 219 | void compressor_params_set_rdo_uastc_quality_scalar(CompressorParams *params, float rdo_uastc_quality_scalar) { 220 | params->pParams->m_rdo_uastc_quality_scalar = rdo_uastc_quality_scalar; 221 | } 222 | 223 | void compressor_params_set_generate_mipmaps(CompressorParams *params, bool generate_mipmaps) { 224 | params->pParams->m_mip_gen = generate_mipmaps; 225 | } 226 | 227 | void compressor_params_set_mip_smallest_dimension(CompressorParams *params, int mip_smallest_dimension) { 228 | params->pParams->m_mip_smallest_dimension = mip_smallest_dimension; 229 | } 230 | 231 | void compressor_params_set_userdata(CompressorParams *params, uint32_t userdata0, uint32_t userdata1) { 232 | params->pParams->m_userdata0 = userdata0; 233 | params->pParams->m_userdata1 = userdata1; 234 | } 235 | 236 | // compressor_params_set_multithreaded is not implemented because this parameter is controlled by thread count 237 | // passed to compressor_new() 238 | 239 | // 240 | // basisu_compressor 241 | // 242 | struct Compressor { 243 | basisu::basis_compressor *pCompressor; 244 | 245 | // I'm making the job pool owned by the compressor because it doesn't look like sharing a job pool between 246 | // compressors is intended (compressors call wait_for_all on the job pool). 247 | basisu::job_pool *pJobPool; 248 | }; 249 | 250 | // num_threads is passed directly to basisu::job_pool 251 | // num_threads is the TOTAL number of job pool threads, including the calling thread! So 2=1 new thread, 3=2 new threads, etc. 252 | Compressor *compressor_new(int num_threads) { 253 | Compressor *compressor = new Compressor; 254 | compressor->pCompressor = new basisu::basis_compressor(); 255 | compressor->pJobPool = new basisu::job_pool(num_threads); 256 | return compressor; 257 | }; 258 | 259 | void compressor_delete(Compressor *compressor) { 260 | delete compressor->pCompressor; 261 | delete compressor->pJobPool; 262 | delete compressor; 263 | } 264 | 265 | bool compressor_init(Compressor *compressor, const CompressorParams *params) { 266 | // Since this wrapper ties the job pool to the compressor, temporarily set it on the params and then clear it 267 | // later. (init() makes a copy of the params stored in the compressor) 268 | params->pParams->m_pJob_pool = compressor->pJobPool; 269 | params->pParams->m_multithreading = compressor->pJobPool->get_total_threads() > 1; 270 | bool result = compressor->pCompressor->init(*params->pParams); 271 | params->pParams->m_pJob_pool = nullptr; 272 | return result; 273 | } 274 | 275 | basisu::basis_compressor::error_code compressor_process(Compressor *compressor) { 276 | return compressor->pCompressor->process(); 277 | } 278 | 279 | struct CompressorBasisFile { 280 | const uint8_t *pData; 281 | size_t length; 282 | }; 283 | 284 | CompressorBasisFile compressor_get_output_basis_file(Compressor *compressor) { 285 | CompressorBasisFile file; 286 | const basisu::uint8_vec &basis_file = compressor->pCompressor->get_output_basis_file(); 287 | file.pData = basis_file.data(); 288 | file.length = basis_file.size(); 289 | return file; 290 | } 291 | 292 | // Not implemented: 293 | // const std::vector &compressor_get_stats(); 294 | 295 | uint32_t compressor_get_basis_file_size(const Compressor *compressor) { 296 | return compressor->pCompressor->get_basis_file_size(); 297 | } 298 | 299 | double compressor_get_basis_bits_per_texel(const Compressor *compressor) { 300 | return compressor->pCompressor->get_basis_bits_per_texel(); 301 | } 302 | 303 | bool compressor_get_any_source_image_has_alpha(const Compressor *compressor) { 304 | return compressor->pCompressor->get_any_source_image_has_alpha(); 305 | } 306 | 307 | void basisu_encoder_init() { 308 | basisu::basisu_encoder_init(); 309 | } 310 | } -------------------------------------------------------------------------------- /basis-universal-sys/vendor/transcoding_wrapper.cpp: -------------------------------------------------------------------------------- 1 | #include "basis_universal/transcoder/basisu_transcoder.h" 2 | 3 | extern "C" { 4 | // A copy of basist::basisu_file_info with problematic fields removed 5 | struct FileInfo 6 | { 7 | void reset(basist::basisu_file_info file_info) { 8 | m_version = file_info.m_version; 9 | m_total_header_size = file_info.m_total_header_size; 10 | 11 | m_total_selectors = file_info.m_total_selectors; 12 | m_selector_codebook_ofs = file_info.m_selector_codebook_ofs; 13 | m_selector_codebook_size = file_info.m_selector_codebook_size; 14 | 15 | m_total_endpoints = file_info.m_total_endpoints; 16 | m_endpoint_codebook_ofs = file_info.m_endpoint_codebook_ofs; 17 | m_endpoint_codebook_size = file_info.m_endpoint_codebook_size; 18 | 19 | m_tables_ofs = file_info.m_tables_ofs; 20 | m_tables_size = file_info.m_tables_size; 21 | 22 | m_slices_size = file_info.m_slices_size; 23 | 24 | m_tex_type = file_info.m_tex_type; 25 | m_us_per_frame = file_info.m_us_per_frame; 26 | 27 | m_total_images = file_info.m_total_images; 28 | 29 | m_userdata0 = file_info.m_userdata0; 30 | m_userdata1 = file_info.m_userdata1; 31 | 32 | m_tex_format = file_info.m_tex_format; 33 | 34 | m_y_flipped = file_info.m_y_flipped; 35 | m_etc1s = file_info.m_etc1s; 36 | m_has_alpha_slices = file_info.m_has_alpha_slices; 37 | } 38 | 39 | uint32_t m_version; 40 | uint32_t m_total_header_size; 41 | 42 | uint32_t m_total_selectors; 43 | uint32_t m_selector_codebook_ofs; 44 | uint32_t m_selector_codebook_size; 45 | 46 | uint32_t m_total_endpoints; 47 | uint32_t m_endpoint_codebook_ofs; 48 | uint32_t m_endpoint_codebook_size; 49 | 50 | uint32_t m_tables_ofs; 51 | uint32_t m_tables_size; 52 | 53 | uint32_t m_slices_size; 54 | 55 | basist::basis_texture_type m_tex_type; 56 | uint32_t m_us_per_frame; 57 | 58 | // Low-level slice information (1 slice per image for color-only basis files, 2 for alpha basis files) 59 | //basist::basisu_slice_info_vec m_slice_info; 60 | 61 | uint32_t m_total_images; // total # of images 62 | //std::vector m_image_mipmap_levels; // the # of mipmap levels for each image 63 | 64 | uint32_t m_userdata0; 65 | uint32_t m_userdata1; 66 | 67 | basist::basis_tex_format m_tex_format; // ETC1S, UASTC, etc. 68 | 69 | bool m_y_flipped; // true if the image was Y flipped 70 | bool m_etc1s; // true if the file is ETC1S 71 | bool m_has_alpha_slices; // true if the texture has alpha slices (for ETC1S: even slices RGB, odd slices alpha) 72 | }; 73 | 74 | // 75 | // "Loose" global functions 76 | // 77 | uint32_t basis_get_bytes_per_block_or_pixel(basist::transcoder_texture_format fmt) { 78 | return basist::basis_get_bytes_per_block_or_pixel(fmt); 79 | } 80 | 81 | const char* basis_get_format_name(basist::transcoder_texture_format fmt) { 82 | return basist::basis_get_format_name(fmt); 83 | } 84 | 85 | const char* basis_get_block_format_name(basist::block_format fmt) { 86 | return basist::basis_get_block_format_name(fmt); 87 | } 88 | 89 | bool basis_transcoder_format_has_alpha(basist::transcoder_texture_format fmt) { 90 | return basist::basis_transcoder_format_has_alpha(fmt); 91 | } 92 | 93 | basisu::texture_format basis_get_basisu_texture_format(basist::transcoder_texture_format fmt) { 94 | return basist::basis_get_basisu_texture_format(fmt); 95 | } 96 | 97 | const char* basis_get_texture_type_name(basist::basis_texture_type tex_type) { 98 | return basist::basis_get_texture_type_name(tex_type); 99 | } 100 | 101 | // Returns true if the transcoder texture type is an uncompressed (raw pixel) format. 102 | bool basis_transcoder_format_is_uncompressed(basist::transcoder_texture_format tex_type) { 103 | return basist::basis_transcoder_format_is_uncompressed(tex_type); 104 | } 105 | 106 | // Returns true if the block format is an uncompressed (raw pixel) format. 107 | bool basis_block_format_is_uncompressed(basist::block_format fmt) { 108 | return basist::basis_block_format_is_uncompressed(fmt); 109 | } 110 | 111 | // Returns the # of bytes per pixel for uncompressed formats, or 0 for block texture formats. 112 | uint32_t basis_get_uncompressed_bytes_per_pixel(basist::transcoder_texture_format fmt) { 113 | return basist::basis_get_uncompressed_bytes_per_pixel(fmt); 114 | } 115 | 116 | // Returns the block width for the specified texture format, which is currently either 4 or 8 for FXT1. 117 | uint32_t basis_get_block_width(basist::transcoder_texture_format tex_type) { 118 | return basist::basis_get_block_width(tex_type); 119 | } 120 | 121 | // Returns the block height for the specified texture format, which is currently always 4. 122 | uint32_t basis_get_block_height(basist::transcoder_texture_format tex_type) { 123 | return basist::basis_get_block_height(tex_type); 124 | } 125 | 126 | // Returns true if the specified format was enabled at compile time. 127 | bool basis_is_format_supported(basist::transcoder_texture_format tex_type, basist::basis_tex_format fmt) { 128 | return basist::basis_is_format_supported(tex_type, fmt); 129 | } 130 | 131 | // Validates that the output buffer is large enough to hold the entire transcoded texture. 132 | // For uncompressed texture formats, most input parameters are in pixels, not blocks. Blocks are 4x4 pixels. 133 | bool basis_validate_output_buffer_size( 134 | basist::transcoder_texture_format target_format, 135 | uint32_t output_blocks_buf_size_in_blocks_or_pixels, 136 | uint32_t orig_width, 137 | uint32_t orig_height, 138 | uint32_t output_row_pitch_in_blocks_or_pixels, 139 | uint32_t output_rows_in_pixels, 140 | uint32_t total_slice_blocks 141 | ) { 142 | return basist::basis_validate_output_buffer_size( 143 | target_format, 144 | output_blocks_buf_size_in_blocks_or_pixels, 145 | orig_width, 146 | orig_height, 147 | output_row_pitch_in_blocks_or_pixels, 148 | output_rows_in_pixels, 149 | total_slice_blocks 150 | ); 151 | } 152 | 153 | 154 | 155 | // 156 | // basisu_lowlevel_etc1s_transcoder 157 | // 158 | 159 | // Not implemented 160 | 161 | // 162 | // basisu_lowlevel_uastc_transcoder 163 | // 164 | 165 | struct LowLevelUastcTranscoder { 166 | basist::basisu_lowlevel_uastc_transcoder *pTranscoder; 167 | }; 168 | 169 | LowLevelUastcTranscoder *low_level_uastc_transcoder_new() { 170 | LowLevelUastcTranscoder *transcoder = new LowLevelUastcTranscoder; 171 | transcoder->pTranscoder = new basist::basisu_lowlevel_uastc_transcoder(); 172 | return transcoder; 173 | } 174 | 175 | void low_level_uastc_transcoder_delete(LowLevelUastcTranscoder *transcoder) { 176 | delete transcoder->pTranscoder; 177 | delete transcoder; 178 | } 179 | 180 | bool low_level_uastc_transcoder_transcode_slice( 181 | LowLevelUastcTranscoder *transcoder, 182 | void* pDst_blocks, 183 | uint32_t num_blocks_x, 184 | uint32_t num_blocks_y, 185 | const uint8_t* pImage_data, 186 | uint32_t image_data_size, 187 | basist::block_format fmt, 188 | uint32_t output_block_or_pixel_stride_in_bytes, 189 | bool bc1_allow_threecolor_blocks, 190 | bool has_alpha, 191 | const uint32_t orig_width, 192 | const uint32_t orig_height, 193 | uint32_t output_row_pitch_in_blocks_or_pixels, 194 | basist::basisu_transcoder_state* pState, 195 | uint32_t output_rows_in_pixels, 196 | int channel0, 197 | int channel1, 198 | int32_t decode_flags // Enums are reflected as signed integers in Rust. 199 | ) { 200 | return transcoder->pTranscoder->transcode_slice( 201 | pDst_blocks, 202 | num_blocks_x, 203 | num_blocks_y, 204 | pImage_data, 205 | image_data_size, 206 | fmt, 207 | output_block_or_pixel_stride_in_bytes, 208 | bc1_allow_threecolor_blocks, 209 | has_alpha, 210 | orig_width, 211 | orig_height, 212 | output_row_pitch_in_blocks_or_pixels, 213 | pState, 214 | output_rows_in_pixels, 215 | channel0, 216 | channel1, 217 | decode_flags 218 | ); 219 | } 220 | 221 | 222 | // 223 | // basisu_transcoder 224 | // 225 | struct Transcoder { 226 | basist::basisu_transcoder *pTranscoder; 227 | }; 228 | 229 | Transcoder *transcoder_new() { 230 | Transcoder *transcoder = new Transcoder; 231 | transcoder->pTranscoder = new basist::basisu_transcoder(); 232 | return transcoder; 233 | }; 234 | 235 | void transcoder_delete(Transcoder *transcoder) { 236 | delete transcoder->pTranscoder; 237 | delete transcoder; 238 | } 239 | 240 | // Validates the .basis file. This computes a crc16 over the entire file, so it's slow. 241 | bool transcoder_validate_file_checksums(const Transcoder *transcoder, const void *pData, uint32_t data_size, bool full_validation) { 242 | return transcoder->pTranscoder->validate_file_checksums(pData, data_size, full_validation); 243 | } 244 | 245 | // Quick header validation - no crc16 checks. 246 | bool transcoder_validate_header(const Transcoder *transcoder, const void *pData, uint32_t data_size) { 247 | return transcoder->pTranscoder->validate_header(pData, data_size); 248 | } 249 | 250 | basist::basis_texture_type transcoder_get_texture_type(const Transcoder *transcoder, const void *pData, uint32_t data_size) { 251 | return transcoder->pTranscoder->get_texture_type(pData, data_size); 252 | } 253 | 254 | bool transcoder_get_userdata(const Transcoder *transcoder, const void *pData, uint32_t data_size, uint32_t &userdata0, uint32_t &userdata1) { 255 | return transcoder->pTranscoder->get_userdata(pData, data_size, userdata0, userdata1); 256 | } 257 | 258 | // Returns the total number of images in the basis file (always 1 or more). 259 | // Note that the number of mipmap levels for each image may differ, and that images may have different resolutions. 260 | uint32_t transcoder_get_total_images(const Transcoder *transcoder, const void *pData, uint32_t data_size) { 261 | return transcoder->pTranscoder->get_total_images(pData, data_size); 262 | } 263 | 264 | basist::basis_tex_format transcoder_get_tex_format(const Transcoder *transcoder, const void* pData, uint32_t data_size) { 265 | return transcoder->pTranscoder->get_tex_format(pData, data_size); 266 | } 267 | 268 | // Returns the number of mipmap levels in an image. 269 | uint32_t transcoder_get_total_image_levels(const Transcoder *transcoder, const void *pData, uint32_t data_size, uint32_t image_index) { 270 | return transcoder->pTranscoder->get_total_image_levels(pData, data_size, image_index); 271 | } 272 | 273 | // Returns basic information about an image. Note that orig_width/orig_height may not be a multiple of 4. 274 | bool transcoder_get_image_level_desc(const Transcoder *transcoder, const void *pData, uint32_t data_size, uint32_t image_index, uint32_t level_index, uint32_t &orig_width, uint32_t &orig_height, uint32_t &total_blocks) { 275 | return transcoder->pTranscoder->get_image_level_desc(pData, data_size, image_index, level_index, orig_width, orig_height, total_blocks); 276 | } 277 | 278 | // Returns information about the specified image. 279 | bool transcoder_get_image_info(const Transcoder *transcoder, const void *pData, uint32_t data_size, basist::basisu_image_info &image_info, uint32_t image_index) { 280 | return transcoder->pTranscoder->get_image_info(pData, data_size, image_info, image_index); 281 | } 282 | 283 | // Returns information about the specified image's mipmap level. 284 | bool transcoder_get_image_level_info(const Transcoder *transcoder, const void *pData, uint32_t data_size, basist::basisu_image_level_info &level_info, uint32_t image_index, uint32_t level_index) { 285 | return transcoder->pTranscoder->get_image_level_info(pData, data_size, level_info, image_index, level_index); 286 | } 287 | 288 | // Get a description of the basis file and low-level information about each slice. 289 | bool transcoder_get_file_info(Transcoder *transcoder, const void *pData, uint32_t data_size, FileInfo &file_info) { 290 | basist::basisu_file_info fi; 291 | if (!transcoder->pTranscoder->get_file_info(pData, data_size, fi)) { 292 | return false; 293 | } 294 | 295 | file_info.reset(fi); 296 | return true; 297 | } 298 | 299 | // start_transcoding() must be called before calling transcode_slice() or transcode_image_level(). 300 | // For ETC1S files, this call decompresses the selector/endpoint codebooks, so ideally you would only call this once per .basis file (not each image/mipmap level). 301 | bool transcoder_start_transcoding(Transcoder *transcoder, const void *pData, uint32_t data_size) { 302 | return transcoder->pTranscoder->start_transcoding(pData, data_size); 303 | } 304 | 305 | bool transcoder_stop_transcoding(Transcoder *transcoder) { 306 | return transcoder->pTranscoder->stop_transcoding(); 307 | } 308 | 309 | // Returns true if start_transcoding() has been called. 310 | bool transcoder_get_ready_to_transcode(const Transcoder *transcoder) { 311 | return transcoder->pTranscoder->get_ready_to_transcode(); 312 | } 313 | 314 | // transcode_image_level() decodes a single mipmap level from the .basis file to any of the supported output texture formats. 315 | // It'll first find the slice(s) to transcode, then call transcode_slice() one or two times to decode both the color and alpha texture data (or RG texture data from two slices for BC5). 316 | // If the .basis file doesn't have alpha slices, the output alpha blocks will be set to fully opaque (all 255's). 317 | // Currently, to decode to PVRTC1 the basis texture's dimensions in pixels must be a power of 2, due to PVRTC1 format requirements. 318 | // output_blocks_buf_size_in_blocks_or_pixels should be at least the image level's total_blocks (num_blocks_x * num_blocks_y), or the total number of output pixels if fmt==cTFRGBA32. 319 | // output_row_pitch_in_blocks_or_pixels: Number of blocks or pixels per row. If 0, the transcoder uses the slice's num_blocks_x or orig_width (NOT num_blocks_x * 4). Ignored for PVRTC1 (due to texture swizzling). 320 | // output_rows_in_pixels: Ignored unless fmt is cRGBA32. The total number of output rows in the output buffer. If 0, the transcoder assumes the slice's orig_height (NOT num_blocks_y * 4). 321 | // Notes: 322 | // - basisu_transcoder_init() must have been called first to initialize the transcoder lookup tables before calling this function. 323 | // - This method assumes the output texture buffer is readable. In some cases to handle alpha, the transcoder will write temporary data to the output texture in 324 | // a first pass, which will be read in a second pass. 325 | bool transcoder_transcode_image_level( 326 | Transcoder *transcoder, 327 | const void *pData, 328 | uint32_t data_size, 329 | uint32_t image_index, 330 | uint32_t level_index, 331 | void *pOutput_blocks, 332 | uint32_t output_blocks_buf_size_in_blocks_or_pixels, 333 | basist::transcoder_texture_format fmt, 334 | basist::basisu_decode_flags decode_flags, // default: 0 335 | uint32_t output_row_pitch_in_blocks_or_pixels, // default: 0 336 | basist::basisu_transcoder_state *pState, // default: nullptr 337 | uint32_t output_rows_in_pixels // default: 0 338 | ) { 339 | return transcoder->pTranscoder->transcode_image_level( 340 | pData, 341 | data_size, 342 | image_index, 343 | level_index, 344 | pOutput_blocks, 345 | output_blocks_buf_size_in_blocks_or_pixels, 346 | fmt, 347 | decode_flags, 348 | output_row_pitch_in_blocks_or_pixels, 349 | pState, 350 | output_rows_in_pixels 351 | ); 352 | } 353 | 354 | // 355 | // // Finds the basis slice corresponding to the specified image/level/alpha params, or -1 if the slice can't be found. 356 | // int find_slice(const void *pData, uint32_t data_size, uint32_t image_index, uint32_t level_index, bool alpha_data) const; 357 | // 358 | // // transcode_slice() decodes a single slice from the .basis file. It's a low-level API - most likely you want to use transcode_image_level(). 359 | // // This is a low-level API, and will be needed to be called multiple times to decode some texture formats (like BC3, BC5, or ETC2). 360 | // // output_blocks_buf_size_in_blocks_or_pixels is just used for verification to make sure the output buffer is large enough. 361 | // // output_blocks_buf_size_in_blocks_or_pixels should be at least the image level's total_blocks (num_blocks_x * num_blocks_y), or the total number of output pixels if fmt==cTFRGBA32. 362 | // // output_block_stride_in_bytes: Number of bytes between each output block. 363 | // // output_row_pitch_in_blocks_or_pixels: Number of blocks or pixels per row. If 0, the transcoder uses the slice's num_blocks_x or orig_width (NOT num_blocks_x * 4). Ignored for PVRTC1 (due to texture swizzling). 364 | // // output_rows_in_pixels: Ignored unless fmt is cRGBA32. The total number of output rows in the output buffer. If 0, the transcoder assumes the slice's orig_height (NOT num_blocks_y * 4). 365 | // // Notes: 366 | // // - basisu_transcoder_init() must have been called first to initialize the transcoder lookup tables before calling this function. 367 | // bool transcode_slice(const void *pData, uint32_t data_size, uint32_t slice_index, 368 | // void *pOutput_blocks, uint32_t output_blocks_buf_size_in_blocks_or_pixels, 369 | // block_format fmt, uint32_t output_block_stride_in_bytes, uint32_t decode_flags = 0, uint32_t output_row_pitch_in_blocks_or_pixels = 0, basisu_transcoder_state * pState = nullptr, void* pAlpha_blocks = nullptr, 370 | // uint32_t output_rows_in_pixels = 0, int channel0 = -1, int channel1 = -1) const; 371 | // 372 | // static void write_opaque_alpha_blocks( 373 | // uint32_t num_blocks_x, uint32_t num_blocks_y, 374 | // void* pOutput_blocks, block_format fmt, 375 | // uint32_t block_stride_in_bytes, uint32_t output_row_pitch_in_blocks_or_pixels); 376 | 377 | 378 | // 379 | // Global functions 380 | // 381 | 382 | // basisu_transcoder_init() must be called before a .basis file can be transcoded. 383 | void basisu_transcoder_init() { 384 | basist::basisu_transcoder_init(); 385 | } 386 | 387 | basist::debug_flags_t get_debug_flags() { 388 | return (basist::debug_flags_t) basist::get_debug_flags(); 389 | } 390 | 391 | void set_debug_flags(basist::debug_flags_t f) { 392 | basist::set_debug_flags(f); 393 | } 394 | } 395 | -------------------------------------------------------------------------------- /basis-universal/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "basis-universal" 3 | version = "0.3.1" 4 | authors = ["Philip Degarmo "] 5 | edition = "2018" 6 | license = "MIT OR Apache-2.0" 7 | readme = "../README.md" 8 | repository = "https://github.com/aclysma/basis-universal-rs" 9 | homepage = "https://github.com/aclysma/basis-universal-rs" 10 | description = "Bindings for the basis-universal Supercompressed GPU Texture Codec by Binomial" 11 | keywords = ["game", "basis-universal", "texture", "compression", "gpu"] 12 | categories = ["game-development", "graphics", "api-bindings", "compression", "encoding"] 13 | 14 | [dependencies] 15 | basis-universal-sys = { version = "0.3.1", path = "../basis-universal-sys" } 16 | lazy_static = "1.4.0" 17 | bitflags = "1.2.1" 18 | 19 | [dev-dependencies] 20 | image = "0.23.13" 21 | lz4 = "1.23" 22 | -------------------------------------------------------------------------------- /basis-universal/examples/benchmark.rs: -------------------------------------------------------------------------------- 1 | use basis_universal::{ 2 | BasisTextureFormat, Compressor, CompressorParams, TranscodeParameters, Transcoder, 3 | TranscoderTextureFormat, 4 | }; 5 | use image::{DynamicImage, GenericImageView}; 6 | use std::io::Write; 7 | 8 | // This is not a proper benchmark, just a quick program for feeling out how options affect 9 | // encode/transcode time in a rough, orders-of-magnitude way. (I suggest changing the texture to 10 | // something larger). 11 | 12 | pub fn main() { 13 | // 14 | // Read the PNG file from disk 15 | // 16 | let source_file = include_bytes!("../test_assets/rust-logo-256x256.png"); 17 | let source_file_format = image::ImageFormat::Png; 18 | 19 | let t0 = std::time::Instant::now(); 20 | let image_data = image::load_from_memory_with_format(source_file, source_file_format).unwrap(); 21 | let t1 = std::time::Instant::now(); 22 | 23 | let source_file_size = source_file.len(); 24 | let source_file_decode_time = (t1 - t0).as_secs_f64() * 1000.0; 25 | let uncompressed_memory_size = image_data.as_bytes().len(); 26 | 27 | println!( 28 | "Using {:?} file as source, decoded {} bytes in {} ms", 29 | source_file_format, source_file_size, source_file_decode_time 30 | ); 31 | 32 | // We need to know how many color channels are in the image 33 | use image::ColorType; 34 | let channel_count = match &image_data.color() { 35 | ColorType::L8 => 1, 36 | ColorType::La8 => 2, 37 | ColorType::Rgb8 => 3, 38 | ColorType::Rgba8 => 4, 39 | ColorType::L16 => 1, 40 | ColorType::La16 => 2, 41 | ColorType::Rgb16 => 3, 42 | ColorType::Rgba16 => 4, 43 | ColorType::Bgr8 => 3, 44 | ColorType::Bgra8 => 4, 45 | _ => unimplemented!(), 46 | }; 47 | 48 | let compression_tests = vec![ 49 | // (BasisTextureFormat::ETC1S, basis_universal::ETC1S_QUALITY_MIN, None), 50 | // (BasisTextureFormat::ETC1S, basis_universal::ETC1S_QUALITY_DEFAULT, None), 51 | // (BasisTextureFormat::ETC1S, basis_universal::ETC1S_QUALITY_MAX, None), 52 | // (BasisTextureFormat::UASTC4x4, basis_universal::UASTC_QUALITY_MIN, None), 53 | // (BasisTextureFormat::UASTC4x4, basis_universal::UASTC_QUALITY_DEFAULT, None), 54 | // (BasisTextureFormat::UASTC4x4, basis_universal::sys::UastcPackFlags_PackUASTCLevelSlower, None), 55 | // (BasisTextureFormat::UASTC4x4, basis_universal::UASTC_QUALITY_MIN, Some(0.5)), 56 | // (BasisTextureFormat::UASTC4x4, basis_universal::UASTC_QUALITY_DEFAULT, Some(0.5)), 57 | // (BasisTextureFormat::UASTC4x4, basis_universal::sys::UastcPackFlags_PackUASTCLevelSlower, Some(0.5)), 58 | // (BasisTextureFormat::UASTC4x4, basis_universal::UASTC_QUALITY_MIN, Some(1.0)), 59 | // (BasisTextureFormat::UASTC4x4, basis_universal::UASTC_QUALITY_DEFAULT, Some(1.0)), 60 | // (BasisTextureFormat::UASTC4x4, basis_universal::sys::UastcPackFlags_PackUASTCLevelSlower, Some(1.0)), 61 | // (BasisTextureFormat::UASTC4x4, basis_universal::UASTC_QUALITY_MIN, Some(4.0)), 62 | // (BasisTextureFormat::UASTC4x4, basis_universal::UASTC_QUALITY_DEFAULT, Some(4.0)), 63 | // (BasisTextureFormat::UASTC4x4, basis_universal::sys::UastcPackFlags_PackUASTCLevelSlower, Some(4.0)), 64 | ]; 65 | 66 | let compressor_thread_count = 1; 67 | 68 | println!("source_file_size: {} KB", source_file_size / 1024); 69 | println!("source_file_decode_time: {} ms", source_file_decode_time); 70 | println!( 71 | "uncompressed_memory_size: {} KB", 72 | uncompressed_memory_size / 1024 73 | ); 74 | println!( 75 | "size: {}x{} channels: {}", 76 | image_data.width(), 77 | image_data.height(), 78 | channel_count 79 | ); 80 | for (format, quality, rdo_scalar) in compression_tests { 81 | benchmark_encode( 82 | &image_data, 83 | channel_count, 84 | format, 85 | quality, 86 | rdo_scalar, 87 | compressor_thread_count, 88 | ); 89 | } 90 | 91 | let transcode_tests = vec![ 92 | ( 93 | BasisTextureFormat::ETC1S, 94 | basis_universal::ETC1S_QUALITY_MIN, 95 | None, 96 | ), 97 | ( 98 | BasisTextureFormat::ETC1S, 99 | basis_universal::ETC1S_QUALITY_DEFAULT, 100 | None, 101 | ), 102 | ( 103 | BasisTextureFormat::UASTC4x4, 104 | basis_universal::UASTC_QUALITY_MIN, 105 | None, 106 | ), 107 | ( 108 | BasisTextureFormat::UASTC4x4, 109 | basis_universal::UASTC_QUALITY_DEFAULT, 110 | None, 111 | ), 112 | ]; 113 | 114 | for (format, quality, rdo_scalar) in transcode_tests { 115 | benchmark_transcode( 116 | &image_data, 117 | channel_count, 118 | format, 119 | quality, 120 | rdo_scalar, 121 | compressor_thread_count, 122 | ); 123 | } 124 | } 125 | 126 | fn benchmark_encode( 127 | image_data: &DynamicImage, 128 | channel_count: u8, 129 | basis_texture_format: BasisTextureFormat, 130 | quality: u32, 131 | rdo_scalar: Option, 132 | compressor_thread_count: u32, 133 | ) { 134 | let mut compressor_params = CompressorParams::new(); 135 | compressor_params.set_generate_mipmaps(false); 136 | compressor_params.set_basis_format(basis_texture_format); 137 | compressor_params.set_rdo_uastc(rdo_scalar); 138 | 139 | match basis_texture_format { 140 | BasisTextureFormat::ETC1S => compressor_params.set_etc1s_quality_level(quality), 141 | BasisTextureFormat::UASTC4x4 => compressor_params.set_uastc_quality_level(quality), 142 | } 143 | 144 | compressor_params.set_print_status_to_stdout(false); 145 | 146 | // 147 | // Set the source image in the params 148 | // 149 | let mut compressor_image = compressor_params.source_image_mut(0); 150 | compressor_image.init( 151 | image_data.as_bytes(), 152 | image_data.width(), 153 | image_data.height(), 154 | channel_count, 155 | ); 156 | 157 | // 158 | // Create the compressor and compress 159 | // 160 | let mut compressor = Compressor::new(compressor_thread_count); 161 | let compression_time = unsafe { 162 | compressor.init(&compressor_params); 163 | let t0 = std::time::Instant::now(); 164 | compressor.process().unwrap(); 165 | let t1 = std::time::Instant::now(); 166 | t1 - t0 167 | }; 168 | 169 | println!( 170 | "Compression time for format {:?} quality: {} rdo: {:?} compressor thread count: {} {}ms", 171 | basis_texture_format, 172 | quality, 173 | rdo_scalar, 174 | compressor_thread_count, 175 | compression_time.as_secs_f32() * 1000.0 176 | ); 177 | 178 | println!( 179 | " basis compressed size: {} KB", 180 | compressor.basis_file_size() / 1024 181 | ); 182 | 183 | // LZ4 compression is recommended for UASTC4x4 files 184 | if basis_texture_format == BasisTextureFormat::UASTC4x4 { 185 | let mut encoder = lz4::EncoderBuilder::new().build(Vec::new()).unwrap(); 186 | encoder.write(compressor.basis_file()).unwrap(); 187 | let (lz4_compressed, result) = encoder.finish(); 188 | result.unwrap(); 189 | println!(" lz4 compressed size: {} KB", lz4_compressed.len() / 1024); 190 | 191 | let t0 = std::time::Instant::now(); 192 | let decoder = lz4::Decoder::new(&*lz4_compressed).unwrap(); 193 | let (_, result) = decoder.finish(); 194 | result.unwrap(); 195 | let t1 = std::time::Instant::now(); 196 | 197 | println!( 198 | " Decompression time: {}ms", 199 | (t1 - t0).as_secs_f32() * 1000.0 200 | ); 201 | } 202 | } 203 | 204 | fn benchmark_transcode( 205 | image_data: &DynamicImage, 206 | channel_count: u8, 207 | basis_texture_format: BasisTextureFormat, 208 | quality: u32, 209 | rdo_scalar: Option, 210 | compressor_thread_count: u32, 211 | ) { 212 | let mut compressor_params = CompressorParams::new(); 213 | compressor_params.set_generate_mipmaps(false); 214 | compressor_params.set_basis_format(basis_texture_format); 215 | compressor_params.set_rdo_uastc(rdo_scalar); 216 | 217 | match basis_texture_format { 218 | BasisTextureFormat::ETC1S => compressor_params.set_etc1s_quality_level(quality), 219 | BasisTextureFormat::UASTC4x4 => compressor_params.set_uastc_quality_level(quality), 220 | } 221 | 222 | compressor_params.set_print_status_to_stdout(false); 223 | 224 | // 225 | // Set the source image in the params 226 | // 227 | let mut compressor_image = compressor_params.source_image_mut(0); 228 | compressor_image.init( 229 | image_data.as_bytes(), 230 | image_data.width(), 231 | image_data.height(), 232 | channel_count, 233 | ); 234 | 235 | // 236 | // Create the compressor and compress 237 | // 238 | let mut compressor = Compressor::new(compressor_thread_count); 239 | let compression_time = unsafe { 240 | compressor.init(&compressor_params); 241 | let t0 = std::time::Instant::now(); 242 | compressor.process().unwrap(); 243 | let t1 = std::time::Instant::now(); 244 | t1 - t0 245 | }; 246 | 247 | println!( 248 | "Transcode test for format {:?} quality: {} rdo: {:?} compressor thread count: {} compression time: {}ms Compressed size: {} KB", 249 | basis_texture_format, 250 | quality, 251 | rdo_scalar, 252 | compressor_thread_count, 253 | compression_time.as_secs_f32() * 1000.0, 254 | compressor.basis_file_size() / 1024 255 | ); 256 | 257 | let mut transcoder = Transcoder::new(); 258 | 259 | let transcode_formats = vec![ 260 | TranscoderTextureFormat::ETC1_RGB, 261 | TranscoderTextureFormat::BC1_RGB, 262 | TranscoderTextureFormat::BC7_RGBA, 263 | TranscoderTextureFormat::ASTC_4x4_RGBA, 264 | TranscoderTextureFormat::RGBA32, 265 | ]; 266 | 267 | for transcode_format in transcode_formats { 268 | // 269 | // Now lets transcode it back to raw images 270 | // 271 | let t0 = std::time::Instant::now(); 272 | transcoder 273 | .prepare_transcoding(compressor.basis_file()) 274 | .unwrap(); 275 | 276 | let result = transcoder 277 | .transcode_image_level( 278 | compressor.basis_file(), 279 | transcode_format, 280 | TranscodeParameters { 281 | image_index: 0, 282 | level_index: 0, 283 | ..Default::default() 284 | }, 285 | ) 286 | .unwrap(); 287 | let t1 = std::time::Instant::now(); 288 | 289 | println!( 290 | " Transcoded to {:?}: {} KB in {} ms", 291 | transcode_format, 292 | result.len() / 1024, 293 | (t1 - t0).as_secs_f64() * 1000.0 294 | ); 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /basis-universal/examples/example.rs: -------------------------------------------------------------------------------- 1 | use basis_universal::{ 2 | BasisTextureFormat, Compressor, CompressorParams, TranscodeParameters, Transcoder, 3 | TranscoderTextureFormat, UserData, 4 | }; 5 | use image::GenericImageView; 6 | 7 | // This example: 8 | // - Loads a PNG file 9 | // - Compresses it to basis-universal using UASTC basis format 10 | // - Transcodes the compresses basis format to ASTC_4x4_RGBA and RGBA32 11 | pub fn main() { 12 | // 13 | // Read the PNG file from disk 14 | // 15 | let png_file = include_bytes!("../test_assets/rust-logo-256x256.png"); 16 | 17 | let t0 = std::time::Instant::now(); 18 | let image_data = 19 | image::load_from_memory_with_format(png_file, image::ImageFormat::Png).unwrap(); 20 | let t1 = std::time::Instant::now(); 21 | 22 | println!( 23 | "Using PNG file as source, decoded {} bytes in {} ms", 24 | png_file.len(), 25 | (t1 - t0).as_secs_f64() * 1000.0 26 | ); 27 | 28 | // We need to know how many color channels are in the image 29 | use image::ColorType; 30 | let channel_count = match &image_data.color() { 31 | ColorType::L8 => 1, 32 | ColorType::La8 => 2, 33 | ColorType::Rgb8 => 3, 34 | ColorType::Rgba8 => 4, 35 | ColorType::L16 => 1, 36 | ColorType::La16 => 2, 37 | ColorType::Rgb16 => 3, 38 | ColorType::Rgba16 => 4, 39 | ColorType::Bgr8 => 3, 40 | ColorType::Bgra8 => 4, 41 | _ => unimplemented!(), 42 | }; 43 | 44 | println!( 45 | "Going to encode {}x{} image with {} channels ({} uncompressed bytes)", 46 | image_data.width(), 47 | image_data.height(), 48 | channel_count, 49 | image_data.as_bytes().len() 50 | ); 51 | 52 | // 53 | // Configure the compressor.. parameters chosen randomly for demonstration purposes 54 | // 55 | let mut compressor_params = CompressorParams::new(); 56 | compressor_params.set_generate_mipmaps(true); 57 | compressor_params.set_basis_format(BasisTextureFormat::UASTC4x4); 58 | compressor_params.set_uastc_quality_level(basis_universal::UASTC_QUALITY_DEFAULT); 59 | compressor_params.set_print_status_to_stdout(false); 60 | let userdata = UserData { 61 | userdata0: 100, 62 | userdata1: 200, 63 | }; 64 | println!("Set userdata {:?}", userdata); 65 | compressor_params.set_userdata(userdata); 66 | 67 | // 68 | // Set the source image in the params 69 | // 70 | let mut compressor_image = compressor_params.source_image_mut(0); 71 | compressor_image.init( 72 | image_data.as_bytes(), 73 | image_data.width(), 74 | image_data.height(), 75 | channel_count, 76 | ); 77 | 78 | // 79 | // Create the compressor and compress 80 | // 81 | let mut compressor = Compressor::default(); 82 | let compression_time = unsafe { 83 | compressor.init(&compressor_params); 84 | let t0 = std::time::Instant::now(); 85 | compressor.process().unwrap(); 86 | let t1 = std::time::Instant::now(); 87 | t1 - t0 88 | }; 89 | 90 | // You could write it to disk like this 91 | let basis_file = compressor.basis_file(); 92 | // std::fs::write("example_encoded_image.basis", basis_file).unwrap(); 93 | 94 | let mut transcoder = Transcoder::new(); 95 | let mip_level_count = transcoder.image_level_count(basis_file, 0); 96 | println!( 97 | "Compressed {} mip levels to {} total bytes in {} ms", 98 | mip_level_count, 99 | compressor.basis_file_size(), 100 | compression_time.as_secs_f64() * 1000.0 101 | ); 102 | 103 | let userdata = transcoder.user_data(basis_file).unwrap(); 104 | println!("Basis file has user data {:?}", userdata); 105 | 106 | // 107 | // Now lets transcode it back to raw images 108 | // 109 | transcoder.prepare_transcoding(basis_file).unwrap(); 110 | 111 | let t0 = std::time::Instant::now(); 112 | let result = transcoder 113 | .transcode_image_level( 114 | basis_file, 115 | TranscoderTextureFormat::ASTC_4x4_RGBA, 116 | TranscodeParameters { 117 | image_index: 0, 118 | level_index: 0, 119 | ..Default::default() 120 | }, 121 | ) 122 | .unwrap(); 123 | let t1 = std::time::Instant::now(); 124 | 125 | println!( 126 | "Transcoded mip level 0 to ASTC_4x4_RGBA: {} bytes {} ms", 127 | result.len(), 128 | (t1 - t0).as_secs_f64() * 1000.0 129 | ); 130 | 131 | let t0 = std::time::Instant::now(); 132 | let result = transcoder 133 | .transcode_image_level( 134 | basis_file, 135 | TranscoderTextureFormat::RGBA32, 136 | TranscodeParameters { 137 | image_index: 0, 138 | level_index: 0, 139 | ..Default::default() 140 | }, 141 | ) 142 | .unwrap(); 143 | let t1 = std::time::Instant::now(); 144 | 145 | println!( 146 | "Transcoded mip level 0 to RGBA32: {} bytes {} ms", 147 | result.len(), 148 | (t1 - t0).as_secs_f64() * 1000.0 149 | ); 150 | 151 | transcoder.end_transcoding(); 152 | 153 | let description = transcoder 154 | .image_level_description(basis_file, 0, 0) 155 | .unwrap(); 156 | let _image = image::RgbaImage::from_raw( 157 | description.original_width, 158 | description.original_height, 159 | result, 160 | ) 161 | .unwrap(); 162 | // You could write it to disk like this 163 | //_image.save_with_format("example_transcoded_image.png", image::ImageFormat::Png).unwrap(); 164 | } 165 | -------------------------------------------------------------------------------- /basis-universal/src/encoding/compressor.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use basis_universal_sys as sys; 3 | pub use basis_universal_sys::ColorU8; 4 | 5 | /// Error codes that can be returned when encoding basis-universal data with a [Compressor] 6 | #[allow(non_camel_case_types)] 7 | #[derive(Copy, Clone, Debug, PartialEq)] 8 | #[repr(i32)] 9 | pub enum CompressorErrorCode { 10 | cECFailedReadingSourceImages = 11 | sys::basisu_basis_compressor_error_code_cECFailedReadingSourceImages, 12 | cECFailedValidating = sys::basisu_basis_compressor_error_code_cECFailedValidating, 13 | cECFailedEncodeUASTC = sys::basisu_basis_compressor_error_code_cECFailedEncodeUASTC, 14 | cECFailedFrontEnd = sys::basisu_basis_compressor_error_code_cECFailedFrontEnd, 15 | cECFailedFontendExtract = sys::basisu_basis_compressor_error_code_cECFailedFontendExtract, 16 | cECFailedBackend = sys::basisu_basis_compressor_error_code_cECFailedBackend, 17 | cECFailedCreateBasisFile = sys::basisu_basis_compressor_error_code_cECFailedCreateBasisFile, 18 | cECFailedWritingOutput = sys::basisu_basis_compressor_error_code_cECFailedWritingOutput, 19 | cECFailedUASTCRDOPostProcess = 20 | sys::basisu_basis_compressor_error_code_cECFailedUASTCRDOPostProcess, 21 | } 22 | 23 | impl Into for CompressorErrorCode { 24 | fn into(self) -> sys::basisu_basis_compressor_error_code { 25 | self as sys::basisu_basis_compressor_error_code 26 | } 27 | } 28 | 29 | impl From for CompressorErrorCode { 30 | fn from(value: sys::basisu_basis_compressor_error_code) -> Self { 31 | unsafe { std::mem::transmute(value as u32) } 32 | } 33 | } 34 | 35 | /// Used to encode raw image data to basis-universal form 36 | pub struct Compressor(pub *mut sys::Compressor); 37 | 38 | unsafe impl Send for Compressor {} 39 | 40 | impl Compressor { 41 | /// total_thread_count is passed directly to basisu::job_pool 42 | /// total_thread_count is the TOTAL number of job pool threads, including the calling thread! So 2=1 new thread, 3=2 new threads, etc. 43 | /// 44 | /// Call `encoder_init` 45 | pub fn new(total_thread_count: u32) -> Self { 46 | encoder_init(); 47 | unsafe { 48 | assert!(total_thread_count > 0); 49 | Compressor(sys::compressor_new(total_thread_count as _)) 50 | } 51 | } 52 | 53 | /// Configure the compressor to compress images. `CompressorParams` includes both the image data 54 | /// and parameters that affect compression (such as quality or whether mipmaps should be 55 | /// generated) 56 | /// 57 | /// # Safety 58 | /// 59 | /// Passing invalid parameters may cause undefined behavior. (The underlying C++ library does 60 | /// not thoroughly validate parameters) 61 | pub unsafe fn init( 62 | &mut self, 63 | params: &CompressorParams, 64 | ) -> bool { 65 | sys::compressor_init(self.0, params.0) 66 | } 67 | 68 | /// Encodes the images as configured when calling `init()` 69 | /// 70 | /// # Safety 71 | /// 72 | /// Compressing with invalid parameters may cause undefined behavior. (The underlying C++ 73 | /// library does not thoroughly validate parameters) 74 | pub unsafe fn process(&mut self) -> Result<(), CompressorErrorCode> { 75 | let result = sys::compressor_process(self.0); 76 | if result == sys::basisu_basis_compressor_error_code_cECSuccess { 77 | Ok(()) 78 | } else { 79 | Err(result.into()) 80 | } 81 | } 82 | 83 | /// Access the compressed data. May be empty if `process()` was not yet called 84 | pub fn basis_file(&self) -> &[u8] { 85 | unsafe { 86 | let result = sys::compressor_get_output_basis_file(self.0); 87 | std::slice::from_raw_parts(result.pData, result.length as usize) 88 | } 89 | } 90 | 91 | /// Return the size of the encoded basis-universal data 92 | pub fn basis_file_size(&self) -> u32 { 93 | unsafe { sys::compressor_get_basis_file_size(self.0) } 94 | } 95 | 96 | /// Returns the number of bits required per texel 97 | pub fn bits_per_texel(&self) -> f64 { 98 | unsafe { sys::compressor_get_basis_bits_per_texel(self.0) } 99 | } 100 | 101 | /// Returns if any source image has alpha 102 | pub fn any_source_image_has_alpha(&self) -> bool { 103 | unsafe { sys::compressor_get_any_source_image_has_alpha(self.0) } 104 | } 105 | } 106 | 107 | impl Default for Compressor { 108 | fn default() -> Self { 109 | Compressor::new(1) 110 | } 111 | } 112 | 113 | impl Drop for Compressor { 114 | fn drop(&mut self) { 115 | unsafe { 116 | sys::compressor_delete(self.0); 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /basis-universal/src/encoding/compressor_image.rs: -------------------------------------------------------------------------------- 1 | use basis_universal_sys as sys; 2 | pub use basis_universal_sys::ColorU8; 3 | 4 | // foreign_types::foreign_type! { 5 | // /// A Foo. 6 | // pub unsafe type Foo 7 | // : Sync + Send // optional 8 | // { 9 | // type CType = sys::basisu_image; 10 | // fn drop = unimplemented!(); 11 | // } 12 | // } 13 | 14 | /// A reference to an image being stored by [CompressorParams](super::CompressorParams). Generally 15 | /// used to insert the source data that is to be encoded by a [Compressor](super::Compressor). 16 | pub struct CompressorImageRef(pub *mut sys::basisu_image); 17 | 18 | impl CompressorImageRef { 19 | /// Sets the image to be completely empty (i.e. 0 width, 0 height). (This was called `clear` in 20 | /// the upstream API.) 21 | pub fn invalidate(&mut self) { 22 | unsafe { 23 | sys::image_clear(self.0); 24 | } 25 | } 26 | 27 | /// Resizes the image to the given width/height 28 | /// 29 | /// By default the pitch will be equal to the width. To customize this, use `resize_with_pitch` 30 | pub fn resize( 31 | &mut self, 32 | width: u32, 33 | height: u32, 34 | ) { 35 | unsafe { 36 | sys::image_resize(self.0, width, height); 37 | } 38 | } 39 | 40 | /// Resize the image to the given width/height with a custom "pitch". The pitch is the 41 | /// offset between rows and is not needed for all formats. By default, the pitch will be equal 42 | /// to the width 43 | pub fn resize_with_pitch( 44 | &mut self, 45 | width: u32, 46 | height: u32, 47 | pitch: u32, 48 | ) { 49 | unsafe { 50 | sys::image_resize_with_pitch(self.0, width, height, pitch); 51 | } 52 | } 53 | 54 | /// Resize the image and populate it with the given data. 55 | /// 56 | /// channel_count should be the number of channels in the image (so >=1 and <= 4) 57 | pub fn init( 58 | &mut self, 59 | data: &[u8], 60 | width: u32, 61 | height: u32, 62 | channel_count: u8, 63 | ) { 64 | unsafe { 65 | sys::image_init(self.0, data.as_ptr(), width, height, channel_count as _); 66 | } 67 | } 68 | 69 | /// Returns the pixel value at a given x,y 70 | pub fn pixel_at( 71 | &self, 72 | width: u32, 73 | height: u32, 74 | ) -> Option { 75 | unsafe { 76 | let mut color = ColorU8 { combined: 0 }; 77 | 78 | if sys::image_get_pixel_at_checked(self.0, width, height, &mut color as *mut _) { 79 | Some(color) 80 | } else { 81 | None 82 | } 83 | } 84 | } 85 | 86 | /// Returns teh pixel value at a given x,y without doing bounds checking 87 | /// 88 | /// # Safety 89 | /// 90 | /// Accessing pixel out of bounds of the image will result in undefined behavior 91 | pub unsafe fn pixel_at_unchecked( 92 | &self, 93 | width: u32, 94 | height: u32, 95 | ) -> ColorU8 { 96 | sys::image_get_pixel_at_unchecked(self.0, width, height) 97 | } 98 | 99 | /// Returns the width of the image in pixels 100 | pub fn width(&self) -> u32 { 101 | unsafe { sys::image_get_width(self.0) } 102 | } 103 | 104 | /// Returns the height of the image in pixels 105 | pub fn height(&self) -> u32 { 106 | unsafe { sys::image_get_height(self.0) } 107 | } 108 | 109 | /// Returns the pitch of the image in pixels, which represents the offset between rows 110 | pub fn pitch(&self) -> u32 { 111 | unsafe { sys::image_get_pitch(self.0) } 112 | } 113 | 114 | /// Returns the total number of pixels in the image 115 | pub fn total_pixels(&self) -> u32 { 116 | unsafe { sys::image_get_total_pixels(self.0) } 117 | } 118 | 119 | /// Returns how many blocks wide the image is, given `w`, the width of a block in pixels 120 | pub fn block_width( 121 | &self, 122 | w: u32, 123 | ) -> u32 { 124 | unsafe { sys::image_get_block_width(self.0, w) } 125 | } 126 | 127 | /// Returns how many blocks high the image is, given `h`, the height of a block in pixels 128 | pub fn block_height( 129 | &self, 130 | h: u32, 131 | ) -> u32 { 132 | unsafe { sys::image_get_block_height(self.0, h) } 133 | } 134 | 135 | /// Returns the number of blocks required to store the image, given `w` and `h`, the width and 136 | /// height of a block in pixels 137 | pub fn total_blocks( 138 | &self, 139 | w: u32, 140 | h: u32, 141 | ) -> u32 { 142 | unsafe { sys::image_get_total_blocks(self.0, w, h) } 143 | } 144 | 145 | /// Returns a mutable reference to the pixel data as a slice of [ColorU8] 146 | pub fn pixel_data_mut(&mut self) -> &mut [ColorU8] { 147 | unsafe { 148 | let data = sys::image_get_pixel_data(self.0); 149 | std::slice::from_raw_parts_mut(data.pData, data.length as usize) 150 | } 151 | } 152 | 153 | /// Returns a mutable reference to the pixel data as a slice of u8 154 | pub fn pixel_data_u8_mut(&mut self) -> &mut [u8] { 155 | unsafe { 156 | let data = sys::image_get_pixel_data(self.0); 157 | std::slice::from_raw_parts_mut( 158 | data.pData as *mut u8, 159 | data.length as usize * std::mem::size_of::(), 160 | ) 161 | } 162 | } 163 | 164 | /// Returns a mutable reference to the pixel data as a slice of u32 165 | pub fn pixel_data_u32_mut(&mut self) -> &mut [u32] { 166 | debug_assert_eq!(std::mem::size_of::(), std::mem::size_of::()); 167 | unsafe { 168 | let data = sys::image_get_pixel_data(self.0); 169 | std::slice::from_raw_parts_mut(data.pData as *mut u32, data.length as usize) 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /basis-universal/src/encoding/compressor_params.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::{BasisTextureFormat, UserData}; 3 | use basis_universal_sys as sys; 4 | pub use basis_universal_sys::ColorU8; 5 | 6 | /// The color space the image to be compressed is encoded in. Using the correct color space will 7 | #[derive(Debug, Copy, Clone)] 8 | pub enum ColorSpace { 9 | /// Used for normal maps or other "data" images 10 | Linear, 11 | 12 | /// Used for color maps and other "visual" images 13 | Srgb, 14 | } 15 | 16 | /// Parameters that are used to configure a [Compressor] 17 | pub struct CompressorParams(pub *mut sys::CompressorParams); 18 | 19 | impl Default for CompressorParams { 20 | fn default() -> Self { 21 | Self::new() 22 | } 23 | } 24 | 25 | impl CompressorParams { 26 | /// Create a compressor with default options 27 | pub fn new() -> Self { 28 | unsafe { 29 | let mut params = CompressorParams(sys::compressor_params_new()); 30 | params.set_default_options(); 31 | params 32 | } 33 | } 34 | 35 | /// Resets the compressor params to default state 36 | pub fn reset(&mut self) { 37 | unsafe { 38 | sys::compressor_params_clear(self.0); 39 | self.set_default_options(); 40 | self.clear_source_image_list(); 41 | } 42 | } 43 | 44 | // The default options that are applied when creating a new compressor or calling reset() on it 45 | fn set_default_options(&mut self) { 46 | // Set a default quality level. Leaving this unset results in undefined behavior, so we set 47 | // it to a working value by default 48 | self.set_etc1s_quality_level(crate::ETC1S_QUALITY_DEFAULT); 49 | self.set_uastc_quality_level(crate::UASTC_QUALITY_DEFAULT); 50 | 51 | // The library by default prints to stdout, but since this is a library we should disable 52 | // that by default 53 | self.set_print_status_to_stdout(false); 54 | } 55 | 56 | // 57 | // These function are used to load image data into the compressor 58 | // 59 | 60 | /// Get a reference to the source index. The internal list of source images is resized as needed 61 | /// such that the image will exist 62 | pub fn source_image_mut( 63 | &mut self, 64 | image_index: u32, 65 | ) -> CompressorImageRef { 66 | unsafe { 67 | CompressorImageRef(sys::compressor_params_get_or_create_source_image( 68 | self.0, 69 | image_index, 70 | )) 71 | } 72 | } 73 | 74 | /// Resizes the source image list. If the provided length is shorter than the list, the data 75 | /// beyond the provided length is truncated. 76 | pub fn resize_source_image_list( 77 | &mut self, 78 | size: u32, 79 | ) { 80 | unsafe { 81 | sys::compressor_params_resize_source_image_list(self.0, size as _); 82 | } 83 | } 84 | 85 | /// Resets the image list to be zero-length 86 | pub fn clear_source_image_list(&mut self) { 87 | unsafe { 88 | sys::compressor_params_clear_source_image_list(self.0); 89 | } 90 | } 91 | 92 | /// Get a reference to the source index. The internal list of source images is resized as needed 93 | /// such that the image will exist 94 | pub fn source_mipmap_image_mut( 95 | &mut self, 96 | image_index: u32, 97 | level: u32, 98 | ) -> CompressorImageRef { 99 | unsafe { 100 | CompressorImageRef(sys::compressor_params_get_or_create_source_mipmap_image( 101 | self.0, 102 | image_index, 103 | level, 104 | )) 105 | } 106 | } 107 | 108 | /// Resizes the source image list. If the provided length is shorter than the list, the data 109 | /// beyond the provided length is truncated. 110 | pub fn resize_source_mipmap_image_list( 111 | &mut self, 112 | size: u32, 113 | ) { 114 | unsafe { 115 | sys::compressor_params_resize_source_mipmap_image_list(self.0, size as _); 116 | } 117 | } 118 | 119 | /// Resizes the source image list. If the provided length is shorter than the list, the data 120 | /// beyond the provided length is truncated. 121 | pub fn resize_source_mipmap_level_image_list( 122 | &mut self, 123 | level: u32, 124 | size: u32, 125 | ) { 126 | unsafe { 127 | sys::compressor_params_resize_source_mipmap_image_level_list( 128 | self.0, level as _, size as _, 129 | ); 130 | } 131 | } 132 | 133 | /// Resets the image list to be zero-length 134 | pub fn clear_source_mipmap_image_list(&mut self) { 135 | unsafe { 136 | sys::compressor_params_clear_source_mipmap_image_list(self.0); 137 | } 138 | } 139 | // 140 | // These set parameters for compression 141 | // 142 | 143 | /// Enable stdout logging 144 | pub fn set_print_status_to_stdout( 145 | &mut self, 146 | print_status_to_stdout: bool, 147 | ) { 148 | unsafe { sys::compressor_params_set_status_output(self.0, print_status_to_stdout) } 149 | } 150 | 151 | /// Set ETC1S quality level. The value MUST be >= [ETC1S_QUALITY_MIN](crate::ETC1S_QUALITY_MIN) 152 | /// and <= [ETC1S_QUALITY_MAX](crate::ETC1S_QUALITY_MAX). 153 | pub fn set_etc1s_quality_level( 154 | &mut self, 155 | quality_level: u32, 156 | ) { 157 | assert!(quality_level >= crate::ETC1S_QUALITY_MIN); 158 | assert!(quality_level <= crate::ETC1S_QUALITY_MAX); 159 | 160 | unsafe { 161 | sys::compressor_params_set_quality_level(self.0, quality_level as i32); 162 | } 163 | } 164 | 165 | /// Sets UASTC quality level. The value MUST be >= [UASTC_QUALITY_MIN](crate::UASTC_QUALITY_MIN) 166 | /// and <= [UASTC_QUALITY_MAX](crate::UASTC_QUALITY_MAX). 167 | pub fn set_uastc_quality_level( 168 | &mut self, 169 | quality_level: u32, 170 | ) { 171 | assert!(quality_level >= crate::UASTC_QUALITY_MIN); 172 | assert!(quality_level <= crate::UASTC_QUALITY_MAX); 173 | 174 | unsafe { 175 | let mut flags = sys::compressor_params_get_pack_uastc_flags(self.0); 176 | flags |= quality_level as i32; // bindgen reflects constants as signed integers. So even if it doesn't make sense for the quality level to be signed, it has to be. 177 | sys::compressor_params_set_pack_uastc_flags(self.0, flags); 178 | } 179 | } 180 | 181 | /// Set the basis format we will compress to. See basis documentation for details. This 182 | /// corresponds to the -uastc flag in the basisu command line tool and the m_uastc boolean param 183 | /// on `basis_compressor_params` in the original library 184 | /// 185 | /// UASTC encoding result in significantly higher texture quality, but larger files. 186 | pub fn set_basis_format( 187 | &mut self, 188 | basis_format: BasisTextureFormat, 189 | ) { 190 | let is_uastc = match basis_format { 191 | BasisTextureFormat::ETC1S => false, 192 | BasisTextureFormat::UASTC4x4 => true, 193 | }; 194 | 195 | unsafe { 196 | sys::compressor_params_set_uastc(self.0, is_uastc); 197 | } 198 | } 199 | 200 | /// Sets the color space the images to be compressed is encoded in 201 | /// 202 | /// Setting a linear color space will: 203 | /// * Use linear colorspace metrics (instead of the default sRGB) 204 | /// * By default use linear (not sRGB) mipmap filtering 205 | pub fn set_color_space( 206 | &mut self, 207 | color_space: ColorSpace, 208 | ) { 209 | let perceptual = match color_space { 210 | ColorSpace::Linear => false, 211 | ColorSpace::Srgb => true, 212 | }; 213 | unsafe { 214 | sys::compressor_params_set_perceptual(self.0, perceptual); 215 | } 216 | } 217 | 218 | /// Override the mipmap generation color space behavior. This function is not necessary to call 219 | /// if you call [set_color_space] with the correct value. 220 | /// 221 | /// * If the color space is sRGB, convert image to linear before filtering, then back to sRGB 222 | /// * If the color space is linear, we keep the image in linear during mipmap filtering 223 | /// (i.e. do not convert to/from sRGB for filtering purposes) 224 | pub fn set_mip_color_space( 225 | &mut self, 226 | color_space: ColorSpace, 227 | ) { 228 | let mip_srgb = match color_space { 229 | ColorSpace::Linear => false, 230 | ColorSpace::Srgb => true, 231 | }; 232 | unsafe { 233 | sys::compressor_params_set_mip_srgb(self.0, mip_srgb); 234 | } 235 | } 236 | 237 | /// Disable backend's selector rate distortion optimizations (slightly faster, less noisy 238 | /// output, but lower quality per output bit) 239 | pub fn set_no_selector_rdo( 240 | &mut self, 241 | no_selector_rdo: bool, 242 | ) { 243 | unsafe { 244 | sys::compressor_params_set_no_selector_rdo(self.0, no_selector_rdo); 245 | } 246 | } 247 | 248 | /// Disable backend's endpoint rate distortion optimizations (slightly faster, less noisy 249 | /// output, but lower quality per output bit) 250 | pub fn set_no_endpoint_rdo( 251 | &mut self, 252 | no_endpoint_rdo: bool, 253 | ) { 254 | unsafe { 255 | sys::compressor_params_set_no_endpoint_rdo(self.0, no_endpoint_rdo); 256 | } 257 | } 258 | 259 | /// Enable/disable UASTC RDO post-processing and set UASTC RDO quality scalar to X. Lower 260 | /// values=higher quality/larger LZ compressed files, higher values=lower quality/smaller LZ 261 | /// compressed files. Good range to try is [.2-4] 262 | pub fn set_rdo_uastc( 263 | &mut self, 264 | rdo_uastc_quality_scalar: Option, 265 | ) { 266 | unsafe { 267 | match rdo_uastc_quality_scalar { 268 | Some(quality_scalar) => { 269 | sys::compressor_params_set_rdo_uastc(self.0, true); 270 | sys::compressor_params_set_rdo_uastc_quality_scalar(self.0, quality_scalar); 271 | } 272 | None => { 273 | sys::compressor_params_set_rdo_uastc(self.0, false); 274 | } 275 | } 276 | } 277 | } 278 | 279 | /// Generate mipmaps for each source image 280 | /// 281 | /// By default, sRGB textures will be converted from sRGB to linear before mipmap filtering. 282 | /// This can be changed by calling [set_color_space] or [set_mip_color_space] 283 | pub fn set_generate_mipmaps( 284 | &mut self, 285 | generate_mipmaps: bool, 286 | ) { 287 | unsafe { 288 | sys::compressor_params_set_generate_mipmaps(self.0, generate_mipmaps); 289 | } 290 | } 291 | 292 | /// Sets the smallest dimension mipmap that will be generated 293 | pub fn set_mipmap_smallest_dimension( 294 | &mut self, 295 | smallest_dimension: u32, 296 | ) { 297 | unsafe { 298 | sys::compressor_params_set_mip_smallest_dimension(self.0, smallest_dimension as _); 299 | } 300 | } 301 | 302 | /// Set arbitrary userdata to be included with the basis-universal binary data 303 | pub fn set_userdata( 304 | &mut self, 305 | userdata: UserData, 306 | ) { 307 | unsafe { 308 | sys::compressor_params_set_userdata(self.0, userdata.userdata0, userdata.userdata1); 309 | } 310 | } 311 | 312 | /// The `basisu` command line compressor offers a -normal_map parameter that sets several 313 | /// values automatically. This convenience function mimics that parameter. 314 | /// 315 | /// * linear colorspace metrics 316 | /// * linear mipmap filtering 317 | /// * no selector RDO 318 | /// * no sRGB 319 | pub fn tune_for_normal_maps(&mut self) { 320 | //TODO 321 | unsafe { 322 | sys::compressor_params_set_perceptual(self.0, false); 323 | sys::compressor_params_set_mip_srgb(self.0, false); 324 | sys::compressor_params_set_no_selector_rdo(self.0, true); 325 | sys::compressor_params_set_no_endpoint_rdo(self.0, true); 326 | } 327 | } 328 | 329 | // set_multithreaded not implemented here as this is controlled by thread count passed to 330 | // `Compressor::new()` 331 | } 332 | 333 | impl Drop for CompressorParams { 334 | fn drop(&mut self) { 335 | unsafe { 336 | sys::compressor_params_delete(self.0); 337 | } 338 | } 339 | } 340 | -------------------------------------------------------------------------------- /basis-universal/src/encoding/encoding_tests.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::BasisTextureFormat; 3 | use image::GenericImageView; 4 | 5 | #[test] 6 | fn test_new_compressor_params() { 7 | let compressor_params = CompressorParams::new(); 8 | std::mem::drop(compressor_params); 9 | } 10 | 11 | #[test] 12 | fn test_new_compressor() { 13 | let compressor = Compressor::default(); 14 | std::mem::drop(compressor); 15 | } 16 | 17 | #[test] 18 | fn test_compressor_init() { 19 | let compressor_params = CompressorParams::new(); 20 | let mut compressor = Compressor::default(); 21 | unsafe { 22 | compressor.init(&compressor_params); 23 | } 24 | std::mem::drop(compressor); 25 | std::mem::drop(compressor_params); 26 | } 27 | 28 | #[test] 29 | fn test_compressor_params_smoketest_bindings() { 30 | let mut compressor_params = CompressorParams::new(); 31 | 32 | // Call every parameter just to smoketest the bindings 33 | compressor_params.source_image_mut(5); 34 | compressor_params.resize_source_image_list(8); 35 | compressor_params.clear_source_image_list(); 36 | compressor_params.set_print_status_to_stdout(false); 37 | compressor_params.set_etc1s_quality_level(crate::ETC1S_QUALITY_DEFAULT); 38 | compressor_params.set_uastc_quality_level(crate::UASTC_QUALITY_DEFAULT); 39 | compressor_params.set_basis_format(BasisTextureFormat::UASTC4x4); 40 | compressor_params.set_generate_mipmaps(true); 41 | 42 | compressor_params.reset(); 43 | } 44 | 45 | #[test] 46 | fn test_image_smoketest_bindings() { 47 | let mut compressor_params = CompressorParams::new(); 48 | 49 | let mut image = compressor_params.source_image_mut(0); 50 | let color = image.pixel_at(50, 50); 51 | assert!(color.is_none()); 52 | image.resize(100, 100); 53 | let color = image.pixel_at(50, 50); 54 | assert!(color.is_some()); 55 | let _color = unsafe { image.pixel_at_unchecked(50, 50) }; 56 | image.invalidate(); 57 | } 58 | 59 | #[test] 60 | fn test_encode_image() { 61 | // 62 | // Read the PNG file from disk 63 | // 64 | let png_file = include_bytes!("../../test_assets/rust-logo.png"); 65 | let image_data = 66 | image::load_from_memory_with_format(png_file, image::ImageFormat::Png).unwrap(); 67 | 68 | use image::ColorType; 69 | let channel_count = match &image_data.color() { 70 | ColorType::L8 => 1, 71 | ColorType::La8 => 2, 72 | ColorType::Rgb8 => 3, 73 | ColorType::Rgba8 => 4, 74 | ColorType::L16 => 1, 75 | ColorType::La16 => 2, 76 | ColorType::Rgb16 => 3, 77 | ColorType::Rgba16 => 4, 78 | ColorType::Bgr8 => 3, 79 | ColorType::Bgra8 => 4, 80 | _ => unimplemented!(), 81 | }; 82 | 83 | let mut compressor_params = CompressorParams::new(); 84 | compressor_params.set_generate_mipmaps(true); 85 | 86 | // 87 | // Set up the source image in the params 88 | // 89 | let mut compressor_image = compressor_params.source_image_mut(0); 90 | compressor_image.init( 91 | image_data.as_bytes(), 92 | image_data.width(), 93 | image_data.height(), 94 | channel_count, 95 | ); 96 | 97 | // 98 | // Create the compressor 99 | // 100 | let mut compressor = Compressor::default(); 101 | unsafe { 102 | compressor.init(&compressor_params); 103 | } 104 | // Drop explicitly here to verify that borrowing rules allow this and that this doesn't cause a crash 105 | std::mem::drop(compressor_params); 106 | 107 | // 108 | // Do the compression 109 | // 110 | unsafe { 111 | compressor.process().unwrap(); 112 | } 113 | 114 | // By default the test shouldn't write to disk, but this is a quick way to put it on disk to 115 | // check that it works with basisu 116 | let _basis_file = compressor.basis_file(); 117 | //std::fs::write("test_assets/test_encode_image.basis", basis_file).unwrap(); 118 | 119 | std::mem::drop(compressor); 120 | } 121 | -------------------------------------------------------------------------------- /basis-universal/src/encoding/mod.rs: -------------------------------------------------------------------------------- 1 | use basis_universal_sys as sys; 2 | 3 | use std::sync::atomic::{AtomicBool, Ordering}; 4 | use std::sync::Mutex; 5 | 6 | mod compressor_image; 7 | pub use compressor_image::*; 8 | 9 | mod compressor_params; 10 | pub use compressor_params::*; 11 | 12 | mod compressor; 13 | pub use compressor::*; 14 | 15 | /// A single uncompressed pixel value 16 | pub use basis_universal_sys::ColorU8; 17 | 18 | #[cfg(test)] 19 | mod encoding_tests; 20 | 21 | static ENCODER_INIT_CALLED: AtomicBool = AtomicBool::new(false); 22 | lazy_static::lazy_static! { 23 | static ref ENCODER_INIT_LOCK: Mutex<()> = Mutex::default(); 24 | } 25 | 26 | /// The underlying C++ library requires that encoder_init() has been called before a .basis file can 27 | /// be encoded. This function allows a user to do this early in the application explicitly. It is 28 | /// protected by a lock and AtomicBool flag so it is safe and cheap to call multiple times, and 29 | /// correctly handles multiple threads trying to initialize at the same time. 30 | pub fn encoder_init() { 31 | unsafe { 32 | // Early out if it has been initialized 33 | if !ENCODER_INIT_CALLED.load(Ordering::Acquire) { 34 | // Lock and check again to ensure that exactly one thread runs the init code and that 35 | // all other threads wait for it to complete and don't re-run it. 36 | let lock = ENCODER_INIT_LOCK.lock().unwrap(); 37 | if !ENCODER_INIT_CALLED.load(Ordering::Acquire) { 38 | // Run the init code 39 | sys::basisu_encoder_init(); 40 | ENCODER_INIT_CALLED.store(true, Ordering::Release); 41 | } 42 | std::mem::drop(lock); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /basis-universal/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Bindings for Binomial LLC's basis-universal Supercompressed GPU Texture Codec 2 | //! 3 | //! basis-universal functionality can be grouped into two categories: 4 | //! 5 | //! * Encoding: Compresses and encode textures (optionally combining multiple images and mipmap 6 | //! layers in a single file/binary blob) 7 | //! * Transcoding: Unpacks the texture into GPU-friendly compression formats. The final format can 8 | //! be chosen based on what the available GPU hardware can support. 9 | //! 10 | //! Encoding can be done ahead of time using a command line tool in the upstream repository. 11 | //! 12 | //! The encoded data can either be stored as a file or a binary blob. This data can include multiple 13 | //! images, and each image can store multiple levels. This is commonly used for storing cube 14 | //! textures and textures with precomputed mipmaps. This library also supports generating mipmaps 15 | //! for you. 16 | //! 17 | //! Please refer to https://github.com/BinomialLLC/basis_universal for more details. 18 | 19 | /// Support for transcoding basis-universal form to GPU-friendly formats. 20 | pub mod transcoding; 21 | pub use transcoding::*; 22 | 23 | /// Support for compressing raw image data to basis-universal form 24 | pub mod encoding; 25 | pub use encoding::*; 26 | 27 | pub use basis_universal_sys as sys; 28 | 29 | /// Arbitrary data that can be attached to a basis-universal file/binary blob 30 | #[derive(Default, Debug, Copy, Clone)] 31 | pub struct UserData { 32 | pub userdata0: u32, 33 | pub userdata1: u32, 34 | } 35 | 36 | /// The default quality level used if [CompressorParams::set_etc1s_quality_level] is not called 37 | pub const ETC1S_QUALITY_DEFAULT: u32 = sys::basisu_BASISU_DEFAULT_QUALITY as u32; 38 | /// The minimum quality level that can be provided to [CompressorParams::set_etc1s_quality_level] 39 | pub const ETC1S_QUALITY_MIN: u32 = sys::basisu_BASISU_QUALITY_MIN as u32; 40 | /// The maximum quality level that can be provided to [CompressorParams::set_etc1s_quality_level] 41 | pub const ETC1S_QUALITY_MAX: u32 = sys::basisu_BASISU_QUALITY_MAX as u32; 42 | 43 | /// The default quality level used if [CompressorParams::set_uastc_quality_level] is not called 44 | pub const UASTC_QUALITY_DEFAULT: u32 = sys::UastcPackFlags_PackUASTCLevelDefault as u32; 45 | /// The minimum quality level that can be provided to [CompressorParams::set_uastc_quality_level] 46 | pub const UASTC_QUALITY_MIN: u32 = sys::UastcPackFlags_PackUASTCLevelFastest as u32; 47 | /// The maximum quality level that can be provided to [CompressorParams::set_uastc_quality_level] 48 | pub const UASTC_QUALITY_MAX: u32 = sys::UastcPackFlags_PackUASTCLevelVerySlow as u32; 49 | 50 | /// Maximum supported texture dimension 51 | pub const TEXTURE_DIMENSION_MAX: u32 = sys::basisu_BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION as u32; 52 | /// Maximum supported image dimension 53 | pub const IMAGE_DIMENSION_MAX: u32 = sys::basisu_BASISU_MAX_IMAGE_DIMENSION as u32; 54 | -------------------------------------------------------------------------------- /basis-universal/src/transcoding/enums.rs: -------------------------------------------------------------------------------- 1 | use basis_universal_sys as sys; 2 | use std::ffi::CStr; 3 | 4 | /// The type of data stored 5 | #[derive(Copy, Clone, Debug, PartialEq)] 6 | #[repr(i32)] 7 | pub enum BasisTextureType { 8 | /// An arbitrary array of 2D RGB or RGBA images with optional mipmaps, array size = # images, each image may have a different resolution and # of mipmap levels 9 | TextureType2D = sys::basist_basis_texture_type_cBASISTexType2D, 10 | /// An array of 2D RGB or RGBA images with optional mipmaps, array size = # images, each image has the same resolution and mipmap levels 11 | TextureType2DArray = sys::basist_basis_texture_type_cBASISTexType2DArray, 12 | /// an array of cubemap levels, total # of images must be divisable by 6, in X+, X-, Y+, Y-, Z+, Z- order, with optional mipmaps 13 | TextureTypeCubemapArray = sys::basist_basis_texture_type_cBASISTexTypeCubemapArray, 14 | /// An array of 2D video frames, with optional mipmaps, # frames = # images, each image has the same resolution and # of mipmap levels 15 | TextureTypeVideoFrames = sys::basist_basis_texture_type_cBASISTexTypeVideoFrames, 16 | /// A 3D texture with optional mipmaps, Z dimension = # images, each image has the same resolution and # of mipmap levels 17 | TextureTypeVolume = sys::basist_basis_texture_type_cBASISTexTypeVolume, 18 | } 19 | 20 | impl Into for BasisTextureType { 21 | fn into(self) -> sys::basist_basis_texture_type { 22 | self as sys::basist_basis_texture_type 23 | } 24 | } 25 | 26 | impl From for BasisTextureType { 27 | fn from(value: sys::basist_basis_texture_type) -> Self { 28 | unsafe { std::mem::transmute(value as u32) } 29 | } 30 | } 31 | 32 | impl BasisTextureType { 33 | /// Returns the texture type's name in ASCII. 34 | pub fn texture_type_name(self) -> &'static str { 35 | unsafe { 36 | let value = sys::basis_get_texture_type_name(self.into()); 37 | CStr::from_ptr(value).to_str().unwrap() 38 | } 39 | } 40 | } 41 | 42 | /// The compression mode/format to use 43 | #[allow(non_camel_case_types)] 44 | #[derive(Copy, Clone, Debug, PartialEq)] 45 | #[repr(i32)] 46 | pub enum BasisTextureFormat { 47 | /// A lower quality mode which is based off a subset of ETC1 called "ETC1S". Includes built-in 48 | /// data compression 49 | ETC1S = sys::basist_basis_tex_format_cETC1S, 50 | 51 | /// Enable UASTC compression mode instead of the default ETC1S mode. Significantly higher 52 | /// texture quality, but larger files. UASTC supports an optional Rate Distortion Optimization 53 | /// (RDO) post-process stage that conditions the encoded UASTC texture data in the .basis file 54 | /// so it can be more effectively LZ compressed by the end user. 55 | UASTC4x4 = sys::basist_basis_tex_format_cUASTC4x4, 56 | } 57 | 58 | impl Into for BasisTextureFormat { 59 | fn into(self) -> sys::basist_basis_tex_format { 60 | self as sys::basist_basis_tex_format 61 | } 62 | } 63 | 64 | impl From for BasisTextureFormat { 65 | fn from(value: sys::basist_basis_tex_format) -> Self { 66 | unsafe { std::mem::transmute(value as i32) } 67 | } 68 | } 69 | 70 | impl BasisTextureFormat { 71 | /// Returns true if the specified format was enabled at compile time. 72 | pub fn can_transcode_to_format( 73 | self, 74 | transcoder_texture_format: TranscoderTextureFormat, 75 | ) -> bool { 76 | unsafe { sys::basis_is_format_supported(transcoder_texture_format.into(), self.into()) } 77 | } 78 | } 79 | 80 | /// The texture format to transcode basis-universal data into 81 | #[allow(non_camel_case_types)] 82 | #[derive(Copy, Clone, Debug, PartialEq)] 83 | #[repr(i32)] 84 | pub enum TranscoderTextureFormat { 85 | /// Opaque only, returns RGB or alpha data if cDecodeFlagsTranscodeAlphaDataToOpaqueFormats flag is specified 86 | ETC1_RGB = sys::basist_transcoder_texture_format_cTFETC1_RGB, 87 | /// Opaque+alpha, ETC2_EAC_A8 block followed by a ETC1 block, alpha channel will be opaque for opaque .basis files 88 | ETC2_RGBA = sys::basist_transcoder_texture_format_cTFETC2_RGBA, 89 | 90 | // 91 | // BC1-5, BC7 (desktop, some mobile devices) 92 | // 93 | /// Opaque only, no punchthrough alpha support yet, transcodes alpha slice if cDecodeFlagsTranscodeAlphaDataToOpaqueFormats flag is specified 94 | BC1_RGB = sys::basist_transcoder_texture_format_cTFBC1_RGB, 95 | /// Opaque+alpha, BC4 followed by a BC1 block, alpha channel will be opaque for opaque .basis files 96 | BC3_RGBA = sys::basist_transcoder_texture_format_cTFBC3_RGBA, 97 | /// Red only, alpha slice is transcoded to output if cDecodeFlagsTranscodeAlphaDataToOpaqueFormats flag is specified 98 | BC4_R = sys::basist_transcoder_texture_format_cTFBC4_R, 99 | /// XY: Two BC4 blocks, X=R and Y=Alpha, .basis file should have alpha data (if not Y will be all 255's) 100 | BC5_RG = sys::basist_transcoder_texture_format_cTFBC5_RG, 101 | /// RGB or RGBA, mode 5 for ETC1S, modes (1,2,3,5,6,7) for UASTC 102 | BC7_RGBA = sys::basist_transcoder_texture_format_cTFBC7_RGBA, 103 | 104 | // 105 | // PVRTC1 4bpp (mobile, PowerVR devices) 106 | // 107 | /// Opaque only, RGB or alpha if cDecodeFlagsTranscodeAlphaDataToOpaqueFormats flag is specified, nearly lowest quality of any texture format. 108 | PVRTC1_4_RGB = sys::basist_transcoder_texture_format_cTFPVRTC1_4_RGB, 109 | /// Opaque+alpha, most useful for simple opacity maps. If .basis file doesn't have alpha cTFPVRTC1_4_RGB will be used instead. Lowest quality of any supported texture format. 110 | PVRTC1_4_RGBA = sys::basist_transcoder_texture_format_cTFPVRTC1_4_RGBA, 111 | 112 | // 113 | // ASTC (mobile, Intel devices, hopefully all desktop GPU's one day) 114 | // 115 | /// Opaque+alpha, ASTC 4x4, alpha channel will be opaque for opaque .basis files. Transcoder uses RGB/RGBA/L/LA modes, void extent, and up to two ([0,47] and [0,255]) endpoint precisions. 116 | ASTC_4x4_RGBA = sys::basist_transcoder_texture_format_cTFASTC_4x4_RGBA, 117 | 118 | // 119 | // ATC (mobile, Adreno devices, this is a niche format) 120 | // 121 | /// Opaque, RGB or alpha if cDecodeFlagsTranscodeAlphaDataToOpaqueFormats flag is specified. ATI ATC (GL_ATC_RGB_AMD) 122 | ATC_RGB = sys::basist_transcoder_texture_format_cTFATC_RGB, 123 | /// Opaque+alpha, alpha channel will be opaque for opaque .basis files. ATI ATC (GL_ATC_RGBA_INTERPOLATED_ALPHA_AMD) 124 | ATC_RGBA = sys::basist_transcoder_texture_format_cTFATC_RGBA, 125 | 126 | // 127 | // FXT1 (desktop, Intel devices, this is a super obscure format) 128 | // 129 | /// Opaque only, uses exclusively CC_MIXED blocks. Notable for having a 8x4 block size. GL_3DFX_texture_compression_FXT1 is supported on Intel integrated GPU's (such as HD 630). 130 | /// Punch-through alpha is relatively easy to support, but full alpha is harder. This format is only here for completeness so opaque-only is fine for now. 131 | /// See the BASISU_USE_ORIGINAL_3DFX_FXT1_ENCODING macro in basisu_transcoder_internal.h. 132 | FXT1_RGB = sys::basist_transcoder_texture_format_cTFFXT1_RGB, 133 | 134 | /// Opaque-only, almost BC1 quality, much faster to transcode and supports arbitrary texture dimensions (unlike PVRTC1 RGB). 135 | PVRTC2_4_RGB = sys::basist_transcoder_texture_format_cTFPVRTC2_4_RGB, 136 | /// Opaque+alpha, slower to encode than cTFPVRTC2_4_RGB. Premultiplied alpha is highly recommended, otherwise the color channel can leak into the alpha channel on transparent blocks. 137 | PVRTC2_4_RGBA = sys::basist_transcoder_texture_format_cTFPVRTC2_4_RGBA, 138 | 139 | /// R only (ETC2 EAC R11 unsigned) 140 | ETC2_EAC_R11 = sys::basist_transcoder_texture_format_cTFETC2_EAC_R11, 141 | /// RG only (ETC2 EAC RG11 unsigned), R=opaque.r, G=alpha - for tangent space normal maps 142 | ETC2_EAC_RG11 = sys::basist_transcoder_texture_format_cTFETC2_EAC_RG11, 143 | 144 | // 145 | // Uncompressed (raw pixel) formats 146 | // 147 | /// 32bpp RGBA image stored in raster (not block) order in memory, R is first byte, A is last byte. 148 | RGBA32 = sys::basist_transcoder_texture_format_cTFRGBA32, 149 | /// 16bpp RGB image stored in raster (not block) order in memory, R at bit position 11 150 | RGB565 = sys::basist_transcoder_texture_format_cTFRGB565, 151 | /// 16bpp RGB image stored in raster (not block) order in memory, R at bit position 0 152 | BGR565 = sys::basist_transcoder_texture_format_cTFBGR565, 153 | /// 16bpp RGBA image stored in raster (not block) order in memory, R at bit position 12, A at bit position 0 154 | RGBA4444 = sys::basist_transcoder_texture_format_cTFRGBA4444, 155 | } 156 | 157 | impl Into for TranscoderTextureFormat { 158 | fn into(self) -> sys::basist_transcoder_texture_format { 159 | self as sys::basist_transcoder_texture_format 160 | } 161 | } 162 | 163 | impl From for TranscoderTextureFormat { 164 | fn from(value: sys::basist_transcoder_texture_format) -> Self { 165 | unsafe { std::mem::transmute(value as i32) } 166 | } 167 | } 168 | 169 | impl TranscoderTextureFormat { 170 | /// For compressed texture formats, this returns the # of bytes per block. For uncompressed, it returns the # of bytes per pixel. 171 | /// NOTE: Previously, this function was called basis_get_bytes_per_block(), and it always returned 16*bytes_per_pixel for uncompressed formats which was confusing. 172 | pub fn bytes_per_block_or_pixel(self) -> u32 { 173 | unsafe { sys::basis_get_bytes_per_block_or_pixel(self.into()) } 174 | } 175 | 176 | /// Returns format's name in ASCII 177 | pub fn format_name(self) -> &'static str { 178 | unsafe { 179 | let value = sys::basis_get_format_name(self.into()); 180 | CStr::from_ptr(value).to_str().unwrap() 181 | } 182 | } 183 | 184 | /// Returns true if the format supports an alpha channel. 185 | pub fn has_alpha(self) -> bool { 186 | unsafe { sys::basis_transcoder_format_has_alpha(self.into()) } 187 | } 188 | 189 | /// Returns true if the transcoder texture type is a compressed format. 190 | pub fn is_compressed(self) -> bool { 191 | unsafe { !sys::basis_transcoder_format_is_uncompressed(self.into()) } 192 | } 193 | 194 | /// Returns the # of bytes per pixel for uncompressed formats, or 0 for block texture formats. 195 | pub fn uncompressed_bytes_per_pixel(self) -> u32 { 196 | unsafe { sys::basis_get_uncompressed_bytes_per_pixel(self.into()) } 197 | } 198 | 199 | /// Returns the block width for the specified texture format, which is currently either 4 or 8 for FXT1. 200 | pub fn block_width(self) -> u32 { 201 | unsafe { sys::basis_get_block_width(self.into()) } 202 | } 203 | 204 | /// Returns the block height for the specified texture format, which is currently always 4. 205 | pub fn block_height(self) -> u32 { 206 | unsafe { sys::basis_get_block_height(self.into()) } 207 | } 208 | 209 | /// Returns true if the specified format was enabled at compile time. 210 | pub fn can_transcode_from_format( 211 | self, 212 | basis_texture_format: BasisTextureFormat, 213 | ) -> bool { 214 | basis_texture_format.can_transcode_to_format(self) 215 | } 216 | 217 | /// Calculate the minimum output buffer required to store transcoded data in blocks for 218 | /// compressed formats and pixels for uncompressed formats 219 | pub fn calculate_minimum_output_buffer_blocks_or_pixels( 220 | self, 221 | original_width: u32, 222 | original_height: u32, 223 | total_slice_blocks: u32, 224 | output_row_pitch_in_blocks_or_pixels: Option, 225 | output_rows_in_pixels: Option, 226 | ) -> u32 { 227 | // Default of 0 is fine for these values 228 | let mut output_row_pitch_in_blocks_or_pixels = 229 | output_row_pitch_in_blocks_or_pixels.unwrap_or(0); 230 | let mut output_rows_in_pixels = output_rows_in_pixels.unwrap_or(0); 231 | 232 | // Derived from implementation of basis_validate_output_buffer_size 233 | let minimum_output_buffer_blocks_or_pixels = if !self.is_compressed() { 234 | // Assume the output buffer is orig_width by orig_height 235 | if output_row_pitch_in_blocks_or_pixels == 0 { 236 | output_row_pitch_in_blocks_or_pixels = original_width; 237 | } 238 | 239 | if output_rows_in_pixels == 0 { 240 | output_rows_in_pixels = original_height; 241 | } 242 | 243 | output_rows_in_pixels * output_row_pitch_in_blocks_or_pixels 244 | } else if self == TranscoderTextureFormat::FXT1_RGB { 245 | let num_blocks_fxt1_x = (original_width + 7) / 8; 246 | let num_blocks_fxt1_y = (original_height + 3) / 4; 247 | num_blocks_fxt1_x * num_blocks_fxt1_y 248 | } else { 249 | total_slice_blocks 250 | }; 251 | 252 | debug_assert!(self.validate_output_buffer_size( 253 | minimum_output_buffer_blocks_or_pixels, 254 | original_width, 255 | original_height, 256 | total_slice_blocks, 257 | Some(output_row_pitch_in_blocks_or_pixels), 258 | Some(output_rows_in_pixels), 259 | )); 260 | 261 | minimum_output_buffer_blocks_or_pixels 262 | } 263 | 264 | /// Calculate the minimum output buffer required to store transcoded data in bytes 265 | pub fn calculate_minimum_output_buffer_bytes( 266 | self, 267 | original_width: u32, 268 | original_height: u32, 269 | total_slice_blocks: u32, 270 | output_row_pitch_in_blocks_or_pixels: Option, 271 | output_rows_in_pixels: Option, 272 | ) -> u32 { 273 | self.calculate_minimum_output_buffer_blocks_or_pixels( 274 | original_width, 275 | original_height, 276 | total_slice_blocks, 277 | output_row_pitch_in_blocks_or_pixels, 278 | output_rows_in_pixels, 279 | ) * self.bytes_per_block_or_pixel() 280 | } 281 | 282 | /// Verify that the buffer size is large enough for the transcoded data 283 | pub fn validate_output_buffer_size( 284 | self, 285 | output_blocks_buf_size_in_blocks_or_pixels: u32, 286 | original_width: u32, 287 | original_height: u32, 288 | total_slice_blocks: u32, 289 | output_row_pitch_in_blocks_or_pixels: Option, 290 | output_rows_in_pixels: Option, 291 | ) -> bool { 292 | unsafe { 293 | sys::basis_validate_output_buffer_size( 294 | self.into(), 295 | output_blocks_buf_size_in_blocks_or_pixels, 296 | original_width, 297 | original_height, 298 | output_row_pitch_in_blocks_or_pixels.unwrap_or(0), 299 | output_rows_in_pixels.unwrap_or(0), 300 | total_slice_blocks, 301 | ) 302 | } 303 | } 304 | } 305 | 306 | bitflags::bitflags! { 307 | /// Flags that affect transcoding 308 | pub struct DecodeFlags: i32 { 309 | /// PVRTC1: decode non-pow2 ETC1S texture level to the next larger power of 2 (not implemented yet, but we're going to support it). Ignored if the slice's dimensions are already a power of 2. 310 | const PVRTC_DECODE_TO_NEXT_POW_2 = sys::basist_basisu_decode_flags_cDecodeFlagsPVRTCDecodeToNextPow2; 311 | 312 | /// When decoding to an opaque texture format, if the basis file has alpha, decode the alpha slice instead of the color slice to the output texture format. 313 | /// This is primarily to allow decoding of textures with alpha to multiple ETC1 textures (one for color, another for alpha). 314 | const TRANSCODE_ALPHA_DATA_TO_OPAQUE_FORMATS = sys::basist_basisu_decode_flags_cDecodeFlagsTranscodeAlphaDataToOpaqueFormats; 315 | 316 | /// Forbid usage of BC1 3 color blocks (we don't support BC1 punchthrough alpha yet). 317 | /// This flag is used internally when decoding to BC3. 318 | const BC1_FORBID_THREE_COLOR_BLOCKS = sys::basist_basisu_decode_flags_cDecodeFlagsBC1ForbidThreeColorBlocks; 319 | 320 | /// The output buffer contains alpha endpoint/selector indices. 321 | /// Used internally when decoding formats like ASTC that require both color and alpha data to be available when transcoding to the output format. 322 | const OUTPUT_HAS_ALPHA_INDICES = sys::basist_basisu_decode_flags_cDecodeFlagsOutputHasAlphaIndices; 323 | 324 | const HIGH_QUALITY = sys::basist_basisu_decode_flags_cDecodeFlagsHighQuality; 325 | } 326 | } 327 | 328 | /// The block format to transcode universal texture data into 329 | #[allow(non_camel_case_types)] 330 | #[derive(Copy, Clone, Debug, PartialEq)] 331 | #[repr(i32)] 332 | pub enum TranscoderBlockFormat { 333 | /// ETC1S RGB 334 | ETC1 = sys::basist_block_format_cETC1, 335 | /// full ETC2 EAC RGBA8 block 336 | ETC2_RGBA = sys::basist_block_format_cETC2_RGBA, 337 | /// DXT1 RGB 338 | BC1 = sys::basist_block_format_cBC1, 339 | /// BC4 block followed by a four color BC1 block 340 | BC3 = sys::basist_block_format_cBC3, 341 | /// DXT5A (alpha block only) 342 | BC4 = sys::basist_block_format_cBC4, 343 | /// two BC4 blocks 344 | BC5 = sys::basist_block_format_cBC5, 345 | /// opaque-only PVRTC1 4bpp 346 | PVRTC1_4_RGB = sys::basist_block_format_cPVRTC1_4_RGB, 347 | /// PVRTC1 4bpp RGBA 348 | PVRTC1_4_RGBA = sys::basist_block_format_cPVRTC1_4_RGBA, 349 | /// Full BC7 block, any mode 350 | BC7 = sys::basist_block_format_cBC7, 351 | /// RGB BC7 mode 5 color (writes an opaque mode 5 block) 352 | BC7_M5_COLOR = sys::basist_block_format_cBC7_M5_COLOR, 353 | /// alpha portion of BC7 mode 5 (cBC7_M5_COLOR output data must have been written to the output buffer first to set the mode/rot fields etc.) 354 | BC7_M5_ALPHA = sys::basist_block_format_cBC7_M5_ALPHA, 355 | /// alpha block of ETC2 EAC (first 8 bytes of the 16-bit ETC2 EAC RGBA format) 356 | ETC2_EAC_A8 = sys::basist_block_format_cETC2_EAC_A8, 357 | /// ASTC 4x4 (either color-only or color+alpha). Note that the transcoder always currently assumes sRGB is not enabled when outputting ASTC 358 | /// data. If you use a sRGB ASTC format you'll get ~1 LSB of additional error, because of the different way ASTC decoders scale 8-bit endpoints to 16-bits during unpacking. 359 | ASTC_4x4 = sys::basist_block_format_cASTC_4x4, 360 | 361 | ATC_RGB = sys::basist_block_format_cATC_RGB, 362 | ATC_RGBA_INTERPOLATED_ALPHA = sys::basist_block_format_cATC_RGBA_INTERPOLATED_ALPHA, 363 | /// Opaque-only, has oddball 8x4 pixel block size 364 | FXT1_RGB = sys::basist_block_format_cFXT1_RGB, 365 | 366 | PVRTC2_4_RGB = sys::basist_block_format_cPVRTC2_4_RGB, 367 | PVRTC2_4_RGBA = sys::basist_block_format_cPVRTC2_4_RGBA, 368 | 369 | ETC2_EAC_R11 = sys::basist_block_format_cETC2_EAC_R11, 370 | ETC2_EAC_RG11 = sys::basist_block_format_cETC2_EAC_RG11, 371 | 372 | /// Used internally: Write 16-bit endpoint and selector indices directly to output (output block must be at least 32-bits) 373 | Indices = sys::basist_block_format_cIndices, 374 | 375 | /// Writes RGB components to 32bpp output pixels 376 | RGB32 = sys::basist_block_format_cRGB32, 377 | /// Writes RGB255 components to 32bpp output pixels 378 | RGBA32 = sys::basist_block_format_cRGBA32, 379 | /// Writes alpha component to 32bpp output pixels 380 | A32 = sys::basist_block_format_cA32, 381 | 382 | RGB565 = sys::basist_block_format_cRGB565, 383 | BGR565 = sys::basist_block_format_cBGR565, 384 | 385 | RGBA4444_COLOR = sys::basist_block_format_cRGBA4444_COLOR, 386 | RGBA4444_ALPHA = sys::basist_block_format_cRGBA4444_ALPHA, 387 | RGBA4444_COLOR_OPAQUE = sys::basist_block_format_cRGBA4444_COLOR_OPAQUE, 388 | RGBA4444 = sys::basist_block_format_cRGBA4444, 389 | } 390 | 391 | impl Into for TranscoderBlockFormat { 392 | fn into(self) -> sys::basist_block_format { 393 | self as sys::basist_block_format 394 | } 395 | } 396 | 397 | impl From for TranscoderBlockFormat { 398 | fn from(value: sys::basist_block_format) -> Self { 399 | unsafe { std::mem::transmute(value as i32) } 400 | } 401 | } 402 | 403 | impl TranscoderBlockFormat { 404 | /// For compressed texture formats, this returns the # of bytes per block. For uncompressed, it returns the # of bytes per pixel. 405 | pub fn bytes_per_block_or_pixel(self) -> u32 { 406 | match self { 407 | TranscoderBlockFormat::ETC1 => 8, 408 | TranscoderBlockFormat::ETC2_RGBA => 16, 409 | TranscoderBlockFormat::BC1 => 8, 410 | TranscoderBlockFormat::BC3 => 16, 411 | TranscoderBlockFormat::BC4 => 8, 412 | TranscoderBlockFormat::BC5 => 16, 413 | TranscoderBlockFormat::PVRTC1_4_RGB => 8, 414 | TranscoderBlockFormat::PVRTC1_4_RGBA => 8, 415 | TranscoderBlockFormat::BC7 => 16, 416 | TranscoderBlockFormat::BC7_M5_COLOR => 16, 417 | TranscoderBlockFormat::BC7_M5_ALPHA => 16, 418 | TranscoderBlockFormat::ETC2_EAC_A8 => 8, 419 | TranscoderBlockFormat::ASTC_4x4 => 16, 420 | TranscoderBlockFormat::ATC_RGB => 8, 421 | TranscoderBlockFormat::ATC_RGBA_INTERPOLATED_ALPHA => 16, 422 | TranscoderBlockFormat::FXT1_RGB => 8, 423 | TranscoderBlockFormat::PVRTC2_4_RGB => 8, 424 | TranscoderBlockFormat::PVRTC2_4_RGBA => 8, 425 | TranscoderBlockFormat::ETC2_EAC_R11 => 8, 426 | TranscoderBlockFormat::ETC2_EAC_RG11 => 16, 427 | TranscoderBlockFormat::Indices => 2, 428 | TranscoderBlockFormat::RGB32 => 4, 429 | TranscoderBlockFormat::RGBA32 => 4, 430 | TranscoderBlockFormat::A32 => 4, 431 | TranscoderBlockFormat::RGB565 => 2, 432 | TranscoderBlockFormat::BGR565 => 2, 433 | TranscoderBlockFormat::RGBA4444_COLOR => 2, 434 | TranscoderBlockFormat::RGBA4444_ALPHA => 2, 435 | TranscoderBlockFormat::RGBA4444_COLOR_OPAQUE => 2, 436 | TranscoderBlockFormat::RGBA4444 => 2, 437 | } 438 | } 439 | 440 | /// Returns format's name in ASCII 441 | pub fn format_name(self) -> &'static str { 442 | unsafe { 443 | let value = sys::basis_get_block_format_name(self.into()); 444 | CStr::from_ptr(value).to_str().unwrap() 445 | } 446 | } 447 | 448 | /// Returns true if the block format is a compressed format. 449 | pub fn is_compressed(self) -> bool { 450 | unsafe { !sys::basis_block_format_is_uncompressed(self.into()) } 451 | } 452 | 453 | /// Returns the block width for the specified texture format, which is currently either 4 or 8 for FXT1. 454 | pub fn block_width(self) -> u32 { 455 | match self { 456 | TranscoderBlockFormat::FXT1_RGB => 8, 457 | _ => 4, 458 | } 459 | } 460 | 461 | /// Returns the block height for the specified texture format, which is currently always 4. 462 | pub fn block_height(self) -> u32 { 463 | 4 464 | } 465 | 466 | /// Calculate the minimum output buffer required to store transcoded data in blocks for 467 | /// compressed formats and pixels for uncompressed formats 468 | pub fn calculate_minimum_output_buffer_blocks_or_pixels( 469 | self, 470 | original_width: u32, 471 | original_height: u32, 472 | total_slice_blocks: u32, 473 | output_row_pitch_in_blocks_or_pixels: Option, 474 | output_rows_in_pixels: Option, 475 | ) -> u32 { 476 | // Default of 0 is fine for these values 477 | let mut output_row_pitch_in_blocks_or_pixels = 478 | output_row_pitch_in_blocks_or_pixels.unwrap_or(0); 479 | let mut output_rows_in_pixels = output_rows_in_pixels.unwrap_or(0); 480 | 481 | // Derived from implementation of basis_validate_output_buffer_size 482 | 483 | if !self.is_compressed() { 484 | // Assume the output buffer is orig_width by orig_height 485 | if output_row_pitch_in_blocks_or_pixels == 0 { 486 | output_row_pitch_in_blocks_or_pixels = original_width; 487 | } 488 | 489 | if output_rows_in_pixels == 0 { 490 | output_rows_in_pixels = original_height; 491 | } 492 | 493 | output_rows_in_pixels * output_row_pitch_in_blocks_or_pixels 494 | } else if self == TranscoderBlockFormat::FXT1_RGB { 495 | let num_blocks_fxt1_x = (original_width + 7) / 8; 496 | let num_blocks_fxt1_y = (original_height + 3) / 4; 497 | num_blocks_fxt1_x * num_blocks_fxt1_y 498 | } else { 499 | total_slice_blocks 500 | } 501 | } 502 | 503 | /// Calculate the minimum output buffer required to store transcoded data in bytes 504 | pub fn calculate_minimum_output_buffer_bytes( 505 | self, 506 | original_width: u32, 507 | original_height: u32, 508 | total_slice_blocks: u32, 509 | output_row_pitch_in_blocks_or_pixels: Option, 510 | output_rows_in_pixels: Option, 511 | ) -> u32 { 512 | self.calculate_minimum_output_buffer_blocks_or_pixels( 513 | original_width, 514 | original_height, 515 | total_slice_blocks, 516 | output_row_pitch_in_blocks_or_pixels, 517 | output_rows_in_pixels, 518 | ) * self.bytes_per_block_or_pixel() 519 | } 520 | } 521 | -------------------------------------------------------------------------------- /basis-universal/src/transcoding/mod.rs: -------------------------------------------------------------------------------- 1 | use basis_universal_sys as sys; 2 | use std::sync::atomic::{AtomicBool, Ordering}; 3 | use std::sync::Mutex; 4 | 5 | mod enums; 6 | pub use enums::*; 7 | 8 | mod transcoder; 9 | pub use transcoder::*; 10 | 11 | #[cfg(test)] 12 | mod transcoding_tests; 13 | 14 | static TRANSCODER_INIT_CALLED: AtomicBool = AtomicBool::new(false); 15 | lazy_static::lazy_static! { 16 | static ref TRANSCODER_INIT_LOCK: Mutex<()> = Mutex::default(); 17 | } 18 | 19 | /// The underlying C++ library requires that transcoder_init() has been called before a .basis file 20 | /// can be encoded. This function allows a user to do this early in the application explicitly. It 21 | /// is protected by a lock and AtomicBool flag so it is safe and cheap to call multiple times, and 22 | /// correctly handles multiple threads trying to initialize at the same time. 23 | pub fn transcoder_init() { 24 | unsafe { 25 | // Early out if it has been initialized 26 | if !TRANSCODER_INIT_CALLED.load(Ordering::Acquire) { 27 | // Lock and check again to ensure that exactly one thread runs the init code and that 28 | // all other threads wait for it to complete and don't re-run it. 29 | let lock = TRANSCODER_INIT_LOCK.lock().unwrap(); 30 | if !TRANSCODER_INIT_CALLED.load(Ordering::Acquire) { 31 | // Run the init code 32 | sys::basisu_encoder_init(); 33 | TRANSCODER_INIT_CALLED.store(true, Ordering::Release); 34 | } 35 | std::mem::drop(lock); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /basis-universal/src/transcoding/transcoder.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::UserData; 3 | use basis_universal_sys as sys; 4 | 5 | /// A transcoder that can convert compressed basis-universal data to compressed GPU formats or raw 6 | /// color data 7 | pub struct Transcoder(*mut sys::Transcoder); 8 | 9 | /// Lightweight description of a mip level on a single image within basis data 10 | #[derive(Default, Debug, Copy, Clone)] 11 | pub struct ImageLevelDescription { 12 | pub original_width: u32, 13 | pub original_height: u32, 14 | pub block_count: u32, 15 | } 16 | 17 | /// Info for an image within basis data 18 | pub type ImageInfo = sys::basist_basisu_image_info; 19 | 20 | /// Info for a mip level of a single image within basis data 21 | pub type ImageLevelInfo = sys::basist_basisu_image_level_info; 22 | 23 | /// Info for the complete basis file 24 | pub type FileInfo = sys::FileInfo; 25 | 26 | /// Extra parameters for transcoding an image 27 | #[derive(Default, Debug, Clone)] 28 | pub struct TranscodeParameters { 29 | /// The image to transcode 30 | pub image_index: u32, 31 | /// The mip level of the image to transcode 32 | pub level_index: u32, 33 | /// Optional flags can affect transcoding in various ways 34 | pub decode_flags: Option, 35 | /// Optional override for row pitch 36 | pub output_row_pitch_in_blocks_or_pixels: Option, 37 | /// Optional override for number of rows to transcode 38 | pub output_rows_in_pixels: Option, 39 | } 40 | 41 | /// Error result from trying to transcode an image 42 | #[derive(Debug, Copy, Clone)] 43 | pub enum TranscodeError { 44 | TranscodeFormatNotSupported, 45 | ImageLevelNotFound, 46 | TranscodeFailed, 47 | } 48 | 49 | impl Default for Transcoder { 50 | fn default() -> Self { 51 | Self::new() 52 | } 53 | } 54 | 55 | impl Transcoder { 56 | /// Create a transcoder 57 | pub fn new() -> Transcoder { 58 | unsafe { Transcoder(sys::transcoder_new()) } 59 | } 60 | 61 | /// Validates the .basis file. This computes a crc16 over the entire file, so it's slow. 62 | pub fn validate_file_checksums( 63 | &self, 64 | data: &[u8], 65 | full_validation: bool, 66 | ) -> bool { 67 | unsafe { 68 | sys::transcoder_validate_file_checksums( 69 | self.0, 70 | data.as_ptr() as _, 71 | data.len() as u32, 72 | full_validation, 73 | ) 74 | } 75 | } 76 | 77 | /// Quick header validation - no crc16 checks. 78 | pub fn validate_header( 79 | &self, 80 | data: &[u8], 81 | ) -> bool { 82 | unsafe { sys::transcoder_validate_header(self.0, data.as_ptr() as _, data.len() as u32) } 83 | } 84 | 85 | /// The type of texture represented by the basis data 86 | pub fn basis_texture_type( 87 | &self, 88 | data: &[u8], 89 | ) -> BasisTextureType { 90 | unsafe { 91 | sys::transcoder_get_texture_type(self.0, data.as_ptr() as _, data.len() as u32).into() 92 | } 93 | } 94 | 95 | /// The basis texture format of the basis data 96 | pub fn basis_texture_format( 97 | &self, 98 | data: &[u8], 99 | ) -> BasisTextureFormat { 100 | unsafe { 101 | sys::transcoder_get_tex_format(self.0, data.as_ptr() as _, data.len() as u32).into() 102 | } 103 | } 104 | 105 | pub fn user_data( 106 | &self, 107 | data: &[u8], 108 | ) -> Result { 109 | let mut userdata = UserData::default(); 110 | let result = unsafe { 111 | sys::transcoder_get_userdata( 112 | self.0, 113 | data.as_ptr() as _, 114 | data.len() as u32, 115 | &mut userdata.userdata0, 116 | &mut userdata.userdata1, 117 | ) 118 | }; 119 | 120 | if result { 121 | Ok(userdata) 122 | } else { 123 | Err(()) 124 | } 125 | } 126 | 127 | /// Number of images in the basis data 128 | pub fn image_count( 129 | &self, 130 | data: &[u8], 131 | ) -> u32 { 132 | unsafe { sys::transcoder_get_total_images(self.0, data.as_ptr() as _, data.len() as u32) } 133 | } 134 | 135 | /// Number of mipmap levels for the specified image in the basis data 136 | pub fn image_level_count( 137 | &self, 138 | data: &[u8], 139 | image_index: u32, 140 | ) -> u32 { 141 | unsafe { 142 | sys::transcoder_get_total_image_levels( 143 | self.0, 144 | data.as_ptr() as _, 145 | data.len() as u32, 146 | image_index, 147 | ) 148 | } 149 | } 150 | 151 | /// Returns basic information about an image. Note that orig_width/orig_height may not be a multiple of 4. 152 | pub fn image_level_description( 153 | &self, 154 | data: &[u8], 155 | image_index: u32, 156 | level_index: u32, 157 | ) -> Option { 158 | let mut description = ImageLevelDescription::default(); 159 | unsafe { 160 | if sys::transcoder_get_image_level_desc( 161 | self.0, 162 | data.as_ptr() as _, 163 | data.len() as u32, 164 | image_index, 165 | level_index, 166 | &mut description.original_width, 167 | &mut description.original_height, 168 | &mut description.block_count, 169 | ) { 170 | Some(description) 171 | } else { 172 | None 173 | } 174 | } 175 | } 176 | 177 | /// Returns information about the specified image. 178 | pub fn image_info( 179 | &self, 180 | data: &[u8], 181 | image_index: u32, 182 | ) -> Option { 183 | let mut image_info = unsafe { std::mem::zeroed::() }; 184 | unsafe { 185 | if sys::transcoder_get_image_info( 186 | self.0, 187 | data.as_ptr() as _, 188 | data.len() as u32, 189 | &mut image_info, 190 | image_index, 191 | ) { 192 | Some(image_info) 193 | } else { 194 | None 195 | } 196 | } 197 | } 198 | 199 | /// Returns information about the specified image's mipmap level. 200 | pub fn image_level_info( 201 | &self, 202 | data: &[u8], 203 | image_index: u32, 204 | level_index: u32, 205 | ) -> Option { 206 | let mut image_level_info = unsafe { std::mem::zeroed::() }; 207 | unsafe { 208 | if sys::transcoder_get_image_level_info( 209 | self.0, 210 | data.as_ptr() as _, 211 | data.len() as u32, 212 | &mut image_level_info, 213 | image_index, 214 | level_index, 215 | ) { 216 | Some(image_level_info) 217 | } else { 218 | None 219 | } 220 | } 221 | } 222 | 223 | /// Get a description of the basis file and low-level information about each slice. 224 | pub fn file_info( 225 | &self, 226 | data: &[u8], 227 | ) -> Option { 228 | let mut file_info = unsafe { std::mem::zeroed::() }; 229 | unsafe { 230 | if sys::transcoder_get_file_info( 231 | self.0, 232 | data.as_ptr() as _, 233 | data.len() as u32, 234 | &mut file_info, 235 | ) { 236 | Some(file_info) 237 | } else { 238 | None 239 | } 240 | } 241 | } 242 | 243 | /// prepare_transcoding() must be called before calling transcode_slice() or transcode_image_level(). 244 | /// This is `start_transcoding` in the original library 245 | /// For ETC1S files, this call decompresses the selector/endpoint codebooks, so ideally you would only call this once per .basis file (not each image/mipmap level). 246 | pub fn prepare_transcoding( 247 | &mut self, 248 | data: &[u8], 249 | ) -> Result<(), ()> { 250 | transcoder_init(); 251 | unsafe { 252 | if sys::transcoder_start_transcoding(self.0, data.as_ptr() as _, data.len() as u32) { 253 | Ok(()) 254 | } else { 255 | Err(()) 256 | } 257 | } 258 | } 259 | 260 | /// Parallel with `prepare_transcoding()`, named `stop_transcoding` in the original library 261 | pub fn end_transcoding(&mut self) { 262 | unsafe { 263 | let result = sys::transcoder_stop_transcoding(self.0); 264 | // I think this function is actually infallible, so don't return a result 265 | debug_assert!(result); 266 | } 267 | } 268 | 269 | /// Returns true if prepare_transcoding() has been called. 270 | pub fn is_prepared_to_transcode(&self) -> bool { 271 | unsafe { sys::transcoder_get_ready_to_transcode(self.0) } 272 | } 273 | 274 | /// transcode_image_level() decodes a single mipmap level from the .basis file to any of the supported output texture formats. 275 | /// It'll first find the slice(s) to transcode, then call transcode_slice() one or two times to decode both the color and alpha texture data (or RG texture data from two slices for BC5). 276 | /// If the .basis file doesn't have alpha slices, the output alpha blocks will be set to fully opaque (all 255's). 277 | /// Currently, to decode to PVRTC1 the basis texture's dimensions in pixels must be a power of 2, due to PVRTC1 format requirements. 278 | /// output_blocks_buf_size_in_blocks_or_pixels should be at least the image level's total_blocks (num_blocks_x * num_blocks_y), or the total number of output pixels if fmt==cTFRGBA32. 279 | /// output_row_pitch_in_blocks_or_pixels: Number of blocks or pixels per row. If 0, the transcoder uses the slice's num_blocks_x or orig_width (NOT num_blocks_x * 4). Ignored for PVRTC1 (due to texture swizzling). 280 | /// output_rows_in_pixels: Ignored unless fmt is cRGBA32. The total number of output rows in the output buffer. If 0, the transcoder assumes the slice's orig_height (NOT num_blocks_y * 4). 281 | /// Notes: 282 | /// - basisu_transcoder_init() must have been called first to initialize the transcoder lookup tables before calling this function. 283 | /// - This method assumes the output texture buffer is readable. In some cases to handle alpha, the transcoder will write temporary data to the output texture in 284 | /// a first pass, which will be read in a second pass. 285 | pub fn transcode_image_level( 286 | &self, 287 | data: &[u8], 288 | transcode_format: TranscoderTextureFormat, 289 | transcode_parameters: TranscodeParameters, 290 | ) -> Result, TranscodeError> { 291 | let image_index = transcode_parameters.image_index; 292 | let level_index = transcode_parameters.level_index; 293 | 294 | // 295 | // Check that the transcode format is supported for the stored texture's basis format 296 | // 297 | let basis_format = self.basis_texture_format(data); 298 | if !basis_format.can_transcode_to_format(transcode_format) { 299 | return Err(TranscodeError::TranscodeFormatNotSupported); 300 | } 301 | 302 | // 303 | // Determine required size for the buffer 304 | // 305 | let description = self 306 | .image_level_description(data, image_index, level_index) 307 | .ok_or(TranscodeError::ImageLevelNotFound)?; 308 | let required_buffer_bytes = transcode_format.calculate_minimum_output_buffer_bytes( 309 | description.original_width, 310 | description.original_height, 311 | description.block_count, 312 | transcode_parameters.output_row_pitch_in_blocks_or_pixels, 313 | transcode_parameters.output_rows_in_pixels, 314 | ) as usize; 315 | 316 | // 317 | // unwrap_or() the optional parameters 318 | // 319 | let decode_flags = transcode_parameters 320 | .decode_flags 321 | .unwrap_or_else(DecodeFlags::empty); 322 | let output_row_pitch_in_blocks_or_pixels = transcode_parameters 323 | .output_row_pitch_in_blocks_or_pixels 324 | .unwrap_or(0); 325 | let output_rows_in_pixels = transcode_parameters.output_rows_in_pixels.unwrap_or(0); 326 | let transcoder_state = std::ptr::null_mut(); 327 | 328 | // 329 | // Transcode 330 | // 331 | let mut output = vec![0_u8; required_buffer_bytes]; 332 | let success = unsafe { 333 | sys::transcoder_transcode_image_level( 334 | self.0, 335 | data.as_ptr() as _, 336 | data.len() as u32, 337 | image_index, 338 | level_index, 339 | output.as_mut_ptr() as _, 340 | output.len() as u32, 341 | transcode_format.into(), 342 | decode_flags.bits(), 343 | output_row_pitch_in_blocks_or_pixels, 344 | transcoder_state, 345 | output_rows_in_pixels, 346 | ) 347 | }; 348 | 349 | if success { 350 | Ok(output) 351 | } else { 352 | Err(TranscodeError::TranscodeFailed) 353 | } 354 | } 355 | 356 | // Not implemented 357 | // 358 | // // Finds the basis slice corresponding to the specified image/level/alpha params, or -1 if the slice can't be found. 359 | // int find_slice(const void *pData, uint32_t data_size, uint32_t image_index, uint32_t level_index, bool alpha_data) const; 360 | // 361 | // // transcode_slice() decodes a single slice from the .basis file. It's a low-level API - most likely you want to use transcode_image_level(). 362 | // // This is a low-level API, and will be needed to be called multiple times to decode some texture formats (like BC3, BC5, or ETC2). 363 | // // output_blocks_buf_size_in_blocks_or_pixels is just used for verification to make sure the output buffer is large enough. 364 | // // output_blocks_buf_size_in_blocks_or_pixels should be at least the image level's total_blocks (num_blocks_x * num_blocks_y), or the total number of output pixels if fmt==cTFRGBA32. 365 | // // output_block_stride_in_bytes: Number of bytes between each output block. 366 | // // output_row_pitch_in_blocks_or_pixels: Number of blocks or pixels per row. If 0, the transcoder uses the slice's num_blocks_x or orig_width (NOT num_blocks_x * 4). Ignored for PVRTC1 (due to texture swizzling). 367 | // // output_rows_in_pixels: Ignored unless fmt is cRGBA32. The total number of output rows in the output buffer. If 0, the transcoder assumes the slice's orig_height (NOT num_blocks_y * 4). 368 | // // Notes: 369 | // // - basisu_transcoder_init() must have been called first to initialize the transcoder lookup tables before calling this function. 370 | // bool transcode_slice(const void *pData, uint32_t data_size, uint32_t slice_index, 371 | // void *pOutput_blocks, uint32_t output_blocks_buf_size_in_blocks_or_pixels, 372 | // block_format fmt, uint32_t output_block_stride_in_bytes, uint32_t decode_flags = 0, uint32_t output_row_pitch_in_blocks_or_pixels = 0, basisu_transcoder_state * pState = nullptr, void* pAlpha_blocks = nullptr, 373 | // uint32_t output_rows_in_pixels = 0, int channel0 = -1, int channel1 = -1) const; 374 | // 375 | // static void write_opaque_alpha_blocks( 376 | // uint32_t num_blocks_x, uint32_t num_blocks_y, 377 | // void* pOutput_blocks, block_format fmt, 378 | // uint32_t block_stride_in_bytes, uint32_t output_row_pitch_in_blocks_or_pixels); 379 | } 380 | 381 | impl Drop for Transcoder { 382 | fn drop(&mut self) { 383 | unsafe { 384 | sys::transcoder_delete(self.0); 385 | } 386 | } 387 | } 388 | 389 | pub struct LowLevelUastcTranscoder(*mut sys::LowLevelUastcTranscoder); 390 | 391 | impl Default for LowLevelUastcTranscoder { 392 | fn default() -> Self { 393 | Self::new() 394 | } 395 | } 396 | 397 | #[derive(Debug)] 398 | pub struct SliceParametersUastc { 399 | pub num_blocks_x: u32, 400 | pub num_blocks_y: u32, 401 | pub has_alpha: bool, 402 | pub original_width: u32, 403 | pub original_height: u32, 404 | } 405 | 406 | impl LowLevelUastcTranscoder { 407 | /// Create a LowLevelUastcTranscoder 408 | pub fn new() -> LowLevelUastcTranscoder { 409 | transcoder_init(); 410 | unsafe { LowLevelUastcTranscoder(sys::low_level_uastc_transcoder_new()) } 411 | } 412 | 413 | pub fn transcode_slice( 414 | &self, 415 | data: &[u8], 416 | slice_parameters: SliceParametersUastc, 417 | decode_flags: DecodeFlags, 418 | transcode_block_format: TranscoderBlockFormat, 419 | ) -> Result, TranscodeError> { 420 | let bc1_allow_threecolor_blocks = false; 421 | let transcoder_state = std::ptr::null_mut(); 422 | let channel0 = 0; 423 | let channel1 = 3; 424 | 425 | let output_row_pitch_in_blocks_or_pixels = 426 | (slice_parameters.original_width + transcode_block_format.block_width() - 1) 427 | / transcode_block_format.block_width(); 428 | let output_rows_in_pixels = slice_parameters.original_height; 429 | let total_slice_blocks = slice_parameters.num_blocks_x * slice_parameters.num_blocks_y; 430 | let required_buffer_bytes = transcode_block_format.calculate_minimum_output_buffer_bytes( 431 | slice_parameters.original_width, 432 | slice_parameters.original_height, 433 | total_slice_blocks, 434 | Some(output_row_pitch_in_blocks_or_pixels), 435 | Some(output_rows_in_pixels), 436 | ) as usize; 437 | 438 | let output_block_or_pixel_stride_in_bytes = 439 | transcode_block_format.bytes_per_block_or_pixel(); 440 | 441 | let mut output = vec![0_u8; required_buffer_bytes]; 442 | let success = unsafe { 443 | sys::low_level_uastc_transcoder_transcode_slice( 444 | self.0, 445 | output.as_mut_ptr() as _, 446 | slice_parameters.num_blocks_x, 447 | slice_parameters.num_blocks_y, 448 | data.as_ptr() as _, 449 | data.len() as u32, 450 | transcode_block_format.into(), 451 | output_block_or_pixel_stride_in_bytes, 452 | bc1_allow_threecolor_blocks, 453 | slice_parameters.has_alpha, 454 | slice_parameters.original_width, 455 | slice_parameters.original_height, 456 | output_row_pitch_in_blocks_or_pixels, 457 | transcoder_state, 458 | output_rows_in_pixels, 459 | channel0, 460 | channel1, 461 | decode_flags.bits(), 462 | ) 463 | }; 464 | 465 | if success { 466 | Ok(output) 467 | } else { 468 | Err(TranscodeError::TranscodeFailed) 469 | } 470 | } 471 | } 472 | 473 | impl Drop for LowLevelUastcTranscoder { 474 | fn drop(&mut self) { 475 | unsafe { 476 | sys::low_level_uastc_transcoder_delete(self.0); 477 | } 478 | } 479 | } 480 | -------------------------------------------------------------------------------- /basis-universal/src/transcoding/transcoding_tests.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[test] 4 | fn test_get_bytes_per_block_or_pixel() { 5 | assert_eq!( 6 | TranscoderTextureFormat::BC1_RGB.bytes_per_block_or_pixel(), 7 | 8 8 | ); 9 | } 10 | 11 | #[test] 12 | fn test_get_format_name() { 13 | assert_eq!(TranscoderTextureFormat::BC1_RGB.format_name(), "BC1_RGB"); 14 | } 15 | 16 | #[test] 17 | fn test_transcoder_format_has_alpha() { 18 | assert_eq!(TranscoderTextureFormat::BC1_RGB.has_alpha(), false); 19 | assert_eq!(TranscoderTextureFormat::BC7_RGBA.has_alpha(), true); 20 | } 21 | 22 | #[test] 23 | fn test_get_texture_type_name() { 24 | assert_eq!(BasisTextureType::TextureType2D.texture_type_name(), "2D"); 25 | } 26 | 27 | #[test] 28 | fn test_new_transcoder() { 29 | let transcoder = Transcoder::new(); 30 | std::mem::drop(transcoder); 31 | } 32 | 33 | #[test] 34 | fn test_transcoder_get_total_images() { 35 | let basis_file = include_bytes!("../../test_assets/rust-logo-etc.basis"); 36 | let transcoder = Transcoder::new(); 37 | assert_eq!(transcoder.image_count(basis_file), 1); 38 | std::mem::drop(transcoder); 39 | } 40 | 41 | #[test] 42 | fn test_transcoder_info() { 43 | let basis_file = include_bytes!("../../test_assets/rust-logo-etc.basis"); 44 | let transcoder = Transcoder::new(); 45 | 46 | let file_info = transcoder.file_info(basis_file).unwrap(); 47 | 48 | // These should all return valid results 49 | assert!(transcoder.image_info(basis_file, 0).is_some()); 50 | assert!(transcoder 51 | .image_level_description(basis_file, 0, 0) 52 | .is_some()); 53 | assert!(transcoder.image_level_info(basis_file, 0, 0).is_some()); 54 | 55 | // These return invalid results because we are passing image index > image count 56 | assert!(transcoder 57 | .image_info(basis_file, file_info.m_total_images + 1) 58 | .is_none()); 59 | assert!(transcoder 60 | .image_level_description(basis_file, file_info.m_total_images + 1, 0) 61 | .is_none()); 62 | assert!(transcoder 63 | .image_level_info(basis_file, file_info.m_total_images + 1, 0) 64 | .is_none()); 65 | 66 | // These return invalid results because we are passing level index > level count 67 | assert!(transcoder 68 | .image_level_description(basis_file, 0, 100) 69 | .is_none()); 70 | assert!(transcoder.image_level_info(basis_file, 0, 100).is_none()); 71 | 72 | std::mem::drop(transcoder); 73 | } 74 | 75 | #[test] 76 | fn test_transcoder_get_tex_format() { 77 | let basis_file = include_bytes!("../../test_assets/rust-logo-etc.basis"); 78 | let transcoder = Transcoder::new(); 79 | assert_eq!( 80 | transcoder.basis_texture_format(basis_file), 81 | BasisTextureFormat::ETC1S 82 | ); 83 | std::mem::drop(transcoder); 84 | 85 | let basis_file = include_bytes!("../../test_assets/rust-logo-uastc.basis"); 86 | let transcoder = Transcoder::new(); 87 | assert_eq!( 88 | transcoder.basis_texture_format(basis_file), 89 | BasisTextureFormat::UASTC4x4 90 | ); 91 | std::mem::drop(transcoder); 92 | } 93 | 94 | #[test] 95 | fn test_transcoder_get_total_image_levels() { 96 | let basis_file = include_bytes!("../../test_assets/rust-logo-etc.basis"); 97 | let transcoder = Transcoder::new(); 98 | assert_eq!(transcoder.image_level_count(basis_file, 0), 7); 99 | std::mem::drop(transcoder); 100 | } 101 | 102 | #[test] 103 | fn test_transcoder_transcode_etc() { 104 | let basis_file = include_bytes!("../../test_assets/rust-logo-etc.basis"); 105 | do_test_transcoder_transcode(basis_file, "test_assets/test_transcode_image_etc.png"); 106 | } 107 | 108 | #[test] 109 | fn test_transcoder_transcode_uastc() { 110 | let basis_file = include_bytes!("../../test_assets/rust-logo-uastc.basis"); 111 | do_test_transcoder_transcode(basis_file, "test_assets/test_transcode_image_uastc.png"); 112 | } 113 | 114 | // Transcode to a variety of formats 115 | fn do_test_transcoder_transcode( 116 | basis_file: &[u8], 117 | _out_path: &str, 118 | ) { 119 | let mut transcoder = Transcoder::new(); 120 | transcoder.prepare_transcoding(basis_file).unwrap(); 121 | 122 | transcoder 123 | .transcode_image_level( 124 | basis_file, 125 | TranscoderTextureFormat::ETC1_RGB, 126 | TranscodeParameters { 127 | image_index: 0, 128 | level_index: 0, 129 | ..Default::default() 130 | }, 131 | ) 132 | .unwrap(); 133 | 134 | transcoder 135 | .transcode_image_level( 136 | basis_file, 137 | TranscoderTextureFormat::ASTC_4x4_RGBA, 138 | TranscodeParameters { 139 | image_index: 0, 140 | level_index: 0, 141 | ..Default::default() 142 | }, 143 | ) 144 | .unwrap(); 145 | 146 | if transcoder.basis_texture_format(basis_file) == BasisTextureFormat::ETC1S { 147 | transcoder 148 | .transcode_image_level( 149 | basis_file, 150 | TranscoderTextureFormat::FXT1_RGB, 151 | TranscodeParameters { 152 | image_index: 0, 153 | level_index: 0, 154 | ..Default::default() 155 | }, 156 | ) 157 | .unwrap(); 158 | } 159 | 160 | let _result = transcoder 161 | .transcode_image_level( 162 | basis_file, 163 | TranscoderTextureFormat::RGBA32, 164 | TranscodeParameters { 165 | image_index: 0, 166 | level_index: 0, 167 | ..Default::default() 168 | }, 169 | ) 170 | .unwrap(); 171 | transcoder.end_transcoding(); 172 | 173 | //let description = transcoder.image_level_description(basis_file, 0, 0).unwrap(); 174 | //let image = image::RgbaImage::from_raw(description.original_width, description.original_height, _result).unwrap(); 175 | //image.save_with_format(_out_path, image::ImageFormat::Png).unwrap(); 176 | 177 | std::mem::drop(transcoder); 178 | } 179 | -------------------------------------------------------------------------------- /basis-universal/test_assets/rebuild_assets.sh: -------------------------------------------------------------------------------- 1 | basisu -mipmap rust-logo.png -output_file rust-logo-etc.basis 2 | basisu -mipmap -uastc rust-logo.png -output_file rust-logo-uastc.basis -------------------------------------------------------------------------------- /basis-universal/test_assets/rust-logo-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aclysma/basis-universal-rs/c1b21c1afdfd13b973c7f53ea7a3240f558b9989/basis-universal/test_assets/rust-logo-256x256.png -------------------------------------------------------------------------------- /basis-universal/test_assets/rust-logo-etc.basis: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aclysma/basis-universal-rs/c1b21c1afdfd13b973c7f53ea7a3240f558b9989/basis-universal/test_assets/rust-logo-etc.basis -------------------------------------------------------------------------------- /basis-universal/test_assets/rust-logo-uastc.basis: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aclysma/basis-universal-rs/c1b21c1afdfd13b973c7f53ea7a3240f558b9989/basis-universal/test_assets/rust-logo-uastc.basis -------------------------------------------------------------------------------- /basis-universal/test_assets/rust-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aclysma/basis-universal-rs/c1b21c1afdfd13b973c7f53ea7a3240f558b9989/basis-universal/test_assets/rust-logo.png -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | # This template contains all of the possible sections and their default values 2 | 3 | # Note that all fields that take a lint level have these possible values: 4 | # * deny - An error will be produced and the check will fail 5 | # * warn - A warning will be produced, but the check will not fail 6 | # * allow - No warning or error will be produced, though in some cases a note 7 | # will be 8 | 9 | # The values provided in this template are the default values that will be used 10 | # when any section or field is not specified in your own configuration 11 | 12 | # If 1 or more target triples (and optionally, target_features) are specified, 13 | # only the specified targets will be checked when running `cargo deny check`. 14 | # This means, if a particular package is only ever used as a target specific 15 | # dependency, such as, for example, the `nix` crate only being used via the 16 | # `target_family = "unix"` configuration, that only having windows targets in 17 | # this list would mean the nix crate, as well as any of its exclusive 18 | # dependencies not shared by any other crates, would be ignored, as the target 19 | # list here is effectively saying which targets you are building for. 20 | targets = [ 21 | # The triple can be any string, but only the target triples built in to 22 | # rustc (as of 1.40) can be checked against actual config expressions 23 | #{ triple = "x86_64-unknown-linux-musl" }, 24 | # You can also specify which target_features you promise are enabled for a 25 | # particular target. target_features are currently not validated against 26 | # the actual valid features supported by the target architecture. 27 | #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, 28 | ] 29 | 30 | # This section is considered when running `cargo deny check advisories` 31 | # More documentation for the advisories section can be found here: 32 | # https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html 33 | [advisories] 34 | # The path where the advisory database is cloned/fetched into 35 | db-path = "~/.cargo/advisory-db" 36 | # The url of the advisory database to use 37 | db-urls = ["https://github.com/rustsec/advisory-db"] 38 | # The lint level for security vulnerabilities 39 | vulnerability = "deny" 40 | # The lint level for unmaintained crates 41 | unmaintained = "warn" 42 | # The lint level for crates that have been yanked from their source registry 43 | yanked = "warn" 44 | # The lint level for crates with security notices. Note that as of 45 | # 2019-12-17 there are no security notice advisories in 46 | # https://github.com/rustsec/advisory-db 47 | notice = "warn" 48 | # A list of advisory IDs to ignore. Note that ignored advisories will still 49 | # output a note when they are encountered. 50 | ignore = [ 51 | #"RUSTSEC-0000-0000", 52 | ] 53 | # Threshold for security vulnerabilities, any vulnerability with a CVSS score 54 | # lower than the range specified will be ignored. Note that ignored advisories 55 | # will still output a note when they are encountered. 56 | # * None - CVSS Score 0.0 57 | # * Low - CVSS Score 0.1 - 3.9 58 | # * Medium - CVSS Score 4.0 - 6.9 59 | # * High - CVSS Score 7.0 - 8.9 60 | # * Critical - CVSS Score 9.0 - 10.0 61 | #severity-threshold = 62 | 63 | # This section is considered when running `cargo deny check licenses` 64 | # More documentation for the licenses section can be found here: 65 | # https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html 66 | [licenses] 67 | # The lint level for crates which do not have a detectable license 68 | unlicensed = "deny" 69 | # List of explictly allowed licenses 70 | # See https://spdx.org/licenses/ for list of possible licenses 71 | # [possible values: any SPDX 3.7 short identifier (+ optional exception)]. 72 | allow = [ 73 | "MIT", 74 | "Apache-2.0", 75 | "ISC", 76 | "CC0-1.0", 77 | "BSD-3-Clause", 78 | "BSD-2-Clause", 79 | "Zlib" 80 | #"Apache-2.0 WITH LLVM-exception", 81 | ] 82 | # List of explictly disallowed licenses 83 | # See https://spdx.org/licenses/ for list of possible licenses 84 | # [possible values: any SPDX 3.7 short identifier (+ optional exception)]. 85 | deny = [ 86 | #"Nokia", 87 | ] 88 | # Lint level for licenses considered copyleft 89 | copyleft = "deny" 90 | # Blanket approval or denial for OSI-approved or FSF Free/Libre licenses 91 | # * both - The license will be approved if it is both OSI-approved *AND* FSF 92 | # * either - The license will be approved if it is either OSI-approved *OR* FSF 93 | # * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF 94 | # * fsf-only - The license will be approved if is FSF *AND NOT* OSI-approved 95 | # * neither - This predicate is ignored and the default lint level is used 96 | allow-osi-fsf-free = "neither" 97 | # Lint level used when no other predicates are matched 98 | # 1. License isn't in the allow or deny lists 99 | # 2. License isn't copyleft 100 | # 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither" 101 | default = "deny" 102 | # The confidence threshold for detecting a license from license text. 103 | # The higher the value, the more closely the license text must be to the 104 | # canonical license text of a valid SPDX license file. 105 | # [possible values: any between 0.0 and 1.0]. 106 | confidence-threshold = 1.0 107 | # Allow 1 or more licenses on a per-crate basis, so that particular licenses 108 | # aren't accepted for every possible crate as with the normal allow list 109 | exceptions = [ 110 | # Each entry is the crate and version constraint, and its specific allow 111 | # list 112 | #{ allow = ["ISC"], name = "inotify-sys", version = "*" }, 113 | ] 114 | 115 | # Some crates don't have (easily) machine readable licensing information, 116 | # adding a clarification entry for it allows you to manually specify the 117 | # licensing information 118 | #[[licenses.clarify]] 119 | # The name of the crate the clarification applies to 120 | #name = "ring" 121 | # THe optional version constraint for the crate 122 | #version = "*" 123 | # The SPDX expression for the license requirements of the crate 124 | #expression = "MIT AND ISC AND OpenSSL" 125 | # One or more files in the crate's source used as the "source of truth" for 126 | # the license expression. If the contents match, the clarification will be used 127 | # when running the license check, otherwise the clarification will be ignored 128 | # and the crate will be checked normally, which may produce warnings or errors 129 | # depending on the rest of your configuration 130 | #license-files = [ 131 | # Each entry is a crate relative path, and the (opaque) hash of its contents 132 | #{ path = "LICENSE", hash = 0xbd0eed23 } 133 | #] 134 | 135 | [licenses.private] 136 | # If true, ignores workspace crates that aren't published, or are only 137 | # published to private registries 138 | ignore = false 139 | # One or more private registries that you might publish crates to, if a crate 140 | # is only published to private registries, and ignore is true, the crate will 141 | # not have its license(s) checked 142 | registries = [ 143 | #"https://sekretz.com/registry 144 | ] 145 | 146 | # This section is considered when running `cargo deny check bans`. 147 | # More documentation about the 'bans' section can be found here: 148 | # https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html 149 | [bans] 150 | # Lint level for when multiple versions of the same crate are detected 151 | multiple-versions = "warn" 152 | # Lint level for when a crate version requirement is `*` 153 | wildcards = "warn" 154 | # The graph highlighting used when creating dotgraphs for crates 155 | # with multiple versions 156 | # * lowest-version - The path to the lowest versioned duplicate is highlighted 157 | # * simplest-path - The path to the version with the fewest edges is highlighted 158 | # * all - Both lowest-version and simplest-path are used 159 | highlight = "all" 160 | # List of crates that are allowed. Use with care! 161 | allow = [ 162 | #{ name = "ansi_term", version = "=0.11.0" }, 163 | ] 164 | # List of crates to deny 165 | deny = [ 166 | # Each entry the name of a crate and a version range. If version is 167 | # not specified, all versions will be matched. 168 | #{ name = "ansi_term", version = "=0.11.0" }, 169 | ] 170 | # Certain crates/versions that will be skipped when doing duplicate detection. 171 | skip = [ 172 | #{ name = "ansi_term", version = "=0.11.0" }, 173 | ] 174 | # Similarly to `skip` allows you to skip certain crates during duplicate 175 | # detection. Unlike skip, it also includes the entire tree of transitive 176 | # dependencies starting at the specified crate, up to a certain depth, which is 177 | # by default infinite 178 | skip-tree = [ 179 | #{ name = "ansi_term", version = "=0.11.0", depth = 20 }, 180 | ] 181 | 182 | # This section is considered when running `cargo deny check sources`. 183 | # More documentation about the 'sources' section can be found here: 184 | # https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html 185 | [sources] 186 | # Lint level for what to happen when a crate from a crate registry that is not 187 | # in the allow list is encountered 188 | unknown-registry = "deny" 189 | # Lint level for what to happen when a crate from a git repository that is not 190 | # in the allow list is encountered 191 | unknown-git = "deny" 192 | # List of URLs for allowed crate registries. Defaults to the crates.io index 193 | # if not specified. If it is specified but empty, no registries are allowed. 194 | allow-registry = ["https://github.com/rust-lang/crates.io-index"] 195 | # List of URLs for allowed Git repositories 196 | allow-git = [ 197 | "https://github.com/gltf-rs/gltf.git", 198 | "https://github.com/amethyst/distill.git", 199 | ] -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | # This makes merge conflicts less likely to occur, and makes it easier to see 2 | # symmetry between parameters. 3 | fn_params_layout = "Vertical" 4 | --------------------------------------------------------------------------------