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
3 changes: 3 additions & 0 deletions assets/alpha0_etc1s.basisu_ktx2
Git LFS file not shown
1 change: 1 addition & 0 deletions crates/basisu_sys/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ fn bindgen() {
.opaque_type("Transcoder")
.bitfield_enum("TextureCompressionMethod")
.newtype_enum("TextureTranscodedFormat")
.newtype_enum("ChannelType")
.generate()
.expect("Unable to generate bindings")
.write_to_file(binding_file)
Expand Down
2 changes: 2 additions & 0 deletions crates/basisu_sys/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ mod transcoding {
include!(concat!(env!("OUT_DIR"), "/transcoding.rs"));
}

pub use transcoding::{ChannelType, TextureCompressionMethod, TextureTranscodedFormat, Transcoder};

#[cfg(not(all(
target_arch = "wasm32",
target_vendor = "unknown",
Expand Down
9 changes: 8 additions & 1 deletion crates/basisu_sys/src/native.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#![expect(clippy::missing_safety_doc, reason = "TODO")]

pub use crate::transcoding::{TextureCompressionMethod, TextureTranscodedFormat, Transcoder};
use crate::ChannelType;
use crate::TextureCompressionMethod;
use crate::TextureTranscodedFormat;
use crate::Transcoder;

pub use crate::transcoding::c_basisu_transcoder_init as basisu_transcoder_init;
pub use crate::transcoding::c_ktx2_transcoder_delete as ktx2_transcoder_delete;
Expand All @@ -17,13 +20,17 @@ pub unsafe fn ktx2_transcoder_transcode_image(
transcoder: *mut Transcoder,
data: Vec<u8>,
supported_compressed_formats: TextureCompressionMethod,
channel_type_hint: ChannelType,
force_transcode_target: TextureTranscodedFormat,
) -> bool {
unsafe {
crate::transcoding::c_ktx2_transcoder_transcode_image(
transcoder,
data.as_ptr(),
u32::try_from(data.len()).unwrap(),
supported_compressed_formats,
channel_type_hint,
force_transcode_target,
)
}
}
Expand Down
14 changes: 12 additions & 2 deletions crates/basisu_sys/src/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,22 @@

use std::cell::OnceCell;

pub use crate::transcoding::{TextureCompressionMethod, TextureTranscodedFormat, Transcoder};

use js_sys::Object;
use js_sys::Reflect;
use js_sys::Uint8Array;

use crate::ChannelType;
use crate::TextureCompressionMethod;
use crate::TextureTranscodedFormat;
use crate::Transcoder;

mod bindings_sys {
use super::Transcoder;
use js_sys::Uint8Array;
use wasm_bindgen::prelude::wasm_bindgen;
type TextureCompressionMethodRepr = u8;
type TextureTranscodedFormatRepr = u32;
type ChannelTypeRepr = u8;

#[wasm_bindgen]
extern "C" {
Expand All @@ -40,6 +44,8 @@ mod bindings_sys {
data: usize,
data_len: u32,
supported_compressed_formats: TextureCompressionMethodRepr,
channel_type_hint: ChannelTypeRepr,
force_transcode_target: TextureTranscodedFormatRepr,
) -> bool;
#[wasm_bindgen(method,js_name=_c_ktx2_transcoder_get_r_dst_buf)]
pub fn js_ktx2_transcoder_get_r_dst_buf(
Expand Down Expand Up @@ -184,6 +190,8 @@ pub unsafe fn ktx2_transcoder_transcode_image(
transcoder: *mut Transcoder,
data: Vec<u8>,
supported_compressed_formats: TextureCompressionMethod,
channel_type_hint: ChannelType,
force_transcode_target: TextureTranscodedFormat,
) -> bool {
BASISU_VENDOR_INSTANCE.with(|inst| {
let inst = inst.get().unwrap();
Expand All @@ -196,6 +204,8 @@ pub unsafe fn ktx2_transcoder_transcode_image(
ptr,
len,
supported_compressed_formats.0,
channel_type_hint.0,
force_transcode_target.0,
);
inst.js_basisu_free(ptr);
result
Expand Down
4 changes: 2 additions & 2 deletions crates/basisu_sys/wasm/basisu_vendor.js
Git LFS file not shown
4 changes: 2 additions & 2 deletions crates/basisu_sys/wasm/basisu_vendor.wasm
Git LFS file not shown
28 changes: 24 additions & 4 deletions examples/test_scene/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use bevy::{
log::LogPlugin,
math::Affine2,
prelude::*,
render::view::Hdr,
render::{render_resource::TextureFormat, view::Hdr},
};
use bevy_basisu_loader::{BasisuLoaderPlugin, BasisuLoaderSettings};

Expand Down Expand Up @@ -80,6 +80,7 @@ fn setup(
base_color_texture: Some(asset_server.load_with_settings(
"desk_uastc_hdr_4x4_mips_10.basisu_ktx2",
|s: &mut BasisuLoaderSettings| {
s.force_transcode_target = Some(TextureFormat::Rgb9e5Ufloat);
s.sampler =
bevy::image::ImageSampler::Descriptor(bevy::image::ImageSamplerDescriptor {
address_mode_u: bevy::image::ImageAddressMode::Repeat,
Expand All @@ -95,9 +96,26 @@ fn setup(
* Transform::from_rotation(Quat::from_rotation_y(-45.0)),
));

commands.spawn((
Mesh3d(meshes.add(Rectangle::new(1.0, 1.0).mesh().build())),
MeshMaterial3d(materials.add(StandardMaterial {
base_color_texture: Some(asset_server.load_with_settings(
"alpha0_etc1s.basisu_ktx2",
|s: &mut BasisuLoaderSettings| {
s.channel_type_hint = bevy_basisu_loader::ChannelType::Rg;
},
)),
alpha_mode: AlphaMode::Blend,
unlit: true,
..Default::default()
})),
Transform::from_xyz(-2.0, 0.0, -2.0)
* Transform::from_rotation(Quat::from_rotation_y(45.0)),
));

// UI
commands.spawn((
Text::new("Press Q, E to rotate camera."),
Text::new("Press Q, E (or ArrowLeft, ArrowRight) to rotate camera."),
Node {
position_type: PositionType::Absolute,
top: px(12),
Expand All @@ -111,9 +129,11 @@ fn rotate_camera(
mut query: Query<&mut Transform, With<Camera3d>>,
keyboard_input: Res<ButtonInput<KeyCode>>,
) {
let rotate = if keyboard_input.pressed(KeyCode::KeyQ) {
let rotate = if keyboard_input.pressed(KeyCode::KeyQ)
|| keyboard_input.pressed(KeyCode::ArrowLeft)
{
0.05
} else if keyboard_input.pressed(KeyCode::KeyE) {
} else if keyboard_input.pressed(KeyCode::KeyE) || keyboard_input.pressed(KeyCode::ArrowRight) {
-0.05
} else {
0.0
Expand Down
8 changes: 8 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ mod loader;

pub use loader::*;

/// Provides a loader for Basis Universal KTX2 textures.
///
/// The file extension must be `.basisu_ktx2` to use this loader. Supports KTX2 UASTC/ETC1S format. Zstd supercompression is supported even if bevy's zstd feature is disabled. No support for `.basis` files.
///
/// Transcode Target Selection:
/// - ETC1S: Bc7Rgba/Bc5Rg/Bc4R > Etc2Rgba8/Etc2Rgb8/EacRg11/EacR11 > Rgba8
/// - UASTC LDR: Astc > Bc7Rgba > Etc2Rgba8/Etc2Rgb8/EacRg11/EacR11 > Rgba8
/// - UASTC HDR: Astc > Bc6hRgbUfloat > Rgba16Float
pub struct BasisuLoaderPlugin;

impl Plugin for BasisuLoaderPlugin {
Expand Down
72 changes: 71 additions & 1 deletion src/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@ impl BasisuLoader {
}
}

#[derive(Serialize, Deserialize, Default, Debug, Clone, Copy)]
#[repr(u8)]
pub enum ChannelType {
#[default]
Auto,
Rgba,
Rgb,
Rg,
R,
}

/// Settings for loading an [`Image`] using an [`BasisuLoader`].
#[derive(Serialize, Deserialize, Default, Debug, Clone)]
pub struct BasisuLoaderSettings {
Expand All @@ -44,6 +55,21 @@ pub struct BasisuLoaderSettings {
/// Where the asset will be used - see the docs on
/// [`RenderAssetUsages`] for details.
pub asset_usage: RenderAssetUsages,
/// Whether the texture should be created as sRGB format.
///
/// If `None`, it will be determined by the KTX2 data format descriptor transfer function.
pub is_srgb: Option<bool>,
/// The channel type hint for transcode target selection.
///
/// If [`ChannelType::Auto`], it will be determined by the KTX2 data format descriptor channel type.
/// Note: This will be ignored when the transcode target format is not ETC2 or BC4/BC5 and usually has no effect for UASTC textures. See [`BasisuLoaderPlugin`](crate::BasisuLoaderPlugin) for more information about the transcode targets.
pub channel_type_hint: ChannelType,
/// Forcibly transcode to a specific [`TextureFormat`] if it's not `None`. Otherwise the format will be 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.
/// Srgb-ness is ignored and will be determined by `is_srgb`.
#[serde(skip)]
pub force_transcode_target: Option<TextureFormat>,
}

/// An error when loading an image using [`BasisuLoader`].
Expand Down Expand Up @@ -91,13 +117,20 @@ impl AssetLoader for BasisuLoader {
transcoder,
data,
self.supported_compressed_formats,
bevy_basisu_loader_sys::ChannelType(settings.channel_type_hint as u8),
texture_bevy_format_to_transcode_format(settings.force_transcode_target),
) {
return Err(BasisuLoaderError::TranscodingError(
"ktx2_transcoder_transcode_image",
));
}

let is_srgb = bevy_basisu_loader_sys::ktx2_transcoder_get_r_is_srgb(transcoder);
let is_srgb =
settings
.is_srgb
.unwrap_or(bevy_basisu_loader_sys::ktx2_transcoder_get_r_is_srgb(
transcoder,
));
let target_format =
bevy_basisu_loader_sys::ktx2_transcoder_get_r_target_format(transcoder);

Expand Down Expand Up @@ -238,3 +271,40 @@ fn texture_transcode_format_to_bevy_format(
}
fmt
}

fn texture_bevy_format_to_transcode_format(
format: Option<TextureFormat>,
) -> TextureTranscodedFormat {
let Some(format) = format else {
return TextureTranscodedFormat::cTFTotalTextureFormats;
};
let format = format.remove_srgb_suffix();
match format {
TextureFormat::Etc2Rgb8Unorm => TextureTranscodedFormat::cTFETC1_RGB,
TextureFormat::Etc2Rgba8Unorm => TextureTranscodedFormat::cTFETC2_RGBA,
TextureFormat::Bc1RgbaUnorm => TextureTranscodedFormat::cTFBC1_RGB,
TextureFormat::Bc3RgbaUnorm => TextureTranscodedFormat::cTFBC3_RGBA,
TextureFormat::Bc4RUnorm => TextureTranscodedFormat::cTFBC4_R,
TextureFormat::Bc5RgUnorm => TextureTranscodedFormat::cTFBC5_RG,
TextureFormat::Bc7RgbaUnorm => TextureTranscodedFormat::cTFBC7_RGBA,
TextureFormat::Astc {
block: AstcBlock::B4x4,
channel: AstcChannel::Unorm,
} => TextureTranscodedFormat::cTFASTC_4x4_RGBA,
TextureFormat::EacR11Unorm => TextureTranscodedFormat::cTFETC2_EAC_R11,
TextureFormat::EacRg11Unorm => TextureTranscodedFormat::cTFETC2_EAC_RG11,
TextureFormat::Bc6hRgbUfloat => TextureTranscodedFormat::cTFBC6H,
TextureFormat::Astc {
block: AstcBlock::B4x4,
channel: AstcChannel::Hdr,
} => TextureTranscodedFormat::cTFASTC_HDR_4x4_RGBA,
TextureFormat::Rgba8Unorm => TextureTranscodedFormat::cTFRGBA32,
TextureFormat::Rgba16Float => TextureTranscodedFormat::cTFRGBA_HALF,
TextureFormat::Rgb9e5Ufloat => TextureTranscodedFormat::cTFRGB_9E5,
TextureFormat::Astc {
block: AstcBlock::B6x6,
channel: AstcChannel::Hdr,
} => TextureTranscodedFormat::cTFASTC_HDR_6x6_RGBA,
_ => unreachable!(),
}
}
29 changes: 16 additions & 13 deletions vendor/transcoding_wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,6 @@

enum TextureCompressionMethod : unsigned char;

enum ChannelType {
CHANNEL_RGB,
CHANNEL_RGBA,
CHANNEL_R,
CHANNEL_RG,
};

static ChannelType channel_id_to_type(bool is_uastc,
basist::ktx2_df_channel_id channel_id0,
basist::ktx2_df_channel_id channel_id1);
Expand Down Expand Up @@ -69,27 +62,30 @@ static bool c_ktx2_transcoder_get_texture_info(Transcoder *transcoder, TextureTr
return true;
}

static void c_ktx2_transcoder_get_target_format(Transcoder *transcoder, TextureCompressionMethod supported_compressed_formats, bool *r_is_srgb, TextureTranscodedFormat *r_format) {
static void c_ktx2_transcoder_get_target_format(Transcoder *transcoder, TextureCompressionMethod supported_compressed_formats, ChannelType channel_type_hint, bool *r_is_srgb, TextureTranscodedFormat *r_format) {
basist::ktx2_transcoder *inner = transcoder->inner;

basist::ktx2_df_channel_id channel_id0 = inner->get_dfd_channel_id0();
basist::ktx2_df_channel_id channel_id1 = inner->get_dfd_channel_id1();
basist::basis_tex_format basis_format = inner->get_basis_tex_format();
ChannelType channel_type = channel_id_to_type(inner->is_uastc(), channel_id0, channel_id1);
ChannelType channel_type = channel_type_hint != CHANNEL_UNDEFINED ? channel_type_hint : channel_id_to_type(inner->is_uastc(), channel_id0, channel_id1);
basist::transcoder_texture_format target_format = get_target_texture_format(basis_format, channel_type, supported_compressed_formats);
*r_is_srgb = inner->get_dfd_transfer_func() == basist::KTX2_KHR_DF_TRANSFER_SRGB;
*r_format = static_cast<TextureTranscodedFormat>(static_cast<uint32_t>(target_format));
}

bool c_ktx2_transcoder_transcode_image(
Transcoder *transcoder, const unsigned char *data, unsigned int data_size,
TextureCompressionMethod supported_compressed_formats) {
TextureCompressionMethod supported_compressed_formats, ChannelType channel_type_hint, TextureTranscodedFormat force_transcode_target) {
basist::ktx2_transcoder *inner = transcoder->inner;
inner->init(data, data_size);
inner->start_transcoding();

c_ktx2_transcoder_get_target_format(transcoder, supported_compressed_formats, &transcoder->r_is_srgb, &transcoder->r_target_format);
const basist::transcoder_texture_format transcode_format = static_cast<basist::transcoder_texture_format>(static_cast<uint32_t>(transcoder->r_target_format));
c_ktx2_transcoder_get_target_format(transcoder, supported_compressed_formats, channel_type_hint, &transcoder->r_is_srgb, &transcoder->r_target_format);
if (force_transcode_target != TextureTranscodedFormat::cTFTotalTextureFormats) {
transcoder->r_target_format = force_transcode_target;
}
basist::transcoder_texture_format transcode_format = static_cast<basist::transcoder_texture_format>(static_cast<uint32_t>(transcoder->r_target_format));

if (!c_ktx2_transcoder_get_texture_info(transcoder, transcoder->r_target_format, &transcoder->r_width, &transcoder->r_height, &transcoder->r_levels, &transcoder->r_layers, &transcoder->r_faces, &transcoder->r_dst_buf_len)) {
return false;
Expand Down Expand Up @@ -212,7 +208,8 @@ static basist::transcoder_texture_format get_target_texture_format(
case CHANNEL_RGB: {
return basist::transcoder_texture_format::cTFETC1_RGB;
} break;
case CHANNEL_RGBA: {
case CHANNEL_RGBA:
case CHANNEL_UNDEFINED: {
return basist::transcoder_texture_format::cTFETC2_RGBA;
} break;
case CHANNEL_R: {
Expand Down Expand Up @@ -242,6 +239,9 @@ static basist::transcoder_texture_format get_target_texture_format(
case CHANNEL_RG: {
return basist::transcoder_texture_format::cTFBC5_RG;
} break;
case CHANNEL_UNDEFINED: {
abort();
}
}
} else if (supported_compressed_formats & TextureCompressionMethod::ETC2) {
switch (channel_type) {
Expand All @@ -257,6 +257,9 @@ static basist::transcoder_texture_format get_target_texture_format(
case CHANNEL_RG: {
return basist::transcoder_texture_format::cTFETC2_EAC_RG11;
} break;
case CHANNEL_UNDEFINED: {
abort();
}
}
} else {
return basist::transcoder_texture_format::cTFRGBA32;
Expand Down
10 changes: 9 additions & 1 deletion vendor/transcoding_wrapper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ class ktx2_transcoder;

extern "C" {

enum ChannelType : unsigned char {
CHANNEL_UNDEFINED = 0,
CHANNEL_RGBA,
CHANNEL_RGB,
CHANNEL_RG,
CHANNEL_R,
};

// This enum must be in sync with the `basist::transcoder_texture_format`.
enum TextureTranscodedFormat : unsigned int {
// Compressed formats
Expand Down Expand Up @@ -112,7 +120,7 @@ Transcoder *c_ktx2_transcoder_new();
void c_ktx2_transcoder_delete(Transcoder *transcoder);

bool c_ktx2_transcoder_transcode_image(Transcoder *transcoder, const unsigned char *data, unsigned int data_size,
TextureCompressionMethod supported_compressed_formats);
TextureCompressionMethod supported_compressed_formats, ChannelType channel_type_hint, TextureTranscodedFormat force_transcode_target);

unsigned char *c_ktx2_transcoder_get_r_dst_buf(Transcoder *transcoder);
unsigned int c_ktx2_transcoder_get_r_dst_buf_len(Transcoder *transcoder);
Expand Down