Skip to content

Commit 7c7b1e9

Browse files
authored
Load and convert RGB8 dds textures (#12952)
# Objective - Closes #12944. ## Solution - Load `R8G8B8` textures by transcoding to an rgba format since `wgpu` does not support texture formats with 3 channels. - Switch to erroring out instead of panicking on an invalid dds file. --- ## Changelog ### Added - DDS Textures with the `R8G8B8` format are now supported. They require an additional conversion step, so using `R8G8B8A8` or a similar format is preferable for texture loading performance.
1 parent 0855a0e commit 7c7b1e9

File tree

2 files changed

+55
-10
lines changed

2 files changed

+55
-10
lines changed

crates/bevy_image/src/dds.rs

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use wgpu_types::{
88
#[cfg(debug_assertions)]
99
use {bevy_utils::once, tracing::warn};
1010

11-
use super::{CompressedImageFormats, Image, TextureError};
11+
use super::{CompressedImageFormats, Image, TextureError, TranscodeFormat};
1212

1313
#[cfg(feature = "dds")]
1414
pub fn dds_buffer_to_image(
@@ -20,7 +20,18 @@ pub fn dds_buffer_to_image(
2020
let mut cursor = Cursor::new(buffer);
2121
let dds = Dds::read(&mut cursor)
2222
.map_err(|error| TextureError::InvalidData(format!("Failed to parse DDS file: {error}")))?;
23-
let texture_format = dds_format_to_texture_format(&dds, is_srgb)?;
23+
let (texture_format, transcode_format) = match dds_format_to_texture_format(&dds, is_srgb) {
24+
Ok(format) => (format, None),
25+
Err(TextureError::FormatRequiresTranscodingError(TranscodeFormat::Rgb8)) => {
26+
let format = if is_srgb {
27+
TextureFormat::Bgra8UnormSrgb
28+
} else {
29+
TextureFormat::Bgra8Unorm
30+
};
31+
(format, Some(TranscodeFormat::Rgb8))
32+
}
33+
Err(error) => return Err(error),
34+
};
2435
if !supported_compressed_formats.supports(texture_format) {
2536
return Err(TextureError::UnsupportedTextureFormat(format!(
2637
"Format not supported by this GPU: {texture_format:?}",
@@ -86,7 +97,29 @@ pub fn dds_buffer_to_image(
8697
..Default::default()
8798
});
8899
}
89-
image.data = Some(dds.data);
100+
101+
// DDS mipmap layout is directly compatible with wgpu's layout (Slice -> Face -> Mip):
102+
// https://learn.microsoft.com/fr-fr/windows/win32/direct3ddds/dx-graphics-dds-reference
103+
image.data = if let Some(transcode_format) = transcode_format {
104+
match transcode_format {
105+
TranscodeFormat::Rgb8 => {
106+
let data = dds
107+
.data
108+
.chunks_exact(3)
109+
.flat_map(|pixel| [pixel[0], pixel[1], pixel[2], u8::MAX])
110+
.collect();
111+
Some(data)
112+
}
113+
_ => {
114+
return Err(TextureError::TranscodeError(format!(
115+
"unsupported transcode from {transcode_format:?} to {texture_format:?}"
116+
)))
117+
}
118+
}
119+
} else {
120+
Some(dds.data)
121+
};
122+
90123
Ok(image)
91124
}
92125

@@ -112,6 +145,9 @@ pub fn dds_format_to_texture_format(
112145
TextureFormat::Bgra8Unorm
113146
}
114147
}
148+
D3DFormat::R8G8B8 => {
149+
return Err(TextureError::FormatRequiresTranscodingError(TranscodeFormat::Rgb8));
150+
},
115151
D3DFormat::G16R16 => TextureFormat::Rg16Uint,
116152
D3DFormat::A2B10G10R10 => TextureFormat::Rgb10a2Unorm,
117153
D3DFormat::A8L8 => TextureFormat::Rg8Uint,
@@ -153,7 +189,6 @@ pub fn dds_format_to_texture_format(
153189
// FIXME: Map to argb format and user has to know to ignore the alpha channel?
154190
| D3DFormat::X8B8G8R8
155191
| D3DFormat::A2R10G10B10
156-
| D3DFormat::R8G8B8
157192
| D3DFormat::X1R5G5B5
158193
| D3DFormat::A4R4G4B4
159194
| D3DFormat::X4R4G4B4

crates/bevy_image/src/image.rs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1491,19 +1491,20 @@ pub enum DataFormat {
14911491
Rg,
14921492
}
14931493

1494+
/// Texture data need to be transcoded from this format for use with `wgpu`.
14941495
#[derive(Clone, Copy, Debug)]
14951496
pub enum TranscodeFormat {
14961497
Etc1s,
14971498
Uastc(DataFormat),
1498-
// Has to be transcoded to R8Unorm for use with `wgpu`
1499+
// Has to be transcoded to R8Unorm for use with `wgpu`.
14991500
R8UnormSrgb,
1500-
// Has to be transcoded to R8G8Unorm for use with `wgpu`
1501+
// Has to be transcoded to R8G8Unorm for use with `wgpu`.
15011502
Rg8UnormSrgb,
1502-
// Has to be transcoded to Rgba8 for use with `wgpu`
1503+
// Has to be transcoded to Rgba8 for use with `wgpu`.
15031504
Rgb8,
15041505
}
15051506

1506-
/// An error that occurs when accessing specific pixels in a texture
1507+
/// An error that occurs when accessing specific pixels in a texture.
15071508
#[derive(Error, Debug)]
15081509
pub enum TextureAccessError {
15091510
#[error("out of bounds (x: {x}, y: {y}, z: {z})")]
@@ -1514,25 +1515,34 @@ pub enum TextureAccessError {
15141515
WrongDimension,
15151516
}
15161517

1517-
/// An error that occurs when loading a texture
1518+
/// An error that occurs when loading a texture.
15181519
#[derive(Error, Debug)]
15191520
pub enum TextureError {
1521+
/// Image MIME type is invalid.
15201522
#[error("invalid image mime type: {0}")]
15211523
InvalidImageMimeType(String),
1524+
/// Image extension is invalid.
15221525
#[error("invalid image extension: {0}")]
15231526
InvalidImageExtension(String),
1527+
/// Failed to load an image.
15241528
#[error("failed to load an image: {0}")]
15251529
ImageError(#[from] image::ImageError),
1530+
/// Texture format isn't supported.
15261531
#[error("unsupported texture format: {0}")]
15271532
UnsupportedTextureFormat(String),
1533+
/// Supercompression isn't supported.
15281534
#[error("supercompression not supported: {0}")]
15291535
SuperCompressionNotSupported(String),
1530-
#[error("failed to load an image: {0}")]
1536+
/// Failed to decompress an image.
1537+
#[error("failed to decompress an image: {0}")]
15311538
SuperDecompressionError(String),
1539+
/// Invalid data.
15321540
#[error("invalid data: {0}")]
15331541
InvalidData(String),
1542+
/// Transcode error.
15341543
#[error("transcode error: {0}")]
15351544
TranscodeError(String),
1545+
/// Format requires transcoding.
15361546
#[error("format requires transcoding: {0:?}")]
15371547
FormatRequiresTranscodingError(TranscodeFormat),
15381548
/// Only cubemaps with six faces are supported.

0 commit comments

Comments
 (0)