├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .vscode └── tasks.json ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── install.ps1 ├── src └── main.rs └── test_data ├── BoxTextured.gltf ├── BoxTexturedBinary.glb └── BoxTexturedBinary_img0.png /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - ci 8 | 9 | pull_request: 10 | branches: 11 | - master 12 | 13 | jobs: 14 | build: 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | os: [windows-latest, ubuntu-latest] 19 | rust_version: [stable, 1.67.1] 20 | 21 | runs-on: ${{ matrix.os }} 22 | name: ${{ matrix.os }} (${{ matrix.rust_version }}) 23 | 24 | steps: 25 | - uses: actions/checkout@v3 26 | 27 | - name: Install Rust 28 | uses: actions-rs/toolchain@v1 29 | with: 30 | toolchain: ${{ matrix.rust_version }} 31 | override: true 32 | profile: minimal 33 | 34 | - name: Build 35 | run: cargo build --verbose ${{ matrix.flags }} 36 | 37 | # TODO: Get tests building 38 | # - name: Run tests 39 | # run: cargo test --verbose ${{ matrix.flags }} 40 | 41 | lint: 42 | name: Rustfmt and Clippy 43 | runs-on: ubuntu-latest 44 | 45 | steps: 46 | - uses: actions/checkout@v3 47 | 48 | - name: Install Rust 49 | uses: actions-rs/toolchain@v1 50 | with: 51 | toolchain: stable 52 | override: true 53 | components: rustfmt, clippy 54 | 55 | - name: Rustfmt 56 | run: cargo fmt -- --check 57 | 58 | - name: Clippy 59 | run: cargo clippy --all-features 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | 3 | # Ignore all outputs created in tests 4 | /test_output 5 | 6 | # Ignore any assets created during testing 7 | *.jpg 8 | *.bin 9 | *.gltf 10 | *.ktx2 -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "cargo", 6 | "command": "test", 7 | "problemMatcher": [ 8 | "$rustc" 9 | ], 10 | "group": { 11 | "kind": "test", 12 | "isDefault": true 13 | }, 14 | "label": "rust: cargo test" 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "adler" 7 | version = "1.0.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 10 | 11 | [[package]] 12 | name = "adler32" 13 | version = "1.2.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" 16 | 17 | [[package]] 18 | name = "aho-corasick" 19 | version = "0.7.20" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" 22 | dependencies = [ 23 | "memchr", 24 | ] 25 | 26 | [[package]] 27 | name = "anyhow" 28 | version = "1.0.69" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" 31 | 32 | [[package]] 33 | name = "autocfg" 34 | version = "1.1.0" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 37 | 38 | [[package]] 39 | name = "base64" 40 | version = "0.12.3" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" 43 | 44 | [[package]] 45 | name = "bit_field" 46 | version = "0.10.1" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "dcb6dd1c2376d2e096796e234a70e17e94cc2d5d54ff8ce42b28cef1d0d359a4" 49 | 50 | [[package]] 51 | name = "bitflags" 52 | version = "1.3.2" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 55 | 56 | [[package]] 57 | name = "bumpalo" 58 | version = "3.10.0" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" 61 | 62 | [[package]] 63 | name = "bytemuck" 64 | version = "1.10.0" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "c53dfa917ec274df8ed3c572698f381a24eef2efba9492d797301b72b6db408a" 67 | 68 | [[package]] 69 | name = "byteorder" 70 | version = "1.4.3" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 73 | 74 | [[package]] 75 | name = "cc" 76 | version = "1.0.79" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" 79 | 80 | [[package]] 81 | name = "cfg-if" 82 | version = "1.0.0" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 85 | 86 | [[package]] 87 | name = "clap" 88 | version = "4.1.6" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "ec0b0588d44d4d63a87dbd75c136c166bbfd9a86a31cb89e09906521c7d3f5e3" 91 | dependencies = [ 92 | "bitflags", 93 | "clap_derive", 94 | "clap_lex", 95 | "is-terminal", 96 | "once_cell", 97 | "strsim", 98 | "termcolor", 99 | ] 100 | 101 | [[package]] 102 | name = "clap_derive" 103 | version = "4.1.0" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8" 106 | dependencies = [ 107 | "heck", 108 | "proc-macro-error", 109 | "proc-macro2", 110 | "quote", 111 | "syn", 112 | ] 113 | 114 | [[package]] 115 | name = "clap_lex" 116 | version = "0.3.1" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade" 119 | dependencies = [ 120 | "os_str_bytes", 121 | ] 122 | 123 | [[package]] 124 | name = "color_quant" 125 | version = "1.1.0" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" 128 | 129 | [[package]] 130 | name = "crc32fast" 131 | version = "1.3.2" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" 134 | dependencies = [ 135 | "cfg-if", 136 | ] 137 | 138 | [[package]] 139 | name = "crossbeam-channel" 140 | version = "0.5.5" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "4c02a4d71819009c192cf4872265391563fd6a84c81ff2c0f2a7026ca4c1d85c" 143 | dependencies = [ 144 | "cfg-if", 145 | "crossbeam-utils", 146 | ] 147 | 148 | [[package]] 149 | name = "crossbeam-deque" 150 | version = "0.8.1" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" 153 | dependencies = [ 154 | "cfg-if", 155 | "crossbeam-epoch", 156 | "crossbeam-utils", 157 | ] 158 | 159 | [[package]] 160 | name = "crossbeam-epoch" 161 | version = "0.9.9" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "07db9d94cbd326813772c968ccd25999e5f8ae22f4f8d1b11effa37ef6ce281d" 164 | dependencies = [ 165 | "autocfg", 166 | "cfg-if", 167 | "crossbeam-utils", 168 | "memoffset", 169 | "once_cell", 170 | "scopeguard", 171 | ] 172 | 173 | [[package]] 174 | name = "crossbeam-utils" 175 | version = "0.8.10" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "7d82ee10ce34d7bc12c2122495e7593a9c41347ecdd64185af4ecf72cb1a7f83" 178 | dependencies = [ 179 | "cfg-if", 180 | "once_cell", 181 | ] 182 | 183 | [[package]] 184 | name = "deflate" 185 | version = "0.8.6" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" 188 | dependencies = [ 189 | "adler32", 190 | "byteorder", 191 | ] 192 | 193 | [[package]] 194 | name = "deflate" 195 | version = "1.0.0" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "c86f7e25f518f4b81808a2cf1c50996a61f5c2eb394b2393bd87f2a4780a432f" 198 | dependencies = [ 199 | "adler32", 200 | ] 201 | 202 | [[package]] 203 | name = "either" 204 | version = "1.7.0" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" 207 | 208 | [[package]] 209 | name = "env_logger" 210 | version = "0.10.0" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" 213 | dependencies = [ 214 | "humantime", 215 | "is-terminal", 216 | "log", 217 | "regex", 218 | "termcolor", 219 | ] 220 | 221 | [[package]] 222 | name = "errno" 223 | version = "0.2.8" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" 226 | dependencies = [ 227 | "errno-dragonfly", 228 | "libc", 229 | "winapi", 230 | ] 231 | 232 | [[package]] 233 | name = "errno-dragonfly" 234 | version = "0.1.2" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" 237 | dependencies = [ 238 | "cc", 239 | "libc", 240 | ] 241 | 242 | [[package]] 243 | name = "exr" 244 | version = "1.4.2" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "14cc0e06fb5f67e5d6beadf3a382fec9baca1aa751c6d5368fdeee7e5932c215" 247 | dependencies = [ 248 | "bit_field", 249 | "deflate 1.0.0", 250 | "flume", 251 | "half", 252 | "inflate", 253 | "lebe", 254 | "smallvec", 255 | "threadpool", 256 | ] 257 | 258 | [[package]] 259 | name = "fastrand" 260 | version = "1.9.0" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" 263 | dependencies = [ 264 | "instant", 265 | ] 266 | 267 | [[package]] 268 | name = "flate2" 269 | version = "1.0.24" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" 272 | dependencies = [ 273 | "crc32fast", 274 | "miniz_oxide 0.5.3", 275 | ] 276 | 277 | [[package]] 278 | name = "flume" 279 | version = "0.10.13" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "1ceeb589a3157cac0ab8cc585feb749bd2cea5cb55a6ee802ad72d9fd38303da" 282 | dependencies = [ 283 | "futures-core", 284 | "futures-sink", 285 | "nanorand", 286 | "pin-project", 287 | "spin", 288 | ] 289 | 290 | [[package]] 291 | name = "fs-err" 292 | version = "2.9.0" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "0845fa252299212f0389d64ba26f34fa32cfe41588355f21ed507c59a0f64541" 295 | 296 | [[package]] 297 | name = "futures-core" 298 | version = "0.3.21" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" 301 | 302 | [[package]] 303 | name = "futures-sink" 304 | version = "0.3.21" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" 307 | 308 | [[package]] 309 | name = "getrandom" 310 | version = "0.2.7" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" 313 | dependencies = [ 314 | "cfg-if", 315 | "js-sys", 316 | "libc", 317 | "wasi", 318 | "wasm-bindgen", 319 | ] 320 | 321 | [[package]] 322 | name = "gif" 323 | version = "0.11.4" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "3edd93c6756b4dfaf2709eafcc345ba2636565295c198a9cfbf75fa5e3e00b06" 326 | dependencies = [ 327 | "color_quant", 328 | "weezl", 329 | ] 330 | 331 | [[package]] 332 | name = "gltf" 333 | version = "1.0.0" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "00e0a0eace786193fc83644907097285396360e9e82e30f81a21e9b1ba836a3e" 336 | dependencies = [ 337 | "base64", 338 | "byteorder", 339 | "gltf-json", 340 | "image 0.23.14", 341 | "lazy_static", 342 | ] 343 | 344 | [[package]] 345 | name = "gltf-derive" 346 | version = "1.0.0" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "bdd53d6e284bb2bf02a6926e4cc4984978c1990914d6cd9deae4e31cf37cd113" 349 | dependencies = [ 350 | "inflections", 351 | "proc-macro2", 352 | "quote", 353 | "syn", 354 | ] 355 | 356 | [[package]] 357 | name = "gltf-json" 358 | version = "1.0.0" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "9949836a9ec5e7f83f76fb9bbcbc77f254a577ebbdb0820867bc11979ef97cad" 361 | dependencies = [ 362 | "gltf-derive", 363 | "serde", 364 | "serde_derive", 365 | "serde_json", 366 | ] 367 | 368 | [[package]] 369 | name = "half" 370 | version = "1.8.2" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" 373 | 374 | [[package]] 375 | name = "heck" 376 | version = "0.4.1" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 379 | 380 | [[package]] 381 | name = "hermit-abi" 382 | version = "0.1.19" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 385 | dependencies = [ 386 | "libc", 387 | ] 388 | 389 | [[package]] 390 | name = "hermit-abi" 391 | version = "0.3.1" 392 | source = "registry+https://github.com/rust-lang/crates.io-index" 393 | checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" 394 | 395 | [[package]] 396 | name = "humantime" 397 | version = "2.1.0" 398 | source = "registry+https://github.com/rust-lang/crates.io-index" 399 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 400 | 401 | [[package]] 402 | name = "image" 403 | version = "0.23.14" 404 | source = "registry+https://github.com/rust-lang/crates.io-index" 405 | checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1" 406 | dependencies = [ 407 | "bytemuck", 408 | "byteorder", 409 | "color_quant", 410 | "jpeg-decoder 0.1.22", 411 | "num-iter", 412 | "num-rational 0.3.2", 413 | "num-traits", 414 | "png 0.16.8", 415 | ] 416 | 417 | [[package]] 418 | name = "image" 419 | version = "0.24.3" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "7e30ca2ecf7666107ff827a8e481de6a132a9b687ed3bb20bb1c144a36c00964" 422 | dependencies = [ 423 | "bytemuck", 424 | "byteorder", 425 | "color_quant", 426 | "exr", 427 | "gif", 428 | "jpeg-decoder 0.2.6", 429 | "num-rational 0.4.1", 430 | "num-traits", 431 | "png 0.17.5", 432 | "scoped_threadpool", 433 | "tiff", 434 | ] 435 | 436 | [[package]] 437 | name = "inflate" 438 | version = "0.4.5" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "1cdb29978cc5797bd8dcc8e5bf7de604891df2a8dc576973d71a281e916db2ff" 441 | dependencies = [ 442 | "adler32", 443 | ] 444 | 445 | [[package]] 446 | name = "inflections" 447 | version = "1.1.1" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "a257582fdcde896fd96463bf2d40eefea0580021c0712a0e2b028b60b47a837a" 450 | 451 | [[package]] 452 | name = "instant" 453 | version = "0.1.12" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 456 | dependencies = [ 457 | "cfg-if", 458 | ] 459 | 460 | [[package]] 461 | name = "io-lifetimes" 462 | version = "1.0.5" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3" 465 | dependencies = [ 466 | "libc", 467 | "windows-sys 0.45.0", 468 | ] 469 | 470 | [[package]] 471 | name = "is-terminal" 472 | version = "0.4.3" 473 | source = "registry+https://github.com/rust-lang/crates.io-index" 474 | checksum = "22e18b0a45d56fe973d6db23972bf5bc46f988a4a2385deac9cc29572f09daef" 475 | dependencies = [ 476 | "hermit-abi 0.3.1", 477 | "io-lifetimes", 478 | "rustix", 479 | "windows-sys 0.45.0", 480 | ] 481 | 482 | [[package]] 483 | name = "itoa" 484 | version = "1.0.2" 485 | source = "registry+https://github.com/rust-lang/crates.io-index" 486 | checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" 487 | 488 | [[package]] 489 | name = "jpeg-decoder" 490 | version = "0.1.22" 491 | source = "registry+https://github.com/rust-lang/crates.io-index" 492 | checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2" 493 | 494 | [[package]] 495 | name = "jpeg-decoder" 496 | version = "0.2.6" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | checksum = "9478aa10f73e7528198d75109c8be5cd7d15fb530238040148d5f9a22d4c5b3b" 499 | dependencies = [ 500 | "rayon", 501 | ] 502 | 503 | [[package]] 504 | name = "js-sys" 505 | version = "0.3.58" 506 | source = "registry+https://github.com/rust-lang/crates.io-index" 507 | checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27" 508 | dependencies = [ 509 | "wasm-bindgen", 510 | ] 511 | 512 | [[package]] 513 | name = "ktx2" 514 | version = "0.3.0" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "87d65e08a9ec02e409d27a0139eaa6b9756b4d81fe7cde71f6941a83730ce838" 517 | dependencies = [ 518 | "bitflags", 519 | ] 520 | 521 | [[package]] 522 | name = "lazy_static" 523 | version = "1.4.0" 524 | source = "registry+https://github.com/rust-lang/crates.io-index" 525 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 526 | 527 | [[package]] 528 | name = "lebe" 529 | version = "0.5.1" 530 | source = "registry+https://github.com/rust-lang/crates.io-index" 531 | checksum = "7efd1d698db0759e6ef11a7cd44407407399a910c774dd804c64c032da7826ff" 532 | 533 | [[package]] 534 | name = "libc" 535 | version = "0.2.139" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" 538 | 539 | [[package]] 540 | name = "linux-raw-sys" 541 | version = "0.1.4" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" 544 | 545 | [[package]] 546 | name = "lock_api" 547 | version = "0.4.7" 548 | source = "registry+https://github.com/rust-lang/crates.io-index" 549 | checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" 550 | dependencies = [ 551 | "autocfg", 552 | "scopeguard", 553 | ] 554 | 555 | [[package]] 556 | name = "log" 557 | version = "0.4.17" 558 | source = "registry+https://github.com/rust-lang/crates.io-index" 559 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 560 | dependencies = [ 561 | "cfg-if", 562 | ] 563 | 564 | [[package]] 565 | name = "memchr" 566 | version = "2.5.0" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 569 | 570 | [[package]] 571 | name = "memoffset" 572 | version = "0.6.5" 573 | source = "registry+https://github.com/rust-lang/crates.io-index" 574 | checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" 575 | dependencies = [ 576 | "autocfg", 577 | ] 578 | 579 | [[package]] 580 | name = "miniz_oxide" 581 | version = "0.3.7" 582 | source = "registry+https://github.com/rust-lang/crates.io-index" 583 | checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" 584 | dependencies = [ 585 | "adler32", 586 | ] 587 | 588 | [[package]] 589 | name = "miniz_oxide" 590 | version = "0.5.3" 591 | source = "registry+https://github.com/rust-lang/crates.io-index" 592 | checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" 593 | dependencies = [ 594 | "adler", 595 | ] 596 | 597 | [[package]] 598 | name = "nanorand" 599 | version = "0.7.0" 600 | source = "registry+https://github.com/rust-lang/crates.io-index" 601 | checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" 602 | dependencies = [ 603 | "getrandom", 604 | ] 605 | 606 | [[package]] 607 | name = "num-integer" 608 | version = "0.1.45" 609 | source = "registry+https://github.com/rust-lang/crates.io-index" 610 | checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" 611 | dependencies = [ 612 | "autocfg", 613 | "num-traits", 614 | ] 615 | 616 | [[package]] 617 | name = "num-iter" 618 | version = "0.1.43" 619 | source = "registry+https://github.com/rust-lang/crates.io-index" 620 | checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" 621 | dependencies = [ 622 | "autocfg", 623 | "num-integer", 624 | "num-traits", 625 | ] 626 | 627 | [[package]] 628 | name = "num-rational" 629 | version = "0.3.2" 630 | source = "registry+https://github.com/rust-lang/crates.io-index" 631 | checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" 632 | dependencies = [ 633 | "autocfg", 634 | "num-integer", 635 | "num-traits", 636 | ] 637 | 638 | [[package]] 639 | name = "num-rational" 640 | version = "0.4.1" 641 | source = "registry+https://github.com/rust-lang/crates.io-index" 642 | checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" 643 | dependencies = [ 644 | "autocfg", 645 | "num-integer", 646 | "num-traits", 647 | ] 648 | 649 | [[package]] 650 | name = "num-traits" 651 | version = "0.2.15" 652 | source = "registry+https://github.com/rust-lang/crates.io-index" 653 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 654 | dependencies = [ 655 | "autocfg", 656 | ] 657 | 658 | [[package]] 659 | name = "num_cpus" 660 | version = "1.13.1" 661 | source = "registry+https://github.com/rust-lang/crates.io-index" 662 | checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" 663 | dependencies = [ 664 | "hermit-abi 0.1.19", 665 | "libc", 666 | ] 667 | 668 | [[package]] 669 | name = "once_cell" 670 | version = "1.13.0" 671 | source = "registry+https://github.com/rust-lang/crates.io-index" 672 | checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" 673 | 674 | [[package]] 675 | name = "os_str_bytes" 676 | version = "6.4.1" 677 | source = "registry+https://github.com/rust-lang/crates.io-index" 678 | checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" 679 | 680 | [[package]] 681 | name = "pin-project" 682 | version = "1.0.11" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "78203e83c48cffbe01e4a2d35d566ca4de445d79a85372fc64e378bfc812a260" 685 | dependencies = [ 686 | "pin-project-internal", 687 | ] 688 | 689 | [[package]] 690 | name = "pin-project-internal" 691 | version = "1.0.11" 692 | source = "registry+https://github.com/rust-lang/crates.io-index" 693 | checksum = "710faf75e1b33345361201d36d04e98ac1ed8909151a017ed384700836104c74" 694 | dependencies = [ 695 | "proc-macro2", 696 | "quote", 697 | "syn", 698 | ] 699 | 700 | [[package]] 701 | name = "png" 702 | version = "0.16.8" 703 | source = "registry+https://github.com/rust-lang/crates.io-index" 704 | checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" 705 | dependencies = [ 706 | "bitflags", 707 | "crc32fast", 708 | "deflate 0.8.6", 709 | "miniz_oxide 0.3.7", 710 | ] 711 | 712 | [[package]] 713 | name = "png" 714 | version = "0.17.5" 715 | source = "registry+https://github.com/rust-lang/crates.io-index" 716 | checksum = "dc38c0ad57efb786dd57b9864e5b18bae478c00c824dc55a38bbc9da95dde3ba" 717 | dependencies = [ 718 | "bitflags", 719 | "crc32fast", 720 | "deflate 1.0.0", 721 | "miniz_oxide 0.5.3", 722 | ] 723 | 724 | [[package]] 725 | name = "proc-macro-error" 726 | version = "1.0.4" 727 | source = "registry+https://github.com/rust-lang/crates.io-index" 728 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 729 | dependencies = [ 730 | "proc-macro-error-attr", 731 | "proc-macro2", 732 | "quote", 733 | "syn", 734 | "version_check", 735 | ] 736 | 737 | [[package]] 738 | name = "proc-macro-error-attr" 739 | version = "1.0.4" 740 | source = "registry+https://github.com/rust-lang/crates.io-index" 741 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 742 | dependencies = [ 743 | "proc-macro2", 744 | "quote", 745 | "version_check", 746 | ] 747 | 748 | [[package]] 749 | name = "proc-macro2" 750 | version = "1.0.51" 751 | source = "registry+https://github.com/rust-lang/crates.io-index" 752 | checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" 753 | dependencies = [ 754 | "unicode-ident", 755 | ] 756 | 757 | [[package]] 758 | name = "quote" 759 | version = "1.0.20" 760 | source = "registry+https://github.com/rust-lang/crates.io-index" 761 | checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" 762 | dependencies = [ 763 | "proc-macro2", 764 | ] 765 | 766 | [[package]] 767 | name = "rayon" 768 | version = "1.5.3" 769 | source = "registry+https://github.com/rust-lang/crates.io-index" 770 | checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" 771 | dependencies = [ 772 | "autocfg", 773 | "crossbeam-deque", 774 | "either", 775 | "rayon-core", 776 | ] 777 | 778 | [[package]] 779 | name = "rayon-core" 780 | version = "1.9.3" 781 | source = "registry+https://github.com/rust-lang/crates.io-index" 782 | checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" 783 | dependencies = [ 784 | "crossbeam-channel", 785 | "crossbeam-deque", 786 | "crossbeam-utils", 787 | "num_cpus", 788 | ] 789 | 790 | [[package]] 791 | name = "redox_syscall" 792 | version = "0.2.16" 793 | source = "registry+https://github.com/rust-lang/crates.io-index" 794 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 795 | dependencies = [ 796 | "bitflags", 797 | ] 798 | 799 | [[package]] 800 | name = "regex" 801 | version = "1.7.1" 802 | source = "registry+https://github.com/rust-lang/crates.io-index" 803 | checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" 804 | dependencies = [ 805 | "aho-corasick", 806 | "memchr", 807 | "regex-syntax", 808 | ] 809 | 810 | [[package]] 811 | name = "regex-syntax" 812 | version = "0.6.28" 813 | source = "registry+https://github.com/rust-lang/crates.io-index" 814 | checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" 815 | 816 | [[package]] 817 | name = "rustix" 818 | version = "0.36.8" 819 | source = "registry+https://github.com/rust-lang/crates.io-index" 820 | checksum = "f43abb88211988493c1abb44a70efa56ff0ce98f233b7b276146f1f3f7ba9644" 821 | dependencies = [ 822 | "bitflags", 823 | "errno", 824 | "io-lifetimes", 825 | "libc", 826 | "linux-raw-sys", 827 | "windows-sys 0.45.0", 828 | ] 829 | 830 | [[package]] 831 | name = "ryu" 832 | version = "1.0.10" 833 | source = "registry+https://github.com/rust-lang/crates.io-index" 834 | checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" 835 | 836 | [[package]] 837 | name = "scoped_threadpool" 838 | version = "0.1.9" 839 | source = "registry+https://github.com/rust-lang/crates.io-index" 840 | checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" 841 | 842 | [[package]] 843 | name = "scopeguard" 844 | version = "1.1.0" 845 | source = "registry+https://github.com/rust-lang/crates.io-index" 846 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 847 | 848 | [[package]] 849 | name = "seahash" 850 | version = "4.1.0" 851 | source = "registry+https://github.com/rust-lang/crates.io-index" 852 | checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" 853 | 854 | [[package]] 855 | name = "serde" 856 | version = "1.0.138" 857 | source = "registry+https://github.com/rust-lang/crates.io-index" 858 | checksum = "1578c6245786b9d168c5447eeacfb96856573ca56c9d68fdcf394be134882a47" 859 | 860 | [[package]] 861 | name = "serde_derive" 862 | version = "1.0.138" 863 | source = "registry+https://github.com/rust-lang/crates.io-index" 864 | checksum = "023e9b1467aef8a10fb88f25611870ada9800ef7e22afce356bb0d2387b6f27c" 865 | dependencies = [ 866 | "proc-macro2", 867 | "quote", 868 | "syn", 869 | ] 870 | 871 | [[package]] 872 | name = "serde_json" 873 | version = "1.0.82" 874 | source = "registry+https://github.com/rust-lang/crates.io-index" 875 | checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" 876 | dependencies = [ 877 | "itoa", 878 | "ryu", 879 | "serde", 880 | ] 881 | 882 | [[package]] 883 | name = "smallvec" 884 | version = "1.9.0" 885 | source = "registry+https://github.com/rust-lang/crates.io-index" 886 | checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" 887 | 888 | [[package]] 889 | name = "spin" 890 | version = "0.9.4" 891 | source = "registry+https://github.com/rust-lang/crates.io-index" 892 | checksum = "7f6002a767bff9e83f8eeecf883ecb8011875a21ae8da43bffb817a57e78cc09" 893 | dependencies = [ 894 | "lock_api", 895 | ] 896 | 897 | [[package]] 898 | name = "squisher" 899 | version = "0.1.0" 900 | dependencies = [ 901 | "anyhow", 902 | "clap", 903 | "env_logger", 904 | "fs-err", 905 | "gltf", 906 | "image 0.24.3", 907 | "ktx2", 908 | "log", 909 | "seahash", 910 | "tempfile", 911 | ] 912 | 913 | [[package]] 914 | name = "strsim" 915 | version = "0.10.0" 916 | source = "registry+https://github.com/rust-lang/crates.io-index" 917 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 918 | 919 | [[package]] 920 | name = "syn" 921 | version = "1.0.98" 922 | source = "registry+https://github.com/rust-lang/crates.io-index" 923 | checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" 924 | dependencies = [ 925 | "proc-macro2", 926 | "quote", 927 | "unicode-ident", 928 | ] 929 | 930 | [[package]] 931 | name = "tempfile" 932 | version = "3.4.0" 933 | source = "registry+https://github.com/rust-lang/crates.io-index" 934 | checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" 935 | dependencies = [ 936 | "cfg-if", 937 | "fastrand", 938 | "redox_syscall", 939 | "rustix", 940 | "windows-sys 0.42.0", 941 | ] 942 | 943 | [[package]] 944 | name = "termcolor" 945 | version = "1.2.0" 946 | source = "registry+https://github.com/rust-lang/crates.io-index" 947 | checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" 948 | dependencies = [ 949 | "winapi-util", 950 | ] 951 | 952 | [[package]] 953 | name = "threadpool" 954 | version = "1.8.1" 955 | source = "registry+https://github.com/rust-lang/crates.io-index" 956 | checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" 957 | dependencies = [ 958 | "num_cpus", 959 | ] 960 | 961 | [[package]] 962 | name = "tiff" 963 | version = "0.7.2" 964 | source = "registry+https://github.com/rust-lang/crates.io-index" 965 | checksum = "7cfada0986f446a770eca461e8c6566cb879682f7d687c8348aa0c857bd52286" 966 | dependencies = [ 967 | "flate2", 968 | "jpeg-decoder 0.2.6", 969 | "weezl", 970 | ] 971 | 972 | [[package]] 973 | name = "unicode-ident" 974 | version = "1.0.1" 975 | source = "registry+https://github.com/rust-lang/crates.io-index" 976 | checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" 977 | 978 | [[package]] 979 | name = "version_check" 980 | version = "0.9.4" 981 | source = "registry+https://github.com/rust-lang/crates.io-index" 982 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 983 | 984 | [[package]] 985 | name = "wasi" 986 | version = "0.11.0+wasi-snapshot-preview1" 987 | source = "registry+https://github.com/rust-lang/crates.io-index" 988 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 989 | 990 | [[package]] 991 | name = "wasm-bindgen" 992 | version = "0.2.81" 993 | source = "registry+https://github.com/rust-lang/crates.io-index" 994 | checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994" 995 | dependencies = [ 996 | "cfg-if", 997 | "wasm-bindgen-macro", 998 | ] 999 | 1000 | [[package]] 1001 | name = "wasm-bindgen-backend" 1002 | version = "0.2.81" 1003 | source = "registry+https://github.com/rust-lang/crates.io-index" 1004 | checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a" 1005 | dependencies = [ 1006 | "bumpalo", 1007 | "lazy_static", 1008 | "log", 1009 | "proc-macro2", 1010 | "quote", 1011 | "syn", 1012 | "wasm-bindgen-shared", 1013 | ] 1014 | 1015 | [[package]] 1016 | name = "wasm-bindgen-macro" 1017 | version = "0.2.81" 1018 | source = "registry+https://github.com/rust-lang/crates.io-index" 1019 | checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa" 1020 | dependencies = [ 1021 | "quote", 1022 | "wasm-bindgen-macro-support", 1023 | ] 1024 | 1025 | [[package]] 1026 | name = "wasm-bindgen-macro-support" 1027 | version = "0.2.81" 1028 | source = "registry+https://github.com/rust-lang/crates.io-index" 1029 | checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" 1030 | dependencies = [ 1031 | "proc-macro2", 1032 | "quote", 1033 | "syn", 1034 | "wasm-bindgen-backend", 1035 | "wasm-bindgen-shared", 1036 | ] 1037 | 1038 | [[package]] 1039 | name = "wasm-bindgen-shared" 1040 | version = "0.2.81" 1041 | source = "registry+https://github.com/rust-lang/crates.io-index" 1042 | checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" 1043 | 1044 | [[package]] 1045 | name = "weezl" 1046 | version = "0.1.7" 1047 | source = "registry+https://github.com/rust-lang/crates.io-index" 1048 | checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" 1049 | 1050 | [[package]] 1051 | name = "winapi" 1052 | version = "0.3.9" 1053 | source = "registry+https://github.com/rust-lang/crates.io-index" 1054 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1055 | dependencies = [ 1056 | "winapi-i686-pc-windows-gnu", 1057 | "winapi-x86_64-pc-windows-gnu", 1058 | ] 1059 | 1060 | [[package]] 1061 | name = "winapi-i686-pc-windows-gnu" 1062 | version = "0.4.0" 1063 | source = "registry+https://github.com/rust-lang/crates.io-index" 1064 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1065 | 1066 | [[package]] 1067 | name = "winapi-util" 1068 | version = "0.1.5" 1069 | source = "registry+https://github.com/rust-lang/crates.io-index" 1070 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1071 | dependencies = [ 1072 | "winapi", 1073 | ] 1074 | 1075 | [[package]] 1076 | name = "winapi-x86_64-pc-windows-gnu" 1077 | version = "0.4.0" 1078 | source = "registry+https://github.com/rust-lang/crates.io-index" 1079 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1080 | 1081 | [[package]] 1082 | name = "windows-sys" 1083 | version = "0.42.0" 1084 | source = "registry+https://github.com/rust-lang/crates.io-index" 1085 | checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" 1086 | dependencies = [ 1087 | "windows_aarch64_gnullvm", 1088 | "windows_aarch64_msvc", 1089 | "windows_i686_gnu", 1090 | "windows_i686_msvc", 1091 | "windows_x86_64_gnu", 1092 | "windows_x86_64_gnullvm", 1093 | "windows_x86_64_msvc", 1094 | ] 1095 | 1096 | [[package]] 1097 | name = "windows-sys" 1098 | version = "0.45.0" 1099 | source = "registry+https://github.com/rust-lang/crates.io-index" 1100 | checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" 1101 | dependencies = [ 1102 | "windows-targets", 1103 | ] 1104 | 1105 | [[package]] 1106 | name = "windows-targets" 1107 | version = "0.42.1" 1108 | source = "registry+https://github.com/rust-lang/crates.io-index" 1109 | checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" 1110 | dependencies = [ 1111 | "windows_aarch64_gnullvm", 1112 | "windows_aarch64_msvc", 1113 | "windows_i686_gnu", 1114 | "windows_i686_msvc", 1115 | "windows_x86_64_gnu", 1116 | "windows_x86_64_gnullvm", 1117 | "windows_x86_64_msvc", 1118 | ] 1119 | 1120 | [[package]] 1121 | name = "windows_aarch64_gnullvm" 1122 | version = "0.42.1" 1123 | source = "registry+https://github.com/rust-lang/crates.io-index" 1124 | checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" 1125 | 1126 | [[package]] 1127 | name = "windows_aarch64_msvc" 1128 | version = "0.42.1" 1129 | source = "registry+https://github.com/rust-lang/crates.io-index" 1130 | checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" 1131 | 1132 | [[package]] 1133 | name = "windows_i686_gnu" 1134 | version = "0.42.1" 1135 | source = "registry+https://github.com/rust-lang/crates.io-index" 1136 | checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" 1137 | 1138 | [[package]] 1139 | name = "windows_i686_msvc" 1140 | version = "0.42.1" 1141 | source = "registry+https://github.com/rust-lang/crates.io-index" 1142 | checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" 1143 | 1144 | [[package]] 1145 | name = "windows_x86_64_gnu" 1146 | version = "0.42.1" 1147 | source = "registry+https://github.com/rust-lang/crates.io-index" 1148 | checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" 1149 | 1150 | [[package]] 1151 | name = "windows_x86_64_gnullvm" 1152 | version = "0.42.1" 1153 | source = "registry+https://github.com/rust-lang/crates.io-index" 1154 | checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" 1155 | 1156 | [[package]] 1157 | name = "windows_x86_64_msvc" 1158 | version = "0.42.1" 1159 | source = "registry+https://github.com/rust-lang/crates.io-index" 1160 | checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" 1161 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "squisher" 3 | description = "Creates optimised, platform specific glTF files" 4 | authors = ["Let Eyes Equals Two"] 5 | version = "0.1.0" 6 | license = "MIT OR Apache-2.0" 7 | repository = "https://github.com/leetvr/squisher" 8 | edition = "2021" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | anyhow = "1.0.69" 14 | clap = { version = "4.1.6", features = ["derive"] } 15 | env_logger = "0.10.0" 16 | fs-err = "2.9.0" 17 | gltf = { version = "1.0", features = ["KHR_lights_punctual", "extras"] } 18 | image = "0.24" 19 | log = "0.4.17" 20 | seahash = "4.1.0" 21 | tempfile = "3.4.0" 22 | 23 | [dev-dependencies] 24 | ktx2 = "0.3" 25 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 Let Eyes Equals Two 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Squisher 2 | 3 | ## What? 4 | `squisher` is a program that takes a glTF or .glb file with PNG/JPG textures and produces a .glb file where the textures have been replaced with ASTC compressed KTX2 files, *explicitly breaking the glTF spec*: [source](https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#_image_mimetype). 5 | 6 | If you want your assets to use optimised textures but don't want to pay the runtime transcoding cost of [KHR_texture_basisu](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_texture_basisu/README.md), this tool is for you. 7 | 8 | ## Why? 9 | Because [hotham](https://github.com/leetvr/hotham) works best with compressed textures, and doing this stuff by hand is time consuming. It takes *literal minutes*. And we all know that [a watched pot never boils](https://www.youtube.com/watch?v=eTFBxp0VW9M). 10 | 11 | ## How? 12 | Install `squisher` with `cargo install`: 13 | 14 | ```bash 15 | cargo install --git https://github.com/leetvr/squisher.git 16 | ``` 17 | 18 | Running `squisher` is easy: 19 | 20 | ```bash 21 | squisher your_file.glb output.glb 22 | ``` 23 | 24 | Which will produce `output.glb`, containing ASTC compressed KTX2 textures. 25 | 26 | You can also use uncompressed RGBA8 textures: 27 | 28 | ```bash 29 | squisher --format rgba8 your_file.glb output.glb 30 | ``` 31 | 32 | ## Requirements 33 | To compile `squisher`, you need: 34 | - [Rust](https://rustup.rs/) 1.67.1 or newer 35 | 36 | To run `squisher` you must have the following available on your system PATH: 37 | - [Khronos Texture Tools](https://github.khronos.org/KTX-Software/ktxtools) 4.1.0 or newer 38 | 39 | ## License 40 | Licensed under either of 41 | 42 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 43 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 44 | 45 | at your option. 46 | 47 | ### Contribution 48 | Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. -------------------------------------------------------------------------------- /install.ps1: -------------------------------------------------------------------------------- 1 | cargo build --release 2 | cp -Force target\release\squisher.exe 'C:\Program Files\LEET\Squisher\squisher.exe' -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | borrow::Cow, 3 | collections::HashMap, 4 | hash::Hasher, 5 | io::{self, Write}, 6 | path::{Path, PathBuf}, 7 | process::{Command, Stdio}, 8 | str::FromStr, 9 | }; 10 | 11 | use anyhow::{bail, Context}; 12 | use clap::Parser; 13 | use gltf::json::{image::MimeType, Index}; 14 | use image::{codecs::png::PngEncoder, ImageEncoder}; 15 | 16 | const MAX_SIZE: u32 = 4096; 17 | 18 | static BIN_TOKTX: &str = "toktx"; 19 | 20 | #[derive(Parser)] 21 | #[command(author, version, about)] 22 | struct Args { 23 | /// The path to the file to process. 24 | input: PathBuf, 25 | 26 | /// Where to output the squished output. 27 | output: PathBuf, 28 | 29 | /// What texture format to use. Can be 'astc' (default) or 'rgba8'. 30 | #[clap(long, default_value = "astc")] 31 | format: TextureFormat, 32 | 33 | /// Enables more verbose logging. 34 | #[clap(short, long)] 35 | verbose: bool, 36 | 37 | /// Disable the image cache, forcing all images to be reprocessed. 38 | #[clap(long)] 39 | no_cache: bool, 40 | 41 | /// Disable using Zstandard supercompression on the images. 42 | #[clap(long)] 43 | no_supercompression: bool, 44 | } 45 | 46 | #[derive(Debug, Clone, Copy, PartialEq)] 47 | enum TextureFormat { 48 | Rgba8, 49 | Astc, 50 | } 51 | 52 | impl FromStr for TextureFormat { 53 | type Err = anyhow::Error; 54 | 55 | fn from_str(s: &str) -> Result { 56 | match s { 57 | "rgba8" => Ok(Self::Rgba8), 58 | "astc" => Ok(Self::Astc), 59 | _ => bail!("unknown texture format '{s}', expected 'rgba8' or 'astc'"), 60 | } 61 | } 62 | } 63 | 64 | fn main() { 65 | let args = Args::parse(); 66 | 67 | if let Err(err) = squish(args) { 68 | log::error!("Fatal error: {err:?}"); 69 | std::process::exit(1); 70 | } 71 | } 72 | 73 | struct SquishContext { 74 | input: Input, 75 | use_cache: bool, 76 | use_supercompression: bool, 77 | texture_format: TextureFormat, 78 | } 79 | 80 | struct Input { 81 | document: gltf::Document, 82 | blob: Vec, 83 | } 84 | 85 | /// Which part of the glTF material model this texture is. 86 | #[derive(PartialEq, Eq, Debug)] 87 | enum TextureType { 88 | BaseColor, 89 | Normal, 90 | MetallicRoughnessOcclusion, 91 | Emissive, 92 | } 93 | 94 | impl TextureType { 95 | pub fn is_srgb(&self) -> bool { 96 | matches!(self, TextureType::BaseColor | TextureType::Emissive) 97 | } 98 | 99 | pub fn block_size(&self) -> &'static str { 100 | match self { 101 | // TextureType::MetallicRoughnessOcclusion => command.arg("6x6"), 102 | // TextureType::Emissive => command.arg("10x10"), 103 | TextureType::BaseColor | TextureType::Emissive => "6x6", 104 | _ => "4x4", 105 | } 106 | } 107 | } 108 | 109 | fn squish(args: Args) -> anyhow::Result<()> { 110 | configure_logging(args.verbose); 111 | 112 | let use_cache = !args.no_cache; 113 | 114 | log::info!("Squishing {}", args.input.display()); 115 | let input = open(&args.input)?; 116 | let context = SquishContext { 117 | input, 118 | use_cache, 119 | texture_format: args.format, 120 | use_supercompression: !args.no_supercompression, 121 | }; 122 | 123 | let optimized_glb = context.optimize()?; 124 | fs_err::write(&args.output, optimized_glb)?; 125 | 126 | log::info!("Squished file: {}! ✨ Enjoy ✨", args.output.display()); 127 | Ok(()) 128 | } 129 | 130 | fn configure_logging(verbose: bool) { 131 | let filter = if verbose { 132 | "squisher=debug,warn" 133 | } else { 134 | "squisher=info,warn" 135 | }; 136 | 137 | let log_env = env_logger::Env::default().default_filter_or(filter); 138 | 139 | // If logging is already configured (like running in a test), we should 140 | // suppress any issues initializing it. 141 | let _ = env_logger::Builder::from_env(log_env) 142 | .format_timestamp(None) 143 | .try_init(); 144 | } 145 | 146 | impl SquishContext { 147 | fn optimize(self) -> anyhow::Result> { 148 | // Ensure our cache directory exists and is ready to use 149 | fs_err::create_dir_all(cache_dir()).context("failed to create cache directory")?; 150 | 151 | let mut image_map: HashMap> = Default::default(); 152 | 153 | // First, compress the images. 154 | // In order to do this, we need to have a bit of information about them first: 155 | let document = &self.input.document; 156 | for material in document.materials() { 157 | // Okiedokie. Each part of the material needs to be treated differently. Let's start with the easy stuff. 158 | let pbr = material.pbr_metallic_roughness(); 159 | if let Some(base_colour) = pbr.base_color_texture() { 160 | let texture = base_colour.texture(); 161 | if let Some(compressed) = self.compress_texture(&texture, TextureType::BaseColor)? { 162 | image_map.insert(texture.source().index(), compressed); 163 | } 164 | } 165 | 166 | if let Some(metallic_roughness) = pbr.metallic_roughness_texture() { 167 | let texture = metallic_roughness.texture(); 168 | if let Some(compressed) = 169 | self.compress_texture(&texture, TextureType::MetallicRoughnessOcclusion)? 170 | { 171 | image_map.insert(texture.source().index(), compressed); 172 | } 173 | } 174 | 175 | if let Some(normal) = material.normal_texture() { 176 | let texture = normal.texture(); 177 | if let Some(compressed) = self.compress_texture(&texture, TextureType::Normal)? { 178 | image_map.insert(texture.source().index(), compressed); 179 | } 180 | } 181 | 182 | if let Some(emissive) = material.emissive_texture() { 183 | let texture = emissive.texture(); 184 | if let Some(compressed) = self.compress_texture(&texture, TextureType::Emissive)? { 185 | image_map.insert(texture.source().index(), compressed); 186 | } 187 | } 188 | 189 | if let Some(occlusion) = material.occlusion_texture() { 190 | let texture = occlusion.texture(); 191 | if let Some(compressed) = 192 | self.compress_texture(&texture, TextureType::MetallicRoughnessOcclusion)? 193 | { 194 | image_map.insert(texture.source().index(), compressed); 195 | } 196 | } 197 | } 198 | 199 | // Okay. Now that's done we need a new GLB file. 200 | self.create_glb_file(image_map) 201 | } 202 | 203 | fn compress_texture( 204 | &self, 205 | texture: &gltf::Texture, 206 | texture_type: TextureType, 207 | ) -> anyhow::Result>> { 208 | log::info!( 209 | "Compressing {texture_type:?} as format {:?}...", 210 | self.texture_format 211 | ); 212 | 213 | let (mut bytes, format) = match texture.source().source() { 214 | gltf::image::Source::View { view, mime_type } => { 215 | let slice = &self.input.blob[view.offset()..view.offset() + view.length()]; 216 | let bytes = Cow::Borrowed(slice); 217 | 218 | let format = match mime_type { 219 | "image/jpeg" => image::ImageFormat::Jpeg, 220 | "image/png" => image::ImageFormat::Png, 221 | "image/ktx2" => return Ok(None), 222 | _ => bail!("unsupported image MIME Type {mime_type}"), 223 | }; 224 | 225 | (bytes, format) 226 | } 227 | gltf::image::Source::Uri { uri, .. } => { 228 | log::warn!("Skipping texture at URI {uri}"); 229 | return Ok(None); 230 | } 231 | }; 232 | 233 | let output_path = file_name(self.texture_format, self.use_supercompression, &bytes); 234 | 235 | // If this file already exists, that means that we already hashed this 236 | // image with the same configuration. We can just slurp it up and return 237 | // here! 238 | if self.use_cache && output_path.exists() { 239 | log::info!("Returning pre-compressed file!"); 240 | let file = fs_err::read(&output_path)?; 241 | 242 | return Ok(Some(file)); 243 | } 244 | 245 | // Now that we've got the image bytes, let's parse its header to see if 246 | // we need to resize it. 247 | let mut image = image::io::Reader::new(io::Cursor::new(&bytes)); 248 | image.set_format(format); 249 | let (width, height) = image.into_dimensions()?; 250 | 251 | // If the image is too big, we'll decode it, resize it and re-encode it 252 | // before passing it onto `toktx`. 253 | // 254 | // TODO: Configurable max size for images. 255 | if height > MAX_SIZE { 256 | log::warn!("Image is too large! ({width}x{height}), resizing to {MAX_SIZE}x{MAX_SIZE}"); 257 | 258 | // `into_dimensions` consumes the image reader, so we need to create 259 | // a new one for resizing. 260 | let mut image = image::io::Reader::new(io::Cursor::new(&bytes)); 261 | image.set_format(format); 262 | let mut image = image.decode()?; 263 | 264 | image = image.resize(MAX_SIZE, MAX_SIZE, image::imageops::Lanczos3); 265 | 266 | // Re-encode the image as PNG to ensure a lossless input image. 267 | let mut output = Vec::new(); 268 | let encoder = PngEncoder::new(&mut output); 269 | encoder 270 | .write_image( 271 | image.as_bytes(), 272 | image.width(), 273 | image.height(), 274 | image.color(), 275 | ) 276 | .unwrap(); 277 | bytes = Cow::Owned(output); 278 | } 279 | 280 | // Pipe the bytes through toktx, giving us spiffy KTX2 image bytes. 281 | let output = toktx( 282 | &bytes, 283 | self.texture_format, 284 | texture_type, 285 | self.use_supercompression, 286 | ) 287 | .context("failed to run toktx")?; 288 | 289 | if self.use_cache { 290 | fs_err::write(output_path, &output) 291 | .context("failed to write converted image to cache")?; 292 | } 293 | 294 | Ok(Some(output)) 295 | } 296 | 297 | fn create_glb_file(self, image_map: HashMap>) -> anyhow::Result> { 298 | // Ugh, this is going to be disgusting. 299 | let mut new_blob: Vec = Vec::new(); 300 | let blob = &self.input.blob; 301 | let mut new_buffer_views: Vec = Vec::new(); 302 | let mut new_root = self.input.document.into_json(); 303 | 304 | // First, we need to make a map that lets us find which image a bufferView points to, if any. 305 | let mut image_buffer_view_indices = HashMap::new(); 306 | for (index, image) in new_root.images.iter().enumerate() { 307 | if let Some(image_view_index) = image.buffer_view { 308 | image_buffer_view_indices.insert(image_view_index.value(), index); 309 | } 310 | } 311 | 312 | // Next, go through each buffer view and write its data into our blob. 313 | for (index, view) in new_root.buffer_views.iter_mut().enumerate() { 314 | // Stash the CURRENT length (eg before we add to it) of the new blob 315 | let new_offset = new_blob.len(); 316 | 317 | // Okay, this buffer view points to an image - we instead want to 318 | // grab the bytes of the compressed image. 319 | let bytes = image_buffer_view_indices 320 | .get(&index) 321 | .and_then(|image_index| image_map.get(image_index)) 322 | .map(|data| data.as_slice()) 323 | .unwrap_or_else(|| { 324 | // This is either not an image or is an image that isn't 325 | // part of the material model we support — just get the 326 | // original data and return it as-is. 327 | let start = view.byte_offset.unwrap_or_default() as usize; 328 | let end = start + view.byte_length as usize; 329 | &blob[start..end] 330 | }); 331 | 332 | // And write it into the new blob. 333 | new_blob.extend_from_slice(bytes); 334 | 335 | // Now create a new view and change its offset to reflect the new blob. 336 | let mut new_view = view.clone(); 337 | new_view.byte_offset = Some(new_offset as _); 338 | new_view.byte_length = bytes.len() as _; 339 | new_buffer_views.push(new_view); 340 | } 341 | 342 | // OK. Now we need to update any images that had their uri set (bufferView and uri are mutually exclusive) 343 | for (index, image) in new_root.images.iter_mut().enumerate() { 344 | // Set the MIME type 345 | image.mime_type = Some(MimeType("image/ktx2".to_string())); 346 | 347 | // This image has already been processed, we can move on. 348 | if image.uri.is_none() { 349 | continue; 350 | } 351 | 352 | // Right. As before, stash the current length of the new blob 353 | let new_offset = new_blob.len(); 354 | 355 | // Clear the URI 356 | image.uri = None; 357 | 358 | // Get the current length of the buffer views to use as an index 359 | let buffer_view_index = new_buffer_views.len(); 360 | 361 | // Now write the new image data into the blob 362 | let image_data = image_map.get(&index).unwrap(); 363 | new_blob.extend(image_data); 364 | 365 | // Create a new buffer view for this image 366 | let view = gltf::json::buffer::View { 367 | buffer: Index::new(0 as _), 368 | byte_length: image_data.len() as _, 369 | byte_offset: Some(new_offset as _), 370 | byte_stride: None, 371 | name: None, 372 | target: None, 373 | extensions: None, 374 | extras: Default::default(), 375 | }; 376 | 377 | // And add it to the list 378 | new_buffer_views.push(view); 379 | 380 | // Finally, update the image to point to this new view. 381 | image.buffer_view = Some(Index::new(buffer_view_index as _)); 382 | } 383 | 384 | // OK! We're done. Set the new root to use the new buffer views.. 385 | new_root.buffer_views = new_buffer_views; 386 | 387 | // And make sure the buffer is set correctly. 388 | new_root.buffers = vec![gltf::json::Buffer { 389 | byte_length: new_blob.len() as _, 390 | name: None, 391 | uri: None, 392 | extensions: None, 393 | extras: Default::default(), 394 | }]; 395 | 396 | // and.. that's it? Maybe? Hopefully. 397 | // This part is mostly lifted from https://github.com/gltf-rs/gltf/blob/master/examples/export/main.rs 398 | 399 | pad_byte_vector(&mut new_blob); 400 | let buffer_length = new_blob.len() as u32; 401 | let json_string = gltf::json::serialize::to_string(&new_root)?; 402 | let mut json_offset = json_string.len() as u32; 403 | align_to_multiple_of_four(&mut json_offset); 404 | 405 | let glb = gltf::binary::Glb { 406 | header: gltf::binary::Header { 407 | magic: *b"glTF", 408 | version: 2, 409 | length: json_offset + buffer_length, 410 | }, 411 | bin: Some(Cow::Owned(new_blob)), 412 | json: Cow::Owned(json_string.into_bytes()), 413 | }; 414 | 415 | // And we're done! Write the entire file to GLB. 416 | Ok(glb.to_vec()?) 417 | } 418 | } 419 | 420 | fn align_to_multiple_of_four(n: &mut u32) { 421 | *n = (*n + 3) & !3; 422 | } 423 | 424 | /// Pads the length of a byte vector to a multiple of four bytes. 425 | fn pad_byte_vector(vec: &mut Vec) { 426 | while vec.len() % 4 != 0 { 427 | vec.push(0); 428 | } 429 | } 430 | 431 | fn toktx( 432 | input_bytes: &[u8], 433 | format: TextureFormat, 434 | texture_type: TextureType, 435 | supercompress: bool, 436 | ) -> anyhow::Result> { 437 | // Create a temporary file to put our image data into. Once `toktx` supports 438 | // stdin inputs, we can remove this code. 439 | let dir = tempfile::tempdir()?; 440 | let input_path = dir.path().join("input"); 441 | fs_err::write(&input_path, input_bytes).context("failed to write to temporary file")?; 442 | 443 | let mut command = Command::new(BIN_TOKTX); 444 | command.args([ 445 | "--t2", // Use KTX2 instead of KTX. 446 | "--genmipmap", // Generate mipmaps. 447 | ]); 448 | 449 | if supercompress { 450 | // Compress with Zstandard, quality 20. 451 | command.args(["--zcmp", "20"]); 452 | } 453 | 454 | match format { 455 | TextureFormat::Rgba8 => { 456 | command.args(["--target_type", "RGBA"]); 457 | } 458 | TextureFormat::Astc => { 459 | command.args(["--encode", "astc", "--astc_blk_d"]); 460 | command.arg(texture_type.block_size()); 461 | command.args(["--astc_quality", "thorough"]); 462 | } 463 | } 464 | 465 | if texture_type == TextureType::Normal { 466 | // Generate a normalized normal map. 467 | command.args(["--normal_mode", "--normalize"]); 468 | } 469 | 470 | // Embed the correct color space into the output. 471 | command.arg("--assign_oetf"); 472 | if texture_type.is_srgb() { 473 | command.arg("srgb"); 474 | } else { 475 | command.arg("linear"); 476 | } 477 | 478 | // Write the result to stdout instead of to a file. 479 | command.arg("-"); 480 | 481 | // Use our temporary file as the input. 482 | command.arg(input_path); 483 | 484 | log::debug!( 485 | "Running {BIN_TOKTX} with args {:?}", 486 | command.get_args().collect::>() 487 | ); 488 | 489 | let mut child = command 490 | .stdin(Stdio::piped()) 491 | .stdout(Stdio::piped()) 492 | .stderr(Stdio::piped()) 493 | .spawn()?; 494 | 495 | // This unwrap is safe because we opted into piped stdin above. 496 | let mut stdin = child.stdin.take().unwrap(); 497 | stdin.write_all(input_bytes)?; 498 | 499 | let output = child.wait_with_output()?; 500 | 501 | if !output.status.success() { 502 | log::error!( 503 | "Error running toktx with args {:?}", 504 | command.get_args().collect::>() 505 | ); 506 | bail!("{}", String::from_utf8_lossy(&output.stderr)); 507 | } 508 | 509 | Ok(output.stdout) 510 | } 511 | 512 | fn cache_dir() -> PathBuf { 513 | let mut path = std::env::temp_dir(); 514 | path.push("squisher-cache"); 515 | path 516 | } 517 | 518 | // Generates a temporary file name suitable for writing a KTX2 file generated 519 | // from the given inputs. 520 | fn file_name(format: TextureFormat, supercompress: bool, file_bytes: &[u8]) -> PathBuf { 521 | let mut hasher = seahash::SeaHasher::new(); 522 | hasher.write_u8(format as _); 523 | hasher.write_u8(supercompress as _); 524 | hasher.write(file_bytes); 525 | let hash = hasher.finish(); 526 | 527 | // Format the file as 16 hexadecimal digits so that all files have a name 528 | // with the same length. 529 | let file_name = format!("{:016X}", hash); 530 | 531 | let mut path = cache_dir(); 532 | path.push(file_name); 533 | path 534 | } 535 | 536 | fn open(path: &Path) -> anyhow::Result { 537 | let reader = fs_err::File::open(path)?; 538 | 539 | match path.extension().and_then(|s| s.to_str()) { 540 | Some("gltf") => { 541 | bail!("gltf files are not currently supported, sorry!"); 542 | } 543 | Some("glb") => { 544 | let glb = gltf::Glb::from_reader(reader).context("unable to parse GLB file")?; 545 | let json = gltf::json::Root::from_slice(&glb.json)?; 546 | let document = gltf::Document::from_json(json).context("invalid JSON in GLB file")?; 547 | let blob = glb.bin.context("no data in GLB file")?.into_owned(); 548 | 549 | Ok(Input { document, blob }) 550 | } 551 | _ => { 552 | bail!( 553 | "File does not have extension gltf or glb: {}", 554 | path.display() 555 | ); 556 | } 557 | } 558 | } 559 | 560 | #[cfg(test)] 561 | mod tests { 562 | use super::*; 563 | 564 | #[test] 565 | fn glb_astc() { 566 | let args = Args { 567 | input: "test_data/BoxTexturedBinary.glb".into(), 568 | output: "test_output/BoxTexturedBinary_astc.glb".into(), 569 | format: TextureFormat::Astc, 570 | verbose: true, 571 | no_cache: true, 572 | no_supercompression: false, 573 | }; 574 | 575 | let verification = VerifyArgs { 576 | path: "test_output/BoxTexturedBinary_astc.glb", 577 | format: ktx2::Format::ASTC_6x6_SRGB_BLOCK, 578 | mip_level_count: 9, 579 | }; 580 | 581 | fs_err::create_dir_all("test_output").unwrap(); 582 | squish(args).unwrap(); 583 | verify(verification); 584 | } 585 | 586 | #[test] 587 | fn glb_rgba8() { 588 | let args = Args { 589 | input: "test_data/BoxTexturedBinary.glb".into(), 590 | output: "test_output/BoxTexturedBinary_raw.glb".into(), 591 | format: TextureFormat::Rgba8, 592 | verbose: true, 593 | no_cache: true, 594 | no_supercompression: false, 595 | }; 596 | 597 | let verification = VerifyArgs { 598 | path: "test_output/BoxTexturedBinary_raw.glb", 599 | format: ktx2::Format::R8G8B8A8_SRGB, 600 | mip_level_count: 9, 601 | }; 602 | 603 | fs_err::create_dir_all("test_output").unwrap(); 604 | squish(args).unwrap(); 605 | verify(verification); 606 | } 607 | 608 | #[test] 609 | fn already_squished() { 610 | let first_args = Args { 611 | input: "test_data/BoxTexturedBinary.glb".into(), 612 | output: "test_output/already_squished_1.glb".into(), 613 | format: TextureFormat::Rgba8, 614 | verbose: true, 615 | no_cache: true, 616 | no_supercompression: false, 617 | }; 618 | 619 | squish(first_args).unwrap(); 620 | 621 | let second_args = Args { 622 | input: "test_output/already_squished_1.glb".into(), 623 | output: "test_output/already_squished_2.glb".into(), 624 | format: TextureFormat::Rgba8, 625 | verbose: true, 626 | no_cache: true, 627 | no_supercompression: false, 628 | }; 629 | 630 | squish(second_args).unwrap(); 631 | 632 | verify(VerifyArgs { 633 | path: "test_output/already_squished_2.glb", 634 | format: ktx2::Format::R8G8B8A8_SRGB, 635 | mip_level_count: 9, 636 | }); 637 | } 638 | 639 | struct VerifyArgs { 640 | path: &'static str, 641 | format: ktx2::Format, 642 | mip_level_count: u32, 643 | } 644 | 645 | fn verify(args: VerifyArgs) { 646 | let path: &Path = args.path.as_ref(); 647 | assert!(path.exists()); 648 | 649 | let input = open(path).unwrap(); 650 | for image in input.document.images() { 651 | match image.source() { 652 | gltf::image::Source::View { view, .. } => { 653 | // Get the image, then make sure it was compressed correctly. 654 | let bytes = &input.blob[view.offset()..view.offset() + view.length()]; 655 | let reader = ktx2::Reader::new(bytes).unwrap(); 656 | let header = reader.header(); 657 | 658 | assert_eq!(header.format, Some(args.format)); 659 | assert_eq!(header.level_count, args.mip_level_count); 660 | } 661 | _ => unreachable!(), 662 | } 663 | } 664 | } 665 | } 666 | -------------------------------------------------------------------------------- /test_data/BoxTextured.gltf: -------------------------------------------------------------------------------- 1 | { 2 | "asset": { 3 | "generator": "COLLADA2GLTF", 4 | "version": "2.0" 5 | }, 6 | "scene": 0, 7 | "scenes": [ 8 | { 9 | "nodes": [ 10 | 0 11 | ] 12 | } 13 | ], 14 | "nodes": [ 15 | { 16 | "name": "Box", 17 | "children": [ 18 | 1 19 | ], 20 | "matrix": [ 21 | 1.0, 22 | 0.0, 23 | 0.0, 24 | 0.0, 25 | 0.0, 26 | 0.0, 27 | -1.0, 28 | 0.0, 29 | 0.0, 30 | 1.0, 31 | 0.0, 32 | 0.0, 33 | 0.0, 34 | 0.0, 35 | 0.0, 36 | 1.0 37 | ] 38 | }, 39 | { 40 | "mesh": 0 41 | } 42 | ], 43 | "meshes": [ 44 | { 45 | "primitives": [ 46 | { 47 | "attributes": { 48 | "NORMAL": 1, 49 | "POSITION": 2, 50 | "TEXCOORD_0": 3 51 | }, 52 | "indices": 0, 53 | "mode": 4, 54 | "material": 0 55 | } 56 | ], 57 | "name": "Mesh" 58 | } 59 | ], 60 | "accessors": [ 61 | { 62 | "bufferView": 0, 63 | "byteOffset": 0, 64 | "componentType": 5123, 65 | "count": 36, 66 | "max": [ 67 | 23 68 | ], 69 | "min": [ 70 | 0 71 | ], 72 | "type": "SCALAR" 73 | }, 74 | { 75 | "bufferView": 1, 76 | "byteOffset": 0, 77 | "componentType": 5126, 78 | "count": 24, 79 | "max": [ 80 | 1.0, 81 | 1.0, 82 | 1.0 83 | ], 84 | "min": [ 85 | -1.0, 86 | -1.0, 87 | -1.0 88 | ], 89 | "type": "VEC3" 90 | }, 91 | { 92 | "bufferView": 1, 93 | "byteOffset": 288, 94 | "componentType": 5126, 95 | "count": 24, 96 | "max": [ 97 | 0.5, 98 | 0.5, 99 | 0.5 100 | ], 101 | "min": [ 102 | -0.5, 103 | -0.5, 104 | -0.5 105 | ], 106 | "type": "VEC3" 107 | }, 108 | { 109 | "bufferView": 2, 110 | "byteOffset": 0, 111 | "componentType": 5126, 112 | "count": 24, 113 | "max": [ 114 | 6.0, 115 | 1.0 116 | ], 117 | "min": [ 118 | 0.0, 119 | 0.0 120 | ], 121 | "type": "VEC2" 122 | } 123 | ], 124 | "materials": [ 125 | { 126 | "pbrMetallicRoughness": { 127 | "baseColorTexture": { 128 | "index": 0 129 | }, 130 | "metallicFactor": 0.0 131 | }, 132 | "name": "Texture" 133 | } 134 | ], 135 | "textures": [ 136 | { 137 | "sampler": 0, 138 | "source": 0 139 | } 140 | ], 141 | "images": [ 142 | { 143 | "uri": "data:image/png;base64,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" 144 | } 145 | ], 146 | "samplers": [ 147 | { 148 | "magFilter": 9729, 149 | "minFilter": 9986, 150 | "wrapS": 10497, 151 | "wrapT": 10497 152 | } 153 | ], 154 | "bufferViews": [ 155 | { 156 | "buffer": 0, 157 | "byteOffset": 768, 158 | "byteLength": 72, 159 | "target": 34963 160 | }, 161 | { 162 | "buffer": 0, 163 | "byteOffset": 0, 164 | "byteLength": 576, 165 | "byteStride": 12, 166 | "target": 34962 167 | }, 168 | { 169 | "buffer": 0, 170 | "byteOffset": 576, 171 | "byteLength": 192, 172 | "byteStride": 8, 173 | "target": 34962 174 | } 175 | ], 176 | "buffers": [ 177 | { 178 | "byteLength": 840, 179 | "uri": "data:application/octet-stream;base64,AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAvwAAAL8AAAA/AAAAPwAAAL8AAAA/AAAAvwAAAD8AAAA/AAAAPwAAAD8AAAA/AAAAPwAAAD8AAAA/AAAAPwAAAL8AAAA/AAAAPwAAAD8AAAC/AAAAPwAAAL8AAAC/AAAAvwAAAD8AAAA/AAAAPwAAAD8AAAA/AAAAvwAAAD8AAAC/AAAAPwAAAD8AAAC/AAAAPwAAAL8AAAA/AAAAvwAAAL8AAAA/AAAAPwAAAL8AAAC/AAAAvwAAAL8AAAC/AAAAvwAAAL8AAAA/AAAAvwAAAD8AAAA/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAPwAAAL8AAAC/AAAAPwAAAD8AAAC/AADAQAAAAAAAAKBAAAAAAAAAwED+/38/AACgQP7/fz8AAIBAAAAAAAAAoEAAAAAAAACAQAAAgD8AAKBAAACAPwAAAEAAAAAAAACAPwAAAAAAAABAAACAPwAAgD8AAIA/AABAQAAAAAAAAIBAAAAAAAAAQEAAAIA/AACAQAAAgD8AAEBAAAAAAAAAAEAAAAAAAABAQAAAgD8AAABAAACAPwAAAAAAAAAAAAAAAP7/fz8AAIA/AAAAAAAAgD/+/38/AAABAAIAAwACAAEABAAFAAYABwAGAAUACAAJAAoACwAKAAkADAANAA4ADwAOAA0AEAARABIAEwASABEAFAAVABYAFwAWABUA" 180 | } 181 | ] 182 | } 183 | -------------------------------------------------------------------------------- /test_data/BoxTexturedBinary.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetvr/squisher/832bc07098036e4f3e1884adc99f49aa7c72852f/test_data/BoxTexturedBinary.glb -------------------------------------------------------------------------------- /test_data/BoxTexturedBinary_img0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetvr/squisher/832bc07098036e4f3e1884adc99f49aa7c72852f/test_data/BoxTexturedBinary_img0.png --------------------------------------------------------------------------------