diff --git a/.gitignore b/.gitignore index b5b92ed77cbeb..d200d47ca5dce 100644 --- a/.gitignore +++ b/.gitignore @@ -21,7 +21,8 @@ Cargo.lock # Bevy Assets assets/**/*.meta crates/bevy_asset/imported_assets -imported_assets +examples/asset/processing/imported_assets +/imported_assets .web-asset-cache examples/large_scenes/bistro/assets/* examples/large_scenes/caldera_hotel/assets/* diff --git a/Cargo.toml b/Cargo.toml index f0931e95afb55..8b22f97ad5122 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -429,10 +429,10 @@ trace_tracy_memory = ["bevy_internal/trace_tracy_memory"] trace = ["bevy_internal/trace", "dep:tracing"] # Basis Universal compressed texture support -basis-universal = ["bevy_internal/basis-universal"] +basis_universal = ["bevy_internal/basis_universal"] -# Enables compressed KTX2 UASTC texture output on the asset processor -compressed_image_saver = ["bevy_internal/compressed_image_saver"] +# Basis Universal saver and asset processor +basis_universal_saver = ["bevy_internal/basis_universal_saver"] # BMP image format support bmp = ["bevy_internal/bmp"] diff --git a/_release-content/migration-guides/basis_universal_improve.md b/_release-content/migration-guides/basis_universal_improve.md new file mode 100644 index 0000000000000..543bec9c46826 --- /dev/null +++ b/_release-content/migration-guides/basis_universal_improve.md @@ -0,0 +1,37 @@ +--- +title: "Basis Universal update and improvement" +pull_requests: [23672] +--- + +Previously bevy used [basis-universal-rs](https://github.com/aclysma/basis-universal-rs) for basis universal support, including `.basis` and ktx2 UASTC texture +loading and `CompressedImageSaver`. However it doesn't support web and uses relatively outdated Basis Universal v1.16. + +Now bevy uses [`basisu_c_sys`](https://docs.rs/basisu_c_sys/latest/basisu_c_sys) which is basis universal v2.10 and supports all the basis universal formats (ETC1S, UASTC, ASTC and XUASTC) and `wasm32-unknown-unknown` on web. + +`ImageFormat::Basis` is removed. `CompressedImageSaver` is replaced by `BasisuSaver`/`BasisuProcessor` which is not added by `ImagePlugin` automatically. Also the `basis-universal` cargo feature is renamed to `basis_universal`, `compressed_image_saver` is replaced by `basis_universal_saver`. + +If you are using `.basis` files, it's recommended to re-compress your textures to `.ktx2` format with basisu tool. Basis universal textures will be handled as `ImageFormat::Ktx2` if `basis_universal` feature is enabled. + +To use the `BasisuProcessor`, enable `basis_universal_saver` feature and add `BasisUniversalProcessorPlugin`: + +```rs +use bevy::image::BasisUniversalProcessorPlugin; +use bevy::prelude::*; + +fn main() { + App::new() + .add_plugins(( + DefaultPlugins + .set(bevy::log::LogPlugin { + filter: "bevy_image=debug,bevy_asset=debug,wgpu=warn".to_string(), + ..Default::default() + }) + .set(AssetPlugin { + mode: AssetMode::Processed, + ..Default::default() + }), + BasisUniversalProcessorPlugin::default(), + )) + .run(); +} +``` diff --git a/crates/bevy_image/Cargo.toml b/crates/bevy_image/Cargo.toml index f45a92afee3db..e4ed6cb01eecf 100644 --- a/crates/bevy_image/Cargo.toml +++ b/crates/bevy_image/Cargo.toml @@ -16,7 +16,6 @@ default = ["bevy_reflect"] bevy_reflect = ["bevy_math/bevy_reflect"] # Image formats -basis-universal = ["dep:basis-universal"] bmp = ["image/bmp"] dds = ["ddsfile"] exr = ["image/exr"] @@ -44,9 +43,8 @@ zstd = [] zstd_rust = ["zstd", "dep:ruzstd"] # Binding to zstd C implementation (faster) zstd_c = ["zstd", "dep:zstd"] - -# Enables compressed KTX2 UASTC texture output on the asset processor -compressed_image_saver = ["basis-universal"] +basis_universal = ["ktx2", "dep:basisu_c_sys"] +basis_universal_saver = ["basis_universal", "basisu_c_sys/encoder"] [dependencies] # bevy @@ -75,7 +73,6 @@ wgpu-types = { version = "29.0.1", default-features = false, features = [ ] } serde = { version = "1", features = ["derive"] } thiserror = { version = "2", default-features = false } -futures-lite = "2.0.1" guillotiere = "0.6.0" rectangle-pack = "0.4" ddsfile = { version = "0.5.2", optional = true } @@ -84,10 +81,14 @@ ktx2 = { version = "0.4.0", optional = true } flate2 = { version = "1.0.22", optional = true } zstd = { version = "0.13.3", optional = true } ruzstd = { version = "0.8.0", optional = true } -# For transcoding of UASTC/ETC1S universal formats, and for .basis file support -basis-universal = { version = "0.3.0", optional = true } tracing = { version = "0.1", default-features = false, features = ["std"] } half = { version = "2.4.1" } +basisu_c_sys = { version = "0.6.1", default-features = false, features = [ + "extra", + "serde", +], optional = true } +bevy_log = { version = "0.19.0-dev", path = "../bevy_log", default-features = false } +bevy_tasks = { version = "0.19.0-dev", path = "../bevy_tasks", default-features = false } [dev-dependencies] bevy_ecs = { path = "../bevy_ecs", version = "0.19.0-dev" } diff --git a/crates/bevy_image/src/basis.rs b/crates/bevy_image/src/basis.rs deleted file mode 100644 index c88edb1fdc7bc..0000000000000 --- a/crates/bevy_image/src/basis.rs +++ /dev/null @@ -1,169 +0,0 @@ -use basis_universal::{ - BasisTextureType, DecodeFlags, TranscodeParameters, Transcoder, TranscoderTextureFormat, -}; -use wgpu_types::{AstcBlock, AstcChannel, Extent3d, TextureDimension, TextureFormat}; - -use super::{CompressedImageFormats, Image, TextureError}; - -pub fn basis_buffer_to_image( - buffer: &[u8], - supported_compressed_formats: CompressedImageFormats, - is_srgb: bool, -) -> Result { - let mut transcoder = Transcoder::new(); - - #[cfg(debug_assertions)] - if !transcoder.validate_file_checksums(buffer, true) { - return Err(TextureError::InvalidData("Invalid checksum".to_string())); - } - if !transcoder.validate_header(buffer) { - return Err(TextureError::InvalidData("Invalid header".to_string())); - } - - let Some(image0_info) = transcoder.image_info(buffer, 0) else { - return Err(TextureError::InvalidData( - "Failed to get image info".to_string(), - )); - }; - - // First deal with transcoding to the desired format - // FIXME: Use external metadata to transcode to more appropriate formats for 1- or 2-component sources - let (transcode_format, texture_format) = - get_transcoded_formats(supported_compressed_formats, is_srgb); - let basis_texture_format = transcoder.basis_texture_format(buffer); - if !basis_texture_format.can_transcode_to_format(transcode_format) { - return Err(TextureError::UnsupportedTextureFormat(format!( - "{basis_texture_format:?} cannot be transcoded to {transcode_format:?}", - ))); - } - transcoder.prepare_transcoding(buffer).map_err(|_| { - TextureError::TranscodeError(format!( - "Failed to prepare for transcoding from {basis_texture_format:?}", - )) - })?; - let mut transcoded = Vec::new(); - - let image_count = transcoder.image_count(buffer); - let texture_type = transcoder.basis_texture_type(buffer); - if texture_type == BasisTextureType::TextureTypeCubemapArray && !image_count.is_multiple_of(6) { - return Err(TextureError::InvalidData(format!( - "Basis file with cube map array texture with non-modulo 6 number of images: {image_count}", - ))); - } - - let image0_mip_level_count = transcoder.image_level_count(buffer, 0); - for image_index in 0..image_count { - if let Some(image_info) = transcoder.image_info(buffer, image_index) - && texture_type == BasisTextureType::TextureType2D - && (image_info.m_orig_width != image0_info.m_orig_width - || image_info.m_orig_height != image0_info.m_orig_height) - { - return Err(TextureError::UnsupportedTextureFormat(format!( - "Basis file with multiple 2D textures with different sizes not supported. Image {} {}x{}, image 0 {}x{}", - image_index, - image_info.m_orig_width, - image_info.m_orig_height, - image0_info.m_orig_width, - image0_info.m_orig_height, - ))); - } - let mip_level_count = transcoder.image_level_count(buffer, image_index); - if mip_level_count != image0_mip_level_count { - return Err(TextureError::InvalidData(format!( - "Array or volume texture has inconsistent number of mip levels. Image {image_index} has {mip_level_count} but image 0 has {image0_mip_level_count}", - ))); - } - for level_index in 0..mip_level_count { - let mut data = transcoder - .transcode_image_level( - buffer, - transcode_format, - TranscodeParameters { - image_index, - level_index, - decode_flags: Some(DecodeFlags::HIGH_QUALITY), - ..Default::default() - }, - ) - .map_err(|error| { - TextureError::TranscodeError(format!( - "Failed to transcode mip level {level_index} from {basis_texture_format:?} to {transcode_format:?}: {error:?}", - )) - })?; - transcoded.append(&mut data); - } - } - - // Then prepare the Image - let mut image = Image::default(); - image.texture_descriptor.size = Extent3d { - width: image0_info.m_orig_width, - height: image0_info.m_orig_height, - depth_or_array_layers: image_count, - } - .physical_size(texture_format); - image.texture_descriptor.mip_level_count = image0_mip_level_count; - image.texture_descriptor.format = texture_format; - image.texture_descriptor.dimension = match texture_type { - BasisTextureType::TextureType2D - | BasisTextureType::TextureType2DArray - | BasisTextureType::TextureTypeCubemapArray => TextureDimension::D2, - BasisTextureType::TextureTypeVolume => TextureDimension::D3, - basis_texture_type => { - return Err(TextureError::UnsupportedTextureFormat(format!( - "{basis_texture_type:?}", - ))) - } - }; - image.data = Some(transcoded); - Ok(image) -} - -pub fn get_transcoded_formats( - supported_compressed_formats: CompressedImageFormats, - is_srgb: bool, -) -> (TranscoderTextureFormat, TextureFormat) { - // NOTE: UASTC can be losslessly transcoded to ASTC4x4 and ASTC uses the same - // space as BC7 (128-bits per 4x4 texel block) so prefer ASTC over BC for - // transcoding speed and quality. - if supported_compressed_formats.contains(CompressedImageFormats::ASTC_LDR) { - ( - TranscoderTextureFormat::ASTC_4x4_RGBA, - TextureFormat::Astc { - block: AstcBlock::B4x4, - channel: if is_srgb { - AstcChannel::UnormSrgb - } else { - AstcChannel::Unorm - }, - }, - ) - } else if supported_compressed_formats.contains(CompressedImageFormats::BC) { - ( - TranscoderTextureFormat::BC7_RGBA, - if is_srgb { - TextureFormat::Bc7RgbaUnormSrgb - } else { - TextureFormat::Bc7RgbaUnorm - }, - ) - } else if supported_compressed_formats.contains(CompressedImageFormats::ETC2) { - ( - TranscoderTextureFormat::ETC2_RGBA, - if is_srgb { - TextureFormat::Etc2Rgba8UnormSrgb - } else { - TextureFormat::Etc2Rgba8Unorm - }, - ) - } else { - ( - TranscoderTextureFormat::RGBA32, - if is_srgb { - TextureFormat::Rgba8UnormSrgb - } else { - TextureFormat::Rgba8Unorm - }, - ) - } -} diff --git a/crates/bevy_image/src/basis_universal/mod.rs b/crates/bevy_image/src/basis_universal/mod.rs new file mode 100644 index 0000000000000..6b89a1978e401 --- /dev/null +++ b/crates/bevy_image/src/basis_universal/mod.rs @@ -0,0 +1,158 @@ +#[cfg(feature = "basis_universal_saver")] +mod saver; +#[cfg(feature = "basis_universal_saver")] +pub use saver::*; + +use crate::{CompressedImageFormats, Image, TextureError}; +use basisu_c_sys::extra::{BasisuTranscoder, SupportedTextureCompression}; +use bevy_app::{App, Plugin}; +#[cfg(all( + target_arch = "wasm32", + target_vendor = "unknown", + target_os = "unknown", +))] +use bevy_platform::sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, +}; + +/// Converts and transcodes KTX2 Basis Universal bytes to a bevy [`Image`] using the given compressed format support. All basis universal compressed formats (ETC1S, UASTC, ASTC, XUASTC) are supported. Zstd supercompression is always supported. No support for `.basis` files. +/// +/// The current integrated basis universl version is 2.10 +/// +/// Default transcode target selection: +/// +/// | BasisU format | Target selection | +/// | ------------------------------ | -------------------------------------------------------------- | +/// | ETC1S | Bc7Rgba/Bc5Rg/Bc4R > Etc2Rgba8/Etc2Rgb8/EacRg11/EacR11 > Rgba8 | +/// | UASTC_LDR, ASTC_LDR, XUASTC_LDR| Astc > Bc7Rgba > Etc2Rgba8/Etc2Rgb8/EacRg11/EacR11 > Rgba8 | +/// | UASTC_HDR, ASTC_HDR | Astc > Bc6hRgbUfloat > Rgba16Float | +pub fn ktx2_basisu_buffer_to_image( + buffer: &[u8], + supported_compressed_formats: CompressedImageFormats, + is_srgb: bool, +) -> Result { + let src_bytes = buffer.len(); + + let _span = bevy_log::info_span!("Transcoding basisu texture").entered(); + let time = if bevy_log::STATIC_MAX_LEVEL >= bevy_log::Level::DEBUG { + Some(bevy_platform::time::Instant::now()) + } else { + None + }; + let mut compressions = SupportedTextureCompression::empty(); + if supported_compressed_formats.contains(CompressedImageFormats::ASTC_LDR) { + compressions |= SupportedTextureCompression::ASTC_LDR; + } + if supported_compressed_formats.contains(CompressedImageFormats::ASTC_HDR) { + compressions |= SupportedTextureCompression::ASTC_HDR; + } + if supported_compressed_formats.contains(CompressedImageFormats::BC) { + compressions |= SupportedTextureCompression::BC; + } + if supported_compressed_formats.contains(CompressedImageFormats::ETC2) { + compressions |= SupportedTextureCompression::ETC2; + } + let mut transcoder = BasisuTranscoder::new(); + let info = transcoder.prepare(buffer, compressions, basisu_c_sys::extra::ChannelType::Auto)?; + + let out_image = transcoder.transcode(None, Some(is_srgb))?; + + if bevy_log::STATIC_MAX_LEVEL >= bevy_log::Level::DEBUG { + bevy_log::debug!( + "Transcoded basisu texture, \ + {:?} -> {:?}, {}kb -> {}kb. \ + Preferred target: {:?}, extents: {:?}, level count: {}, view dimension: {:?} in {:?}", + info.basis_format, + out_image.texture_descriptor.format, + src_bytes as f32 / 1000.0, + out_image.data.as_ref().unwrap().len() as f32 / 1000.0, + info.preferred_target, + out_image.texture_descriptor.size, + info.levels, + out_image + .texture_view_descriptor + .as_ref() + .unwrap() + .dimension + .unwrap(), + time.unwrap().elapsed(), + ); + } + + Ok(Image { + data: out_image.data, + data_order: out_image.data_order, + texture_descriptor: out_image.texture_descriptor, + texture_view_descriptor: out_image.texture_view_descriptor, + ..Default::default() + }) +} + +/// Provides the necessary basis universal initialization. +/// Any bassiu encoding or transcoding will fail before this plugin is initialized. +pub struct BasisUniversalPlugin; + +#[cfg(all( + target_arch = "wasm32", + target_vendor = "unknown", + target_os = "unknown", +))] +#[derive(bevy_ecs::resource::Resource, Clone)] +struct BasisuWasmReady(Arc); + +impl Plugin for BasisUniversalPlugin { + fn build(&self, _app: &mut App) { + #[cfg(all( + target_arch = "wasm32", + target_vendor = "unknown", + target_os = "unknown", + ))] + { + let ready = BasisuWasmReady(Arc::new(AtomicUsize::new(0))); + let r = ready.clone(); + bevy_tasks::IoTaskPool::get() + .spawn_local(async move { + basisu_c_sys::extra::basisu_transcoder_init().await; + #[cfg(feature = "basis_universal_saver")] + basisu_c_sys::extra::basisu_encoder_init().await; + bevy_log::debug!("Basisu wasm initialized"); + r.0.store(1, Ordering::Release); + }) + .detach(); + _app.insert_resource(ready); + } + #[cfg(not(all( + target_arch = "wasm32", + target_vendor = "unknown", + target_os = "unknown", + )))] + { + bevy_tasks::block_on(basisu_c_sys::extra::basisu_transcoder_init()); + #[cfg(feature = "basis_universal_saver")] + bevy_tasks::block_on(basisu_c_sys::extra::basisu_encoder_init()); + } + } + + #[cfg(all( + target_arch = "wasm32", + target_vendor = "unknown", + target_os = "unknown", + ))] + fn ready(&self, app: &App) -> bool { + app.world() + .resource::() + .0 + .load(Ordering::Acquire) + != 0 + } + + fn finish(&self, _app: &mut App) { + #[cfg(all( + target_arch = "wasm32", + target_vendor = "unknown", + target_os = "unknown", + ))] + _app.world_mut().remove_resource::(); + } +} diff --git a/crates/bevy_image/src/basis_universal/saver.rs b/crates/bevy_image/src/basis_universal/saver.rs new file mode 100644 index 0000000000000..01b1326e9065b --- /dev/null +++ b/crates/bevy_image/src/basis_universal/saver.rs @@ -0,0 +1,140 @@ +//! Asset saver and processor for Basis Universal KTX2 textures. +use crate::{Image, ImageLoader, ImageLoaderSettings}; +use basisu_c_sys::extra::BasisuEncoder; +pub use basisu_c_sys::extra::{BasisuEncodeError, BasisuEncoderParams}; +use bevy_app::{App, Plugin}; +use bevy_asset::{ + processor::LoadTransformAndSave, saver::AssetSaver, transformer::IdentityAssetTransformer, + AsyncWriteExt, +}; +use bevy_reflect::TypePath; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +/// Provides basis universal asset processor +pub struct BasisUniversalProcessorPlugin { + /// The file extensions handled by the basisu asset processor. + /// + /// Default is [`ImageLoader::SUPPORTED_FILE_EXTENSIONS`] except ktx2 and .dds. + pub processor_extensions: Vec, + /// Default basisu encoder params. + /// See the documents and `BU_COMP_FLAGS_*` in [`basisu_c_sys`] if you want more controls, + /// like mipmap generation. + pub default_encoder_params: BasisuEncoderParams, +} + +impl Default for BasisUniversalProcessorPlugin { + fn default() -> Self { + Self { + processor_extensions: ImageLoader::SUPPORTED_FILE_EXTENSIONS + .iter() + .filter(|s| !["ktx2", "dds"].contains(s)) + .map(ToString::to_string) + .collect(), + default_encoder_params: BasisuEncoderParams::new_with_srgb_defaults( + basisu_c_sys::BasisTextureFormat::XuastcLdr4x4, + ), + } + } +} + +impl Plugin for BasisUniversalProcessorPlugin { + fn build(&self, app: &mut App) { + if let Some(asset_processor) = app + .world() + .get_resource::() + { + asset_processor.register_processor::( + BasisuSaver { + default_encoder_params: self.default_encoder_params, + } + .into(), + ); + for ext in &self.processor_extensions { + asset_processor.set_default_processor::(ext.as_str()); + } + } + } +} + +/// Basis universal asset processor. +pub type BasisuProcessor = + LoadTransformAndSave, BasisuSaver>; + +/// Basis universal texture saver. +#[derive(TypePath)] +pub struct BasisuSaver { + /// Default basisu encoder params. + /// See the documents and `BU_COMP_FLAGS_*` in [`basisu_c_sys`] if you want more controls, + /// like mipmap generation. + pub default_encoder_params: BasisuEncoderParams, +} + +/// Basis universal texture saver settings. +#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)] +pub struct BasisuSaverSettings { + /// Basisu encoder params. If it's None the [`BasisuSaver::default_encoder_params`] will be used. + pub encoder_params: Option, +} + +/// An error when encoding an image using [`BasisuSaver`]. +#[derive(Debug, Error)] +#[non_exhaustive] +pub enum BasisuSaverError { + /// An error occurred while trying to load the bytes. + #[error(transparent)] + Io(#[from] std::io::Error), + /// An error occurred while trying to encode the image. + #[error(transparent)] + BasisuEncodeError(#[from] BasisuEncodeError), +} + +impl AssetSaver for BasisuSaver { + type Asset = Image; + type Settings = BasisuSaverSettings; + type OutputLoader = ImageLoader; + type Error = BasisuSaverError; + + async fn save( + &self, + writer: &mut bevy_asset::io::Writer, + asset: bevy_asset::saver::SavedAsset<'_, '_, Self::Asset>, + settings: &Self::Settings, + asset_path: bevy_asset::AssetPath<'_>, + ) -> Result<::Settings, Self::Error> { + let _span = bevy_log::info_span!("Encoding basisu texture").entered(); + let time = bevy_platform::time::Instant::now(); + + let mut encoder = BasisuEncoder::new(); + encoder.set_image(basisu_c_sys::extra::SourceImage { + data: asset.data.as_deref().unwrap_or(&[]), + texture_descriptor: &asset.texture_descriptor, + texture_view_descriptor: &asset.texture_view_descriptor, + })?; + let result = encoder.compress( + settings + .encoder_params + .unwrap_or(self.default_encoder_params), + )?; + + bevy_log::debug!( + "Encoded basisu texture \"{}\", {}kb -> {}kb in {:?}", + asset_path, + asset.data.as_deref().unwrap_or(&[]).len() as f32 / 1000.0, + result.len() as f32 / 1000.0, + time.elapsed(), + ); + drop(_span); + + writer.write_all(&result).await?; + + Ok(ImageLoaderSettings { + asset_usage: asset.asset_usage, + sampler: asset.sampler.clone(), + array_layout: None, + is_srgb: true, + texture_format: None, + format: crate::ImageFormatSetting::Format(crate::ImageFormat::Ktx2), + }) + } +} diff --git a/crates/bevy_image/src/compressed_image_saver.rs b/crates/bevy_image/src/compressed_image_saver.rs deleted file mode 100644 index 6b6348a1a3c30..0000000000000 --- a/crates/bevy_image/src/compressed_image_saver.rs +++ /dev/null @@ -1,86 +0,0 @@ -use crate::{Image, ImageFormat, ImageFormatSetting, ImageLoader, ImageLoaderSettings}; - -use bevy_asset::{ - saver::{AssetSaver, SavedAsset}, - AssetPath, -}; -use bevy_reflect::TypePath; -use futures_lite::AsyncWriteExt; -use thiserror::Error; - -/// An [`AssetSaver`] that writes compressed basis universal (.ktx2) files. -#[derive(TypePath)] -pub struct CompressedImageSaver; - -/// Errors encountered when writing compressed images. -#[non_exhaustive] -#[derive(Debug, Error, TypePath)] -pub enum CompressedImageSaverError { - /// I/O error. - #[error(transparent)] - Io(#[from] std::io::Error), - /// Attempted to save an image with uninitialized data. - #[error("Cannot compress an uninitialized image")] - UninitializedImage, -} - -impl AssetSaver for CompressedImageSaver { - type Asset = Image; - - type Settings = (); - type OutputLoader = ImageLoader; - type Error = CompressedImageSaverError; - - async fn save( - &self, - writer: &mut bevy_asset::io::Writer, - image: SavedAsset<'_, '_, Self::Asset>, - _settings: &Self::Settings, - _asset_path: AssetPath<'_>, - ) -> Result { - let is_srgb = image.texture_descriptor.format.is_srgb(); - - let compressed_basis_data = { - let mut compressor_params = basis_universal::CompressorParams::new(); - compressor_params.set_basis_format(basis_universal::BasisTextureFormat::UASTC4x4); - compressor_params.set_generate_mipmaps(true); - let color_space = if is_srgb { - basis_universal::ColorSpace::Srgb - } else { - basis_universal::ColorSpace::Linear - }; - compressor_params.set_color_space(color_space); - compressor_params.set_uastc_quality_level(basis_universal::UASTC_QUALITY_DEFAULT); - - let mut source_image = compressor_params.source_image_mut(0); - let size = image.size(); - let Some(ref data) = image.data else { - return Err(CompressedImageSaverError::UninitializedImage); - }; - source_image.init(data, size.x, size.y, 4); - - let mut compressor = basis_universal::Compressor::new(4); - #[expect( - unsafe_code, - reason = "The basis-universal compressor cannot be interacted with except through unsafe functions" - )] - // SAFETY: the CompressorParams are "valid" to the best of our knowledge. The basis-universal - // library bindings note that invalid params might produce undefined behavior. - unsafe { - compressor.init(&compressor_params); - compressor.process().unwrap(); - } - compressor.basis_file().to_vec() - }; - - writer.write_all(&compressed_basis_data).await?; - Ok(ImageLoaderSettings { - format: ImageFormatSetting::Format(ImageFormat::Basis), - is_srgb, - sampler: image.sampler.clone(), - asset_usage: image.asset_usage, - texture_format: None, - array_layout: None, - }) - } -} diff --git a/crates/bevy_image/src/image.rs b/crates/bevy_image/src/image.rs index 456c61406f2b0..cb50424226ae0 100644 --- a/crates/bevy_image/src/image.rs +++ b/crates/bevy_image/src/image.rs @@ -1,11 +1,13 @@ +#[cfg(feature = "basis_universal")] +use crate::basis_universal::BasisUniversalPlugin; use crate::ImageLoader; -#[cfg(feature = "basis-universal")] -use super::basis::*; #[cfg(feature = "dds")] use super::dds::*; #[cfg(feature = "ktx2")] use super::ktx2::*; +#[cfg(feature = "basis_universal")] +pub use basisu_c_sys::extra::BasisuTranscodeError; use bevy_app::{App, Plugin}; #[cfg(not(feature = "bevy_reflect"))] use bevy_reflect::TypePath; @@ -200,6 +202,9 @@ impl ImagePlugin { impl Plugin for ImagePlugin { fn build(&self, app: &mut App) { + #[cfg(feature = "basis_universal")] + app.add_plugins(BasisUniversalPlugin); + #[cfg(feature = "exr")] app.init_asset_loader::(); @@ -219,33 +224,32 @@ impl Plugin for ImagePlugin { .insert(&TRANSPARENT_IMAGE_HANDLE, Image::transparent()) .unwrap(); - #[cfg(feature = "compressed_image_saver")] - if let Some(processor) = app - .world() - .get_resource::() - { - processor.register_processor::, - crate::CompressedImageSaver, - >>(crate::CompressedImageSaver.into()); - processor.set_default_processor::, - crate::CompressedImageSaver, - >>("png"); - } - app.preregister_asset_loader::(ImageLoader::SUPPORTED_FILE_EXTENSIONS); } + + fn finish(&self, app: &mut App) { + if !ImageLoader::SUPPORTED_FORMATS.is_empty() { + let supported_compressed_formats = if let Some(resource) = + app.world().get_resource::() + { + resource.0 + } else { + bevy_log::warn!( + "CompressedImageFormatSupport resource not found. \ + It should either be initialized in finish() of \ + RenderPlugin, or manually if not using the RenderPlugin or the WGPU backend." + ); + CompressedImageFormats::NONE + }; + + app.register_asset_loader(ImageLoader::new(supported_compressed_formats)); + } + } } /// The format of an on-disk image asset. #[derive(Debug, Serialize, Deserialize, Copy, Clone)] pub enum ImageFormat { - /// An image in basis universal format. - #[cfg(feature = "basis-universal")] - Basis, /// An image in BMP format. #[cfg(feature = "bmp")] Bmp, @@ -309,8 +313,6 @@ impl ImageFormat { /// Gets the file extensions for a given format. pub const fn to_file_extensions(&self) -> &'static [&'static str] { match self { - #[cfg(feature = "basis-universal")] - ImageFormat::Basis => &["basis"], #[cfg(feature = "bmp")] ImageFormat::Bmp => &["bmp"], #[cfg(feature = "dds")] @@ -359,8 +361,6 @@ impl ImageFormat { /// If a format doesn't have any dedicated MIME types, this list will be empty. pub const fn to_mime_types(&self) -> &'static [&'static str] { match self { - #[cfg(feature = "basis-universal")] - ImageFormat::Basis => &["image/basis", "image/x-basis"], #[cfg(feature = "bmp")] ImageFormat::Bmp => &["image/bmp", "image/x-bmp"], #[cfg(feature = "dds")] @@ -423,7 +423,6 @@ impl ImageFormat { )] Some(match mime_type.to_ascii_lowercase().as_str() { // note: farbfeld does not have a MIME type - "image/basis" | "image/x-basis" => feature_gate!("basis-universal", Basis), "image/bmp" | "image/x-bmp" => feature_gate!("bmp", Bmp), "image/vnd-ms.dds" => feature_gate!("dds", Dds), "image/vnd.radiance" => feature_gate!("hdr", Hdr), @@ -458,7 +457,6 @@ impl ImageFormat { reason = "If all features listed below are disabled, then all arms will have a `return None`, keeping the surrounding `Some()` from being constructed." )] Some(match extension.to_ascii_lowercase().as_str() { - "basis" => feature_gate!("basis-universal", Basis), "bmp" => feature_gate!("bmp", Bmp), "dds" => feature_gate!("dds", Dds), "ff" | "farbfeld" => feature_gate!("ff", Farbfeld), @@ -517,8 +515,6 @@ impl ImageFormat { ImageFormat::Tiff => image::ImageFormat::Tiff, #[cfg(feature = "webp")] ImageFormat::WebP => image::ImageFormat::WebP, - #[cfg(feature = "basis-universal")] - ImageFormat::Basis => return None, #[cfg(feature = "ktx2")] ImageFormat::Ktx2 => return None, // FIXME: https://github.com/rust-lang/rust/issues/129031 @@ -1427,7 +1423,7 @@ impl Image { buffer: &[u8], image_type: ImageType, #[cfg_attr( - not(any(feature = "basis-universal", feature = "dds", feature = "ktx2")), + not(any(feature = "dds", feature = "ktx2")), expect(unused_variables, reason = "only used with certain features") )] supported_compressed_formats: CompressedImageFormats, @@ -1444,10 +1440,6 @@ impl Image { // cases. let mut image = match format { - #[cfg(feature = "basis-universal")] - ImageFormat::Basis => { - basis_buffer_to_image(buffer, supported_compressed_formats, is_srgb)? - } #[cfg(feature = "dds")] ImageFormat::Dds => dds_buffer_to_image(buffer, supported_compressed_formats, is_srgb)?, #[cfg(feature = "ktx2")] @@ -1484,6 +1476,9 @@ impl Image { format_description .required_features() .contains(Features::TEXTURE_COMPRESSION_ASTC) + || format_description + .required_features() + .contains(Features::TEXTURE_COMPRESSION_ASTC_HDR) || format_description .required_features() .contains(Features::TEXTURE_COMPRESSION_BC) @@ -1996,30 +1991,9 @@ impl Image { } } -/// A [UASTC] texture channel layout -/// -/// [UASTC]: https://github.com/BinomialLLC/basis_universal/wiki/UASTC-Texture-Specification/b624c07ad3c659e7b0f0badcb36e9a6b8820a99d -#[derive(Clone, Copy, Debug)] -pub enum TextureChannelLayout { - /// 3-color - Rgb, - /// 4-color - Rgba, - /// 1-color (R) extended to 3 (RRR) - Rrr, - /// 2-color (RG) extended to 4 (RRRG) - Rrrg, - /// 2-color - Rg, -} - /// Texture data need to be transcoded from this format for use with `wgpu`. #[derive(Clone, Copy, Debug)] pub enum TranscodeFormat { - /// Has to be transcoded from a compressed ETC1S texture. - Etc1s, - /// Has to be transcoded from a compressed UASTC texture. - Uastc(TextureChannelLayout), /// Has to be transcoded from `R8UnormSrgb` to `R8Unorm` for use with `wgpu`. R8UnormSrgb, /// Has to be transcoded from `Rg8UnormSrgb` to `R8G8Unorm` for use with `wgpu`. @@ -2118,6 +2092,10 @@ pub enum TextureError { /// Only cubemaps with six faces are supported. #[error("only cubemaps with six faces are supported")] IncompleteCubemap, + /// Basis universal transcode error. + #[cfg(feature = "basis_universal")] + #[error(transparent)] + BasisuTranscodeError(#[from] BasisuTranscodeError), } /// The type of a raw image buffer. @@ -2186,6 +2164,11 @@ bitflags::bitflags! { /// /// [ASTC Format Specification]: https://registry.khronos.org/DataFormat/specs/1.3/dataformat.1.3.html#ASTC const ASTC_LDR = 1 << 0; + /// Support for ASTC HDR textures. + /// + /// For more information see: + /// - [`Features::TEXTURE_COMPRESSION_ASTC_HDR`] + const ASTC_HDR = 1 << 1; /// Support for Block Compressed textures. /// /// For more information see: @@ -2197,7 +2180,7 @@ bitflags::bitflags! { /// [S3TC Format Specification]: https://registry.khronos.org/DataFormat/specs/1.3/dataformat.1.3.html#S3TC /// [RGTC Format Specification]: https://registry.khronos.org/DataFormat/specs/1.3/dataformat.1.3.html#RGTC /// [BPTC Format Specification]: https://registry.khronos.org/DataFormat/specs/1.3/dataformat.1.3.html#BPTC - const BC = 1 << 1; + const BC = 1 << 2; /// Support for Ericsson Texture Compression. /// /// For more information see: @@ -2205,7 +2188,7 @@ bitflags::bitflags! { /// - [ETC2 Format Specification] /// /// [ETC2 Format Specification]: https://registry.khronos.org/DataFormat/specs/1.3/dataformat.1.3.html#ETC2 - const ETC2 = 1 << 2; + const ETC2 = 1 << 3; } } @@ -2216,6 +2199,9 @@ impl CompressedImageFormats { if features.contains(Features::TEXTURE_COMPRESSION_ASTC) { supported_compressed_formats |= Self::ASTC_LDR; } + if features.contains(Features::TEXTURE_COMPRESSION_ASTC_HDR) { + supported_compressed_formats |= Self::ASTC_HDR; + } if features.contains(Features::TEXTURE_COMPRESSION_BC) { supported_compressed_formats |= Self::BC; } @@ -2255,6 +2241,10 @@ impl CompressedImageFormats { | TextureFormat::EacR11Snorm | TextureFormat::EacRg11Unorm | TextureFormat::EacRg11Snorm => self.contains(CompressedImageFormats::ETC2), + TextureFormat::Astc { + channel: wgpu_types::AstcChannel::Hdr, + .. + } => self.contains(CompressedImageFormats::ASTC_HDR), TextureFormat::Astc { .. } => self.contains(CompressedImageFormats::ASTC_LDR), _ => true, } diff --git a/crates/bevy_image/src/image_loader.rs b/crates/bevy_image/src/image_loader.rs index 79442a0a82e74..1221061a5fc68 100644 --- a/crates/bevy_image/src/image_loader.rs +++ b/crates/bevy_image/src/image_loader.rs @@ -18,8 +18,6 @@ pub struct ImageLoader { impl ImageLoader { /// Full list of supported formats. pub const SUPPORTED_FORMATS: &'static [ImageFormat] = &[ - #[cfg(feature = "basis-universal")] - ImageFormat::Basis, #[cfg(feature = "bmp")] ImageFormat::Bmp, #[cfg(feature = "dds")] diff --git a/crates/bevy_image/src/ktx2.rs b/crates/bevy_image/src/ktx2.rs index 7491c5446851c..c47d8af0b62be 100644 --- a/crates/bevy_image/src/ktx2.rs +++ b/crates/bevy_image/src/ktx2.rs @@ -1,10 +1,6 @@ #[cfg(any(feature = "flate2", feature = "zstd_rust"))] use std::io::Read; -#[cfg(feature = "basis-universal")] -use basis_universal::{ - DecodeFlags, LowLevelUastcTranscoder, SliceParametersUastc, TranscoderBlockFormat, -}; use bevy_color::Srgba; use bevy_utils::default; #[cfg(any(feature = "flate2", feature = "zstd_rust", feature = "zstd_c"))] @@ -18,7 +14,7 @@ use wgpu_types::{ TextureViewDimension, }; -use super::{CompressedImageFormats, Image, TextureChannelLayout, TextureError, TranscodeFormat}; +use super::{CompressedImageFormats, Image, TextureError, TranscodeFormat}; /// Converts KTX2 bytes to a bevy [`Image`] using the given compressed format support. /// @@ -34,6 +30,21 @@ pub fn ktx2_buffer_to_image( ) -> Result { let ktx2 = ktx2::Reader::new(buffer) .map_err(|err| TextureError::InvalidData(format!("Failed to parse ktx2 file: {err:?}")))?; + + #[cfg(feature = "basis_universal")] + for (key, value) in ktx2.key_value_data() { + // Recognize if the ktx2 file is basis universal format. + // We can't use `ColorModel` because basis universal can also be standard ASTC. + const BASISU_VERSION: &[u8] = b"Basis Universal 2."; + if key == "KTXwriter" && &value[..BASISU_VERSION.len()] == BASISU_VERSION { + return crate::ktx2_basisu_buffer_to_image( + buffer, + supported_compressed_formats, + is_srgb, + ); + } + } + let Header { pixel_width: width, pixel_height: height, @@ -50,6 +61,14 @@ pub fn ktx2_buffer_to_image( // Handle supercompression let mut levels: Vec>; + + #[cfg_attr( + not(any(feature = "flate2", feature = "zstd_rust", feature = "zstd_c")), + expect( + clippy::redundant_else, + reason = "else block is redundant when flate2, zstd_rust and zstd_c are all disabled" + ) + )] if let Some(supercompression_scheme) = supercompression_scheme { match supercompression_scheme { #[cfg(feature = "flate2")] @@ -146,7 +165,8 @@ pub fn ktx2_buffer_to_image( TranscodeFormat::Rgb8 => { let mut rgba = vec![255u8; width as usize * height as usize * 4]; for (level, level_data) in levels.iter().enumerate() { - let n_pixels = (width as usize >> level).max(1) * (height as usize >> level).max(1); + let n_pixels = + (width as usize >> level).max(1) * (height as usize >> level).max(1); let mut offset = 0; for _layer in 0..layer_count { @@ -168,77 +188,6 @@ pub fn ktx2_buffer_to_image( TextureFormat::Rgba8Unorm } } - #[cfg(feature = "basis-universal")] - TranscodeFormat::Uastc(data_format) => { - let (transcode_block_format, texture_format) = - get_transcoded_formats(supported_compressed_formats, data_format, is_srgb); - let texture_format_info = texture_format; - let (block_width_pixels, block_height_pixels) = ( - texture_format_info.block_dimensions().0, - texture_format_info.block_dimensions().1, - ); - // Texture is not a depth or stencil format, it is possible to pass `None` and unwrap - let block_bytes = texture_format_info.block_copy_size(None).unwrap(); - - let transcoder = LowLevelUastcTranscoder::new(); - for (level, level_data) in levels.iter().enumerate() { - let (level_width, level_height) = ( - (width >> level as u32).max(1), - (height >> level as u32).max(1), - ); - let (num_blocks_x, num_blocks_y) = ( - level_width.div_ceil(block_width_pixels) .max(1), - level_height.div_ceil(block_height_pixels) .max(1), - ); - let level_bytes = (num_blocks_x * num_blocks_y * block_bytes) as usize; - - let mut offset = 0; - for _layer in 0..layer_count { - for _face in 0..face_count { - // NOTE: SliceParametersUastc does not implement Clone nor Copy so - // it has to be created per use - let slice_parameters = SliceParametersUastc { - num_blocks_x, - num_blocks_y, - has_alpha: false, - original_width: level_width, - original_height: level_height, - }; - transcoder - .transcode_slice( - &level_data[offset..(offset + level_bytes)], - slice_parameters, - DecodeFlags::HIGH_QUALITY, - transcode_block_format, - ) - .map(|mut transcoded_level| transcoded[level].append(&mut transcoded_level)) - .map_err(|error| { - TextureError::SuperDecompressionError(format!( - "Failed to transcode mip level {level} from UASTC to {transcode_block_format:?}: {error:?}", - )) - })?; - offset += level_bytes; - } - } - } - texture_format - } - // ETC1S is a subset of ETC1 which is a subset of ETC2 - // TODO: Implement transcoding - TranscodeFormat::Etc1s => { - let texture_format = if is_srgb { - TextureFormat::Etc2Rgb8UnormSrgb - } else { - TextureFormat::Etc2Rgb8Unorm - }; - if !supported_compressed_formats.supports(texture_format) { - return Err(error); - } - transcoded = levels.to_vec(); - texture_format - } - #[cfg(not(feature = "basis-universal"))] - _ => return Err(error), }; levels = transcoded; Ok(texture_format) @@ -304,89 +253,6 @@ pub fn ktx2_buffer_to_image( Ok(image) } -/// Determines an appropriate wgpu-compatible format based on compressed format support, and a -/// basis universal [`TextureChannelLayout`]. -#[cfg(feature = "basis-universal")] -pub fn get_transcoded_formats( - supported_compressed_formats: CompressedImageFormats, - data_format: TextureChannelLayout, - is_srgb: bool, -) -> (TranscoderBlockFormat, TextureFormat) { - match data_format { - TextureChannelLayout::Rrr => { - if supported_compressed_formats.contains(CompressedImageFormats::BC) { - (TranscoderBlockFormat::BC4, TextureFormat::Bc4RUnorm) - } else if supported_compressed_formats.contains(CompressedImageFormats::ETC2) { - ( - TranscoderBlockFormat::ETC2_EAC_R11, - TextureFormat::EacR11Unorm, - ) - } else { - (TranscoderBlockFormat::RGBA32, TextureFormat::R8Unorm) - } - } - TextureChannelLayout::Rrrg | TextureChannelLayout::Rg => { - if supported_compressed_formats.contains(CompressedImageFormats::BC) { - (TranscoderBlockFormat::BC5, TextureFormat::Bc5RgUnorm) - } else if supported_compressed_formats.contains(CompressedImageFormats::ETC2) { - ( - TranscoderBlockFormat::ETC2_EAC_RG11, - TextureFormat::EacRg11Unorm, - ) - } else { - (TranscoderBlockFormat::RGBA32, TextureFormat::Rg8Unorm) - } - } - // NOTE: Rgba16Float should be transcoded to BC6H/ASTC_HDR. Neither are supported by - // basis-universal, nor is ASTC_HDR supported by wgpu - TextureChannelLayout::Rgb | TextureChannelLayout::Rgba => { - // NOTE: UASTC can be losslessly transcoded to ASTC4x4 and ASTC uses the same - // space as BC7 (128-bits per 4x4 texel block) so prefer ASTC over BC for - // transcoding speed and quality. - if supported_compressed_formats.contains(CompressedImageFormats::ASTC_LDR) { - ( - TranscoderBlockFormat::ASTC_4x4, - TextureFormat::Astc { - block: AstcBlock::B4x4, - channel: if is_srgb { - AstcChannel::UnormSrgb - } else { - AstcChannel::Unorm - }, - }, - ) - } else if supported_compressed_formats.contains(CompressedImageFormats::BC) { - ( - TranscoderBlockFormat::BC7, - if is_srgb { - TextureFormat::Bc7RgbaUnormSrgb - } else { - TextureFormat::Bc7RgbaUnorm - }, - ) - } else if supported_compressed_formats.contains(CompressedImageFormats::ETC2) { - ( - TranscoderBlockFormat::ETC2_RGBA, - if is_srgb { - TextureFormat::Etc2Rgba8UnormSrgb - } else { - TextureFormat::Etc2Rgba8Unorm - }, - ) - } else { - ( - TranscoderBlockFormat::RGBA32, - if is_srgb { - TextureFormat::Rgba8UnormSrgb - } else { - TextureFormat::Rgba8Unorm - }, - ) - } - } - } -} - /// Reads the [`TextureFormat`] from a [`ktx2::Reader`]. /// /// # Errors @@ -1160,11 +1026,6 @@ pub fn ktx2_dfd_header_to_texture_format( AstcChannel::Unorm }, }, - Some(ColorModel::ETC1S) => { - return Err(TextureError::FormatRequiresTranscodingError( - TranscodeFormat::Etc1s, - )); - } Some(ColorModel::PVRTC) => { return Err(TextureError::UnsupportedTextureFormat( "PVRTC is not supported".to_string(), @@ -1175,22 +1036,6 @@ pub fn ktx2_dfd_header_to_texture_format( "PVRTC2 is not supported".to_string(), )); } - Some(ColorModel::UASTC) => { - return Err(TextureError::FormatRequiresTranscodingError( - TranscodeFormat::Uastc(match sample_information[0].channel_type { - 0 => TextureChannelLayout::Rgb, - 3 => TextureChannelLayout::Rgba, - 4 => TextureChannelLayout::Rrr, - 5 => TextureChannelLayout::Rrrg, - 6 => TextureChannelLayout::Rg, - channel_type => { - return Err(TextureError::UnsupportedTextureFormat(format!( - "Invalid KTX2 UASTC channel type: {channel_type}", - ))) - } - }), - )); - } None => { return Err(TextureError::UnsupportedTextureFormat( "Unspecified KTX2 color model".to_string(), @@ -1499,6 +1344,62 @@ pub fn ktx2_format_to_texture_format( }, } } + ktx2::Format::ASTC_4x4_SFLOAT_BLOCK => TextureFormat::Astc { + block: AstcBlock::B4x4, + channel: AstcChannel::Hdr, + }, + ktx2::Format::ASTC_5x4_SFLOAT_BLOCK => TextureFormat::Astc { + block: AstcBlock::B5x4, + channel: AstcChannel::Hdr, + }, + ktx2::Format::ASTC_5x5_SFLOAT_BLOCK => TextureFormat::Astc { + block: AstcBlock::B5x5, + channel: AstcChannel::Hdr, + }, + ktx2::Format::ASTC_6x5_SFLOAT_BLOCK => TextureFormat::Astc { + block: AstcBlock::B6x5, + channel: AstcChannel::Hdr, + }, + ktx2::Format::ASTC_6x6_SFLOAT_BLOCK => TextureFormat::Astc { + block: AstcBlock::B6x6, + channel: AstcChannel::Hdr, + }, + ktx2::Format::ASTC_8x5_SFLOAT_BLOCK => TextureFormat::Astc { + block: AstcBlock::B8x5, + channel: AstcChannel::Hdr, + }, + ktx2::Format::ASTC_8x6_SFLOAT_BLOCK => TextureFormat::Astc { + block: AstcBlock::B8x6, + channel: AstcChannel::Hdr, + }, + ktx2::Format::ASTC_8x8_SFLOAT_BLOCK => TextureFormat::Astc { + block: AstcBlock::B8x8, + channel: AstcChannel::Hdr, + }, + ktx2::Format::ASTC_10x5_SFLOAT_BLOCK => TextureFormat::Astc { + block: AstcBlock::B10x5, + channel: AstcChannel::Hdr, + }, + ktx2::Format::ASTC_10x6_SFLOAT_BLOCK => TextureFormat::Astc { + block: AstcBlock::B10x6, + channel: AstcChannel::Hdr, + }, + ktx2::Format::ASTC_10x8_SFLOAT_BLOCK => TextureFormat::Astc { + block: AstcBlock::B10x8, + channel: AstcChannel::Hdr, + }, + ktx2::Format::ASTC_10x10_SFLOAT_BLOCK => TextureFormat::Astc { + block: AstcBlock::B10x10, + channel: AstcChannel::Hdr, + }, + ktx2::Format::ASTC_12x10_SFLOAT_BLOCK => TextureFormat::Astc { + block: AstcBlock::B12x10, + channel: AstcChannel::Hdr, + }, + ktx2::Format::ASTC_12x12_SFLOAT_BLOCK => TextureFormat::Astc { + block: AstcBlock::B12x12, + channel: AstcChannel::Hdr, + }, _ => { return Err(TextureError::UnsupportedTextureFormat(format!( "{ktx2_format:?}" diff --git a/crates/bevy_image/src/lib.rs b/crates/bevy_image/src/lib.rs index 8f47a238a852b..2c2978bef4a9f 100644 --- a/crates/bevy_image/src/lib.rs +++ b/crates/bevy_image/src/lib.rs @@ -22,10 +22,8 @@ pub use self::image::*; mod serialized_image; #[cfg(feature = "serialize")] pub use self::serialized_image::*; -#[cfg(feature = "basis-universal")] -mod basis; -#[cfg(feature = "compressed_image_saver")] -mod compressed_image_saver; +#[cfg(feature = "basis_universal")] +mod basis_universal; #[cfg(feature = "dds")] mod dds; mod dynamic_texture_atlas_builder; @@ -40,8 +38,8 @@ mod saver; mod texture_atlas; mod texture_atlas_builder; -#[cfg(feature = "compressed_image_saver")] -pub use compressed_image_saver::*; +#[cfg(feature = "basis_universal")] +pub use basis_universal::*; #[cfg(feature = "dds")] pub use dds::*; pub use dynamic_texture_atlas_builder::*; diff --git a/crates/bevy_image/src/saver.rs b/crates/bevy_image/src/saver.rs index e601326f0a3e3..8e5492d85536d 100644 --- a/crates/bevy_image/src/saver.rs +++ b/crates/bevy_image/src/saver.rs @@ -11,7 +11,7 @@ use crate::{Image, ImageFormat, ImageFormatSetting, ImageLoader, ImageLoaderSett /// [`AssetSaver`] for images that can be saved by the `image` crate. /// -/// Unlike `CompressedImageSaver`, this does not attempt to do any "texture optimization", like +/// This does not attempt to do any "texture optimization", like /// compression (though some file formats intrinsically perform some compression, e.g., JPEG). /// /// Some file formats do not support all texture formats (e.g., PNG does not support diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index 6969d104b9773..a82404068378a 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -29,9 +29,6 @@ detailed_trace = ["bevy_ecs/detailed_trace", "bevy_render?/detailed_trace"] sysinfo_plugin = ["bevy_diagnostic/sysinfo_plugin"] -# Enables compressed KTX2 UASTC texture output on the asset processor -compressed_image_saver = ["bevy_image/compressed_image_saver"] - # For ktx2 supercompression zlib = ["bevy_image/zlib"] zstd = ["bevy_image/zstd"] @@ -39,7 +36,6 @@ zstd_rust = ["bevy_image/zstd_rust"] zstd_c = ["bevy_image/zstd_c"] # Image format support (HDR and PNG enabled by default) -basis-universal = ["bevy_image/basis-universal"] bmp = ["bevy_image/bmp"] ff = ["bevy_image/ff"] gif = ["bevy_image/gif"] @@ -56,6 +52,9 @@ exr = ["bevy_image/exr"] hdr = ["bevy_image/hdr"] ktx2 = ["bevy_image/ktx2"] +basis_universal = ["bevy_image/basis_universal"] +basis_universal_saver = ["basis_universal", "bevy_image/basis_universal_saver"] + # Enable SPIR-V passthrough spirv_shader_passthrough = ["bevy_render/spirv_shader_passthrough"] diff --git a/crates/bevy_log/src/lib.rs b/crates/bevy_log/src/lib.rs index e084d42ced365..0b98bc4ed893d 100644 --- a/crates/bevy_log/src/lib.rs +++ b/crates/bevy_log/src/lib.rs @@ -47,8 +47,8 @@ pub mod prelude { pub use bevy_utils::once; pub use tracing::{ - self, debug, debug_span, error, error_span, event, info, info_span, trace, trace_span, warn, - warn_span, Level, + self, debug, debug_span, error, error_span, event, info, info_span, + level_filters::STATIC_MAX_LEVEL, trace, trace_span, warn, warn_span, Level, }; pub use tracing_subscriber; diff --git a/crates/bevy_render/src/texture/mod.rs b/crates/bevy_render/src/texture/mod.rs index dc6b93da0c734..96821b0c391ee 100644 --- a/crates/bevy_render/src/texture/mod.rs +++ b/crates/bevy_render/src/texture/mod.rs @@ -5,7 +5,7 @@ mod texture_attachment; mod texture_cache; pub use crate::render_resource::DefaultImageSampler; -use bevy_image::{CompressedImageFormatSupport, CompressedImageFormats, ImageLoader, ImagePlugin}; +use bevy_image::ImagePlugin; pub use fallback_image::*; pub use gpu_image::*; pub use manual_texture_view::*; @@ -18,9 +18,7 @@ use crate::{ RenderStartup, RenderSystems, }; use bevy_app::{App, Plugin}; -use bevy_asset::AssetApp; use bevy_ecs::prelude::*; -use bevy_log::warn; #[derive(Default)] pub struct TexturePlugin; @@ -44,19 +42,6 @@ impl Plugin for TexturePlugin { } fn finish(&self, app: &mut App) { - if !ImageLoader::SUPPORTED_FORMATS.is_empty() { - let supported_compressed_formats = if let Some(resource) = - app.world().get_resource::() - { - resource.0 - } else { - warn!("CompressedImageFormatSupport resource not found. It should either be initialized in finish() of \ - RenderPlugin, or manually if not using the RenderPlugin or the WGPU backend."); - CompressedImageFormats::NONE - }; - - app.register_asset_loader(ImageLoader::new(supported_compressed_formats)); - } let default_sampler = app.get_added_plugins::()[0] .default_sampler .clone(); diff --git a/docs/cargo_features.md b/docs/cargo_features.md index 722ee3cb6bddb..33ea9647977e2 100644 --- a/docs/cargo_features.md +++ b/docs/cargo_features.md @@ -65,7 +65,8 @@ This is the complete `bevy` cargo feature list, without "profiles" or "collectio |asset_processor|Enables the built-in asset processor for processed assets.| |async-io|Use async-io's implementation of block_on instead of futures-lite's implementation. This is preferred if your application uses async-io.| |async_executor|Uses `async-executor` as a task execution backend.| -|basis-universal|Basis Universal compressed texture support| +|basis_universal|Basis Universal compressed texture support| +|basis_universal_saver|Basis Universal saver and asset processor| |bevy_animation|Provides animation functionality| |bevy_anti_alias|Provides various anti aliasing solutions| |bevy_asset|Provides asset functionality| @@ -110,7 +111,6 @@ This is the complete `bevy` cargo feature list, without "profiles" or "collectio |bevy_world_serialization|Provides ECS serialization functionality| |bluenoise_texture|Include spatio-temporal blue noise KTX2 file used by generated environment maps, Solari and atmosphere| |bmp|BMP image format support| -|compressed_image_saver|Enables compressed KTX2 UASTC texture output on the asset processor| |critical-section|`critical-section` provides the building blocks for synchronization primitives on all platforms, including `no_std`.| |custom_cursor|Enable winit custom cursor support| |dds|DDS compressed texture support| diff --git a/examples/large_scenes/mipmap_generator/README.md b/examples/large_scenes/mipmap_generator/README.md index 14e28c1221dd3..3c74b517b11e8 100644 --- a/examples/large_scenes/mipmap_generator/README.md +++ b/examples/large_scenes/mipmap_generator/README.md @@ -18,7 +18,7 @@ Test loading a gLTF, computing mips with texture compression, and caching compre Bevy supports a [variety of compressed image formats](https://docs.rs/bevy/latest/bevy/render/texture/enum.ImageFormat.html) that can also contain mipmaps. This plugin is intended for situations where the use of those formats is impractical (mostly prototyping/testing). With this plugin, mipmap generation happens slowly on the cpu. -Instead of using this plugin, consider using the new [CompressedImageSaver](https://bevyengine.org/news/bevy-0-12/#compressedimagesaver). +Instead of using this plugin, consider using the `BasisUniversalPlugin`'s asset processor that generates textures ahead of time. For generating compressed textures ahead of time also check out: diff --git a/examples/wasm/imported_assets b/examples/wasm/imported_assets new file mode 120000 index 0000000000000..b14502c5587e3 --- /dev/null +++ b/examples/wasm/imported_assets @@ -0,0 +1 @@ +../../imported_assets/ \ No newline at end of file