#[cfg(any(feature = "flate2", feature = "ruzstd"))]
use std::io::Read;
use crate::color::SrgbColorSpace;
#[cfg(feature = "basis-universal")]
use basis_universal::{
DecodeFlags, LowLevelUastcTranscoder, SliceParametersUastc, TranscoderBlockFormat,
};
use bevy_utils::default;
#[cfg(any(feature = "flate2", feature = "ruzstd"))]
use ktx2::SupercompressionScheme;
use ktx2::{
BasicDataFormatDescriptor, ChannelTypeQualifiers, ColorModel, DataFormatDescriptorHeader,
Header, SampleInformation,
};
use wgpu::{
AstcBlock, AstcChannel, Extent3d, TextureDimension, TextureFormat, TextureViewDescriptor,
TextureViewDimension,
};
use super::{CompressedImageFormats, DataFormat, Image, TextureError, TranscodeFormat};
pub fn ktx2_buffer_to_image(
buffer: &[u8],
supported_compressed_formats: CompressedImageFormats,
is_srgb: bool,
) -> Result<Image, TextureError> {
let ktx2 = ktx2::Reader::new(buffer)
.map_err(|err| TextureError::InvalidData(format!("Failed to parse ktx2 file: {err:?}")))?;
let Header {
pixel_width: width,
pixel_height: height,
pixel_depth: depth,
layer_count,
face_count,
level_count,
supercompression_scheme,
..
} = ktx2.header();
let layer_count = layer_count.max(1);
let face_count = face_count.max(1);
let depth = depth.max(1);
let mut levels = Vec::new();
if let Some(supercompression_scheme) = supercompression_scheme {
for (_level, _level_data) in ktx2.levels().enumerate() {
match supercompression_scheme {
#[cfg(feature = "flate2")]
SupercompressionScheme::ZLIB => {
let mut decoder = flate2::bufread::ZlibDecoder::new(_level_data);
let mut decompressed = Vec::new();
decoder.read_to_end(&mut decompressed).map_err(|err| {
TextureError::SuperDecompressionError(format!(
"Failed to decompress {supercompression_scheme:?} for mip {_level}: {err:?}",
))
})?;
levels.push(decompressed);
}
#[cfg(feature = "ruzstd")]
SupercompressionScheme::Zstandard => {
let mut cursor = std::io::Cursor::new(_level_data);
let mut decoder = ruzstd::StreamingDecoder::new(&mut cursor)
.map_err(|err| TextureError::SuperDecompressionError(err.to_string()))?;
let mut decompressed = Vec::new();
decoder.read_to_end(&mut decompressed).map_err(|err| {
TextureError::SuperDecompressionError(format!(
"Failed to decompress {supercompression_scheme:?} for mip {_level}: {err:?}",
))
})?;
levels.push(decompressed);
}
_ => {
return Err(TextureError::SuperDecompressionError(format!(
"Unsupported supercompression scheme: {supercompression_scheme:?}",
)));
}
}
}
} else {
levels = ktx2.levels().map(|level| level.to_vec()).collect();
}
let texture_format = ktx2_get_texture_format(&ktx2, is_srgb).or_else(|error| match error {
TextureError::FormatRequiresTranscodingError(transcode_format) => {
let mut transcoded = vec![Vec::default(); levels.len()];
let texture_format = match transcode_format {
TranscodeFormat::R8UnormSrgb => {
let (mut original_width, mut original_height) = (width, height);
for level_data in &levels {
transcoded.push(
level_data
.iter()
.copied()
.map(|v| v.nonlinear_to_linear_srgb())
.collect::<Vec<u8>>(),
);
original_width = (original_width / 2).max(1);
original_height = (original_height / 2).max(1);
}
TextureFormat::R8Unorm
}
TranscodeFormat::Rg8UnormSrgb => {
let (mut original_width, mut original_height) = (width, height);
for level_data in &levels {
transcoded.push(
level_data
.iter()
.copied()
.map(|v| v.nonlinear_to_linear_srgb())
.collect::<Vec<u8>>(),
);
original_width = (original_width / 2).max(1);
original_height = (original_height / 2).max(1);
}
TextureFormat::Rg8Unorm
}
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 mut offset = 0;
for _layer in 0..layer_count {
for _face in 0..face_count {
for i in 0..n_pixels {
rgba[i * 4] = level_data[offset];
rgba[i * 4 + 1] = level_data[offset + 1];
rgba[i * 4 + 2] = level_data[offset + 2];
offset += 3;
}
transcoded[level].extend_from_slice(&rgba[0..n_pixels * 4]);
}
}
}
if is_srgb {
TextureFormat::Rgba8UnormSrgb
} else {
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,
);
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 + block_width_pixels - 1) / block_width_pixels) .max(1),
((level_height + block_height_pixels - 1) / 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 {
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
}
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)
}
_ => Err(error),
})?;
if !supported_compressed_formats.supports(texture_format) {
return Err(TextureError::UnsupportedTextureFormat(format!(
"Format not supported by this GPU: {texture_format:?}",
)));
}
let texture_format_info = texture_format;
let (block_width_pixels, block_height_pixels) = (
texture_format_info.block_dimensions().0 as usize,
texture_format_info.block_dimensions().1 as usize,
);
let block_bytes = texture_format_info.block_copy_size(None).unwrap() as usize;
let mut wgpu_data = vec![Vec::default(); (layer_count * face_count) as usize];
for (level, level_data) in levels.iter().enumerate() {
let (level_width, level_height, level_depth) = (
(width as usize >> level).max(1),
(height as usize >> level).max(1),
(depth as usize >> level).max(1),
);
let (num_blocks_x, num_blocks_y) = (
((level_width + block_width_pixels - 1) / block_width_pixels).max(1),
((level_height + block_height_pixels - 1) / block_height_pixels).max(1),
);
let level_bytes = num_blocks_x * num_blocks_y * level_depth * block_bytes;
let mut index = 0;
for _layer in 0..layer_count {
for _face in 0..face_count {
let offset = index * level_bytes;
wgpu_data[index].extend_from_slice(&level_data[offset..(offset + level_bytes)]);
index += 1;
}
}
}
let mut image = Image::default();
image.texture_descriptor.format = texture_format;
image.data = wgpu_data.into_iter().flatten().collect::<Vec<_>>();
image.texture_descriptor.size = Extent3d {
width,
height,
depth_or_array_layers: if layer_count > 1 || face_count > 1 {
layer_count * face_count
} else {
depth
}
.max(1),
}
.physical_size(texture_format);
image.texture_descriptor.mip_level_count = level_count;
image.texture_descriptor.dimension = if depth > 1 {
TextureDimension::D3
} else if image.is_compressed() || height > 1 {
TextureDimension::D2
} else {
TextureDimension::D1
};
let mut dimension = None;
if face_count == 6 {
dimension = Some(if layer_count > 1 {
TextureViewDimension::CubeArray
} else {
TextureViewDimension::Cube
});
} else if layer_count > 1 {
dimension = Some(TextureViewDimension::D2Array);
} else if depth > 1 {
dimension = Some(TextureViewDimension::D3);
}
if dimension.is_some() {
image.texture_view_descriptor = Some(TextureViewDescriptor {
dimension,
..default()
});
}
Ok(image)
}
#[cfg(feature = "basis-universal")]
pub fn get_transcoded_formats(
supported_compressed_formats: CompressedImageFormats,
data_format: DataFormat,
is_srgb: bool,
) -> (TranscoderBlockFormat, TextureFormat) {
match data_format {
DataFormat::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)
}
}
DataFormat::Rrrg | DataFormat::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)
}
}
DataFormat::Rgb | DataFormat::Rgba => {
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
},
)
}
}
}
}
pub fn ktx2_get_texture_format<Data: AsRef<[u8]>>(
ktx2: &ktx2::Reader<Data>,
is_srgb: bool,
) -> Result<TextureFormat, TextureError> {
if let Some(format) = ktx2.header().format {
return ktx2_format_to_texture_format(format, is_srgb);
}
for data_format_descriptor in ktx2.data_format_descriptors() {
if data_format_descriptor.header == DataFormatDescriptorHeader::BASIC {
let basic_data_format_descriptor =
BasicDataFormatDescriptor::parse(data_format_descriptor.data)
.map_err(|err| TextureError::InvalidData(format!("KTX2: {err:?}")))?;
let sample_information = basic_data_format_descriptor
.sample_information()
.collect::<Vec<_>>();
return ktx2_dfd_to_texture_format(
&basic_data_format_descriptor,
&sample_information,
is_srgb,
);
}
}
Err(TextureError::UnsupportedTextureFormat(
"Unknown".to_string(),
))
}
enum DataType {
Unorm,
UnormSrgb,
Snorm,
Float,
Uint,
Sint,
}
const F32_1_AS_U32: u32 = 1065353216;
fn sample_information_to_data_type(
sample: &SampleInformation,
is_srgb: bool,
) -> Result<DataType, TextureError> {
if sample
.channel_type_qualifiers
.contains(ChannelTypeQualifiers::EXPONENT)
{
return Err(TextureError::UnsupportedTextureFormat(
"Unsupported KTX2 channel type qualifier: exponent".to_string(),
));
}
Ok(
if sample
.channel_type_qualifiers
.contains(ChannelTypeQualifiers::FLOAT)
{
if sample
.channel_type_qualifiers
.contains(ChannelTypeQualifiers::SIGNED)
{
if sample.upper == F32_1_AS_U32 {
DataType::Snorm
} else {
DataType::Float
}
} else if is_srgb {
DataType::UnormSrgb
} else {
DataType::Unorm
}
} else if sample
.channel_type_qualifiers
.contains(ChannelTypeQualifiers::SIGNED)
{
DataType::Sint
} else {
DataType::Uint
},
)
}
pub fn ktx2_dfd_to_texture_format(
data_format_descriptor: &BasicDataFormatDescriptor,
sample_information: &[SampleInformation],
is_srgb: bool,
) -> Result<TextureFormat, TextureError> {
Ok(match data_format_descriptor.color_model {
Some(ColorModel::RGBSDA) => {
match sample_information.len() {
1 => {
if sample_information[0].channel_type != 0 {
return Err(TextureError::UnsupportedTextureFormat(
"Only red-component single-component KTX2 RGBSDA formats supported"
.to_string(),
));
}
let sample = &sample_information[0];
let data_type = sample_information_to_data_type(sample, false)?;
match sample.bit_length {
8 => match data_type {
DataType::Unorm => TextureFormat::R8Unorm,
DataType::UnormSrgb => {
return Err(TextureError::UnsupportedTextureFormat(
"UnormSrgb not supported for R8".to_string(),
));
}
DataType::Snorm => TextureFormat::R8Snorm,
DataType::Float => {
return Err(TextureError::UnsupportedTextureFormat(
"Float not supported for R8".to_string(),
));
}
DataType::Uint => TextureFormat::R8Uint,
DataType::Sint => TextureFormat::R8Sint,
},
16 => match data_type {
DataType::Unorm => TextureFormat::R16Unorm,
DataType::UnormSrgb => {
return Err(TextureError::UnsupportedTextureFormat(
"UnormSrgb not supported for R16".to_string(),
));
}
DataType::Snorm => TextureFormat::R16Snorm,
DataType::Float => TextureFormat::R16Float,
DataType::Uint => TextureFormat::R16Uint,
DataType::Sint => TextureFormat::R16Sint,
},
32 => match data_type {
DataType::Unorm => {
return Err(TextureError::UnsupportedTextureFormat(
"Unorm not supported for R32".to_string(),
));
}
DataType::UnormSrgb => {
return Err(TextureError::UnsupportedTextureFormat(
"UnormSrgb not supported for R32".to_string(),
));
}
DataType::Snorm => {
return Err(TextureError::UnsupportedTextureFormat(
"Snorm not supported for R32".to_string(),
));
}
DataType::Float => TextureFormat::R32Float,
DataType::Uint => TextureFormat::R32Uint,
DataType::Sint => TextureFormat::R32Sint,
},
v => {
return Err(TextureError::UnsupportedTextureFormat(format!(
"Unsupported sample bit length for RGBSDA 1-channel format: {v}",
)));
}
}
}
2 => {
if sample_information[0].channel_type != 0
|| sample_information[1].channel_type != 1
{
return Err(TextureError::UnsupportedTextureFormat(
"Only red-green-component two-component KTX2 RGBSDA formats supported"
.to_string(),
));
}
assert_eq!(
sample_information[0].bit_length,
sample_information[1].bit_length
);
assert_eq!(
sample_information[0].channel_type_qualifiers,
sample_information[1].channel_type_qualifiers
);
assert_eq!(sample_information[0].lower, sample_information[1].lower);
assert_eq!(sample_information[0].upper, sample_information[1].upper);
let sample = &sample_information[0];
let data_type = sample_information_to_data_type(sample, false)?;
match sample.bit_length {
8 => match data_type {
DataType::Unorm => TextureFormat::Rg8Unorm,
DataType::UnormSrgb => {
return Err(TextureError::UnsupportedTextureFormat(
"UnormSrgb not supported for Rg8".to_string(),
));
}
DataType::Snorm => TextureFormat::Rg8Snorm,
DataType::Float => {
return Err(TextureError::UnsupportedTextureFormat(
"Float not supported for Rg8".to_string(),
));
}
DataType::Uint => TextureFormat::Rg8Uint,
DataType::Sint => TextureFormat::Rg8Sint,
},
16 => match data_type {
DataType::Unorm => TextureFormat::Rg16Unorm,
DataType::UnormSrgb => {
return Err(TextureError::UnsupportedTextureFormat(
"UnormSrgb not supported for Rg16".to_string(),
));
}
DataType::Snorm => TextureFormat::Rg16Snorm,
DataType::Float => TextureFormat::Rg16Float,
DataType::Uint => TextureFormat::Rg16Uint,
DataType::Sint => TextureFormat::Rg16Sint,
},
32 => match data_type {
DataType::Unorm => {
return Err(TextureError::UnsupportedTextureFormat(
"Unorm not supported for Rg32".to_string(),
));
}
DataType::UnormSrgb => {
return Err(TextureError::UnsupportedTextureFormat(
"UnormSrgb not supported for Rg32".to_string(),
));
}
DataType::Snorm => {
return Err(TextureError::UnsupportedTextureFormat(
"Snorm not supported for Rg32".to_string(),
));
}
DataType::Float => TextureFormat::Rg32Float,
DataType::Uint => TextureFormat::Rg32Uint,
DataType::Sint => TextureFormat::Rg32Sint,
},
v => {
return Err(TextureError::UnsupportedTextureFormat(format!(
"Unsupported sample bit length for RGBSDA 2-channel format: {v}",
)));
}
}
}
3 => {
if sample_information[0].channel_type == 0
&& sample_information[0].bit_length == 11
&& sample_information[1].channel_type == 1
&& sample_information[1].bit_length == 11
&& sample_information[2].channel_type == 2
&& sample_information[2].bit_length == 10
{
TextureFormat::Rg11b10Float
} else if sample_information[0].channel_type == 0
&& sample_information[0].bit_length == 9
&& sample_information[1].channel_type == 1
&& sample_information[1].bit_length == 9
&& sample_information[2].channel_type == 2
&& sample_information[2].bit_length == 9
{
TextureFormat::Rgb9e5Ufloat
} else if sample_information[0].channel_type == 0
&& sample_information[0].bit_length == 8
&& sample_information[1].channel_type == 1
&& sample_information[1].bit_length == 8
&& sample_information[2].channel_type == 2
&& sample_information[2].bit_length == 8
{
return Err(TextureError::FormatRequiresTranscodingError(
TranscodeFormat::Rgb8,
));
} else {
return Err(TextureError::UnsupportedTextureFormat(
"3-component formats not supported".to_string(),
));
}
}
4 => {
let is_rgba = sample_information[0].channel_type == 0;
assert!(
sample_information[0].channel_type == 0
|| sample_information[0].channel_type == 2
);
assert_eq!(sample_information[1].channel_type, 1);
assert_eq!(
sample_information[2].channel_type,
if is_rgba { 2 } else { 0 }
);
assert_eq!(sample_information[3].channel_type, 15);
if sample_information[0].bit_length == 10
&& sample_information[1].bit_length == 10
&& sample_information[2].bit_length == 10
&& sample_information[3].bit_length == 2
{
return Ok(TextureFormat::Rgb10a2Unorm);
}
assert!(
sample_information[0].bit_length == sample_information[1].bit_length
&& sample_information[0].bit_length == sample_information[2].bit_length
&& sample_information[0].bit_length == sample_information[3].bit_length
);
assert!(
sample_information[0].lower == sample_information[1].lower
&& sample_information[0].lower == sample_information[2].lower
&& sample_information[0].lower == sample_information[3].lower
);
assert!(
sample_information[0].upper == sample_information[1].upper
&& sample_information[0].upper == sample_information[2].upper
&& sample_information[0].upper == sample_information[3].upper
);
let sample = &sample_information[0];
let data_type = sample_information_to_data_type(sample, is_srgb)?;
match sample.bit_length {
8 => match data_type {
DataType::Unorm => {
if is_rgba {
TextureFormat::Rgba8Unorm
} else {
TextureFormat::Bgra8Unorm
}
}
DataType::UnormSrgb => {
if is_rgba {
TextureFormat::Rgba8UnormSrgb
} else {
TextureFormat::Bgra8UnormSrgb
}
}
DataType::Snorm => {
if is_rgba {
TextureFormat::Rgba8Snorm
} else {
return Err(TextureError::UnsupportedTextureFormat(
"Bgra8 not supported for Snorm".to_string(),
));
}
}
DataType::Float => {
return Err(TextureError::UnsupportedTextureFormat(
"Float not supported for Rgba8/Bgra8".to_string(),
));
}
DataType::Uint => {
if is_rgba {
if is_srgb {
TextureFormat::Rgba8UnormSrgb
} else {
TextureFormat::Rgba8Unorm
}
} else {
return Err(TextureError::UnsupportedTextureFormat(
"Bgra8 not supported for Uint".to_string(),
));
}
}
DataType::Sint => {
if is_rgba {
TextureFormat::Rgba8Snorm
} else {
return Err(TextureError::UnsupportedTextureFormat(
"Bgra8 not supported for Sint".to_string(),
));
}
}
},
16 => match data_type {
DataType::Unorm => {
if is_rgba {
TextureFormat::Rgba16Unorm
} else {
return Err(TextureError::UnsupportedTextureFormat(
"Bgra16 not supported for Unorm".to_string(),
));
}
}
DataType::UnormSrgb => {
return Err(TextureError::UnsupportedTextureFormat(
"UnormSrgb not supported for Rgba16/Bgra16".to_string(),
));
}
DataType::Snorm => {
if is_rgba {
TextureFormat::Rgba16Snorm
} else {
return Err(TextureError::UnsupportedTextureFormat(
"Bgra16 not supported for Snorm".to_string(),
));
}
}
DataType::Float => {
if is_rgba {
TextureFormat::Rgba16Float
} else {
return Err(TextureError::UnsupportedTextureFormat(
"Bgra16 not supported for Float".to_string(),
));
}
}
DataType::Uint => {
if is_rgba {
TextureFormat::Rgba16Uint
} else {
return Err(TextureError::UnsupportedTextureFormat(
"Bgra16 not supported for Uint".to_string(),
));
}
}
DataType::Sint => {
if is_rgba {
TextureFormat::Rgba16Sint
} else {
return Err(TextureError::UnsupportedTextureFormat(
"Bgra16 not supported for Sint".to_string(),
));
}
}
},
32 => match data_type {
DataType::Unorm => {
return Err(TextureError::UnsupportedTextureFormat(
"Unorm not supported for Rgba32/Bgra32".to_string(),
));
}
DataType::UnormSrgb => {
return Err(TextureError::UnsupportedTextureFormat(
"UnormSrgb not supported for Rgba32/Bgra32".to_string(),
));
}
DataType::Snorm => {
return Err(TextureError::UnsupportedTextureFormat(
"Snorm not supported for Rgba32/Bgra32".to_string(),
));
}
DataType::Float => {
if is_rgba {
TextureFormat::Rgba32Float
} else {
return Err(TextureError::UnsupportedTextureFormat(
"Bgra32 not supported for Float".to_string(),
));
}
}
DataType::Uint => {
if is_rgba {
TextureFormat::Rgba32Uint
} else {
return Err(TextureError::UnsupportedTextureFormat(
"Bgra32 not supported for Uint".to_string(),
));
}
}
DataType::Sint => {
if is_rgba {
TextureFormat::Rgba32Sint
} else {
return Err(TextureError::UnsupportedTextureFormat(
"Bgra32 not supported for Sint".to_string(),
));
}
}
},
v => {
return Err(TextureError::UnsupportedTextureFormat(format!(
"Unsupported sample bit length for RGBSDA 4-channel format: {v}",
)));
}
}
}
v => {
return Err(TextureError::UnsupportedTextureFormat(format!(
"Unsupported channel count for RGBSDA format: {v}",
)));
}
}
}
Some(ColorModel::YUVSDA)
| Some(ColorModel::YIQSDA)
| Some(ColorModel::LabSDA)
| Some(ColorModel::CMYKA)
| Some(ColorModel::HSVAAng)
| Some(ColorModel::HSLAAng)
| Some(ColorModel::HSVAHex)
| Some(ColorModel::HSLAHex)
| Some(ColorModel::YCgCoA)
| Some(ColorModel::YcCbcCrc)
| Some(ColorModel::ICtCp)
| Some(ColorModel::CIEXYZ)
| Some(ColorModel::CIEXYY) => {
return Err(TextureError::UnsupportedTextureFormat(format!(
"{:?}",
data_format_descriptor.color_model
)));
}
Some(ColorModel::XYZW) => {
assert_eq!(
data_format_descriptor.texel_block_dimensions[0] as usize,
sample_information.len()
);
match sample_information.len() {
4 => {
assert_eq!(sample_information[0].channel_type, 0);
assert_eq!(sample_information[1].channel_type, 1);
assert_eq!(sample_information[2].channel_type, 2);
assert_eq!(sample_information[3].channel_type, 3);
assert!(
sample_information[0].bit_length == sample_information[1].bit_length
&& sample_information[0].bit_length == sample_information[2].bit_length
&& sample_information[0].bit_length == sample_information[3].bit_length
);
assert!(
sample_information[0].channel_type_qualifiers
== sample_information[1].channel_type_qualifiers
&& sample_information[0].channel_type_qualifiers
== sample_information[2].channel_type_qualifiers
&& sample_information[0].channel_type_qualifiers
== sample_information[3].channel_type_qualifiers
);
assert!(
sample_information[0].lower == sample_information[1].lower
&& sample_information[0].lower == sample_information[2].lower
&& sample_information[0].lower == sample_information[3].lower
);
assert!(
sample_information[0].upper == sample_information[1].upper
&& sample_information[0].upper == sample_information[2].upper
&& sample_information[0].upper == sample_information[3].upper
);
let sample = &sample_information[0];
let data_type = sample_information_to_data_type(sample, false)?;
match sample.bit_length {
8 => match data_type {
DataType::Unorm => TextureFormat::Rgba8Unorm,
DataType::UnormSrgb => {
return Err(TextureError::UnsupportedTextureFormat(
"UnormSrgb not supported for XYZW".to_string(),
));
}
DataType::Snorm => TextureFormat::Rgba8Snorm,
DataType::Float => {
return Err(TextureError::UnsupportedTextureFormat(
"Float not supported for Rgba8/Bgra8".to_string(),
));
}
DataType::Uint => TextureFormat::Rgba8Uint,
DataType::Sint => TextureFormat::Rgba8Sint,
},
16 => match data_type {
DataType::Unorm => TextureFormat::Rgba16Unorm,
DataType::UnormSrgb => {
return Err(TextureError::UnsupportedTextureFormat(
"UnormSrgb not supported for Rgba16/Bgra16".to_string(),
));
}
DataType::Snorm => TextureFormat::Rgba16Snorm,
DataType::Float => TextureFormat::Rgba16Float,
DataType::Uint => TextureFormat::Rgba16Uint,
DataType::Sint => TextureFormat::Rgba16Sint,
},
32 => match data_type {
DataType::Unorm => {
return Err(TextureError::UnsupportedTextureFormat(
"Unorm not supported for Rgba32/Bgra32".to_string(),
));
}
DataType::UnormSrgb => {
return Err(TextureError::UnsupportedTextureFormat(
"UnormSrgb not supported for Rgba32/Bgra32".to_string(),
));
}
DataType::Snorm => {
return Err(TextureError::UnsupportedTextureFormat(
"Snorm not supported for Rgba32/Bgra32".to_string(),
));
}
DataType::Float => TextureFormat::Rgba32Float,
DataType::Uint => TextureFormat::Rgba32Uint,
DataType::Sint => TextureFormat::Rgba32Sint,
},
v => {
return Err(TextureError::UnsupportedTextureFormat(format!(
"Unsupported sample bit length for XYZW 4-channel format: {v}",
)));
}
}
}
v => {
return Err(TextureError::UnsupportedTextureFormat(format!(
"Unsupported channel count for XYZW format: {v}",
)));
}
}
}
Some(ColorModel::BC1A) => {
if is_srgb {
TextureFormat::Bc1RgbaUnormSrgb
} else {
TextureFormat::Bc1RgbaUnorm
}
}
Some(ColorModel::BC2) => {
if is_srgb {
TextureFormat::Bc2RgbaUnormSrgb
} else {
TextureFormat::Bc2RgbaUnorm
}
}
Some(ColorModel::BC3) => {
if is_srgb {
TextureFormat::Bc3RgbaUnormSrgb
} else {
TextureFormat::Bc3RgbaUnorm
}
}
Some(ColorModel::BC4) => {
if sample_information[0].lower == 0 {
TextureFormat::Bc4RUnorm
} else {
TextureFormat::Bc4RSnorm
}
}
Some(ColorModel::BC5) => {
if sample_information[0].lower == 0 {
TextureFormat::Bc5RgUnorm
} else {
TextureFormat::Bc5RgSnorm
}
}
Some(ColorModel::BC6H) => {
if sample_information[0].lower == 0 {
TextureFormat::Bc6hRgbUfloat
} else {
TextureFormat::Bc6hRgbFloat
}
}
Some(ColorModel::BC7) => {
if is_srgb {
TextureFormat::Bc7RgbaUnormSrgb
} else {
TextureFormat::Bc7RgbaUnorm
}
}
Some(ColorModel::ETC1) => {
if is_srgb {
TextureFormat::Etc2Rgb8UnormSrgb
} else {
TextureFormat::Etc2Rgb8Unorm
}
}
Some(ColorModel::ETC2) => match sample_information.len() {
1 => {
let sample = &sample_information[0];
match sample.channel_type {
0 => {
if sample_information[0]
.channel_type_qualifiers
.contains(ChannelTypeQualifiers::SIGNED)
{
TextureFormat::EacR11Snorm
} else {
TextureFormat::EacR11Unorm
}
}
2 => {
if is_srgb {
TextureFormat::Etc2Rgb8UnormSrgb
} else {
TextureFormat::Etc2Rgb8Unorm
}
}
_ => {
return Err(TextureError::UnsupportedTextureFormat(format!(
"Invalid ETC2 sample channel type: {}",
sample.channel_type
)))
}
}
}
2 => {
let sample0 = &sample_information[0];
let sample1 = &sample_information[1];
if sample0.channel_type == 0 && sample1.channel_type == 1 {
if sample0
.channel_type_qualifiers
.contains(ChannelTypeQualifiers::SIGNED)
{
TextureFormat::EacRg11Snorm
} else {
TextureFormat::EacRg11Unorm
}
} else if sample0.channel_type == 2 && sample1.channel_type == 15 {
if is_srgb {
TextureFormat::Etc2Rgb8A1UnormSrgb
} else {
TextureFormat::Etc2Rgb8A1Unorm
}
} else if sample0.channel_type == 15 && sample1.channel_type == 2 {
if is_srgb {
TextureFormat::Etc2Rgba8UnormSrgb
} else {
TextureFormat::Etc2Rgba8Unorm
}
} else {
return Err(TextureError::UnsupportedTextureFormat(format!(
"Invalid ETC2 2-sample channel types: {} {}",
sample0.channel_type, sample1.channel_type
)));
}
}
v => {
return Err(TextureError::UnsupportedTextureFormat(format!(
"Unsupported channel count for ETC2 format: {v}",
)));
}
},
Some(ColorModel::ASTC) => TextureFormat::Astc {
block: match (
data_format_descriptor.texel_block_dimensions[0],
data_format_descriptor.texel_block_dimensions[1],
) {
(4, 4) => AstcBlock::B4x4,
(5, 4) => AstcBlock::B5x4,
(5, 5) => AstcBlock::B5x5,
(6, 5) => AstcBlock::B6x5,
(8, 5) => AstcBlock::B8x5,
(8, 8) => AstcBlock::B8x8,
(10, 5) => AstcBlock::B10x5,
(10, 6) => AstcBlock::B10x6,
(10, 8) => AstcBlock::B10x8,
(10, 10) => AstcBlock::B10x10,
(12, 10) => AstcBlock::B12x10,
(12, 12) => AstcBlock::B12x12,
d => {
return Err(TextureError::UnsupportedTextureFormat(format!(
"Invalid ASTC dimension: {} x {}",
d.0, d.1
)))
}
},
channel: if is_srgb {
AstcChannel::UnormSrgb
} else {
AstcChannel::Unorm
},
},
Some(ColorModel::ETC1S) => {
return Err(TextureError::FormatRequiresTranscodingError(
TranscodeFormat::Etc1s,
));
}
Some(ColorModel::PVRTC) => {
return Err(TextureError::UnsupportedTextureFormat(
"PVRTC is not supported".to_string(),
));
}
Some(ColorModel::PVRTC2) => {
return Err(TextureError::UnsupportedTextureFormat(
"PVRTC2 is not supported".to_string(),
));
}
Some(ColorModel::UASTC) => {
return Err(TextureError::FormatRequiresTranscodingError(
TranscodeFormat::Uastc(match sample_information[0].channel_type {
0 => DataFormat::Rgb,
3 => DataFormat::Rgba,
4 => DataFormat::Rrr,
5 => DataFormat::Rrrg,
6 => DataFormat::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(),
));
}
_ => {
return Err(TextureError::UnsupportedTextureFormat(format!(
"Unknown KTX2 color model: {:?}",
data_format_descriptor.color_model
)));
}
})
}
pub fn ktx2_format_to_texture_format(
ktx2_format: ktx2::Format,
is_srgb: bool,
) -> Result<TextureFormat, TextureError> {
Ok(match ktx2_format {
ktx2::Format::R8_UNORM | ktx2::Format::R8_SRGB => {
if is_srgb {
return Err(TextureError::FormatRequiresTranscodingError(
TranscodeFormat::R8UnormSrgb,
));
}
TextureFormat::R8Unorm
}
ktx2::Format::R8_SNORM => TextureFormat::R8Snorm,
ktx2::Format::R8_UINT => TextureFormat::R8Uint,
ktx2::Format::R8_SINT => TextureFormat::R8Sint,
ktx2::Format::R8G8_UNORM | ktx2::Format::R8G8_SRGB => {
if is_srgb {
return Err(TextureError::FormatRequiresTranscodingError(
TranscodeFormat::Rg8UnormSrgb,
));
}
TextureFormat::Rg8Unorm
}
ktx2::Format::R8G8_SNORM => TextureFormat::Rg8Snorm,
ktx2::Format::R8G8_UINT => TextureFormat::Rg8Uint,
ktx2::Format::R8G8_SINT => TextureFormat::Rg8Sint,
ktx2::Format::R8G8B8_UNORM | ktx2::Format::R8G8B8_SRGB => {
return Err(TextureError::FormatRequiresTranscodingError(
TranscodeFormat::Rgb8,
));
}
ktx2::Format::R8G8B8A8_UNORM | ktx2::Format::R8G8B8A8_SRGB => {
if is_srgb {
TextureFormat::Rgba8UnormSrgb
} else {
TextureFormat::Rgba8Unorm
}
}
ktx2::Format::R8G8B8A8_SNORM => TextureFormat::Rgba8Snorm,
ktx2::Format::R8G8B8A8_UINT => TextureFormat::Rgba8Uint,
ktx2::Format::R8G8B8A8_SINT => TextureFormat::Rgba8Sint,
ktx2::Format::B8G8R8A8_UNORM | ktx2::Format::B8G8R8A8_SRGB => {
if is_srgb {
TextureFormat::Bgra8UnormSrgb
} else {
TextureFormat::Bgra8Unorm
}
}
ktx2::Format::A2R10G10B10_UNORM_PACK32 => TextureFormat::Rgb10a2Unorm,
ktx2::Format::R16_UNORM => TextureFormat::R16Unorm,
ktx2::Format::R16_SNORM => TextureFormat::R16Snorm,
ktx2::Format::R16_UINT => TextureFormat::R16Uint,
ktx2::Format::R16_SINT => TextureFormat::R16Sint,
ktx2::Format::R16_SFLOAT => TextureFormat::R16Float,
ktx2::Format::R16G16_UNORM => TextureFormat::Rg16Unorm,
ktx2::Format::R16G16_SNORM => TextureFormat::Rg16Snorm,
ktx2::Format::R16G16_UINT => TextureFormat::Rg16Uint,
ktx2::Format::R16G16_SINT => TextureFormat::Rg16Sint,
ktx2::Format::R16G16_SFLOAT => TextureFormat::Rg16Float,
ktx2::Format::R16G16B16A16_UNORM => TextureFormat::Rgba16Unorm,
ktx2::Format::R16G16B16A16_SNORM => TextureFormat::Rgba16Snorm,
ktx2::Format::R16G16B16A16_UINT => TextureFormat::Rgba16Uint,
ktx2::Format::R16G16B16A16_SINT => TextureFormat::Rgba16Sint,
ktx2::Format::R16G16B16A16_SFLOAT => TextureFormat::Rgba16Float,
ktx2::Format::R32_UINT => TextureFormat::R32Uint,
ktx2::Format::R32_SINT => TextureFormat::R32Sint,
ktx2::Format::R32_SFLOAT => TextureFormat::R32Float,
ktx2::Format::R32G32_UINT => TextureFormat::Rg32Uint,
ktx2::Format::R32G32_SINT => TextureFormat::Rg32Sint,
ktx2::Format::R32G32_SFLOAT => TextureFormat::Rg32Float,
ktx2::Format::R32G32B32A32_UINT => TextureFormat::Rgba32Uint,
ktx2::Format::R32G32B32A32_SINT => TextureFormat::Rgba32Sint,
ktx2::Format::R32G32B32A32_SFLOAT => TextureFormat::Rgba32Float,
ktx2::Format::B10G11R11_UFLOAT_PACK32 => TextureFormat::Rg11b10Float,
ktx2::Format::E5B9G9R9_UFLOAT_PACK32 => TextureFormat::Rgb9e5Ufloat,
ktx2::Format::X8_D24_UNORM_PACK32 => TextureFormat::Depth24Plus,
ktx2::Format::D32_SFLOAT => TextureFormat::Depth32Float,
ktx2::Format::D24_UNORM_S8_UINT => TextureFormat::Depth24PlusStencil8,
ktx2::Format::BC1_RGB_UNORM_BLOCK
| ktx2::Format::BC1_RGB_SRGB_BLOCK
| ktx2::Format::BC1_RGBA_UNORM_BLOCK
| ktx2::Format::BC1_RGBA_SRGB_BLOCK => {
if is_srgb {
TextureFormat::Bc1RgbaUnormSrgb
} else {
TextureFormat::Bc1RgbaUnorm
}
}
ktx2::Format::BC2_UNORM_BLOCK | ktx2::Format::BC2_SRGB_BLOCK => {
if is_srgb {
TextureFormat::Bc2RgbaUnormSrgb
} else {
TextureFormat::Bc2RgbaUnorm
}
}
ktx2::Format::BC3_UNORM_BLOCK | ktx2::Format::BC3_SRGB_BLOCK => {
if is_srgb {
TextureFormat::Bc3RgbaUnormSrgb
} else {
TextureFormat::Bc3RgbaUnorm
}
}
ktx2::Format::BC4_UNORM_BLOCK => TextureFormat::Bc4RUnorm,
ktx2::Format::BC4_SNORM_BLOCK => TextureFormat::Bc4RSnorm,
ktx2::Format::BC5_UNORM_BLOCK => TextureFormat::Bc5RgUnorm,
ktx2::Format::BC5_SNORM_BLOCK => TextureFormat::Bc5RgSnorm,
ktx2::Format::BC6H_UFLOAT_BLOCK => TextureFormat::Bc6hRgbUfloat,
ktx2::Format::BC6H_SFLOAT_BLOCK => TextureFormat::Bc6hRgbFloat,
ktx2::Format::BC7_UNORM_BLOCK | ktx2::Format::BC7_SRGB_BLOCK => {
if is_srgb {
TextureFormat::Bc7RgbaUnormSrgb
} else {
TextureFormat::Bc7RgbaUnorm
}
}
ktx2::Format::ETC2_R8G8B8_UNORM_BLOCK | ktx2::Format::ETC2_R8G8B8_SRGB_BLOCK => {
if is_srgb {
TextureFormat::Etc2Rgb8UnormSrgb
} else {
TextureFormat::Etc2Rgb8Unorm
}
}
ktx2::Format::ETC2_R8G8B8A1_UNORM_BLOCK | ktx2::Format::ETC2_R8G8B8A1_SRGB_BLOCK => {
if is_srgb {
TextureFormat::Etc2Rgb8A1UnormSrgb
} else {
TextureFormat::Etc2Rgb8A1Unorm
}
}
ktx2::Format::ETC2_R8G8B8A8_UNORM_BLOCK | ktx2::Format::ETC2_R8G8B8A8_SRGB_BLOCK => {
if is_srgb {
TextureFormat::Etc2Rgba8UnormSrgb
} else {
TextureFormat::Etc2Rgba8Unorm
}
}
ktx2::Format::EAC_R11_UNORM_BLOCK => TextureFormat::EacR11Unorm,
ktx2::Format::EAC_R11_SNORM_BLOCK => TextureFormat::EacR11Snorm,
ktx2::Format::EAC_R11G11_UNORM_BLOCK => TextureFormat::EacRg11Unorm,
ktx2::Format::EAC_R11G11_SNORM_BLOCK => TextureFormat::EacRg11Snorm,
ktx2::Format::ASTC_4x4_UNORM_BLOCK | ktx2::Format::ASTC_4x4_SRGB_BLOCK => {
TextureFormat::Astc {
block: AstcBlock::B4x4,
channel: if is_srgb {
AstcChannel::UnormSrgb
} else {
AstcChannel::Unorm
},
}
}
ktx2::Format::ASTC_5x4_UNORM_BLOCK | ktx2::Format::ASTC_5x4_SRGB_BLOCK => {
TextureFormat::Astc {
block: AstcBlock::B5x4,
channel: if is_srgb {
AstcChannel::UnormSrgb
} else {
AstcChannel::Unorm
},
}
}
ktx2::Format::ASTC_5x5_UNORM_BLOCK | ktx2::Format::ASTC_5x5_SRGB_BLOCK => {
TextureFormat::Astc {
block: AstcBlock::B5x5,
channel: if is_srgb {
AstcChannel::UnormSrgb
} else {
AstcChannel::Unorm
},
}
}
ktx2::Format::ASTC_6x5_UNORM_BLOCK | ktx2::Format::ASTC_6x5_SRGB_BLOCK => {
TextureFormat::Astc {
block: AstcBlock::B6x5,
channel: if is_srgb {
AstcChannel::UnormSrgb
} else {
AstcChannel::Unorm
},
}
}
ktx2::Format::ASTC_6x6_UNORM_BLOCK | ktx2::Format::ASTC_6x6_SRGB_BLOCK => {
TextureFormat::Astc {
block: AstcBlock::B6x6,
channel: if is_srgb {
AstcChannel::UnormSrgb
} else {
AstcChannel::Unorm
},
}
}
ktx2::Format::ASTC_8x5_UNORM_BLOCK | ktx2::Format::ASTC_8x5_SRGB_BLOCK => {
TextureFormat::Astc {
block: AstcBlock::B8x5,
channel: if is_srgb {
AstcChannel::UnormSrgb
} else {
AstcChannel::Unorm
},
}
}
ktx2::Format::ASTC_8x6_UNORM_BLOCK | ktx2::Format::ASTC_8x6_SRGB_BLOCK => {
TextureFormat::Astc {
block: AstcBlock::B8x6,
channel: if is_srgb {
AstcChannel::UnormSrgb
} else {
AstcChannel::Unorm
},
}
}
ktx2::Format::ASTC_8x8_UNORM_BLOCK | ktx2::Format::ASTC_8x8_SRGB_BLOCK => {
TextureFormat::Astc {
block: AstcBlock::B8x8,
channel: if is_srgb {
AstcChannel::UnormSrgb
} else {
AstcChannel::Unorm
},
}
}
ktx2::Format::ASTC_10x5_UNORM_BLOCK | ktx2::Format::ASTC_10x5_SRGB_BLOCK => {
TextureFormat::Astc {
block: AstcBlock::B10x5,
channel: if is_srgb {
AstcChannel::UnormSrgb
} else {
AstcChannel::Unorm
},
}
}
ktx2::Format::ASTC_10x6_UNORM_BLOCK | ktx2::Format::ASTC_10x6_SRGB_BLOCK => {
TextureFormat::Astc {
block: AstcBlock::B10x6,
channel: if is_srgb {
AstcChannel::UnormSrgb
} else {
AstcChannel::Unorm
},
}
}
ktx2::Format::ASTC_10x8_UNORM_BLOCK | ktx2::Format::ASTC_10x8_SRGB_BLOCK => {
TextureFormat::Astc {
block: AstcBlock::B10x8,
channel: if is_srgb {
AstcChannel::UnormSrgb
} else {
AstcChannel::Unorm
},
}
}
ktx2::Format::ASTC_10x10_UNORM_BLOCK | ktx2::Format::ASTC_10x10_SRGB_BLOCK => {
TextureFormat::Astc {
block: AstcBlock::B10x10,
channel: if is_srgb {
AstcChannel::UnormSrgb
} else {
AstcChannel::Unorm
},
}
}
ktx2::Format::ASTC_12x10_UNORM_BLOCK | ktx2::Format::ASTC_12x10_SRGB_BLOCK => {
TextureFormat::Astc {
block: AstcBlock::B12x10,
channel: if is_srgb {
AstcChannel::UnormSrgb
} else {
AstcChannel::Unorm
},
}
}
ktx2::Format::ASTC_12x12_UNORM_BLOCK | ktx2::Format::ASTC_12x12_SRGB_BLOCK => {
TextureFormat::Astc {
block: AstcBlock::B12x12,
channel: if is_srgb {
AstcChannel::UnormSrgb
} else {
AstcChannel::Unorm
},
}
}
_ => {
return Err(TextureError::UnsupportedTextureFormat(format!(
"{ktx2_format:?}"
)))
}
})
}