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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/static.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
8 changes: 4 additions & 4 deletions crates/basisu_sys/src/bin/build_wasm_cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand All @@ -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 {
Expand All @@ -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())
Expand All @@ -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");
}
}
}
Expand Down
12 changes: 11 additions & 1 deletion crates/basisu_sys/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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 {
Expand Down
4 changes: 3 additions & 1 deletion crates/basisu_sys/src/native.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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
}
10 changes: 5 additions & 5 deletions crates/basisu_sys/src/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
68 changes: 28 additions & 40 deletions examples/test_scene/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use bevy::{
asset::AssetMetaCheck,
core_pipeline::{Skybox, tonemapping::Tonemapping},
log::LogPlugin,
math::Affine2,
Expand All @@ -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()
Expand All @@ -44,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,
Expand All @@ -60,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((
Expand All @@ -70,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((
Expand All @@ -92,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((
Expand All @@ -109,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."),
Expand Down
86 changes: 63 additions & 23 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -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::*;
Expand All @@ -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 |
/// | ------------------------------ | -------------------------------------------------------------- |
Expand All @@ -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<AtomicBool>);

impl Plugin for BasisuLoaderPlugin {
fn build(&self, app: &mut App) {
app.preregister_asset_loader::<BasisuLoader>(&["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::<BasisuLoader>(&["basisu.ktx2"]);
}

#[cfg(all(
target_arch = "wasm32",
target_vendor = "unknown",
target_os = "unknown",
))]
fn ready(&self, app: &App) -> bool {
app.world()
.resource::<BasisuWasmReady>()
.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::<BasisuWasmReady>();
let device = app
.sub_app_mut(RenderApp)
.world()
Expand Down
2 changes: 1 addition & 1 deletion src/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<TextureFormat>,
}
Expand Down
Loading