mirror of
https://github.com/bevyengine/bevy
synced 2024-11-21 20:23:28 +00:00
KTX2/DDS/.basis compressed texture support (#3884)
# Objective - Support compressed textures including 'universal' formats (ETC1S, UASTC) and transcoding of them to - Support `.dds`, `.ktx2`, and `.basis` files ## Solution - Fixes https://github.com/bevyengine/bevy/issues/3608 Look there for more details. - Note that the functionality is all enabled through non-default features. If it is desirable to enable some by default, I can do that. - The `basis-universal` crate, used for `.basis` file support and for transcoding, is built on bindings against a C++ library. It's not feasible to rewrite in Rust in a short amount of time. There are no Rust alternatives of which I am aware and it's specialised code. In its current state it doesn't support the wasm target, but I don't know for sure. However, it is possible to build the upstream C++ library with emscripten, so there is perhaps a way to add support for web too with some shenanigans. - There's no support for transcoding from BasisLZ/ETC1S in KTX2 files as it was quite non-trivial to implement and didn't feel important given people could use `.basis` files for ETC1S.
This commit is contained in:
parent
9dfd4e4b08
commit
0529f633f9
19 changed files with 2749 additions and 89 deletions
|
@ -64,10 +64,15 @@ wgpu_trace = ["bevy_internal/wgpu_trace"]
|
|||
# Image format support for texture loading (PNG and HDR are enabled by default)
|
||||
hdr = ["bevy_internal/hdr"]
|
||||
png = ["bevy_internal/png"]
|
||||
dds = ["bevy_internal/dds"]
|
||||
tga = ["bevy_internal/tga"]
|
||||
jpeg = ["bevy_internal/jpeg"]
|
||||
bmp = ["bevy_internal/bmp"]
|
||||
basis-universal = ["bevy_internal/basis-universal"]
|
||||
dds = ["bevy_internal/dds"]
|
||||
ktx2 = ["bevy_internal/ktx2"]
|
||||
# For ktx2 supercompression
|
||||
zlib = ["bevy_internal/zlib"]
|
||||
zstd = ["bevy_internal/zstd"]
|
||||
|
||||
# Audio format support (vorbis is enabled by default)
|
||||
flac = ["bevy_internal/flac"]
|
||||
|
|
|
@ -3,7 +3,7 @@ use bevy_asset::{
|
|||
AssetIoError, AssetLoader, AssetPath, BoxedFuture, Handle, LoadContext, LoadedAsset,
|
||||
};
|
||||
use bevy_core::Name;
|
||||
use bevy_ecs::world::World;
|
||||
use bevy_ecs::{prelude::FromWorld, world::World};
|
||||
use bevy_hierarchy::{BuildWorldChildren, WorldChildBuilder};
|
||||
use bevy_log::warn;
|
||||
use bevy_math::{Mat4, Vec3};
|
||||
|
@ -18,10 +18,9 @@ use bevy_render::{
|
|||
color::Color,
|
||||
mesh::{Indices, Mesh, VertexAttributeValues},
|
||||
primitives::{Aabb, Frustum},
|
||||
render_resource::{
|
||||
AddressMode, FilterMode, PrimitiveTopology, SamplerDescriptor, TextureFormat,
|
||||
},
|
||||
texture::{Image, ImageType, TextureError},
|
||||
render_resource::{AddressMode, FilterMode, PrimitiveTopology, SamplerDescriptor},
|
||||
renderer::RenderDevice,
|
||||
texture::{CompressedImageFormats, Image, ImageType, TextureError},
|
||||
view::VisibleEntities,
|
||||
};
|
||||
use bevy_scene::Scene;
|
||||
|
@ -60,8 +59,9 @@ pub enum GltfError {
|
|||
}
|
||||
|
||||
/// Loads glTF files with all of their data as their corresponding bevy representations.
|
||||
#[derive(Default)]
|
||||
pub struct GltfLoader;
|
||||
pub struct GltfLoader {
|
||||
supported_compressed_formats: CompressedImageFormats,
|
||||
}
|
||||
|
||||
impl AssetLoader for GltfLoader {
|
||||
fn load<'a>(
|
||||
|
@ -69,7 +69,9 @@ impl AssetLoader for GltfLoader {
|
|||
bytes: &'a [u8],
|
||||
load_context: &'a mut LoadContext,
|
||||
) -> BoxedFuture<'a, Result<()>> {
|
||||
Box::pin(async move { Ok(load_gltf(bytes, load_context).await?) })
|
||||
Box::pin(async move {
|
||||
Ok(load_gltf(bytes, load_context, self.supported_compressed_formats).await?)
|
||||
})
|
||||
}
|
||||
|
||||
fn extensions(&self) -> &[&str] {
|
||||
|
@ -77,10 +79,21 @@ impl AssetLoader for GltfLoader {
|
|||
}
|
||||
}
|
||||
|
||||
impl FromWorld for GltfLoader {
|
||||
fn from_world(world: &mut World) -> Self {
|
||||
Self {
|
||||
supported_compressed_formats: CompressedImageFormats::from_features(
|
||||
world.resource::<RenderDevice>().features(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads an entire glTF file.
|
||||
async fn load_gltf<'a, 'b>(
|
||||
bytes: &'a [u8],
|
||||
load_context: &'a mut LoadContext<'b>,
|
||||
supported_compressed_formats: CompressedImageFormats,
|
||||
) -> Result<(), GltfError> {
|
||||
let gltf = gltf::Gltf::from_slice(bytes)?;
|
||||
let buffer_data = load_buffers(&gltf, load_context, load_context.path()).await?;
|
||||
|
@ -251,8 +264,14 @@ async fn load_gltf<'a, 'b>(
|
|||
// to avoid https://github.com/bevyengine/bevy/pull/2725
|
||||
if gltf.textures().len() == 1 || cfg!(target_arch = "wasm32") {
|
||||
for gltf_texture in gltf.textures() {
|
||||
let (texture, label) =
|
||||
load_texture(gltf_texture, &buffer_data, &linear_textures, load_context).await?;
|
||||
let (texture, label) = load_texture(
|
||||
gltf_texture,
|
||||
&buffer_data,
|
||||
&linear_textures,
|
||||
load_context,
|
||||
supported_compressed_formats,
|
||||
)
|
||||
.await?;
|
||||
load_context.set_labeled_asset(&label, LoadedAsset::new(texture));
|
||||
}
|
||||
} else {
|
||||
|
@ -265,7 +284,14 @@ async fn load_gltf<'a, 'b>(
|
|||
let load_context: &LoadContext = load_context;
|
||||
let buffer_data = &buffer_data;
|
||||
scope.spawn(async move {
|
||||
load_texture(gltf_texture, buffer_data, linear_textures, load_context).await
|
||||
load_texture(
|
||||
gltf_texture,
|
||||
buffer_data,
|
||||
linear_textures,
|
||||
load_context,
|
||||
supported_compressed_formats,
|
||||
)
|
||||
.await
|
||||
});
|
||||
});
|
||||
})
|
||||
|
@ -334,13 +360,20 @@ async fn load_texture<'a>(
|
|||
buffer_data: &[Vec<u8>],
|
||||
linear_textures: &HashSet<usize>,
|
||||
load_context: &LoadContext<'a>,
|
||||
supported_compressed_formats: CompressedImageFormats,
|
||||
) -> Result<(Image, String), GltfError> {
|
||||
let is_srgb = !linear_textures.contains(&gltf_texture.index());
|
||||
let mut texture = match gltf_texture.source().source() {
|
||||
gltf::image::Source::View { view, mime_type } => {
|
||||
let start = view.offset() as usize;
|
||||
let end = (view.offset() + view.length()) as usize;
|
||||
let buffer = &buffer_data[view.buffer().index()][start..end];
|
||||
Image::from_buffer(buffer, ImageType::MimeType(mime_type))?
|
||||
Image::from_buffer(
|
||||
buffer,
|
||||
ImageType::MimeType(mime_type),
|
||||
supported_compressed_formats,
|
||||
is_srgb,
|
||||
)?
|
||||
}
|
||||
gltf::image::Source::Uri { uri, mime_type } => {
|
||||
let uri = percent_encoding::percent_decode_str(uri)
|
||||
|
@ -363,13 +396,12 @@ async fn load_texture<'a>(
|
|||
Image::from_buffer(
|
||||
&bytes,
|
||||
mime_type.map(ImageType::MimeType).unwrap_or(image_type),
|
||||
supported_compressed_formats,
|
||||
is_srgb,
|
||||
)?
|
||||
}
|
||||
};
|
||||
texture.sampler_descriptor = texture_sampler(&gltf_texture);
|
||||
if (linear_textures).contains(&gltf_texture.index()) {
|
||||
texture.texture_descriptor.format = TextureFormat::Rgba8Unorm;
|
||||
}
|
||||
|
||||
Ok((texture, texture_label(&gltf_texture)))
|
||||
}
|
||||
|
|
|
@ -19,10 +19,15 @@ debug_asset_server = ["bevy_asset/debug_asset_server"]
|
|||
# Image format support for texture loading (PNG and HDR are enabled by default)
|
||||
hdr = ["bevy_render/hdr"]
|
||||
png = ["bevy_render/png"]
|
||||
dds = ["bevy_render/dds"]
|
||||
tga = ["bevy_render/tga"]
|
||||
jpeg = ["bevy_render/jpeg"]
|
||||
bmp = ["bevy_render/bmp"]
|
||||
basis-universal = ["bevy_render/basis-universal"]
|
||||
dds = ["bevy_render/dds"]
|
||||
ktx2 = ["bevy_render/ktx2"]
|
||||
# For ktx2 supercompression
|
||||
zlib = ["bevy_render/zlib"]
|
||||
zstd = ["bevy_render/zstd"]
|
||||
|
||||
# Audio format support (vorbis is enabled by default)
|
||||
flac = ["bevy_audio/flac"]
|
||||
|
|
|
@ -58,6 +58,8 @@ impl PluginGroup for DefaultPlugins {
|
|||
#[cfg(feature = "bevy_pbr")]
|
||||
group.add(bevy_pbr::PbrPlugin::default());
|
||||
|
||||
// NOTE: Load this after renderer initialization so that it knows about the supported
|
||||
// compressed texture formats
|
||||
#[cfg(feature = "bevy_gltf")]
|
||||
group.add(bevy_gltf::GltfPlugin::default());
|
||||
|
||||
|
|
|
@ -123,6 +123,7 @@ bitflags::bitflags! {
|
|||
const ALPHA_MODE_OPAQUE = (1 << 6);
|
||||
const ALPHA_MODE_MASK = (1 << 7);
|
||||
const ALPHA_MODE_BLEND = (1 << 8);
|
||||
const TWO_COMPONENT_NORMAL_MAP = (1 << 9);
|
||||
const NONE = 0;
|
||||
const UNINITIALIZED = 0xFFFF;
|
||||
}
|
||||
|
@ -246,6 +247,22 @@ impl RenderAsset for StandardMaterial {
|
|||
flags |= StandardMaterialFlags::UNLIT;
|
||||
}
|
||||
let has_normal_map = material.normal_map_texture.is_some();
|
||||
if has_normal_map {
|
||||
match gpu_images
|
||||
.get(material.normal_map_texture.as_ref().unwrap())
|
||||
.unwrap()
|
||||
.texture_format
|
||||
{
|
||||
// All 2-component unorm formats
|
||||
TextureFormat::Rg8Unorm
|
||||
| TextureFormat::Rg16Unorm
|
||||
| TextureFormat::Bc5RgUnorm
|
||||
| TextureFormat::EacRg11Unorm => {
|
||||
flags |= StandardMaterialFlags::TWO_COMPONENT_NORMAL_MAP
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
// NOTE: 0.5 is from the glTF default - do we want this?
|
||||
let mut alpha_cutoff = 0.5;
|
||||
match material.alpha_mode {
|
||||
|
|
|
@ -327,6 +327,7 @@ impl FromWorld for MeshPipeline {
|
|||
GpuImage {
|
||||
texture,
|
||||
texture_view,
|
||||
texture_format: image.texture_descriptor.format,
|
||||
sampler,
|
||||
size: Size::new(
|
||||
image.texture_descriptor.size.width as f32,
|
||||
|
|
|
@ -58,6 +58,7 @@ let STANDARD_MATERIAL_FLAGS_UNLIT_BIT: u32 = 32u;
|
|||
let STANDARD_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE: u32 = 64u;
|
||||
let STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK: u32 = 128u;
|
||||
let STANDARD_MATERIAL_FLAGS_ALPHA_MODE_BLEND: u32 = 256u;
|
||||
let STANDARD_MATERIAL_FLAGS_TWO_COMPONENT_NORMAL_MAP: u32 = 512u;
|
||||
|
||||
[[group(1), binding(0)]]
|
||||
var<uniform> material: StandardMaterial;
|
||||
|
@ -515,7 +516,16 @@ fn fragment(in: FragmentInput) -> [[location(0)]] vec4<f32> {
|
|||
#ifdef VERTEX_TANGENTS
|
||||
#ifdef STANDARDMATERIAL_NORMAL_MAP
|
||||
let TBN = mat3x3<f32>(T, B, N);
|
||||
N = TBN * normalize(textureSample(normal_map_texture, normal_map_sampler, in.uv).rgb * 2.0 - 1.0);
|
||||
// Nt is the tangent-space normal.
|
||||
var Nt: vec3<f32>;
|
||||
if ((material.flags & STANDARD_MATERIAL_FLAGS_TWO_COMPONENT_NORMAL_MAP) != 0u) {
|
||||
// Only use the xy components and derive z for 2-component normal maps.
|
||||
Nt = vec3<f32>(textureSample(normal_map_texture, normal_map_sampler, in.uv).rg * 2.0 - 1.0, 0.0);
|
||||
Nt.z = sqrt(1.0 - Nt.x * Nt.x - Nt.y * Nt.y);
|
||||
} else {
|
||||
Nt = textureSample(normal_map_texture, normal_map_sampler, in.uv).rgb * 2.0 - 1.0;
|
||||
}
|
||||
N = normalize(TBN * Nt);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
|
|
@ -11,10 +11,15 @@ keywords = ["bevy"]
|
|||
[features]
|
||||
png = ["image/png"]
|
||||
hdr = ["image/hdr"]
|
||||
dds = ["image/dds"]
|
||||
tga = ["image/tga"]
|
||||
jpeg = ["image/jpeg"]
|
||||
bmp = ["image/bmp"]
|
||||
dds = ["ddsfile"]
|
||||
|
||||
# For ktx2 supercompression
|
||||
zlib = ["flate2"]
|
||||
zstd = ["ruzstd"]
|
||||
|
||||
trace = []
|
||||
wgpu_trace = ["wgpu/trace"]
|
||||
ci_limits = []
|
||||
|
@ -54,3 +59,10 @@ hexasphere = "7.0.0"
|
|||
parking_lot = "0.11.0"
|
||||
regex = "1.5"
|
||||
copyless = "0.1.5"
|
||||
ddsfile = { version = "0.5.0", optional = true }
|
||||
ktx2 = { version = "0.3.0", optional = true }
|
||||
# For ktx2 supercompression
|
||||
flate2 = { version = "1.0.22", optional = true }
|
||||
ruzstd = { version = "0.2.4", optional = true }
|
||||
# For transcoding of UASTC/ETC1S universal formats, and for .basis file support
|
||||
basis-universal = { version = "0.2.0", optional = true }
|
||||
|
|
|
@ -289,6 +289,8 @@ impl Plugin for RenderPlugin {
|
|||
.add_plugin(CameraPlugin)
|
||||
.add_plugin(ViewPlugin)
|
||||
.add_plugin(MeshPlugin)
|
||||
// NOTE: Load this after renderer initialization so that it knows about the supported
|
||||
// compressed texture formats
|
||||
.add_plugin(ImagePlugin);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@ use futures_lite::future;
|
|||
use std::sync::Arc;
|
||||
use wgpu::util::DeviceExt;
|
||||
|
||||
use super::RenderQueue;
|
||||
|
||||
/// This GPU device is responsible for the creation of most rendering and compute resources.
|
||||
#[derive(Clone)]
|
||||
pub struct RenderDevice {
|
||||
|
@ -121,6 +123,22 @@ impl RenderDevice {
|
|||
Buffer::from(wgpu_buffer)
|
||||
}
|
||||
|
||||
/// Creates a new [`Texture`] and initializes it with the specified data.
|
||||
///
|
||||
/// `desc` specifies the general format of the texture.
|
||||
/// `data` is the raw data.
|
||||
pub fn create_texture_with_data(
|
||||
&self,
|
||||
render_queue: &RenderQueue,
|
||||
desc: &wgpu::TextureDescriptor,
|
||||
data: &[u8],
|
||||
) -> Texture {
|
||||
let wgpu_texture = self
|
||||
.device
|
||||
.create_texture_with_data(render_queue.as_ref(), desc, data);
|
||||
Texture::from(wgpu_texture)
|
||||
}
|
||||
|
||||
/// Creates a new [`Texture`].
|
||||
///
|
||||
/// `desc` specifies the general format of the texture.
|
||||
|
|
176
crates/bevy_render/src/texture/basis.rs
Normal file
176
crates/bevy_render/src/texture/basis.rs
Normal file
|
@ -0,0 +1,176 @@
|
|||
use basis_universal::{
|
||||
BasisTextureType, DecodeFlags, TranscodeParameters, Transcoder, TranscoderTextureFormat,
|
||||
};
|
||||
use wgpu::{Extent3d, TextureDimension, TextureFormat};
|
||||
|
||||
use super::{CompressedImageFormats, Image, TextureError};
|
||||
|
||||
pub fn basis_buffer_to_image(
|
||||
buffer: &[u8],
|
||||
supported_compressed_formats: CompressedImageFormats,
|
||||
is_srgb: bool,
|
||||
) -> Result<Image, TextureError> {
|
||||
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 image0_info = if let Some(image_info) = transcoder.image_info(buffer, 0) {
|
||||
image_info
|
||||
} 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!(
|
||||
"{:?} cannot be transcoded to {:?}",
|
||||
basis_texture_format, 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 % 6 != 0 {
|
||||
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) {
|
||||
if 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 {} has {} but image 0 has {}",
|
||||
image_index,
|
||||
mip_level_count,
|
||||
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 {} from {:?} to {:?}: {:?}",
|
||||
level_index, basis_texture_format, 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,
|
||||
};
|
||||
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 => TextureDimension::D2,
|
||||
BasisTextureType::TextureType2DArray => TextureDimension::D2,
|
||||
BasisTextureType::TextureTypeCubemapArray => TextureDimension::D2,
|
||||
BasisTextureType::TextureTypeVolume => TextureDimension::D3,
|
||||
basis_texture_type => {
|
||||
return Err(TextureError::UnsupportedTextureFormat(format!(
|
||||
"{:?}",
|
||||
basis_texture_type
|
||||
)))
|
||||
}
|
||||
};
|
||||
image.data = 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,
|
||||
if is_srgb {
|
||||
TextureFormat::Astc4x4RgbaUnormSrgb
|
||||
} else {
|
||||
TextureFormat::Astc4x4RgbaUnorm
|
||||
},
|
||||
)
|
||||
} 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
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
338
crates/bevy_render/src/texture/dds.rs
Normal file
338
crates/bevy_render/src/texture/dds.rs
Normal file
|
@ -0,0 +1,338 @@
|
|||
use ddsfile::{D3DFormat, Dds, DxgiFormat};
|
||||
use std::io::Cursor;
|
||||
use wgpu::{Extent3d, TextureDimension, TextureFormat};
|
||||
|
||||
use super::{CompressedImageFormats, Image, TextureError};
|
||||
|
||||
pub fn dds_buffer_to_image(
|
||||
buffer: &[u8],
|
||||
supported_compressed_formats: CompressedImageFormats,
|
||||
is_srgb: bool,
|
||||
) -> Result<Image, TextureError> {
|
||||
let mut cursor = Cursor::new(buffer);
|
||||
let dds = Dds::read(&mut cursor).expect("Failed to parse DDS file");
|
||||
let texture_format = dds_format_to_texture_format(&dds, is_srgb)?;
|
||||
if !supported_compressed_formats.supports(texture_format) {
|
||||
return Err(TextureError::UnsupportedTextureFormat(format!(
|
||||
"Format not supported by this GPU: {:?}",
|
||||
texture_format
|
||||
)));
|
||||
}
|
||||
let mut image = Image::default();
|
||||
image.texture_descriptor.size = Extent3d {
|
||||
width: dds.get_width(),
|
||||
height: dds.get_height(),
|
||||
depth_or_array_layers: if dds.get_num_array_layers() > 1 {
|
||||
dds.get_num_array_layers()
|
||||
} else {
|
||||
dds.get_depth()
|
||||
},
|
||||
};
|
||||
image.texture_descriptor.mip_level_count = dds.get_num_mipmap_levels();
|
||||
image.texture_descriptor.format = texture_format;
|
||||
image.texture_descriptor.dimension = if dds.get_depth() > 1 {
|
||||
TextureDimension::D3
|
||||
} else if image.is_compressed() || dds.get_height() > 1 {
|
||||
TextureDimension::D2
|
||||
} else {
|
||||
TextureDimension::D1
|
||||
};
|
||||
image.data = dds.data;
|
||||
Ok(image)
|
||||
}
|
||||
|
||||
pub fn dds_format_to_texture_format(
|
||||
dds: &Dds,
|
||||
is_srgb: bool,
|
||||
) -> Result<TextureFormat, TextureError> {
|
||||
Ok(if let Some(d3d_format) = dds.get_d3d_format() {
|
||||
match d3d_format {
|
||||
D3DFormat::A8B8G8R8 => {
|
||||
if is_srgb {
|
||||
TextureFormat::Rgba8UnormSrgb
|
||||
} else {
|
||||
TextureFormat::Rgba8Unorm
|
||||
}
|
||||
}
|
||||
D3DFormat::A8 => TextureFormat::R8Unorm,
|
||||
D3DFormat::A8R8G8B8 => {
|
||||
if is_srgb {
|
||||
TextureFormat::Bgra8UnormSrgb
|
||||
} else {
|
||||
TextureFormat::Bgra8Unorm
|
||||
}
|
||||
}
|
||||
D3DFormat::G16R16 => TextureFormat::Rg16Uint,
|
||||
D3DFormat::A2B10G10R10 => TextureFormat::Rgb10a2Unorm,
|
||||
D3DFormat::A8L8 => TextureFormat::Rg8Uint,
|
||||
D3DFormat::L16 => TextureFormat::R16Uint,
|
||||
D3DFormat::L8 => TextureFormat::R8Uint,
|
||||
D3DFormat::DXT1 => {
|
||||
if is_srgb {
|
||||
TextureFormat::Bc1RgbaUnormSrgb
|
||||
} else {
|
||||
TextureFormat::Bc1RgbaUnorm
|
||||
}
|
||||
}
|
||||
D3DFormat::DXT3 => {
|
||||
if is_srgb {
|
||||
TextureFormat::Bc2RgbaUnormSrgb
|
||||
} else {
|
||||
TextureFormat::Bc2RgbaUnorm
|
||||
}
|
||||
}
|
||||
D3DFormat::DXT5 => {
|
||||
if is_srgb {
|
||||
TextureFormat::Bc3RgbaUnormSrgb
|
||||
} else {
|
||||
TextureFormat::Bc3RgbaUnorm
|
||||
}
|
||||
}
|
||||
D3DFormat::A16B16G16R16 => TextureFormat::Rgba16Uint,
|
||||
D3DFormat::Q16W16V16U16 => TextureFormat::Rgba16Sint,
|
||||
D3DFormat::R16F => TextureFormat::R16Float,
|
||||
D3DFormat::G16R16F => TextureFormat::Rg16Float,
|
||||
D3DFormat::A16B16G16R16F => TextureFormat::Rgba16Float,
|
||||
D3DFormat::R32F => TextureFormat::R32Float,
|
||||
D3DFormat::G32R32F => TextureFormat::Rg32Float,
|
||||
D3DFormat::A32B32G32R32F => TextureFormat::Rgba32Float,
|
||||
D3DFormat::DXT2 => {
|
||||
if is_srgb {
|
||||
TextureFormat::Bc2RgbaUnormSrgb
|
||||
} else {
|
||||
TextureFormat::Bc2RgbaUnorm
|
||||
}
|
||||
}
|
||||
D3DFormat::DXT4 => {
|
||||
if is_srgb {
|
||||
TextureFormat::Bc3RgbaUnormSrgb
|
||||
} else {
|
||||
TextureFormat::Bc3RgbaUnorm
|
||||
}
|
||||
}
|
||||
D3DFormat::A1R5G5B5
|
||||
| D3DFormat::R5G6B5
|
||||
// FIXME: Map to argb format and user has to know to ignore the alpha channel?
|
||||
| D3DFormat::X8R8G8B8
|
||||
// FIXME: Map to argb format and user has to know to ignore the alpha channel?
|
||||
| D3DFormat::X8B8G8R8
|
||||
| D3DFormat::A2R10G10B10
|
||||
| D3DFormat::R8G8B8
|
||||
| D3DFormat::X1R5G5B5
|
||||
| D3DFormat::A4R4G4B4
|
||||
| D3DFormat::X4R4G4B4
|
||||
| D3DFormat::A8R3G3B2
|
||||
| D3DFormat::A4L4
|
||||
| D3DFormat::R8G8_B8G8
|
||||
| D3DFormat::G8R8_G8B8
|
||||
| D3DFormat::UYVY
|
||||
| D3DFormat::YUY2
|
||||
| D3DFormat::CXV8U8 => {
|
||||
return Err(TextureError::UnsupportedTextureFormat(format!(
|
||||
"{:?}",
|
||||
d3d_format
|
||||
)))
|
||||
}
|
||||
}
|
||||
} else if let Some(dxgi_format) = dds.get_dxgi_format() {
|
||||
match dxgi_format {
|
||||
DxgiFormat::R32G32B32A32_Typeless => TextureFormat::Rgba32Float,
|
||||
DxgiFormat::R32G32B32A32_Float => TextureFormat::Rgba32Float,
|
||||
DxgiFormat::R32G32B32A32_UInt => TextureFormat::Rgba32Uint,
|
||||
DxgiFormat::R32G32B32A32_SInt => TextureFormat::Rgba32Sint,
|
||||
DxgiFormat::R16G16B16A16_Typeless => TextureFormat::Rgba16Float,
|
||||
DxgiFormat::R16G16B16A16_Float => TextureFormat::Rgba16Float,
|
||||
DxgiFormat::R16G16B16A16_UNorm => TextureFormat::Rgba16Unorm,
|
||||
DxgiFormat::R16G16B16A16_UInt => TextureFormat::Rgba16Uint,
|
||||
DxgiFormat::R16G16B16A16_SNorm => TextureFormat::Rgba16Snorm,
|
||||
DxgiFormat::R16G16B16A16_SInt => TextureFormat::Rgba16Sint,
|
||||
DxgiFormat::R32G32_Typeless => TextureFormat::Rg32Float,
|
||||
DxgiFormat::R32G32_Float => TextureFormat::Rg32Float,
|
||||
DxgiFormat::R32G32_UInt => TextureFormat::Rg32Uint,
|
||||
DxgiFormat::R32G32_SInt => TextureFormat::Rg32Sint,
|
||||
DxgiFormat::R10G10B10A2_Typeless => TextureFormat::Rgb10a2Unorm,
|
||||
DxgiFormat::R10G10B10A2_UNorm => TextureFormat::Rgb10a2Unorm,
|
||||
DxgiFormat::R11G11B10_Float => TextureFormat::Rg11b10Float,
|
||||
DxgiFormat::R8G8B8A8_Typeless => {
|
||||
if is_srgb {
|
||||
TextureFormat::Rgba8UnormSrgb
|
||||
} else {
|
||||
TextureFormat::Rgba8Unorm
|
||||
}
|
||||
}
|
||||
DxgiFormat::R8G8B8A8_UNorm => {
|
||||
if is_srgb {
|
||||
TextureFormat::Rgba8UnormSrgb
|
||||
} else {
|
||||
TextureFormat::Rgba8Unorm
|
||||
}
|
||||
}
|
||||
DxgiFormat::R8G8B8A8_UNorm_sRGB => {
|
||||
if is_srgb {
|
||||
TextureFormat::Rgba8UnormSrgb
|
||||
} else {
|
||||
TextureFormat::Rgba8Unorm
|
||||
}
|
||||
}
|
||||
DxgiFormat::R8G8B8A8_UInt => TextureFormat::Rgba8Uint,
|
||||
DxgiFormat::R8G8B8A8_SNorm => TextureFormat::Rgba8Snorm,
|
||||
DxgiFormat::R8G8B8A8_SInt => TextureFormat::Rgba8Sint,
|
||||
DxgiFormat::R16G16_Typeless => TextureFormat::Rg16Float,
|
||||
DxgiFormat::R16G16_Float => TextureFormat::Rg16Float,
|
||||
DxgiFormat::R16G16_UNorm => TextureFormat::Rg16Unorm,
|
||||
DxgiFormat::R16G16_UInt => TextureFormat::Rg16Uint,
|
||||
DxgiFormat::R16G16_SNorm => TextureFormat::Rg16Snorm,
|
||||
DxgiFormat::R16G16_SInt => TextureFormat::Rg16Sint,
|
||||
DxgiFormat::R32_Typeless => TextureFormat::R32Float,
|
||||
DxgiFormat::D32_Float => TextureFormat::Depth32Float,
|
||||
DxgiFormat::R32_Float => TextureFormat::R32Float,
|
||||
DxgiFormat::R32_UInt => TextureFormat::R32Uint,
|
||||
DxgiFormat::R32_SInt => TextureFormat::R32Sint,
|
||||
DxgiFormat::R24G8_Typeless => TextureFormat::Depth24PlusStencil8,
|
||||
DxgiFormat::D24_UNorm_S8_UInt => TextureFormat::Depth24PlusStencil8,
|
||||
DxgiFormat::R24_UNorm_X8_Typeless => TextureFormat::Depth24Plus,
|
||||
DxgiFormat::R8G8_Typeless => TextureFormat::Rg8Unorm,
|
||||
DxgiFormat::R8G8_UNorm => TextureFormat::Rg8Unorm,
|
||||
DxgiFormat::R8G8_UInt => TextureFormat::Rg8Uint,
|
||||
DxgiFormat::R8G8_SNorm => TextureFormat::Rg8Snorm,
|
||||
DxgiFormat::R8G8_SInt => TextureFormat::Rg8Sint,
|
||||
DxgiFormat::R16_Typeless => TextureFormat::R16Float,
|
||||
DxgiFormat::R16_Float => TextureFormat::R16Float,
|
||||
DxgiFormat::R16_UNorm => TextureFormat::R16Unorm,
|
||||
DxgiFormat::R16_UInt => TextureFormat::R16Uint,
|
||||
DxgiFormat::R16_SNorm => TextureFormat::R16Snorm,
|
||||
DxgiFormat::R16_SInt => TextureFormat::R16Sint,
|
||||
DxgiFormat::R8_Typeless => TextureFormat::R8Unorm,
|
||||
DxgiFormat::R8_UNorm => TextureFormat::R8Unorm,
|
||||
DxgiFormat::R8_UInt => TextureFormat::R8Uint,
|
||||
DxgiFormat::R8_SNorm => TextureFormat::R8Snorm,
|
||||
DxgiFormat::R8_SInt => TextureFormat::R8Sint,
|
||||
DxgiFormat::R9G9B9E5_SharedExp => TextureFormat::Rgb9e5Ufloat,
|
||||
DxgiFormat::BC1_Typeless => {
|
||||
if is_srgb {
|
||||
TextureFormat::Bc1RgbaUnormSrgb
|
||||
} else {
|
||||
TextureFormat::Bc1RgbaUnorm
|
||||
}
|
||||
}
|
||||
DxgiFormat::BC1_UNorm => {
|
||||
if is_srgb {
|
||||
TextureFormat::Bc1RgbaUnormSrgb
|
||||
} else {
|
||||
TextureFormat::Bc1RgbaUnorm
|
||||
}
|
||||
}
|
||||
DxgiFormat::BC1_UNorm_sRGB => {
|
||||
if is_srgb {
|
||||
TextureFormat::Bc1RgbaUnormSrgb
|
||||
} else {
|
||||
TextureFormat::Bc1RgbaUnorm
|
||||
}
|
||||
}
|
||||
DxgiFormat::BC2_Typeless => {
|
||||
if is_srgb {
|
||||
TextureFormat::Bc2RgbaUnormSrgb
|
||||
} else {
|
||||
TextureFormat::Bc2RgbaUnorm
|
||||
}
|
||||
}
|
||||
DxgiFormat::BC2_UNorm => {
|
||||
if is_srgb {
|
||||
TextureFormat::Bc2RgbaUnormSrgb
|
||||
} else {
|
||||
TextureFormat::Bc2RgbaUnorm
|
||||
}
|
||||
}
|
||||
DxgiFormat::BC2_UNorm_sRGB => {
|
||||
if is_srgb {
|
||||
TextureFormat::Bc2RgbaUnormSrgb
|
||||
} else {
|
||||
TextureFormat::Bc2RgbaUnorm
|
||||
}
|
||||
}
|
||||
DxgiFormat::BC3_Typeless => {
|
||||
if is_srgb {
|
||||
TextureFormat::Bc3RgbaUnormSrgb
|
||||
} else {
|
||||
TextureFormat::Bc3RgbaUnorm
|
||||
}
|
||||
}
|
||||
DxgiFormat::BC3_UNorm => {
|
||||
if is_srgb {
|
||||
TextureFormat::Bc3RgbaUnormSrgb
|
||||
} else {
|
||||
TextureFormat::Bc3RgbaUnorm
|
||||
}
|
||||
}
|
||||
DxgiFormat::BC3_UNorm_sRGB => {
|
||||
if is_srgb {
|
||||
TextureFormat::Bc3RgbaUnormSrgb
|
||||
} else {
|
||||
TextureFormat::Bc3RgbaUnorm
|
||||
}
|
||||
}
|
||||
DxgiFormat::BC4_Typeless => TextureFormat::Bc4RUnorm,
|
||||
DxgiFormat::BC4_UNorm => TextureFormat::Bc4RUnorm,
|
||||
DxgiFormat::BC4_SNorm => TextureFormat::Bc4RSnorm,
|
||||
DxgiFormat::BC5_Typeless => TextureFormat::Bc5RgUnorm,
|
||||
DxgiFormat::BC5_UNorm => TextureFormat::Bc5RgUnorm,
|
||||
DxgiFormat::BC5_SNorm => TextureFormat::Bc5RgSnorm,
|
||||
DxgiFormat::B8G8R8A8_UNorm => {
|
||||
if is_srgb {
|
||||
TextureFormat::Bgra8UnormSrgb
|
||||
} else {
|
||||
TextureFormat::Bgra8Unorm
|
||||
}
|
||||
}
|
||||
DxgiFormat::B8G8R8A8_Typeless => {
|
||||
if is_srgb {
|
||||
TextureFormat::Bgra8UnormSrgb
|
||||
} else {
|
||||
TextureFormat::Bgra8Unorm
|
||||
}
|
||||
}
|
||||
DxgiFormat::B8G8R8A8_UNorm_sRGB => {
|
||||
if is_srgb {
|
||||
TextureFormat::Bgra8UnormSrgb
|
||||
} else {
|
||||
TextureFormat::Bgra8Unorm
|
||||
}
|
||||
}
|
||||
|
||||
DxgiFormat::BC6H_Typeless => TextureFormat::Bc6hRgbUfloat,
|
||||
DxgiFormat::BC6H_UF16 => TextureFormat::Bc6hRgbUfloat,
|
||||
DxgiFormat::BC6H_SF16 => TextureFormat::Bc6hRgbSfloat,
|
||||
DxgiFormat::BC7_Typeless => {
|
||||
if is_srgb {
|
||||
TextureFormat::Bc7RgbaUnormSrgb
|
||||
} else {
|
||||
TextureFormat::Bc7RgbaUnorm
|
||||
}
|
||||
}
|
||||
DxgiFormat::BC7_UNorm => {
|
||||
if is_srgb {
|
||||
TextureFormat::Bc7RgbaUnormSrgb
|
||||
} else {
|
||||
TextureFormat::Bc7RgbaUnorm
|
||||
}
|
||||
}
|
||||
DxgiFormat::BC7_UNorm_sRGB => {
|
||||
if is_srgb {
|
||||
TextureFormat::Bc7RgbaUnormSrgb
|
||||
} else {
|
||||
TextureFormat::Bc7RgbaUnorm
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(TextureError::UnsupportedTextureFormat(format!(
|
||||
"{:?}",
|
||||
dxgi_format
|
||||
)))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(TextureError::UnsupportedTextureFormat(
|
||||
"unspecified".to_string(),
|
||||
));
|
||||
})
|
||||
}
|
|
@ -1,3 +1,10 @@
|
|||
#[cfg(feature = "basis-universal")]
|
||||
use super::basis::*;
|
||||
#[cfg(feature = "dds")]
|
||||
use super::dds::*;
|
||||
#[cfg(feature = "ktx2")]
|
||||
use super::ktx2::*;
|
||||
|
||||
use super::image_texture_conversion::image_to_texture;
|
||||
use crate::{
|
||||
render_asset::{PrepareAssetError, RenderAsset},
|
||||
|
@ -20,6 +27,82 @@ pub const SAMPLER_ASSET_INDEX: u64 = 1;
|
|||
pub const DEFAULT_IMAGE_HANDLE: HandleUntyped =
|
||||
HandleUntyped::weak_from_u64(Image::TYPE_UUID, 13148262314052771789);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ImageFormat {
|
||||
Avif,
|
||||
Basis,
|
||||
Bmp,
|
||||
Dds,
|
||||
Farbfeld,
|
||||
Gif,
|
||||
Hdr,
|
||||
Ico,
|
||||
Jpeg,
|
||||
Ktx2,
|
||||
Png,
|
||||
Pnm,
|
||||
Tga,
|
||||
Tiff,
|
||||
WebP,
|
||||
}
|
||||
|
||||
impl ImageFormat {
|
||||
pub fn from_mime_type(mime_type: &str) -> Option<Self> {
|
||||
Some(match mime_type.to_ascii_lowercase().as_str() {
|
||||
"image/bmp" => ImageFormat::Bmp,
|
||||
"image/x-bmp" => ImageFormat::Bmp,
|
||||
"image/vnd-ms.dds" => ImageFormat::Dds,
|
||||
"image/jpeg" => ImageFormat::Jpeg,
|
||||
"image/ktx2" => ImageFormat::Ktx2,
|
||||
"image/png" => ImageFormat::Png,
|
||||
"image/x-targa" => ImageFormat::Tga,
|
||||
"image/x-tga" => ImageFormat::Tga,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_extension(extension: &str) -> Option<Self> {
|
||||
Some(match extension.to_ascii_lowercase().as_str() {
|
||||
"avif" => ImageFormat::Avif,
|
||||
"basis" => ImageFormat::Basis,
|
||||
"bmp" => ImageFormat::Bmp,
|
||||
"dds" => ImageFormat::Dds,
|
||||
"ff" | "farbfeld" => ImageFormat::Farbfeld,
|
||||
"gif" => ImageFormat::Gif,
|
||||
"hdr" => ImageFormat::Hdr,
|
||||
"ico" => ImageFormat::Ico,
|
||||
"jpg" | "jpeg" => ImageFormat::Jpeg,
|
||||
"ktx2" => ImageFormat::Ktx2,
|
||||
"pbm" | "pam" | "ppm" | "pgm" => ImageFormat::Pnm,
|
||||
"png" => ImageFormat::Png,
|
||||
"tga" => ImageFormat::Tga,
|
||||
"tif" | "tiff" => ImageFormat::Tiff,
|
||||
"webp" => ImageFormat::WebP,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn as_image_crate_format(&self) -> Option<image::ImageFormat> {
|
||||
Some(match self {
|
||||
ImageFormat::Avif => image::ImageFormat::Avif,
|
||||
ImageFormat::Basis => return None,
|
||||
ImageFormat::Bmp => image::ImageFormat::Bmp,
|
||||
ImageFormat::Dds => image::ImageFormat::Dds,
|
||||
ImageFormat::Farbfeld => image::ImageFormat::Farbfeld,
|
||||
ImageFormat::Gif => image::ImageFormat::Gif,
|
||||
ImageFormat::Hdr => image::ImageFormat::Hdr,
|
||||
ImageFormat::Ico => image::ImageFormat::Ico,
|
||||
ImageFormat::Jpeg => image::ImageFormat::Jpeg,
|
||||
ImageFormat::Ktx2 => return None,
|
||||
ImageFormat::Png => image::ImageFormat::Png,
|
||||
ImageFormat::Pnm => image::ImageFormat::Pnm,
|
||||
ImageFormat::Tga => image::ImageFormat::Tga,
|
||||
ImageFormat::Tiff => image::ImageFormat::Tiff,
|
||||
ImageFormat::WebP => image::ImageFormat::WebP,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, TypeUuid)]
|
||||
#[uuid = "6ea26da6-6cf8-4ea2-9986-1d7bf6c17d6f"]
|
||||
pub struct Image {
|
||||
|
@ -181,38 +264,35 @@ impl Image {
|
|||
pub fn convert(&self, new_format: TextureFormat) -> Option<Self> {
|
||||
super::image_texture_conversion::texture_to_image(self)
|
||||
.and_then(|img| match new_format {
|
||||
TextureFormat::R8Unorm => Some(image::DynamicImage::ImageLuma8(img.into_luma8())),
|
||||
TextureFormat::Rg8Unorm => {
|
||||
Some(image::DynamicImage::ImageLumaA8(img.into_luma_alpha8()))
|
||||
TextureFormat::R8Unorm => {
|
||||
Some((image::DynamicImage::ImageLuma8(img.into_luma8()), false))
|
||||
}
|
||||
TextureFormat::Rg8Unorm => Some((
|
||||
image::DynamicImage::ImageLumaA8(img.into_luma_alpha8()),
|
||||
false,
|
||||
)),
|
||||
TextureFormat::Rgba8UnormSrgb => {
|
||||
Some(image::DynamicImage::ImageRgba8(img.into_rgba8()))
|
||||
Some((image::DynamicImage::ImageRgba8(img.into_rgba8()), true))
|
||||
}
|
||||
TextureFormat::Bgra8UnormSrgb => {
|
||||
Some(image::DynamicImage::ImageBgra8(img.into_bgra8()))
|
||||
Some((image::DynamicImage::ImageBgra8(img.into_bgra8()), true))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.map(super::image_texture_conversion::image_to_texture)
|
||||
.map(|(dyn_img, is_srgb)| {
|
||||
super::image_texture_conversion::image_to_texture(dyn_img, is_srgb)
|
||||
})
|
||||
}
|
||||
|
||||
/// Load a bytes buffer in a [`Image`], according to type `image_type`, using the `image`
|
||||
/// crate
|
||||
pub fn from_buffer(buffer: &[u8], image_type: ImageType) -> Result<Image, TextureError> {
|
||||
let format = match image_type {
|
||||
ImageType::MimeType(mime_type) => match mime_type {
|
||||
"image/png" => Ok(image::ImageFormat::Png),
|
||||
"image/vnd-ms.dds" => Ok(image::ImageFormat::Dds),
|
||||
"image/x-targa" => Ok(image::ImageFormat::Tga),
|
||||
"image/x-tga" => Ok(image::ImageFormat::Tga),
|
||||
"image/jpeg" => Ok(image::ImageFormat::Jpeg),
|
||||
"image/bmp" => Ok(image::ImageFormat::Bmp),
|
||||
"image/x-bmp" => Ok(image::ImageFormat::Bmp),
|
||||
_ => Err(TextureError::InvalidImageMimeType(mime_type.to_string())),
|
||||
},
|
||||
ImageType::Extension(extension) => image::ImageFormat::from_extension(extension)
|
||||
.ok_or_else(|| TextureError::InvalidImageMimeType(extension.to_string())),
|
||||
}?;
|
||||
pub fn from_buffer(
|
||||
buffer: &[u8],
|
||||
image_type: ImageType,
|
||||
#[allow(unused_variables)] supported_compressed_formats: CompressedImageFormats,
|
||||
is_srgb: bool,
|
||||
) -> Result<Image, TextureError> {
|
||||
let format = image_type.to_image_format()?;
|
||||
|
||||
// Load the image in the expected format.
|
||||
// Some formats like PNG allow for R or RG textures too, so the texture
|
||||
|
@ -220,20 +300,80 @@ impl Image {
|
|||
// needs to be added, so the image data needs to be converted in those
|
||||
// cases.
|
||||
|
||||
let dyn_img = image::load_from_memory_with_format(buffer, format)?;
|
||||
Ok(image_to_texture(dyn_img))
|
||||
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")]
|
||||
ImageFormat::Ktx2 => {
|
||||
ktx2_buffer_to_image(buffer, supported_compressed_formats, is_srgb)
|
||||
}
|
||||
_ => {
|
||||
let image_crate_format = format.as_image_crate_format().ok_or_else(|| {
|
||||
TextureError::UnsupportedTextureFormat(format!("{:?}", format))
|
||||
})?;
|
||||
let dyn_img = image::load_from_memory_with_format(buffer, image_crate_format)?;
|
||||
Ok(image_to_texture(dyn_img, is_srgb))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the texture format is compressed or uncompressed
|
||||
pub fn is_compressed(&self) -> bool {
|
||||
let format_description = self.texture_descriptor.format.describe();
|
||||
format_description
|
||||
.required_features
|
||||
.contains(wgpu::Features::TEXTURE_COMPRESSION_ASTC_LDR)
|
||||
|| format_description
|
||||
.required_features
|
||||
.contains(wgpu::Features::TEXTURE_COMPRESSION_BC)
|
||||
|| format_description
|
||||
.required_features
|
||||
.contains(wgpu::Features::TEXTURE_COMPRESSION_ETC2)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum DataFormat {
|
||||
R8,
|
||||
Rg8,
|
||||
Rgb8,
|
||||
Rgba8,
|
||||
Rgba16Float,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum TranscodeFormat {
|
||||
Etc1s,
|
||||
// Has to be transcoded to Rgba8 for use with `wgpu`
|
||||
Rgb8,
|
||||
Uastc(DataFormat),
|
||||
}
|
||||
|
||||
/// An error that occurs when loading a texture
|
||||
#[derive(Error, Debug)]
|
||||
pub enum TextureError {
|
||||
#[error("invalid image mime type")]
|
||||
#[error("invalid image mime type: {0}")]
|
||||
InvalidImageMimeType(String),
|
||||
#[error("invalid image extension")]
|
||||
#[error("invalid image extension: {0}")]
|
||||
InvalidImageExtension(String),
|
||||
#[error("failed to load an image: {0}")]
|
||||
ImageError(#[from] image::ImageError),
|
||||
#[error("unsupported texture format: {0}")]
|
||||
UnsupportedTextureFormat(String),
|
||||
#[error("supercompression not supported: {0}")]
|
||||
SuperCompressionNotSupported(String),
|
||||
#[error("failed to load an image: {0}")]
|
||||
SuperDecompressionError(String),
|
||||
#[error("invalid data: {0}")]
|
||||
InvalidData(String),
|
||||
#[error("transcode error: {0}")]
|
||||
TranscodeError(String),
|
||||
#[error("format requires transcoding: {0:?}")]
|
||||
FormatRequiresTranscodingError(TranscodeFormat),
|
||||
}
|
||||
|
||||
/// The type of a raw image buffer.
|
||||
|
@ -244,6 +384,17 @@ pub enum ImageType<'a> {
|
|||
Extension(&'a str),
|
||||
}
|
||||
|
||||
impl<'a> ImageType<'a> {
|
||||
pub fn to_image_format(&self) -> Result<ImageFormat, TextureError> {
|
||||
match self {
|
||||
ImageType::MimeType(mime_type) => ImageFormat::from_mime_type(mime_type)
|
||||
.ok_or_else(|| TextureError::InvalidImageMimeType(mime_type.to_string())),
|
||||
ImageType::Extension(extension) => ImageFormat::from_extension(extension)
|
||||
.ok_or_else(|| TextureError::InvalidImageExtension(extension.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to calculate the volume of an item.
|
||||
pub trait Volume {
|
||||
fn volume(&self) -> usize;
|
||||
|
@ -387,6 +538,7 @@ impl TextureFormatPixelInfo for TextureFormat {
|
|||
pub struct GpuImage {
|
||||
pub texture: Texture,
|
||||
pub texture_view: TextureView,
|
||||
pub texture_format: TextureFormat,
|
||||
pub sampler: Sampler,
|
||||
pub size: Size,
|
||||
}
|
||||
|
@ -406,49 +558,149 @@ impl RenderAsset for Image {
|
|||
image: Self::ExtractedAsset,
|
||||
(render_device, render_queue): &mut SystemParamItem<Self::Param>,
|
||||
) -> Result<Self::PreparedAsset, PrepareAssetError<Self::ExtractedAsset>> {
|
||||
let texture = render_device.create_texture(&image.texture_descriptor);
|
||||
let sampler = render_device.create_sampler(&image.sampler_descriptor);
|
||||
|
||||
let format_size = image.texture_descriptor.format.pixel_size();
|
||||
render_queue.write_texture(
|
||||
ImageCopyTexture {
|
||||
texture: &texture,
|
||||
mip_level: 0,
|
||||
origin: Origin3d::ZERO,
|
||||
aspect: wgpu::TextureAspect::All,
|
||||
},
|
||||
&image.data,
|
||||
ImageDataLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: Some(
|
||||
std::num::NonZeroU32::new(
|
||||
image.texture_descriptor.size.width * format_size as u32,
|
||||
)
|
||||
.unwrap(),
|
||||
),
|
||||
rows_per_image: if image.texture_descriptor.size.depth_or_array_layers > 1 {
|
||||
std::num::NonZeroU32::new(image.texture_descriptor.size.height)
|
||||
} else {
|
||||
None
|
||||
let texture = if image.texture_descriptor.mip_level_count > 1 || image.is_compressed() {
|
||||
render_device.create_texture_with_data(
|
||||
render_queue,
|
||||
&image.texture_descriptor,
|
||||
&image.data,
|
||||
)
|
||||
} else {
|
||||
let texture = render_device.create_texture(&image.texture_descriptor);
|
||||
let format_size = image.texture_descriptor.format.pixel_size();
|
||||
render_queue.write_texture(
|
||||
ImageCopyTexture {
|
||||
texture: &texture,
|
||||
mip_level: 0,
|
||||
origin: Origin3d::ZERO,
|
||||
aspect: wgpu::TextureAspect::All,
|
||||
},
|
||||
},
|
||||
image.texture_descriptor.size,
|
||||
);
|
||||
&image.data,
|
||||
ImageDataLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: Some(
|
||||
std::num::NonZeroU32::new(
|
||||
image.texture_descriptor.size.width * format_size as u32,
|
||||
)
|
||||
.unwrap(),
|
||||
),
|
||||
rows_per_image: if image.texture_descriptor.size.depth_or_array_layers > 1 {
|
||||
std::num::NonZeroU32::new(image.texture_descriptor.size.height)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
},
|
||||
image.texture_descriptor.size,
|
||||
);
|
||||
texture
|
||||
};
|
||||
|
||||
let texture_view = texture.create_view(&TextureViewDescriptor::default());
|
||||
let size = Size::new(
|
||||
image.texture_descriptor.size.width as f32,
|
||||
image.texture_descriptor.size.height as f32,
|
||||
);
|
||||
let sampler = render_device.create_sampler(&image.sampler_descriptor);
|
||||
Ok(GpuImage {
|
||||
texture,
|
||||
texture_view,
|
||||
texture_format: image.texture_descriptor.format,
|
||||
sampler,
|
||||
size,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
bitflags::bitflags! {
|
||||
#[derive(Default)]
|
||||
#[repr(transparent)]
|
||||
pub struct CompressedImageFormats: u32 {
|
||||
const NONE = 0;
|
||||
const ASTC_LDR = (1 << 0);
|
||||
const BC = (1 << 1);
|
||||
const ETC2 = (1 << 2);
|
||||
}
|
||||
}
|
||||
|
||||
impl CompressedImageFormats {
|
||||
pub fn from_features(features: wgpu::Features) -> Self {
|
||||
let mut supported_compressed_formats = Self::default();
|
||||
if features.contains(wgpu::Features::TEXTURE_COMPRESSION_ASTC_LDR) {
|
||||
supported_compressed_formats |= Self::ASTC_LDR;
|
||||
}
|
||||
if features.contains(wgpu::Features::TEXTURE_COMPRESSION_BC) {
|
||||
supported_compressed_formats |= Self::BC;
|
||||
}
|
||||
if features.contains(wgpu::Features::TEXTURE_COMPRESSION_ETC2) {
|
||||
supported_compressed_formats |= Self::ETC2;
|
||||
}
|
||||
supported_compressed_formats
|
||||
}
|
||||
|
||||
pub fn supports(&self, format: TextureFormat) -> bool {
|
||||
match format {
|
||||
TextureFormat::Bc1RgbaUnorm => self.contains(CompressedImageFormats::BC),
|
||||
TextureFormat::Bc1RgbaUnormSrgb => self.contains(CompressedImageFormats::BC),
|
||||
TextureFormat::Bc2RgbaUnorm => self.contains(CompressedImageFormats::BC),
|
||||
TextureFormat::Bc2RgbaUnormSrgb => self.contains(CompressedImageFormats::BC),
|
||||
TextureFormat::Bc3RgbaUnorm => self.contains(CompressedImageFormats::BC),
|
||||
TextureFormat::Bc3RgbaUnormSrgb => self.contains(CompressedImageFormats::BC),
|
||||
TextureFormat::Bc4RUnorm => self.contains(CompressedImageFormats::BC),
|
||||
TextureFormat::Bc4RSnorm => self.contains(CompressedImageFormats::BC),
|
||||
TextureFormat::Bc5RgUnorm => self.contains(CompressedImageFormats::BC),
|
||||
TextureFormat::Bc5RgSnorm => self.contains(CompressedImageFormats::BC),
|
||||
TextureFormat::Bc6hRgbUfloat => self.contains(CompressedImageFormats::BC),
|
||||
TextureFormat::Bc6hRgbSfloat => self.contains(CompressedImageFormats::BC),
|
||||
TextureFormat::Bc7RgbaUnorm => self.contains(CompressedImageFormats::BC),
|
||||
TextureFormat::Bc7RgbaUnormSrgb => self.contains(CompressedImageFormats::BC),
|
||||
TextureFormat::Etc2Rgb8Unorm => self.contains(CompressedImageFormats::ETC2),
|
||||
TextureFormat::Etc2Rgb8UnormSrgb => self.contains(CompressedImageFormats::ETC2),
|
||||
TextureFormat::Etc2Rgb8A1Unorm => self.contains(CompressedImageFormats::ETC2),
|
||||
TextureFormat::Etc2Rgb8A1UnormSrgb => self.contains(CompressedImageFormats::ETC2),
|
||||
TextureFormat::Etc2Rgba8Unorm => self.contains(CompressedImageFormats::ETC2),
|
||||
TextureFormat::Etc2Rgba8UnormSrgb => self.contains(CompressedImageFormats::ETC2),
|
||||
TextureFormat::EacR11Unorm => self.contains(CompressedImageFormats::ETC2),
|
||||
TextureFormat::EacR11Snorm => self.contains(CompressedImageFormats::ETC2),
|
||||
TextureFormat::EacRg11Unorm => self.contains(CompressedImageFormats::ETC2),
|
||||
TextureFormat::EacRg11Snorm => self.contains(CompressedImageFormats::ETC2),
|
||||
TextureFormat::Astc4x4RgbaUnorm => self.contains(CompressedImageFormats::ASTC_LDR),
|
||||
TextureFormat::Astc4x4RgbaUnormSrgb => self.contains(CompressedImageFormats::ASTC_LDR),
|
||||
TextureFormat::Astc5x4RgbaUnorm => self.contains(CompressedImageFormats::ASTC_LDR),
|
||||
TextureFormat::Astc5x4RgbaUnormSrgb => self.contains(CompressedImageFormats::ASTC_LDR),
|
||||
TextureFormat::Astc5x5RgbaUnorm => self.contains(CompressedImageFormats::ASTC_LDR),
|
||||
TextureFormat::Astc5x5RgbaUnormSrgb => self.contains(CompressedImageFormats::ASTC_LDR),
|
||||
TextureFormat::Astc6x5RgbaUnorm => self.contains(CompressedImageFormats::ASTC_LDR),
|
||||
TextureFormat::Astc6x5RgbaUnormSrgb => self.contains(CompressedImageFormats::ASTC_LDR),
|
||||
TextureFormat::Astc6x6RgbaUnorm => self.contains(CompressedImageFormats::ASTC_LDR),
|
||||
TextureFormat::Astc6x6RgbaUnormSrgb => self.contains(CompressedImageFormats::ASTC_LDR),
|
||||
TextureFormat::Astc8x5RgbaUnorm => self.contains(CompressedImageFormats::ASTC_LDR),
|
||||
TextureFormat::Astc8x5RgbaUnormSrgb => self.contains(CompressedImageFormats::ASTC_LDR),
|
||||
TextureFormat::Astc8x6RgbaUnorm => self.contains(CompressedImageFormats::ASTC_LDR),
|
||||
TextureFormat::Astc8x6RgbaUnormSrgb => self.contains(CompressedImageFormats::ASTC_LDR),
|
||||
TextureFormat::Astc10x5RgbaUnorm => self.contains(CompressedImageFormats::ASTC_LDR),
|
||||
TextureFormat::Astc10x5RgbaUnormSrgb => self.contains(CompressedImageFormats::ASTC_LDR),
|
||||
TextureFormat::Astc10x6RgbaUnorm => self.contains(CompressedImageFormats::ASTC_LDR),
|
||||
TextureFormat::Astc10x6RgbaUnormSrgb => self.contains(CompressedImageFormats::ASTC_LDR),
|
||||
TextureFormat::Astc8x8RgbaUnorm => self.contains(CompressedImageFormats::ASTC_LDR),
|
||||
TextureFormat::Astc8x8RgbaUnormSrgb => self.contains(CompressedImageFormats::ASTC_LDR),
|
||||
TextureFormat::Astc10x8RgbaUnorm => self.contains(CompressedImageFormats::ASTC_LDR),
|
||||
TextureFormat::Astc10x8RgbaUnormSrgb => self.contains(CompressedImageFormats::ASTC_LDR),
|
||||
TextureFormat::Astc10x10RgbaUnorm => self.contains(CompressedImageFormats::ASTC_LDR),
|
||||
TextureFormat::Astc10x10RgbaUnormSrgb => {
|
||||
self.contains(CompressedImageFormats::ASTC_LDR)
|
||||
}
|
||||
TextureFormat::Astc12x10RgbaUnorm => self.contains(CompressedImageFormats::ASTC_LDR),
|
||||
TextureFormat::Astc12x10RgbaUnormSrgb => {
|
||||
self.contains(CompressedImageFormats::ASTC_LDR)
|
||||
}
|
||||
TextureFormat::Astc12x12RgbaUnorm => self.contains(CompressedImageFormats::ASTC_LDR),
|
||||
TextureFormat::Astc12x12RgbaUnormSrgb => {
|
||||
self.contains(CompressedImageFormats::ASTC_LDR)
|
||||
}
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ use wgpu::{Extent3d, TextureDimension, TextureFormat};
|
|||
|
||||
// TODO: fix name?
|
||||
/// Converts a [`DynamicImage`] to an [`Image`].
|
||||
pub(crate) fn image_to_texture(dyn_img: DynamicImage) -> Image {
|
||||
pub(crate) fn image_to_texture(dyn_img: DynamicImage, is_srgb: bool) -> Image {
|
||||
use bevy_core::cast_slice;
|
||||
let width;
|
||||
let height;
|
||||
|
@ -17,7 +17,11 @@ pub(crate) fn image_to_texture(dyn_img: DynamicImage) -> Image {
|
|||
let i = DynamicImage::ImageLuma8(i).into_rgba8();
|
||||
width = i.width();
|
||||
height = i.height();
|
||||
format = TextureFormat::Rgba8UnormSrgb;
|
||||
format = if is_srgb {
|
||||
TextureFormat::Rgba8UnormSrgb
|
||||
} else {
|
||||
TextureFormat::Rgba8Unorm
|
||||
};
|
||||
|
||||
data = i.into_raw();
|
||||
}
|
||||
|
@ -25,7 +29,11 @@ pub(crate) fn image_to_texture(dyn_img: DynamicImage) -> Image {
|
|||
let i = DynamicImage::ImageLumaA8(i).into_rgba8();
|
||||
width = i.width();
|
||||
height = i.height();
|
||||
format = TextureFormat::Rgba8UnormSrgb;
|
||||
format = if is_srgb {
|
||||
TextureFormat::Rgba8UnormSrgb
|
||||
} else {
|
||||
TextureFormat::Rgba8Unorm
|
||||
};
|
||||
|
||||
data = i.into_raw();
|
||||
}
|
||||
|
@ -33,14 +41,22 @@ pub(crate) fn image_to_texture(dyn_img: DynamicImage) -> Image {
|
|||
let i = DynamicImage::ImageRgb8(i).into_rgba8();
|
||||
width = i.width();
|
||||
height = i.height();
|
||||
format = TextureFormat::Rgba8UnormSrgb;
|
||||
format = if is_srgb {
|
||||
TextureFormat::Rgba8UnormSrgb
|
||||
} else {
|
||||
TextureFormat::Rgba8Unorm
|
||||
};
|
||||
|
||||
data = i.into_raw();
|
||||
}
|
||||
DynamicImage::ImageRgba8(i) => {
|
||||
width = i.width();
|
||||
height = i.height();
|
||||
format = TextureFormat::Rgba8UnormSrgb;
|
||||
format = if is_srgb {
|
||||
TextureFormat::Rgba8UnormSrgb
|
||||
} else {
|
||||
TextureFormat::Rgba8Unorm
|
||||
};
|
||||
|
||||
data = i.into_raw();
|
||||
}
|
||||
|
@ -49,14 +65,22 @@ pub(crate) fn image_to_texture(dyn_img: DynamicImage) -> Image {
|
|||
|
||||
width = i.width();
|
||||
height = i.height();
|
||||
format = TextureFormat::Bgra8UnormSrgb;
|
||||
format = if is_srgb {
|
||||
TextureFormat::Bgra8UnormSrgb
|
||||
} else {
|
||||
TextureFormat::Bgra8Unorm
|
||||
};
|
||||
|
||||
data = i.into_raw();
|
||||
}
|
||||
DynamicImage::ImageBgra8(i) => {
|
||||
width = i.width();
|
||||
height = i.height();
|
||||
format = TextureFormat::Bgra8UnormSrgb;
|
||||
format = if is_srgb {
|
||||
TextureFormat::Bgra8UnormSrgb
|
||||
} else {
|
||||
TextureFormat::Bgra8Unorm
|
||||
};
|
||||
|
||||
data = i.into_raw();
|
||||
}
|
||||
|
|
|
@ -1,15 +1,27 @@
|
|||
use anyhow::Result;
|
||||
use bevy_asset::{AssetLoader, LoadContext, LoadedAsset};
|
||||
use bevy_ecs::prelude::{FromWorld, World};
|
||||
use bevy_utils::BoxedFuture;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::texture::{Image, ImageType, TextureError};
|
||||
use crate::{
|
||||
renderer::RenderDevice,
|
||||
texture::{Image, ImageType, TextureError},
|
||||
};
|
||||
|
||||
use super::CompressedImageFormats;
|
||||
|
||||
/// Loader for images that can be read by the `image` crate.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct ImageTextureLoader;
|
||||
#[derive(Clone)]
|
||||
pub struct ImageTextureLoader {
|
||||
supported_compressed_formats: CompressedImageFormats,
|
||||
}
|
||||
|
||||
const FILE_EXTENSIONS: &[&str] = &[
|
||||
#[cfg(feature = "basis-universal")]
|
||||
"basis",
|
||||
#[cfg(feature = "bmp")]
|
||||
"bmp",
|
||||
#[cfg(feature = "png")]
|
||||
"png",
|
||||
#[cfg(feature = "dds")]
|
||||
|
@ -20,8 +32,8 @@ const FILE_EXTENSIONS: &[&str] = &[
|
|||
"jpg",
|
||||
#[cfg(feature = "jpeg")]
|
||||
"jpeg",
|
||||
#[cfg(feature = "bmp")]
|
||||
"bmp",
|
||||
#[cfg(feature = "ktx2")]
|
||||
"ktx2",
|
||||
];
|
||||
|
||||
impl AssetLoader for ImageTextureLoader {
|
||||
|
@ -34,11 +46,15 @@ impl AssetLoader for ImageTextureLoader {
|
|||
// use the file extension for the image type
|
||||
let ext = load_context.path().extension().unwrap().to_str().unwrap();
|
||||
|
||||
let dyn_img = Image::from_buffer(bytes, ImageType::Extension(ext)).map_err(|err| {
|
||||
FileTextureError {
|
||||
error: err,
|
||||
path: format!("{}", load_context.path().display()),
|
||||
}
|
||||
let dyn_img = Image::from_buffer(
|
||||
bytes,
|
||||
ImageType::Extension(ext),
|
||||
self.supported_compressed_formats,
|
||||
true,
|
||||
)
|
||||
.map_err(|err| FileTextureError {
|
||||
error: err,
|
||||
path: format!("{}", load_context.path().display()),
|
||||
})?;
|
||||
|
||||
load_context.set_default_asset(LoadedAsset::new(dyn_img));
|
||||
|
@ -51,6 +67,16 @@ impl AssetLoader for ImageTextureLoader {
|
|||
}
|
||||
}
|
||||
|
||||
impl FromWorld for ImageTextureLoader {
|
||||
fn from_world(world: &mut World) -> Self {
|
||||
Self {
|
||||
supported_compressed_formats: CompressedImageFormats::from_features(
|
||||
world.resource::<RenderDevice>().features(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An error that occurs when loading a texture from a file.
|
||||
#[derive(Error, Debug)]
|
||||
pub struct FileTextureError {
|
||||
|
|
1723
crates/bevy_render/src/texture/ktx2.rs
Normal file
1723
crates/bevy_render/src/texture/ktx2.rs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,13 +1,23 @@
|
|||
#[cfg(feature = "basis-universal")]
|
||||
mod basis;
|
||||
#[cfg(feature = "dds")]
|
||||
mod dds;
|
||||
#[cfg(feature = "hdr")]
|
||||
mod hdr_texture_loader;
|
||||
#[allow(clippy::module_inception)]
|
||||
mod image;
|
||||
mod image_texture_loader;
|
||||
#[cfg(feature = "ktx2")]
|
||||
mod ktx2;
|
||||
mod texture_cache;
|
||||
|
||||
pub(crate) mod image_texture_conversion;
|
||||
|
||||
pub use self::image::*;
|
||||
#[cfg(feature = "ktx2")]
|
||||
pub use self::ktx2::*;
|
||||
#[cfg(feature = "dds")]
|
||||
pub use dds::*;
|
||||
#[cfg(feature = "hdr")]
|
||||
pub use hdr_texture_loader::*;
|
||||
|
||||
|
@ -29,7 +39,9 @@ impl Plugin for ImagePlugin {
|
|||
feature = "dds",
|
||||
feature = "tga",
|
||||
feature = "jpeg",
|
||||
feature = "bmp"
|
||||
feature = "bmp",
|
||||
feature = "basis-universal",
|
||||
feature = "ktx2",
|
||||
))]
|
||||
{
|
||||
app.init_asset_loader::<ImageTextureLoader>();
|
||||
|
|
|
@ -192,6 +192,7 @@ impl FromWorld for Mesh2dPipeline {
|
|||
GpuImage {
|
||||
texture,
|
||||
texture_view,
|
||||
texture_format: image.texture_descriptor.format,
|
||||
sampler,
|
||||
size: Size::new(
|
||||
image.texture_descriptor.size.width as f32,
|
||||
|
|
|
@ -26,6 +26,10 @@
|
|||
|trace_tracy|Enables [Tracy](https://github.com/wolfpld/tracy) as bevy_log output. This allows `Tracy` to connect to and capture profiling data as well as visualize system execution in real-time, present statistics about system execution times, and more.|
|
||||
|wgpu_trace|For tracing wgpu.|
|
||||
|dds|DDS picture format support.|
|
||||
|ktx2|KTX2 picture format support.|
|
||||
|zlib|KTX2 Zlib supercompression support.|
|
||||
|zstd|KTX2 Zstandard supercompression support.|
|
||||
|basis-universal|Basis Universal picture format support and, if the `ktx2` feature is enabled, also KTX2 UASTC picture format transcoding support.|
|
||||
|tga|TGA picture format support.|
|
||||
|jpeg|JPEG picture format support.|
|
||||
|bmp|BMP picture format support.|
|
||||
|
|
Loading…
Reference in a new issue