mirror of
https://github.com/bevyengine/bevy
synced 2024-11-25 14:10:19 +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)
|
# Image format support for texture loading (PNG and HDR are enabled by default)
|
||||||
hdr = ["bevy_internal/hdr"]
|
hdr = ["bevy_internal/hdr"]
|
||||||
png = ["bevy_internal/png"]
|
png = ["bevy_internal/png"]
|
||||||
dds = ["bevy_internal/dds"]
|
|
||||||
tga = ["bevy_internal/tga"]
|
tga = ["bevy_internal/tga"]
|
||||||
jpeg = ["bevy_internal/jpeg"]
|
jpeg = ["bevy_internal/jpeg"]
|
||||||
bmp = ["bevy_internal/bmp"]
|
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)
|
# Audio format support (vorbis is enabled by default)
|
||||||
flac = ["bevy_internal/flac"]
|
flac = ["bevy_internal/flac"]
|
||||||
|
|
|
@ -3,7 +3,7 @@ use bevy_asset::{
|
||||||
AssetIoError, AssetLoader, AssetPath, BoxedFuture, Handle, LoadContext, LoadedAsset,
|
AssetIoError, AssetLoader, AssetPath, BoxedFuture, Handle, LoadContext, LoadedAsset,
|
||||||
};
|
};
|
||||||
use bevy_core::Name;
|
use bevy_core::Name;
|
||||||
use bevy_ecs::world::World;
|
use bevy_ecs::{prelude::FromWorld, world::World};
|
||||||
use bevy_hierarchy::{BuildWorldChildren, WorldChildBuilder};
|
use bevy_hierarchy::{BuildWorldChildren, WorldChildBuilder};
|
||||||
use bevy_log::warn;
|
use bevy_log::warn;
|
||||||
use bevy_math::{Mat4, Vec3};
|
use bevy_math::{Mat4, Vec3};
|
||||||
|
@ -18,10 +18,9 @@ use bevy_render::{
|
||||||
color::Color,
|
color::Color,
|
||||||
mesh::{Indices, Mesh, VertexAttributeValues},
|
mesh::{Indices, Mesh, VertexAttributeValues},
|
||||||
primitives::{Aabb, Frustum},
|
primitives::{Aabb, Frustum},
|
||||||
render_resource::{
|
render_resource::{AddressMode, FilterMode, PrimitiveTopology, SamplerDescriptor},
|
||||||
AddressMode, FilterMode, PrimitiveTopology, SamplerDescriptor, TextureFormat,
|
renderer::RenderDevice,
|
||||||
},
|
texture::{CompressedImageFormats, Image, ImageType, TextureError},
|
||||||
texture::{Image, ImageType, TextureError},
|
|
||||||
view::VisibleEntities,
|
view::VisibleEntities,
|
||||||
};
|
};
|
||||||
use bevy_scene::Scene;
|
use bevy_scene::Scene;
|
||||||
|
@ -60,8 +59,9 @@ pub enum GltfError {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Loads glTF files with all of their data as their corresponding bevy representations.
|
/// 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 {
|
impl AssetLoader for GltfLoader {
|
||||||
fn load<'a>(
|
fn load<'a>(
|
||||||
|
@ -69,7 +69,9 @@ impl AssetLoader for GltfLoader {
|
||||||
bytes: &'a [u8],
|
bytes: &'a [u8],
|
||||||
load_context: &'a mut LoadContext,
|
load_context: &'a mut LoadContext,
|
||||||
) -> BoxedFuture<'a, Result<()>> {
|
) -> 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] {
|
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.
|
/// Loads an entire glTF file.
|
||||||
async fn load_gltf<'a, 'b>(
|
async fn load_gltf<'a, 'b>(
|
||||||
bytes: &'a [u8],
|
bytes: &'a [u8],
|
||||||
load_context: &'a mut LoadContext<'b>,
|
load_context: &'a mut LoadContext<'b>,
|
||||||
|
supported_compressed_formats: CompressedImageFormats,
|
||||||
) -> Result<(), GltfError> {
|
) -> Result<(), GltfError> {
|
||||||
let gltf = gltf::Gltf::from_slice(bytes)?;
|
let gltf = gltf::Gltf::from_slice(bytes)?;
|
||||||
let buffer_data = load_buffers(&gltf, load_context, load_context.path()).await?;
|
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
|
// to avoid https://github.com/bevyengine/bevy/pull/2725
|
||||||
if gltf.textures().len() == 1 || cfg!(target_arch = "wasm32") {
|
if gltf.textures().len() == 1 || cfg!(target_arch = "wasm32") {
|
||||||
for gltf_texture in gltf.textures() {
|
for gltf_texture in gltf.textures() {
|
||||||
let (texture, label) =
|
let (texture, label) = load_texture(
|
||||||
load_texture(gltf_texture, &buffer_data, &linear_textures, load_context).await?;
|
gltf_texture,
|
||||||
|
&buffer_data,
|
||||||
|
&linear_textures,
|
||||||
|
load_context,
|
||||||
|
supported_compressed_formats,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
load_context.set_labeled_asset(&label, LoadedAsset::new(texture));
|
load_context.set_labeled_asset(&label, LoadedAsset::new(texture));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -265,7 +284,14 @@ async fn load_gltf<'a, 'b>(
|
||||||
let load_context: &LoadContext = load_context;
|
let load_context: &LoadContext = load_context;
|
||||||
let buffer_data = &buffer_data;
|
let buffer_data = &buffer_data;
|
||||||
scope.spawn(async move {
|
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>],
|
buffer_data: &[Vec<u8>],
|
||||||
linear_textures: &HashSet<usize>,
|
linear_textures: &HashSet<usize>,
|
||||||
load_context: &LoadContext<'a>,
|
load_context: &LoadContext<'a>,
|
||||||
|
supported_compressed_formats: CompressedImageFormats,
|
||||||
) -> Result<(Image, String), GltfError> {
|
) -> Result<(Image, String), GltfError> {
|
||||||
|
let is_srgb = !linear_textures.contains(&gltf_texture.index());
|
||||||
let mut texture = match gltf_texture.source().source() {
|
let mut texture = match gltf_texture.source().source() {
|
||||||
gltf::image::Source::View { view, mime_type } => {
|
gltf::image::Source::View { view, mime_type } => {
|
||||||
let start = view.offset() as usize;
|
let start = view.offset() as usize;
|
||||||
let end = (view.offset() + view.length()) as usize;
|
let end = (view.offset() + view.length()) as usize;
|
||||||
let buffer = &buffer_data[view.buffer().index()][start..end];
|
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 } => {
|
gltf::image::Source::Uri { uri, mime_type } => {
|
||||||
let uri = percent_encoding::percent_decode_str(uri)
|
let uri = percent_encoding::percent_decode_str(uri)
|
||||||
|
@ -363,13 +396,12 @@ async fn load_texture<'a>(
|
||||||
Image::from_buffer(
|
Image::from_buffer(
|
||||||
&bytes,
|
&bytes,
|
||||||
mime_type.map(ImageType::MimeType).unwrap_or(image_type),
|
mime_type.map(ImageType::MimeType).unwrap_or(image_type),
|
||||||
|
supported_compressed_formats,
|
||||||
|
is_srgb,
|
||||||
)?
|
)?
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
texture.sampler_descriptor = texture_sampler(&gltf_texture);
|
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)))
|
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)
|
# Image format support for texture loading (PNG and HDR are enabled by default)
|
||||||
hdr = ["bevy_render/hdr"]
|
hdr = ["bevy_render/hdr"]
|
||||||
png = ["bevy_render/png"]
|
png = ["bevy_render/png"]
|
||||||
dds = ["bevy_render/dds"]
|
|
||||||
tga = ["bevy_render/tga"]
|
tga = ["bevy_render/tga"]
|
||||||
jpeg = ["bevy_render/jpeg"]
|
jpeg = ["bevy_render/jpeg"]
|
||||||
bmp = ["bevy_render/bmp"]
|
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)
|
# Audio format support (vorbis is enabled by default)
|
||||||
flac = ["bevy_audio/flac"]
|
flac = ["bevy_audio/flac"]
|
||||||
|
|
|
@ -58,6 +58,8 @@ impl PluginGroup for DefaultPlugins {
|
||||||
#[cfg(feature = "bevy_pbr")]
|
#[cfg(feature = "bevy_pbr")]
|
||||||
group.add(bevy_pbr::PbrPlugin::default());
|
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")]
|
#[cfg(feature = "bevy_gltf")]
|
||||||
group.add(bevy_gltf::GltfPlugin::default());
|
group.add(bevy_gltf::GltfPlugin::default());
|
||||||
|
|
||||||
|
|
|
@ -123,6 +123,7 @@ bitflags::bitflags! {
|
||||||
const ALPHA_MODE_OPAQUE = (1 << 6);
|
const ALPHA_MODE_OPAQUE = (1 << 6);
|
||||||
const ALPHA_MODE_MASK = (1 << 7);
|
const ALPHA_MODE_MASK = (1 << 7);
|
||||||
const ALPHA_MODE_BLEND = (1 << 8);
|
const ALPHA_MODE_BLEND = (1 << 8);
|
||||||
|
const TWO_COMPONENT_NORMAL_MAP = (1 << 9);
|
||||||
const NONE = 0;
|
const NONE = 0;
|
||||||
const UNINITIALIZED = 0xFFFF;
|
const UNINITIALIZED = 0xFFFF;
|
||||||
}
|
}
|
||||||
|
@ -246,6 +247,22 @@ impl RenderAsset for StandardMaterial {
|
||||||
flags |= StandardMaterialFlags::UNLIT;
|
flags |= StandardMaterialFlags::UNLIT;
|
||||||
}
|
}
|
||||||
let has_normal_map = material.normal_map_texture.is_some();
|
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?
|
// NOTE: 0.5 is from the glTF default - do we want this?
|
||||||
let mut alpha_cutoff = 0.5;
|
let mut alpha_cutoff = 0.5;
|
||||||
match material.alpha_mode {
|
match material.alpha_mode {
|
||||||
|
|
|
@ -327,6 +327,7 @@ impl FromWorld for MeshPipeline {
|
||||||
GpuImage {
|
GpuImage {
|
||||||
texture,
|
texture,
|
||||||
texture_view,
|
texture_view,
|
||||||
|
texture_format: image.texture_descriptor.format,
|
||||||
sampler,
|
sampler,
|
||||||
size: Size::new(
|
size: Size::new(
|
||||||
image.texture_descriptor.size.width as f32,
|
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_OPAQUE: u32 = 64u;
|
||||||
let STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK: u32 = 128u;
|
let STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK: u32 = 128u;
|
||||||
let STANDARD_MATERIAL_FLAGS_ALPHA_MODE_BLEND: u32 = 256u;
|
let STANDARD_MATERIAL_FLAGS_ALPHA_MODE_BLEND: u32 = 256u;
|
||||||
|
let STANDARD_MATERIAL_FLAGS_TWO_COMPONENT_NORMAL_MAP: u32 = 512u;
|
||||||
|
|
||||||
[[group(1), binding(0)]]
|
[[group(1), binding(0)]]
|
||||||
var<uniform> material: StandardMaterial;
|
var<uniform> material: StandardMaterial;
|
||||||
|
@ -515,7 +516,16 @@ fn fragment(in: FragmentInput) -> [[location(0)]] vec4<f32> {
|
||||||
#ifdef VERTEX_TANGENTS
|
#ifdef VERTEX_TANGENTS
|
||||||
#ifdef STANDARDMATERIAL_NORMAL_MAP
|
#ifdef STANDARDMATERIAL_NORMAL_MAP
|
||||||
let TBN = mat3x3<f32>(T, B, N);
|
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
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -11,10 +11,15 @@ keywords = ["bevy"]
|
||||||
[features]
|
[features]
|
||||||
png = ["image/png"]
|
png = ["image/png"]
|
||||||
hdr = ["image/hdr"]
|
hdr = ["image/hdr"]
|
||||||
dds = ["image/dds"]
|
|
||||||
tga = ["image/tga"]
|
tga = ["image/tga"]
|
||||||
jpeg = ["image/jpeg"]
|
jpeg = ["image/jpeg"]
|
||||||
bmp = ["image/bmp"]
|
bmp = ["image/bmp"]
|
||||||
|
dds = ["ddsfile"]
|
||||||
|
|
||||||
|
# For ktx2 supercompression
|
||||||
|
zlib = ["flate2"]
|
||||||
|
zstd = ["ruzstd"]
|
||||||
|
|
||||||
trace = []
|
trace = []
|
||||||
wgpu_trace = ["wgpu/trace"]
|
wgpu_trace = ["wgpu/trace"]
|
||||||
ci_limits = []
|
ci_limits = []
|
||||||
|
@ -54,3 +59,10 @@ hexasphere = "7.0.0"
|
||||||
parking_lot = "0.11.0"
|
parking_lot = "0.11.0"
|
||||||
regex = "1.5"
|
regex = "1.5"
|
||||||
copyless = "0.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(CameraPlugin)
|
||||||
.add_plugin(ViewPlugin)
|
.add_plugin(ViewPlugin)
|
||||||
.add_plugin(MeshPlugin)
|
.add_plugin(MeshPlugin)
|
||||||
|
// NOTE: Load this after renderer initialization so that it knows about the supported
|
||||||
|
// compressed texture formats
|
||||||
.add_plugin(ImagePlugin);
|
.add_plugin(ImagePlugin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,8 @@ use futures_lite::future;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use wgpu::util::DeviceExt;
|
use wgpu::util::DeviceExt;
|
||||||
|
|
||||||
|
use super::RenderQueue;
|
||||||
|
|
||||||
/// This GPU device is responsible for the creation of most rendering and compute resources.
|
/// This GPU device is responsible for the creation of most rendering and compute resources.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct RenderDevice {
|
pub struct RenderDevice {
|
||||||
|
@ -121,6 +123,22 @@ impl RenderDevice {
|
||||||
Buffer::from(wgpu_buffer)
|
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`].
|
/// Creates a new [`Texture`].
|
||||||
///
|
///
|
||||||
/// `desc` specifies the general format of the 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 super::image_texture_conversion::image_to_texture;
|
||||||
use crate::{
|
use crate::{
|
||||||
render_asset::{PrepareAssetError, RenderAsset},
|
render_asset::{PrepareAssetError, RenderAsset},
|
||||||
|
@ -20,6 +27,82 @@ pub const SAMPLER_ASSET_INDEX: u64 = 1;
|
||||||
pub const DEFAULT_IMAGE_HANDLE: HandleUntyped =
|
pub const DEFAULT_IMAGE_HANDLE: HandleUntyped =
|
||||||
HandleUntyped::weak_from_u64(Image::TYPE_UUID, 13148262314052771789);
|
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)]
|
#[derive(Debug, Clone, TypeUuid)]
|
||||||
#[uuid = "6ea26da6-6cf8-4ea2-9986-1d7bf6c17d6f"]
|
#[uuid = "6ea26da6-6cf8-4ea2-9986-1d7bf6c17d6f"]
|
||||||
pub struct Image {
|
pub struct Image {
|
||||||
|
@ -181,38 +264,35 @@ impl Image {
|
||||||
pub fn convert(&self, new_format: TextureFormat) -> Option<Self> {
|
pub fn convert(&self, new_format: TextureFormat) -> Option<Self> {
|
||||||
super::image_texture_conversion::texture_to_image(self)
|
super::image_texture_conversion::texture_to_image(self)
|
||||||
.and_then(|img| match new_format {
|
.and_then(|img| match new_format {
|
||||||
TextureFormat::R8Unorm => Some(image::DynamicImage::ImageLuma8(img.into_luma8())),
|
TextureFormat::R8Unorm => {
|
||||||
TextureFormat::Rg8Unorm => {
|
Some((image::DynamicImage::ImageLuma8(img.into_luma8()), false))
|
||||||
Some(image::DynamicImage::ImageLumaA8(img.into_luma_alpha8()))
|
|
||||||
}
|
}
|
||||||
|
TextureFormat::Rg8Unorm => Some((
|
||||||
|
image::DynamicImage::ImageLumaA8(img.into_luma_alpha8()),
|
||||||
|
false,
|
||||||
|
)),
|
||||||
TextureFormat::Rgba8UnormSrgb => {
|
TextureFormat::Rgba8UnormSrgb => {
|
||||||
Some(image::DynamicImage::ImageRgba8(img.into_rgba8()))
|
Some((image::DynamicImage::ImageRgba8(img.into_rgba8()), true))
|
||||||
}
|
}
|
||||||
TextureFormat::Bgra8UnormSrgb => {
|
TextureFormat::Bgra8UnormSrgb => {
|
||||||
Some(image::DynamicImage::ImageBgra8(img.into_bgra8()))
|
Some((image::DynamicImage::ImageBgra8(img.into_bgra8()), true))
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => 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`
|
/// Load a bytes buffer in a [`Image`], according to type `image_type`, using the `image`
|
||||||
/// crate
|
/// crate
|
||||||
pub fn from_buffer(buffer: &[u8], image_type: ImageType) -> Result<Image, TextureError> {
|
pub fn from_buffer(
|
||||||
let format = match image_type {
|
buffer: &[u8],
|
||||||
ImageType::MimeType(mime_type) => match mime_type {
|
image_type: ImageType,
|
||||||
"image/png" => Ok(image::ImageFormat::Png),
|
#[allow(unused_variables)] supported_compressed_formats: CompressedImageFormats,
|
||||||
"image/vnd-ms.dds" => Ok(image::ImageFormat::Dds),
|
is_srgb: bool,
|
||||||
"image/x-targa" => Ok(image::ImageFormat::Tga),
|
) -> Result<Image, TextureError> {
|
||||||
"image/x-tga" => Ok(image::ImageFormat::Tga),
|
let format = image_type.to_image_format()?;
|
||||||
"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())),
|
|
||||||
}?;
|
|
||||||
|
|
||||||
// Load the image in the expected format.
|
// Load the image in the expected format.
|
||||||
// Some formats like PNG allow for R or RG textures too, so the texture
|
// 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
|
// needs to be added, so the image data needs to be converted in those
|
||||||
// cases.
|
// cases.
|
||||||
|
|
||||||
let dyn_img = image::load_from_memory_with_format(buffer, format)?;
|
match format {
|
||||||
Ok(image_to_texture(dyn_img))
|
#[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
|
/// An error that occurs when loading a texture
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum TextureError {
|
pub enum TextureError {
|
||||||
#[error("invalid image mime type")]
|
#[error("invalid image mime type: {0}")]
|
||||||
InvalidImageMimeType(String),
|
InvalidImageMimeType(String),
|
||||||
#[error("invalid image extension")]
|
#[error("invalid image extension: {0}")]
|
||||||
InvalidImageExtension(String),
|
InvalidImageExtension(String),
|
||||||
#[error("failed to load an image: {0}")]
|
#[error("failed to load an image: {0}")]
|
||||||
ImageError(#[from] image::ImageError),
|
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.
|
/// The type of a raw image buffer.
|
||||||
|
@ -244,6 +384,17 @@ pub enum ImageType<'a> {
|
||||||
Extension(&'a str),
|
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.
|
/// Used to calculate the volume of an item.
|
||||||
pub trait Volume {
|
pub trait Volume {
|
||||||
fn volume(&self) -> usize;
|
fn volume(&self) -> usize;
|
||||||
|
@ -387,6 +538,7 @@ impl TextureFormatPixelInfo for TextureFormat {
|
||||||
pub struct GpuImage {
|
pub struct GpuImage {
|
||||||
pub texture: Texture,
|
pub texture: Texture,
|
||||||
pub texture_view: TextureView,
|
pub texture_view: TextureView,
|
||||||
|
pub texture_format: TextureFormat,
|
||||||
pub sampler: Sampler,
|
pub sampler: Sampler,
|
||||||
pub size: Size,
|
pub size: Size,
|
||||||
}
|
}
|
||||||
|
@ -406,9 +558,14 @@ impl RenderAsset for Image {
|
||||||
image: Self::ExtractedAsset,
|
image: Self::ExtractedAsset,
|
||||||
(render_device, render_queue): &mut SystemParamItem<Self::Param>,
|
(render_device, render_queue): &mut SystemParamItem<Self::Param>,
|
||||||
) -> Result<Self::PreparedAsset, PrepareAssetError<Self::ExtractedAsset>> {
|
) -> Result<Self::PreparedAsset, PrepareAssetError<Self::ExtractedAsset>> {
|
||||||
|
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 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();
|
let format_size = image.texture_descriptor.format.pixel_size();
|
||||||
render_queue.write_texture(
|
render_queue.write_texture(
|
||||||
ImageCopyTexture {
|
ImageCopyTexture {
|
||||||
|
@ -434,21 +591,116 @@ impl RenderAsset for Image {
|
||||||
},
|
},
|
||||||
image.texture_descriptor.size,
|
image.texture_descriptor.size,
|
||||||
);
|
);
|
||||||
|
texture
|
||||||
|
};
|
||||||
|
|
||||||
let texture_view = texture.create_view(&TextureViewDescriptor::default());
|
let texture_view = texture.create_view(&TextureViewDescriptor::default());
|
||||||
let size = Size::new(
|
let size = Size::new(
|
||||||
image.texture_descriptor.size.width as f32,
|
image.texture_descriptor.size.width as f32,
|
||||||
image.texture_descriptor.size.height as f32,
|
image.texture_descriptor.size.height as f32,
|
||||||
);
|
);
|
||||||
|
let sampler = render_device.create_sampler(&image.sampler_descriptor);
|
||||||
Ok(GpuImage {
|
Ok(GpuImage {
|
||||||
texture,
|
texture,
|
||||||
texture_view,
|
texture_view,
|
||||||
|
texture_format: image.texture_descriptor.format,
|
||||||
sampler,
|
sampler,
|
||||||
size,
|
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)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ use wgpu::{Extent3d, TextureDimension, TextureFormat};
|
||||||
|
|
||||||
// TODO: fix name?
|
// TODO: fix name?
|
||||||
/// Converts a [`DynamicImage`] to an [`Image`].
|
/// 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;
|
use bevy_core::cast_slice;
|
||||||
let width;
|
let width;
|
||||||
let height;
|
let height;
|
||||||
|
@ -17,7 +17,11 @@ pub(crate) fn image_to_texture(dyn_img: DynamicImage) -> Image {
|
||||||
let i = DynamicImage::ImageLuma8(i).into_rgba8();
|
let i = DynamicImage::ImageLuma8(i).into_rgba8();
|
||||||
width = i.width();
|
width = i.width();
|
||||||
height = i.height();
|
height = i.height();
|
||||||
format = TextureFormat::Rgba8UnormSrgb;
|
format = if is_srgb {
|
||||||
|
TextureFormat::Rgba8UnormSrgb
|
||||||
|
} else {
|
||||||
|
TextureFormat::Rgba8Unorm
|
||||||
|
};
|
||||||
|
|
||||||
data = i.into_raw();
|
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();
|
let i = DynamicImage::ImageLumaA8(i).into_rgba8();
|
||||||
width = i.width();
|
width = i.width();
|
||||||
height = i.height();
|
height = i.height();
|
||||||
format = TextureFormat::Rgba8UnormSrgb;
|
format = if is_srgb {
|
||||||
|
TextureFormat::Rgba8UnormSrgb
|
||||||
|
} else {
|
||||||
|
TextureFormat::Rgba8Unorm
|
||||||
|
};
|
||||||
|
|
||||||
data = i.into_raw();
|
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();
|
let i = DynamicImage::ImageRgb8(i).into_rgba8();
|
||||||
width = i.width();
|
width = i.width();
|
||||||
height = i.height();
|
height = i.height();
|
||||||
format = TextureFormat::Rgba8UnormSrgb;
|
format = if is_srgb {
|
||||||
|
TextureFormat::Rgba8UnormSrgb
|
||||||
|
} else {
|
||||||
|
TextureFormat::Rgba8Unorm
|
||||||
|
};
|
||||||
|
|
||||||
data = i.into_raw();
|
data = i.into_raw();
|
||||||
}
|
}
|
||||||
DynamicImage::ImageRgba8(i) => {
|
DynamicImage::ImageRgba8(i) => {
|
||||||
width = i.width();
|
width = i.width();
|
||||||
height = i.height();
|
height = i.height();
|
||||||
format = TextureFormat::Rgba8UnormSrgb;
|
format = if is_srgb {
|
||||||
|
TextureFormat::Rgba8UnormSrgb
|
||||||
|
} else {
|
||||||
|
TextureFormat::Rgba8Unorm
|
||||||
|
};
|
||||||
|
|
||||||
data = i.into_raw();
|
data = i.into_raw();
|
||||||
}
|
}
|
||||||
|
@ -49,14 +65,22 @@ pub(crate) fn image_to_texture(dyn_img: DynamicImage) -> Image {
|
||||||
|
|
||||||
width = i.width();
|
width = i.width();
|
||||||
height = i.height();
|
height = i.height();
|
||||||
format = TextureFormat::Bgra8UnormSrgb;
|
format = if is_srgb {
|
||||||
|
TextureFormat::Bgra8UnormSrgb
|
||||||
|
} else {
|
||||||
|
TextureFormat::Bgra8Unorm
|
||||||
|
};
|
||||||
|
|
||||||
data = i.into_raw();
|
data = i.into_raw();
|
||||||
}
|
}
|
||||||
DynamicImage::ImageBgra8(i) => {
|
DynamicImage::ImageBgra8(i) => {
|
||||||
width = i.width();
|
width = i.width();
|
||||||
height = i.height();
|
height = i.height();
|
||||||
format = TextureFormat::Bgra8UnormSrgb;
|
format = if is_srgb {
|
||||||
|
TextureFormat::Bgra8UnormSrgb
|
||||||
|
} else {
|
||||||
|
TextureFormat::Bgra8Unorm
|
||||||
|
};
|
||||||
|
|
||||||
data = i.into_raw();
|
data = i.into_raw();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,27 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use bevy_asset::{AssetLoader, LoadContext, LoadedAsset};
|
use bevy_asset::{AssetLoader, LoadContext, LoadedAsset};
|
||||||
|
use bevy_ecs::prelude::{FromWorld, World};
|
||||||
use bevy_utils::BoxedFuture;
|
use bevy_utils::BoxedFuture;
|
||||||
use thiserror::Error;
|
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.
|
/// Loader for images that can be read by the `image` crate.
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone)]
|
||||||
pub struct ImageTextureLoader;
|
pub struct ImageTextureLoader {
|
||||||
|
supported_compressed_formats: CompressedImageFormats,
|
||||||
|
}
|
||||||
|
|
||||||
const FILE_EXTENSIONS: &[&str] = &[
|
const FILE_EXTENSIONS: &[&str] = &[
|
||||||
|
#[cfg(feature = "basis-universal")]
|
||||||
|
"basis",
|
||||||
|
#[cfg(feature = "bmp")]
|
||||||
|
"bmp",
|
||||||
#[cfg(feature = "png")]
|
#[cfg(feature = "png")]
|
||||||
"png",
|
"png",
|
||||||
#[cfg(feature = "dds")]
|
#[cfg(feature = "dds")]
|
||||||
|
@ -20,8 +32,8 @@ const FILE_EXTENSIONS: &[&str] = &[
|
||||||
"jpg",
|
"jpg",
|
||||||
#[cfg(feature = "jpeg")]
|
#[cfg(feature = "jpeg")]
|
||||||
"jpeg",
|
"jpeg",
|
||||||
#[cfg(feature = "bmp")]
|
#[cfg(feature = "ktx2")]
|
||||||
"bmp",
|
"ktx2",
|
||||||
];
|
];
|
||||||
|
|
||||||
impl AssetLoader for ImageTextureLoader {
|
impl AssetLoader for ImageTextureLoader {
|
||||||
|
@ -34,11 +46,15 @@ impl AssetLoader for ImageTextureLoader {
|
||||||
// use the file extension for the image type
|
// use the file extension for the image type
|
||||||
let ext = load_context.path().extension().unwrap().to_str().unwrap();
|
let ext = load_context.path().extension().unwrap().to_str().unwrap();
|
||||||
|
|
||||||
let dyn_img = Image::from_buffer(bytes, ImageType::Extension(ext)).map_err(|err| {
|
let dyn_img = Image::from_buffer(
|
||||||
FileTextureError {
|
bytes,
|
||||||
|
ImageType::Extension(ext),
|
||||||
|
self.supported_compressed_formats,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.map_err(|err| FileTextureError {
|
||||||
error: err,
|
error: err,
|
||||||
path: format!("{}", load_context.path().display()),
|
path: format!("{}", load_context.path().display()),
|
||||||
}
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
load_context.set_default_asset(LoadedAsset::new(dyn_img));
|
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.
|
/// An error that occurs when loading a texture from a file.
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub struct FileTextureError {
|
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")]
|
#[cfg(feature = "hdr")]
|
||||||
mod hdr_texture_loader;
|
mod hdr_texture_loader;
|
||||||
#[allow(clippy::module_inception)]
|
#[allow(clippy::module_inception)]
|
||||||
mod image;
|
mod image;
|
||||||
mod image_texture_loader;
|
mod image_texture_loader;
|
||||||
|
#[cfg(feature = "ktx2")]
|
||||||
|
mod ktx2;
|
||||||
mod texture_cache;
|
mod texture_cache;
|
||||||
|
|
||||||
pub(crate) mod image_texture_conversion;
|
pub(crate) mod image_texture_conversion;
|
||||||
|
|
||||||
pub use self::image::*;
|
pub use self::image::*;
|
||||||
|
#[cfg(feature = "ktx2")]
|
||||||
|
pub use self::ktx2::*;
|
||||||
|
#[cfg(feature = "dds")]
|
||||||
|
pub use dds::*;
|
||||||
#[cfg(feature = "hdr")]
|
#[cfg(feature = "hdr")]
|
||||||
pub use hdr_texture_loader::*;
|
pub use hdr_texture_loader::*;
|
||||||
|
|
||||||
|
@ -29,7 +39,9 @@ impl Plugin for ImagePlugin {
|
||||||
feature = "dds",
|
feature = "dds",
|
||||||
feature = "tga",
|
feature = "tga",
|
||||||
feature = "jpeg",
|
feature = "jpeg",
|
||||||
feature = "bmp"
|
feature = "bmp",
|
||||||
|
feature = "basis-universal",
|
||||||
|
feature = "ktx2",
|
||||||
))]
|
))]
|
||||||
{
|
{
|
||||||
app.init_asset_loader::<ImageTextureLoader>();
|
app.init_asset_loader::<ImageTextureLoader>();
|
||||||
|
|
|
@ -192,6 +192,7 @@ impl FromWorld for Mesh2dPipeline {
|
||||||
GpuImage {
|
GpuImage {
|
||||||
texture,
|
texture,
|
||||||
texture_view,
|
texture_view,
|
||||||
|
texture_format: image.texture_descriptor.format,
|
||||||
sampler,
|
sampler,
|
||||||
size: Size::new(
|
size: Size::new(
|
||||||
image.texture_descriptor.size.width as f32,
|
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.|
|
|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.|
|
|wgpu_trace|For tracing wgpu.|
|
||||||
|dds|DDS picture format support.|
|
|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.|
|
|tga|TGA picture format support.|
|
||||||
|jpeg|JPEG picture format support.|
|
|jpeg|JPEG picture format support.|
|
||||||
|bmp|BMP picture format support.|
|
|bmp|BMP picture format support.|
|
||||||
|
|
Loading…
Reference in a new issue