use basisu_c_sys::TranscodeTargetFormat;
use bevy::asset::{AssetLoader, RenderAssetUsages};
use bevy::image::ImageSampler;
use bevy::prelude::*;
use bevy::render::render_resource::WgpuFeatures as Features;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::transcoder::{
BasisuTranscodeError, BasisuTranscoder, ChannelType, SupportedTextureCompression,
};
#[derive(TypePath)]
pub struct BasisuLoader {
supported_compressed_formats: SupportedTextureCompression,
}
impl BasisuLoader {
pub fn from_features(features: Features) -> Self {
let mut supported_compressed_formats = SupportedTextureCompression::empty();
if features.contains(Features::TEXTURE_COMPRESSION_ASTC) {
supported_compressed_formats |= SupportedTextureCompression::ASTC_LDR;
}
if features.contains(Features::TEXTURE_COMPRESSION_ASTC_HDR) {
supported_compressed_formats |= SupportedTextureCompression::ASTC_HDR;
}
if features.contains(Features::TEXTURE_COMPRESSION_BC) {
supported_compressed_formats |= SupportedTextureCompression::BC;
}
if features.contains(Features::TEXTURE_COMPRESSION_ETC2) {
supported_compressed_formats |= SupportedTextureCompression::ETC2;
}
Self {
supported_compressed_formats,
}
}
}
#[derive(Serialize, Deserialize, Default, Debug, Clone)]
pub struct BasisuLoaderSettings {
pub sampler: ImageSampler,
pub asset_usage: RenderAssetUsages,
pub is_srgb: Option<bool>,
pub channel_type_hint: ChannelType,
pub force_transcode_target: Option<TranscodeTargetFormat>,
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum BasisuLoaderError {
#[error("Failed to load image bytes: {0}")]
Io(#[from] std::io::Error),
#[error("BasisU failed to transcode texture: {0}")]
TranscodeError(#[from] BasisuTranscodeError),
}
impl AssetLoader for BasisuLoader {
type Asset = Image;
type Settings = BasisuLoaderSettings;
type Error = BasisuLoaderError;
async fn load(
&self,
reader: &mut dyn bevy::asset::io::Reader,
settings: &Self::Settings,
_load_context: &mut bevy::asset::LoadContext<'_>,
) -> Result<Self::Asset, Self::Error> {
let mut data = Vec::new();
reader.read_to_end(&mut data).await?;
let src_bytes = data.len();
let _span = bevy::log::info_span!("transcoding basisu texture").entered();
let time = if log::STATIC_MAX_LEVEL >= log::LevelFilter::Debug {
Some(bevy::platform::time::Instant::now())
} else {
None
};
let mut transcoder = BasisuTranscoder::new();
let info = transcoder.prepare(
&data,
self.supported_compressed_formats,
settings.channel_type_hint,
)?;
let mut out_image =
transcoder.transcode(settings.force_transcode_target, settings.is_srgb)?;
if log::STATIC_MAX_LEVEL >= log::LevelFilter::Debug {
bevy::log::debug!(
"Transcoded a basisu texture {:?} -> {:?}, {:?}kb -> {:?}kb, preferred_target {:?}, extents {:?}, levels {:?}, 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(),
);
}
out_image.sampler = settings.sampler.clone();
out_image.asset_usage = settings.asset_usage;
Ok(out_image)
}
fn extensions(&self) -> &[&str] {
&["basisu.ktx2"]
}
}