Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,21 @@ jobs:
- name: Doc
run: cargo doc --workspace --all-features --no-deps --document-private-items --keep-going

- name: Build Debug
if: ${{ matrix.os != 'ubuntu-latest' }}
run: cargo build

- name: Test Debug
if: ${{ matrix.os != 'ubuntu-latest' }}
run: cargo test --workspace

- name: Build
if: ${{ matrix.os == 'ubuntu-latest' }}
run: cargo build -r

- name: Test
run: cargo test -r
if: ${{ matrix.os == 'ubuntu-latest' }}
run: cargo test --workspace -r

build-wasm:
name: Build wasm
Expand Down Expand Up @@ -78,7 +88,7 @@ jobs:
path: crates/bevy_basisu_loader_sys/wasm/

- name: Build wasm
run: RUSTFLAGS="-Ctarget-feature=+simd128" cargo build --target wasm32-unknown-unknown
run: RUSTFLAGS="-Ctarget-feature=+simd128" cargo build -p test_scene --target wasm32-unknown-unknown

build-android:
name: Build android
Expand Down Expand Up @@ -106,4 +116,4 @@ jobs:
export ANDROID_NDK_HOME=${ANDROID_HOME}/ndk/${{env.ANDROID_NDK_VERSION}} &&
export ANDROID_NDK_ROOT=${ANDROID_NDK_HOME} &&
cargo ndk-env -t arm64-v8a &&
cargo ndk -t arm64-v8a build --features bevy/android-game-activity
cargo ndk -t arm64-v8a -P 26 build --features bevy/android-game-activity
10 changes: 9 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ env:
DRY_RUN: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run }}

jobs:
release:
release-basisu-loader:
name: Release ${{ (github.event_name == 'workflow_dispatch' && inputs.dry_run && '(Dry Run)') || '' }}
runs-on: ubuntu-latest
steps:
Expand All @@ -44,6 +44,7 @@ jobs:

- name: Build vendored basisu to wasm
run: cargo r -p bevy_basisu_loader_sys --bin build-wasm-cli --features build-wasm-cli -- --emcc-flags="-Os -msimd128 -flto=full -sEVAL_CTORS" --wasm-opt-flags="-Os --enable-simd --enable-bulk-memory-opt --enable-nontrapping-float-to-int"

- name: Upload artifact
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
Expand All @@ -56,10 +57,17 @@ jobs:

- uses: katyo/publish-crates@v2
with:
path: crates/bevy_basisu_loader_sys
args: --allow-dirty # TODO: cargo doesn't respect `.gitignore`. See issue https://github.com/rust-lang/cargo/issues/16547
registry-token: ${{ steps.auth.outputs.token }}
dry-run: ${{ env.DRY_RUN }}

- uses: katyo/publish-crates@v2
with:
path: crates/bevy_basisu_loader
registry-token: ${{ steps.auth.outputs.token }}
dry-run: ${{ env.DRY_RUN }}

- name: Create GitHub release
if: ${{ env.DRY_RUN == 'false' }}
uses: softprops/action-gh-release@v2
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ exclude: |
.*thirdparty/.*|
.*\.svg|
vendor/basis_universal/.*|
crates/bevy_basisu_loader_sys/src/snapshots/.*|
.*snapshots/.*|
)$
repos:
- repo: https://github.com/codespell-project/codespell
Expand Down
5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ repository = "https://github.com/beicause/bevy_basisu_loader"
license = "MIT OR Apache-2.0"

[workspace]
members = ["crates/*", "examples/test_scene"]
members = ["crates/*", "examples/*"]
resolver = "3"

[profile.dev]
opt-level = 1

[profile.web_release]
inherits = "release"
opt-level = "s"
Expand Down
90 changes: 6 additions & 84 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,85 +1,7 @@
# Bevy KTX2 BasisU texture loader
# Bevy KTX2 BasisU texture loader and saver

[![Build](https://github.com/beicause/bevy_basisu_loader/actions/workflows/ci.yml/badge.svg)](https://github.com/beicause/bevy_basisu_loader/actions)
[![License](https://img.shields.io/badge/license-Apache--2.0_OR_MIT-blue.svg)](https://github.com/beicause/bevy_basisu_loader)
[![Cargo](https://img.shields.io/crates/v/bevy_basisu_loader.svg)](https://crates.io/crates/bevy_basisu_loader)
[![Documentation](https://docs.rs/bevy_basisu_loader/badge.svg)](https://docs.rs/bevy_basisu_loader)

A lightweight, cross-platform Bevy plugin that provides a KTX2 Basis Universal texture loader.

Although Bevy's `ImageLoader` has built-in support for Basis Universal textures via the [`basis-universal-rs`](https://github.com/aclysma/basis-universal-rs) crate, it has some limitations:
1. It uses a very old version of [Basis Universal][].
2. No support for UASTC HDR yet, either ASTC, XUASTC which are added in basis universal v2.0.
3. No support for Web. Bevy can't be compiled to `wasm32-unknown-emscripten` and `basis-universal-rs` can't be compiled to `wasm32-unknown-unknown`.
4. It compiles both the encoder and transcoder and includes transcoding formats not supported by wgpu, which increases binary size.

This plugin adds a loader for Basis Universal KTX2 textures with support for all formats supported by basis universal v2.0 (ETC1S, UASTC, ASTC, XUASTC), and web support through JavaScript glue to call [Basis Universal][] C++ library compiled with Emscripten which includes only the transcoder and necessary transcoding formats.

Note: This doesn't include BasisU encoder. To encode textures to `.ktx2`, use the command line tool in [Basis Universal](https://github.com/BinomialLLC/basis_universal/?tab=readme-ov-file#compressing-and-unpacking-ktx2basis-files) repo.

## Usage

1. Add the Cargo dependency:
```sh
cargo add bevy_basisu_loader
```

2. Add `BasisuLoaderPlugin`:
```rs
use bevy_basisu_loader::BasisuLoaderPlugin;

pub fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugins(BasisuLoaderPlugin);
}
```

1. Load ktx2 basis universal textures. Supports `D2`, `D2Array` and `Cube` texture types. Zstd supercompression is supported. Note: Only supports `.ktx2` format, `.basis` format is not supported.
```rs
let image_handle = asset_server.load("gl_skybox_etc1s_cubemap_mips_12.basisu.ktx2");
```

⚠️Note: you have to rename the file extension to `.basisu.ktx2` to load it with this `BasisuLoader`, otherwise bevy will load `.ktx2` files with the builtin `ImageLoader`.

⚠️Note: The compressed texture dimensions must be a multiplier of block size. See https://github.com/gfx-rs/wgpu/issues/7677 for more context. Also because basisu can transcode to textures with different block size on different platforms,
the texture dimensions should satisfy all possible block sizes. For example, XUASTC 6x6 can transcode to ASTC 6x6 and BC7, so its dimensions should be a multiplier of 12.

## Test status of this repository

This repository contains snapshot tests for decoding BasisU textures in CI. Also a web demo is deployed: https://beicause.github.io/bevy_basisu_loader

## Run on web

TLDR: Just build your bevy application to `wasm32-unknown-unknown` normally.

The prebuilt wasm in `crates/bevy_basisu_loader_sys/wasm` is automatically embedded in binary when building `wasm32-unknown-unknown`. It was prebuilt through CI with:
```sh
cargo r -p bevy_basisu_loader_sys --bin build-wasm-cli --features build-wasm-cli -- --emcc-flags="-Os -msimd128 -flto=full -sEVAL_CTORS" --wasm-opt-flags="-Os --enable-simd --enable-bulk-memory-opt --enable-nontrapping-float-to-int"
```

To run on web, this repo uses a solution:

The `crates/bevy_basisu_loader_sys/` contains a high level wrapper of the basis universal C++ library.

For native platforms, it just builds and statically links the C++ library.

For web, it contains a tool to build vendored basisu using Emscripten and produce js and wasm files. The basisu wrapper is designed so that it does not need to share memory with main Wasm module, instead its memory is copied from/into main Wasm module through javascript. When building this plugin targeting `wasm32-unknown-unknown`, the basisu js and wasm files are embedded into binary and is called through `wasm-bindgen` and `js-sys`.

## Bevy version compatibility

| `bevy` | `bevy_basisu_loader` | `basis_universal` |
| ------ | -------------------- | ----------------- |
| 0.18 | 0.3, 0.4 | v2_1_0 |
| 0.17 | 0.1, 0.2 | v1_60_snapshot |

## License

Except where noted (below and/or in individual files), all code in this repository is dual-licensed under either:

* MIT License ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT))
* Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0))

at your option.

[Basis Universal]: https://github.com/BinomialLLC/basis_universal/
| crate | description |
| --- | ------------- |
| [bevy_basisu_loader](./crates/bevy_basisu_loader)| Bevy basisu texture loader|
| [basisu_c_sys](./crates/basisu_c_sys)| Raw Rust binding for basisu pure C API|
| [bevy_basisu_saver](./crates/bevy_basisu_saver/)| Bevy basisu saver and asset processor |
15 changes: 15 additions & 0 deletions crates/basisu_c_sys/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "basisu_c_sys"
version = "0.1.4"
edition = "2024"
repository.workspace = true
license.workspace = true
description = "Raw Rust ffi binding for the Basis Universal pure C API"
keywords = ["basis-universal", "ffi"]
include = ["vendor/basis_universal", "build.rs", "README.md", "src/*.rs"]

[dependencies]

[build-dependencies]
cc = "1.2"
bindgen = "0.72"
10 changes: 10 additions & 0 deletions crates/basisu_c_sys/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Raw Rust binding for the Basis Universal pure C API.

[![Build](https://github.com/beicause/bevy_basisu_loader/actions/workflows/ci.yml/badge.svg)](https://github.com/beicause/bevy_basisu_loader/actions)
[![License](https://img.shields.io/badge/license-Apache--2.0_OR_MIT-blue.svg)](https://github.com/beicause/bevy_basisu_loader)
[![Cargo](https://img.shields.io/crates/v/basisu_c_sys.svg)](https://crates.io/crates/basisu_c_sys)
[![Documentation](https://docs.rs/basisu_c_sys/badge.svg)](https://docs.rs/basisu_c_sys)

See also https://github.com/BinomialLLC/basis_universal/wiki#encoder-and-transcoding-c-api-documentation

This doesn't support `wasm32-unknown-unknown`.
116 changes: 116 additions & 0 deletions crates/basisu_c_sys/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
const FLAGS: &[&str] = &[
"-w",
"-fno-exceptions",
// Fix gcc optimization issue.
// See vendor/basis_universal/transcoder/basisu.h
// See https://github.com/godotengine/godot/pull/114839
"-fno-strict-aliasing",
];

// Disable PVRTC1/2, ATC, FXT1 as wgpu does not support them.
const DEFINES: &[(&str, &str)] = &[
// ("BASISU_FORCE_DEVEL_MESSAGES", "1"), // Enable debug message.
// ("BASISD_SUPPORT_KTX2", "1"),
// ("BASISD_SUPPORT_KTX2_ZSTD", "1"),
// ("BASISD_SUPPORT_UASTC", "1"),
("BASISD_SUPPORT_DXT1", "0"), //(BC1)
// ("BASISD_SUPPORT_DXT5A", "1"), //(BC3 / 4 / 5)
// ("BASISD_SUPPORT_BC7", "1"),
// ("BASISD_SUPPORT_BC7_MODE5", "1"),
("BASISD_SUPPORT_PVRTC1", "0"),
// ("BASISD_SUPPORT_ETC2_EAC_A8", "1"),
// ("BASISD_SUPPORT_ASTC", "1"),
// ("BASISD_SUPPORT_XUASTC", "1"),
("BASISD_SUPPORT_ATC", "0"),
// ("BASISD_SUPPORT_ETC2_EAC_RG11", "1"),
// ("BASISD_SUPPORT_ASTC_HIGHER_OPAQUE_QUALITY", "1"),
("BASISD_SUPPORT_FXT1", "0"),
("BASISD_SUPPORT_PVRTC2", "0"),
// ("BASISD_SUPPORT_UASTC_HDR", "1"),
];
const SRCS: &[&str] = &[
"vendor/basis_universal/encoder/basisu_astc_hdr_6x6_enc.cpp",
"vendor/basis_universal/encoder/basisu_astc_hdr_common.cpp",
"vendor/basis_universal/encoder/basisu_astc_ldr_common.cpp",
"vendor/basis_universal/encoder/basisu_astc_ldr_encode.cpp",
"vendor/basis_universal/encoder/basisu_backend.cpp",
"vendor/basis_universal/encoder/basisu_basis_file.cpp",
"vendor/basis_universal/encoder/basisu_bc7enc.cpp",
"vendor/basis_universal/encoder/basisu_comp.cpp",
"vendor/basis_universal/encoder/basisu_enc.cpp",
"vendor/basis_universal/encoder/basisu_etc.cpp",
"vendor/basis_universal/encoder/basisu_frontend.cpp",
"vendor/basis_universal/encoder/basisu_gpu_texture.cpp",
"vendor/basis_universal/encoder/basisu_kernels_sse.cpp",
"vendor/basis_universal/encoder/basisu_opencl.cpp",
"vendor/basis_universal/encoder/basisu_pvrtc1_4.cpp",
"vendor/basis_universal/encoder/basisu_resample_filters.cpp",
"vendor/basis_universal/encoder/basisu_resampler.cpp",
"vendor/basis_universal/encoder/basisu_ssim.cpp",
"vendor/basis_universal/encoder/basisu_uastc_enc.cpp",
"vendor/basis_universal/encoder/basisu_uastc_hdr_4x4_enc.cpp",
"vendor/basis_universal/encoder/basisu_wasm_api.cpp",
"vendor/basis_universal/encoder/basisu_wasm_transcoder_api.cpp",
"vendor/basis_universal/encoder/jpgd.cpp",
"vendor/basis_universal/encoder/pvpngreader.cpp",
"vendor/basis_universal/encoder/3rdparty/android_astc_decomp.cpp",
"vendor/basis_universal/encoder/3rdparty/tinyexr.cpp",
"vendor/basis_universal/transcoder/basisu_transcoder.cpp",
"vendor/basis_universal/zstd/zstd.c",
];

fn main() {
bindgen();
let target = std::env::var("TARGET").unwrap();
if target != "wasm32-unknown-unknown" {
compile_basisu_static();
} else {
panic!("basisu_c_sys doesn't support wasm32-unknown-unknown yet");
}
println!("cargo::rerun-if-changed=vendor/");
}

fn bindgen() {
let binding_file =
std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap()).join("basisu_c_api.rs");
bindgen::Builder::default()
.clang_args(&["-fvisibility=default"])
.header("vendor/basis_universal/encoder/basisu_wasm_api.h")
.use_core()
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.generate()
.expect("Unable to generate bindings")
.write_to_file(binding_file)
.expect("Couldn't write bindings!");

let binding_file = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap())
.join("basisu_c_transcoder_api.rs");
bindgen::Builder::default()
.clang_args(&["-fvisibility=default"])
.header("vendor/basis_universal/encoder/basisu_wasm_transcoder_api.h")
.use_core()
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.generate()
.expect("Unable to generate bindings")
.write_to_file(binding_file)
.expect("Couldn't write bindings!");
}

fn compile_basisu_static() {
let mut build = cc::Build::new();

// Use c++_static for Android.
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
if target_os == "android" {
build.cpp_link_stdlib("c++_static").flag("-U_GNU_SOURCE");
}

build.cpp(true).std("c++17");
for f in FLAGS {
build.flag_if_supported(f);
}
for (define, value) in DEFINES {
build.define(define, *value);
}
build.files(SRCS).compile("basisu_c_api_vendor");
}
11 changes: 11 additions & 0 deletions crates/basisu_c_sys/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//! Raw Rust ffi binding for the Basis Universal pure C API.

#[expect(nonstandard_style, reason = "Generated code is ok")]
pub mod encoder {
include!(concat!(env!("OUT_DIR"), "/basisu_c_api.rs"));
}

#[expect(nonstandard_style, reason = "Generated code is ok")]
pub mod transcoder {
include!(concat!(env!("OUT_DIR"), "/basisu_c_transcoder_api.rs"));
}
1 change: 1 addition & 0 deletions crates/basisu_c_sys/vendor
File renamed without changes.
1 change: 0 additions & 1 deletion crates/bevy_basisu_loader/README.md

This file was deleted.

Loading
Loading