From ce33cd37dcf0e9c056bdf3ff8ce4fe11864ae597 Mon Sep 17 00:00:00 2001 From: Luo Zhihao Date: Tue, 10 Feb 2026 15:06:51 +0800 Subject: [PATCH 1/3] Use `core`/`alloc` instead of `std` --- crates/basisu_sys/src/lib.rs | 12 +++++++++++- crates/basisu_sys/src/native.rs | 4 +++- crates/basisu_sys/src/web.rs | 10 +++++----- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/crates/basisu_sys/src/lib.rs b/crates/basisu_sys/src/lib.rs index 93663f5..01d65b3 100644 --- a/crates/basisu_sys/src/lib.rs +++ b/crates/basisu_sys/src/lib.rs @@ -1,3 +1,13 @@ +#![cfg_attr( + not(all( + target_arch = "wasm32", + target_vendor = "unknown", + target_os = "unknown", + )), + no_std +)] +extern crate alloc; + #[expect( non_upper_case_globals, non_camel_case_types, @@ -11,7 +21,7 @@ ), expect( unused, - reason = "On wasm32 we use js bindings so native functions are unused" + reason = "On wasm32 we use js bindings thus native functions are expected to be unused" ) )] mod transcoding { diff --git a/crates/basisu_sys/src/native.rs b/crates/basisu_sys/src/native.rs index 003a129..75045b5 100644 --- a/crates/basisu_sys/src/native.rs +++ b/crates/basisu_sys/src/native.rs @@ -1,5 +1,7 @@ #![expect(clippy::missing_safety_doc, reason = "TODO")] +use alloc::{vec, vec::Vec}; + use crate::ChannelType; use crate::SupportedTextureCompressionMethods; use crate::TextureTranscodedFormat; @@ -39,6 +41,6 @@ pub unsafe fn ktx2_transcoder_get_r_dst_buf(transcoder: *mut Transcoder) -> Vec< let ptr = unsafe { crate::transcoding::c_ktx2_transcoder_get_r_dst_buf(transcoder) }; let len = unsafe { crate::transcoding::c_ktx2_transcoder_get_r_dst_buf_len(transcoder) }; let mut ret = vec![0; len as usize]; - unsafe { std::ptr::copy_nonoverlapping(ptr, ret.as_mut_ptr(), len as usize) }; + unsafe { core::ptr::copy_nonoverlapping(ptr, ret.as_mut_ptr(), len as usize) }; ret } diff --git a/crates/basisu_sys/src/web.rs b/crates/basisu_sys/src/web.rs index e967341..25f135a 100644 --- a/crates/basisu_sys/src/web.rs +++ b/crates/basisu_sys/src/web.rs @@ -61,27 +61,27 @@ mod bindings_sys { pub fn js_ktx2_transcoder_get_r_width( this: &BasisuVendor, transcoder: *mut Transcoder, - ) -> ::std::os::raw::c_uint; + ) -> ::core::ffi::c_uint; #[wasm_bindgen(method,js_name=_c_ktx2_transcoder_get_r_height)] pub fn js_ktx2_transcoder_get_r_height( this: &BasisuVendor, transcoder: *mut Transcoder, - ) -> ::std::os::raw::c_uint; + ) -> ::core::ffi::c_uint; #[wasm_bindgen(method,js_name=_c_ktx2_transcoder_get_r_levels)] pub fn js_ktx2_transcoder_get_r_levels( this: &BasisuVendor, transcoder: *mut Transcoder, - ) -> ::std::os::raw::c_uint; + ) -> ::core::ffi::c_uint; #[wasm_bindgen(method,js_name=_c_ktx2_transcoder_get_r_layers)] pub fn js_ktx2_transcoder_get_r_layers( this: &BasisuVendor, transcoder: *mut Transcoder, - ) -> ::std::os::raw::c_uint; + ) -> ::core::ffi::c_uint; #[wasm_bindgen(method,js_name=_c_ktx2_transcoder_get_r_faces)] pub fn js_ktx2_transcoder_get_r_faces( this: &BasisuVendor, transcoder: *mut Transcoder, - ) -> ::std::os::raw::c_uint; + ) -> ::core::ffi::c_uint; #[wasm_bindgen(method,js_name=_c_ktx2_transcoder_get_r_target_format)] pub fn js_ktx2_transcoder_get_r_target_format( this: &BasisuVendor, From ece2a9365050088cc3b7ac7a7f8514bfc7c9e501 Mon Sep 17 00:00:00 2001 From: Luo Zhihao Date: Tue, 10 Feb 2026 15:58:28 +0800 Subject: [PATCH 2/3] Guarantee basisu wasm is initialized during plugin adding --- .github/workflows/ci.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/static.yml | 2 +- README.md | 2 +- crates/basisu_sys/src/bin/build_wasm_cli.rs | 8 +- examples/test_scene/src/lib.rs | 5 ++ src/lib.rs | 86 +++++++++++++++------ src/loader.rs | 2 +- 8 files changed, 77 insertions(+), 32 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e26f98a..c96f685 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -70,7 +70,7 @@ jobs: - name: Install wasm-opt run: wget https://github.com/WebAssembly/binaryen/releases/download/version_125/binaryen-version_125-x86_64-linux.tar.gz && tar -xvf binaryen-version_125-x86_64-linux.tar.gz && sudo cp binaryen-version_125/bin/wasm-opt /usr/local/bin/ && rm -r binaryen-version_125 && rm binaryen-version_125-x86_64-linux.tar.gz - - name: Build basisu vendor wasm + - 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 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 65a85d6..d8da8f3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -44,7 +44,7 @@ jobs: toolchain: stable targets: wasm32-unknown-unknown - - name: Build basisu vendor wasm + - 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 diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index 944191a..f0ec766 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -56,7 +56,7 @@ jobs: - name: Install wasm-bindgen run: cargo binstall --force wasm-bindgen-cli - - name: Build basisu vendor wasm + - 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 diff --git a/README.md b/README.md index 1045632..fc1b736 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ The `crates/basisu_sys/` contains a high level wrapper of the basis universal C+ For native platforms, it just builds and statically links the C++ library. -For web, it contains a tool to build basisu vendor 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 vendor js and wasm files are embedded into binary and is called through `wasm-bindgen` and `js-sys`. +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 diff --git a/crates/basisu_sys/src/bin/build_wasm_cli.rs b/crates/basisu_sys/src/bin/build_wasm_cli.rs index e757ab0..6edb943 100644 --- a/crates/basisu_sys/src/bin/build_wasm_cli.rs +++ b/crates/basisu_sys/src/bin/build_wasm_cli.rs @@ -26,7 +26,7 @@ pub fn build_wasm_cmd() { } println!( - "Building basisu vendor wasm: {:#?}", + "Building vendored basisu to wasm: {:#?}", [emcc_cmd.get_program()] .iter() .map(|s| s.to_string_lossy().to_string()) @@ -39,7 +39,7 @@ pub fn build_wasm_cmd() { if !exit_status.success() { panic!("emcc didn't exit with success status: {}", exit_status); } else { - println!("Build basisu vendor wasm successfully"); + println!("Build vendored basisu to wasm successfully"); } if let Some(flags) = user_args.wasm_opt_flags { @@ -49,7 +49,7 @@ pub fn build_wasm_cmd() { wasm_opt_cmd.args(flags.split(" ").filter(|s| !s.is_empty())); println!( - "Optimizing basisu vendor wasm: {:#?}", + "Optimizing basisu wasm: {:#?}", [wasm_opt_cmd.get_program()] .iter() .map(|s| s.to_string_lossy().to_string()) @@ -66,7 +66,7 @@ pub fn build_wasm_cmd() { if !exit_status.success() { panic!("wasm-opt didn't exit with success status: {}", exit_status); } else { - println!("Optimize basisu vendor wasm successfully"); + println!("Optimize basisu wasm successfully"); } } } diff --git a/examples/test_scene/src/lib.rs b/examples/test_scene/src/lib.rs index 5ec4c95..356796b 100644 --- a/examples/test_scene/src/lib.rs +++ b/examples/test_scene/src/lib.rs @@ -1,4 +1,5 @@ use bevy::{ + asset::AssetMetaCheck, core_pipeline::{Skybox, tonemapping::Tonemapping}, log::LogPlugin, math::Affine2, @@ -21,6 +22,10 @@ pub fn main() { }), ..default() }) + .set(AssetPlugin { + meta_check: AssetMetaCheck::Never, + ..Default::default() + }) .set(LogPlugin { filter: "bevy_basisu_loader=debug".to_string(), ..Default::default() diff --git a/src/lib.rs b/src/lib.rs index 9ee75a2..22ad89f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,16 @@ use bevy::prelude::*; use bevy::render::{RenderApp, renderer::RenderDevice}; +#[cfg(all( + target_arch = "wasm32", + target_vendor = "unknown", + target_os = "unknown", +))] +use bevy::platform::sync::{ + Arc, + atomic::{AtomicBool, Ordering}, +}; + mod loader; pub use loader::*; @@ -9,7 +19,7 @@ pub use loader::*; /// /// The file extension must be `.basisu.ktx2` to use this loader. All basis universal compressed formats (ETC1S, UASTC, XUASTC) are supported. Zstd supercompression is always supported. No support for `.basis` files. /// -/// Transcode Target Selection: +/// Default transcode target selection: /// /// | BasisU format | target selection | /// | ------------------------------ | -------------------------------------------------------------- | @@ -19,33 +29,63 @@ pub use loader::*; /// pub struct BasisuLoaderPlugin; +#[cfg(all( + target_arch = "wasm32", + target_vendor = "unknown", + target_os = "unknown", +))] +#[derive(Resource, Clone, Deref)] +struct BasisuWasmReady(Arc); + impl Plugin for BasisuLoaderPlugin { fn build(&self, app: &mut App) { - app.preregister_asset_loader::(&["basisu.ktx2"]) - .add_systems(PreStartup, || { - #[cfg(all( - target_arch = "wasm32", - target_vendor = "unknown", - target_os = "unknown", - ))] - bevy::tasks::IoTaskPool::get() - .spawn_local(async { - bevy_basisu_loader_sys::basisu_sys_init_vendor().await; - unsafe { bevy_basisu_loader_sys::basisu_transcoder_init() }; - }) - .detach(); - #[cfg(not(all( - target_arch = "wasm32", - target_vendor = "unknown", - target_os = "unknown", - )))] - unsafe { - bevy_basisu_loader_sys::basisu_transcoder_init() - }; - }); + #[cfg(all( + target_arch = "wasm32", + target_vendor = "unknown", + target_os = "unknown", + ))] + { + let ready = BasisuWasmReady(Arc::new(AtomicBool::new(false))); + let r = ready.clone(); + bevy::tasks::IoTaskPool::get() + .spawn_local(async move { + bevy_basisu_loader_sys::basisu_sys_init_vendor().await; + unsafe { bevy_basisu_loader_sys::basisu_transcoder_init() }; + r.store(true, Ordering::Release); + bevy::log::debug!("Basisu wasm initialized") + }) + .detach(); + app.insert_resource(ready); + } + #[cfg(not(all( + target_arch = "wasm32", + target_vendor = "unknown", + target_os = "unknown", + )))] + unsafe { + bevy_basisu_loader_sys::basisu_transcoder_init() + }; + app.preregister_asset_loader::(&["basisu.ktx2"]); + } + + #[cfg(all( + target_arch = "wasm32", + target_vendor = "unknown", + target_os = "unknown", + ))] + fn ready(&self, app: &App) -> bool { + app.world() + .resource::() + .load(Ordering::Acquire) } fn finish(&self, app: &mut App) { + #[cfg(all( + target_arch = "wasm32", + target_vendor = "unknown", + target_os = "unknown", + ))] + app.world_mut().remove_resource::(); let device = app .sub_app_mut(RenderApp) .world() diff --git a/src/loader.rs b/src/loader.rs index 7f9a3db..d379449 100644 --- a/src/loader.rs +++ b/src/loader.rs @@ -72,7 +72,7 @@ pub struct BasisuLoaderSettings { pub channel_type_hint: ChannelType, /// Forcibly transcode to a specific [`TextureFormat`]. If `None` the target format is selected automatically. /// - /// Only set this if you know what you're doing and use this with caution, it will fail if the transcode target is not supported by Basis Universal or the texture format is not supported by the device. + /// One use case is transcoding HDR textures to [`TextureFormat::Rgb9e5Ufloat`]. Use this with caution, it will fail if the transcode target is not supported by Basis Universal or the texture format is not supported by the device. /// The srgb-ness of the texture format is ignored and will be determined by `is_srgb`. pub force_transcode_target: Option, } From 7e511938ba09226e1bb359499ca7da2def4d4d16 Mon Sep 17 00:00:00 2001 From: Luo Zhihao Date: Tue, 10 Feb 2026 16:51:42 +0800 Subject: [PATCH 3/3] Improve test scene --- examples/test_scene/src/lib.rs | 63 +++++++++++++--------------------- web/index.html | 6 ++-- 2 files changed, 26 insertions(+), 43 deletions(-) diff --git a/examples/test_scene/src/lib.rs b/examples/test_scene/src/lib.rs index 356796b..6ab8900 100644 --- a/examples/test_scene/src/lib.rs +++ b/examples/test_scene/src/lib.rs @@ -49,8 +49,7 @@ fn setup( Camera3d::default(), Hdr, Tonemapping::None, - Msaa::Off, - Transform::from_xyz(0.0, 0.0, 0.0).looking_at(Vec3::ZERO, Vec3::Y), + Transform::from_xyz(0.0, 0.0, 3.0).looking_at(Vec3::ZERO, Vec3::Y), Skybox { image: skybox_handle.clone(), brightness: 1000.0, @@ -65,7 +64,7 @@ fn setup( unlit: true, ..Default::default() })), - Transform::from_xyz(-1.0, 0.0, -2.5), + Transform::from_xyz(-1.0, 1.0, -2.5), )); commands.spawn(( @@ -75,7 +74,7 @@ fn setup( unlit: true, ..Default::default() })), - Transform::from_xyz(1.0, 0.0, -5.0), + Transform::from_xyz(1.0, 1.0, -5.0), )); commands.spawn(( @@ -97,8 +96,7 @@ fn setup( unlit: true, ..Default::default() })), - Transform::from_xyz(3.0, 0.0, -3.0) - * Transform::from_rotation(Quat::from_rotation_y(-45.0)), + Transform::from_xyz(3.0, 1.0, -5.0).with_rotation(Quat::from_rotation_y(-0.5)), )); commands.spawn(( @@ -114,49 +112,34 @@ fn setup( unlit: true, ..Default::default() })), - Transform::from_xyz(-2.0, 0.0, -2.0) - * Transform::from_rotation(Quat::from_rotation_y(45.0)), + Transform::from_xyz(-2.0, 1.0, -2.0).with_rotation(Quat::from_rotation_y(0.5)), )); commands.spawn(( - ImageNode { - image: asset_server.load("wikipedia_xuastc_ldr_6x6.basisu.ktx2"), - ..Default::default() - }, Node { + display: Display::Flex, + flex_direction: FlexDirection::Row, + column_gap: px(8), position_type: PositionType::Absolute, - top: px(12), - right: px(12), - width: px(256), - max_width: px(256), - max_height: px(256), - ..default() - }, - )); - - commands.spawn(( - ImageNode { - image: asset_server.load("kodim20_astc_ldr_8x8.basisu.ktx2"), + bottom: px(8), + right: px(8), + left: px(8), + width: percent(100), + height: percent(25), ..Default::default() }, - Node { - position_type: PositionType::Absolute, - top: px(12 + 256), - right: px(12), - width: px(256), - max_width: px(256), - max_height: px(256), - ..default() - }, + children![ + ImageNode { + image: asset_server.load("wikipedia_xuastc_ldr_6x6.basisu.ktx2"), + ..Default::default() + }, + ImageNode { + image: asset_server.load("kodim20_astc_ldr_8x8.basisu.ktx2"), + ..Default::default() + }, + ], )); - commands.spawn((Node { - position_type: PositionType::Absolute, - top: px(12), - left: px(12), - ..default() - },)); - // UI commands.spawn(( Text::new("Press Q, E (or ArrowLeft, ArrowRight) to rotate camera."), diff --git a/web/index.html b/web/index.html index 3b453f8..e8efca9 100644 --- a/web/index.html +++ b/web/index.html @@ -31,8 +31,8 @@ } .game-container { - width: 1280px; - height: 720px; + width: 95%; + height: 100%; display: flex; justify-content: center; align-items: center; @@ -72,7 +72,7 @@ z-index: 2; } - Wasm Example + Bevy BasisU Loader Wasm Example