Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_image/src/dds.rs
6595 views
1
//! [DirectDraw Surface](https://en.wikipedia.org/wiki/DirectDraw_Surface) functionality.
2
3
use ddsfile::{Caps2, D3DFormat, Dds, DxgiFormat};
4
use std::io::Cursor;
5
use wgpu_types::{
6
Extent3d, TextureDimension, TextureFormat, TextureViewDescriptor, TextureViewDimension,
7
};
8
#[cfg(debug_assertions)]
9
use {bevy_utils::once, tracing::warn};
10
11
use super::{CompressedImageFormats, Image, TextureError, TranscodeFormat};
12
13
#[cfg(feature = "dds")]
14
pub fn dds_buffer_to_image(
15
buffer: &[u8],
16
supported_compressed_formats: CompressedImageFormats,
17
is_srgb: bool,
18
) -> Result<Image, TextureError> {
19
let mut cursor = Cursor::new(buffer);
20
let dds = Dds::read(&mut cursor)
21
.map_err(|error| TextureError::InvalidData(format!("Failed to parse DDS file: {error}")))?;
22
let (texture_format, transcode_format) = match dds_format_to_texture_format(&dds, is_srgb) {
23
Ok(format) => (format, None),
24
Err(TextureError::FormatRequiresTranscodingError(TranscodeFormat::Rgb8)) => {
25
let format = if is_srgb {
26
TextureFormat::Rgba8UnormSrgb
27
} else {
28
TextureFormat::Rgba8Unorm
29
};
30
(format, Some(TranscodeFormat::Rgb8))
31
}
32
Err(error) => return Err(error),
33
};
34
if !supported_compressed_formats.supports(texture_format) {
35
return Err(TextureError::UnsupportedTextureFormat(format!(
36
"Format not supported by this GPU: {texture_format:?}",
37
)));
38
}
39
let mut image = Image::default();
40
let is_cubemap = dds.header.caps2.contains(Caps2::CUBEMAP);
41
let depth_or_array_layers = if dds.get_num_array_layers() > 1 {
42
dds.get_num_array_layers()
43
} else {
44
dds.get_depth()
45
};
46
if is_cubemap
47
&& !dds.header.caps2.contains(
48
Caps2::CUBEMAP_NEGATIVEX
49
| Caps2::CUBEMAP_NEGATIVEY
50
| Caps2::CUBEMAP_NEGATIVEZ
51
| Caps2::CUBEMAP_POSITIVEX
52
| Caps2::CUBEMAP_POSITIVEY
53
| Caps2::CUBEMAP_POSITIVEZ,
54
)
55
{
56
return Err(TextureError::IncompleteCubemap);
57
}
58
image.texture_descriptor.size = Extent3d {
59
width: dds.get_width(),
60
height: dds.get_height(),
61
depth_or_array_layers,
62
}
63
.physical_size(texture_format);
64
let mip_map_level = match dds.get_num_mipmap_levels() {
65
0 => {
66
#[cfg(debug_assertions)]
67
once!(warn!("Mipmap levels for texture are 0, bumping them to 1",));
68
1
69
}
70
t => t,
71
};
72
image.texture_descriptor.mip_level_count = mip_map_level;
73
image.texture_descriptor.format = texture_format;
74
image.texture_descriptor.dimension = if dds.get_depth() > 1 {
75
TextureDimension::D3
76
// 1x1 textures should generally be interpreted as solid 2D
77
} else if ((dds.get_width() > 1 || dds.get_height() > 1)
78
&& !(dds.get_width() > 1 && dds.get_height() > 1))
79
&& !image.is_compressed()
80
{
81
TextureDimension::D1
82
} else {
83
TextureDimension::D2
84
};
85
if is_cubemap {
86
let dimension = if image.texture_descriptor.size.depth_or_array_layers > 6 {
87
TextureViewDimension::CubeArray
88
} else {
89
TextureViewDimension::Cube
90
};
91
image.texture_view_descriptor = Some(TextureViewDescriptor {
92
dimension: Some(dimension),
93
..Default::default()
94
});
95
}
96
97
// DDS mipmap layout is directly compatible with wgpu's layout (Slice -> Face -> Mip):
98
// https://learn.microsoft.com/fr-fr/windows/win32/direct3ddds/dx-graphics-dds-reference
99
image.data = if let Some(transcode_format) = transcode_format {
100
match transcode_format {
101
TranscodeFormat::Rgb8 => {
102
let data = dds
103
.data
104
.chunks_exact(3)
105
.flat_map(|pixel| [pixel[0], pixel[1], pixel[2], u8::MAX])
106
.collect();
107
Some(data)
108
}
109
_ => {
110
return Err(TextureError::TranscodeError(format!(
111
"unsupported transcode from {transcode_format:?} to {texture_format:?}"
112
)))
113
}
114
}
115
} else {
116
Some(dds.data)
117
};
118
119
Ok(image)
120
}
121
122
#[cfg(feature = "dds")]
123
pub fn dds_format_to_texture_format(
124
dds: &Dds,
125
is_srgb: bool,
126
) -> Result<TextureFormat, TextureError> {
127
Ok(if let Some(d3d_format) = dds.get_d3d_format() {
128
match d3d_format {
129
D3DFormat::A8B8G8R8 => {
130
if is_srgb {
131
TextureFormat::Rgba8UnormSrgb
132
} else {
133
TextureFormat::Rgba8Unorm
134
}
135
}
136
D3DFormat::A8 => TextureFormat::R8Unorm,
137
D3DFormat::A8R8G8B8 => {
138
if is_srgb {
139
TextureFormat::Bgra8UnormSrgb
140
} else {
141
TextureFormat::Bgra8Unorm
142
}
143
}
144
D3DFormat::R8G8B8 => {
145
return Err(TextureError::FormatRequiresTranscodingError(TranscodeFormat::Rgb8));
146
},
147
D3DFormat::G16R16 => TextureFormat::Rg16Uint,
148
D3DFormat::A2B10G10R10 => TextureFormat::Rgb10a2Unorm,
149
D3DFormat::A8L8 => TextureFormat::Rg8Uint,
150
D3DFormat::L16 => TextureFormat::R16Uint,
151
D3DFormat::L8 => TextureFormat::R8Uint,
152
D3DFormat::DXT1 => {
153
if is_srgb {
154
TextureFormat::Bc1RgbaUnormSrgb
155
} else {
156
TextureFormat::Bc1RgbaUnorm
157
}
158
}
159
D3DFormat::DXT3 | D3DFormat::DXT2 => {
160
if is_srgb {
161
TextureFormat::Bc2RgbaUnormSrgb
162
} else {
163
TextureFormat::Bc2RgbaUnorm
164
}
165
}
166
D3DFormat::DXT5 | D3DFormat::DXT4 => {
167
if is_srgb {
168
TextureFormat::Bc3RgbaUnormSrgb
169
} else {
170
TextureFormat::Bc3RgbaUnorm
171
}
172
}
173
D3DFormat::A16B16G16R16 => TextureFormat::Rgba16Unorm,
174
D3DFormat::Q16W16V16U16 => TextureFormat::Rgba16Sint,
175
D3DFormat::R16F => TextureFormat::R16Float,
176
D3DFormat::G16R16F => TextureFormat::Rg16Float,
177
D3DFormat::A16B16G16R16F => TextureFormat::Rgba16Float,
178
D3DFormat::R32F => TextureFormat::R32Float,
179
D3DFormat::G32R32F => TextureFormat::Rg32Float,
180
D3DFormat::A32B32G32R32F => TextureFormat::Rgba32Float,
181
D3DFormat::A1R5G5B5
182
| D3DFormat::R5G6B5
183
// FIXME: Map to argb format and user has to know to ignore the alpha channel?
184
| D3DFormat::X8R8G8B8
185
// FIXME: Map to argb format and user has to know to ignore the alpha channel?
186
| D3DFormat::X8B8G8R8
187
| D3DFormat::A2R10G10B10
188
| D3DFormat::X1R5G5B5
189
| D3DFormat::A4R4G4B4
190
| D3DFormat::X4R4G4B4
191
| D3DFormat::A8R3G3B2
192
| D3DFormat::A4L4
193
| D3DFormat::R8G8_B8G8
194
| D3DFormat::G8R8_G8B8
195
| D3DFormat::UYVY
196
| D3DFormat::YUY2
197
| D3DFormat::CXV8U8 => {
198
return Err(TextureError::UnsupportedTextureFormat(format!(
199
"{d3d_format:?}",
200
)))
201
}
202
}
203
} else if let Some(dxgi_format) = dds.get_dxgi_format() {
204
match dxgi_format {
205
DxgiFormat::R32G32B32A32_Typeless | DxgiFormat::R32G32B32A32_Float => {
206
TextureFormat::Rgba32Float
207
}
208
DxgiFormat::R32G32B32A32_UInt => TextureFormat::Rgba32Uint,
209
DxgiFormat::R32G32B32A32_SInt => TextureFormat::Rgba32Sint,
210
DxgiFormat::R16G16B16A16_Typeless | DxgiFormat::R16G16B16A16_Float => {
211
TextureFormat::Rgba16Float
212
}
213
DxgiFormat::R16G16B16A16_UNorm => TextureFormat::Rgba16Unorm,
214
DxgiFormat::R16G16B16A16_UInt => TextureFormat::Rgba16Uint,
215
DxgiFormat::R16G16B16A16_SNorm => TextureFormat::Rgba16Snorm,
216
DxgiFormat::R16G16B16A16_SInt => TextureFormat::Rgba16Sint,
217
DxgiFormat::R32G32_Typeless | DxgiFormat::R32G32_Float => TextureFormat::Rg32Float,
218
DxgiFormat::R32G32_UInt => TextureFormat::Rg32Uint,
219
DxgiFormat::R32G32_SInt => TextureFormat::Rg32Sint,
220
DxgiFormat::R10G10B10A2_Typeless | DxgiFormat::R10G10B10A2_UNorm => {
221
TextureFormat::Rgb10a2Unorm
222
}
223
DxgiFormat::R11G11B10_Float => TextureFormat::Rg11b10Ufloat,
224
DxgiFormat::R8G8B8A8_Typeless
225
| DxgiFormat::R8G8B8A8_UNorm
226
| DxgiFormat::R8G8B8A8_UNorm_sRGB => {
227
if is_srgb {
228
TextureFormat::Rgba8UnormSrgb
229
} else {
230
TextureFormat::Rgba8Unorm
231
}
232
}
233
DxgiFormat::R8G8B8A8_UInt => TextureFormat::Rgba8Uint,
234
DxgiFormat::R8G8B8A8_SNorm => TextureFormat::Rgba8Snorm,
235
DxgiFormat::R8G8B8A8_SInt => TextureFormat::Rgba8Sint,
236
DxgiFormat::R16G16_Typeless | DxgiFormat::R16G16_Float => TextureFormat::Rg16Float,
237
DxgiFormat::R16G16_UNorm => TextureFormat::Rg16Unorm,
238
DxgiFormat::R16G16_UInt => TextureFormat::Rg16Uint,
239
DxgiFormat::R16G16_SNorm => TextureFormat::Rg16Snorm,
240
DxgiFormat::R16G16_SInt => TextureFormat::Rg16Sint,
241
DxgiFormat::R32_Typeless | DxgiFormat::R32_Float => TextureFormat::R32Float,
242
DxgiFormat::D32_Float => TextureFormat::Depth32Float,
243
DxgiFormat::R32_UInt => TextureFormat::R32Uint,
244
DxgiFormat::R32_SInt => TextureFormat::R32Sint,
245
DxgiFormat::R24G8_Typeless | DxgiFormat::D24_UNorm_S8_UInt => {
246
TextureFormat::Depth24PlusStencil8
247
}
248
DxgiFormat::R24_UNorm_X8_Typeless => TextureFormat::Depth24Plus,
249
DxgiFormat::R8G8_Typeless | DxgiFormat::R8G8_UNorm => TextureFormat::Rg8Unorm,
250
DxgiFormat::R8G8_UInt => TextureFormat::Rg8Uint,
251
DxgiFormat::R8G8_SNorm => TextureFormat::Rg8Snorm,
252
DxgiFormat::R8G8_SInt => TextureFormat::Rg8Sint,
253
DxgiFormat::R16_Typeless | DxgiFormat::R16_Float => TextureFormat::R16Float,
254
DxgiFormat::R16_UNorm => TextureFormat::R16Unorm,
255
DxgiFormat::R16_UInt => TextureFormat::R16Uint,
256
DxgiFormat::R16_SNorm => TextureFormat::R16Snorm,
257
DxgiFormat::R16_SInt => TextureFormat::R16Sint,
258
DxgiFormat::R8_Typeless | DxgiFormat::R8_UNorm => TextureFormat::R8Unorm,
259
DxgiFormat::R8_UInt => TextureFormat::R8Uint,
260
DxgiFormat::R8_SNorm => TextureFormat::R8Snorm,
261
DxgiFormat::R8_SInt => TextureFormat::R8Sint,
262
DxgiFormat::R9G9B9E5_SharedExp => TextureFormat::Rgb9e5Ufloat,
263
DxgiFormat::BC1_Typeless | DxgiFormat::BC1_UNorm | DxgiFormat::BC1_UNorm_sRGB => {
264
if is_srgb {
265
TextureFormat::Bc1RgbaUnormSrgb
266
} else {
267
TextureFormat::Bc1RgbaUnorm
268
}
269
}
270
DxgiFormat::BC2_Typeless | DxgiFormat::BC2_UNorm | DxgiFormat::BC2_UNorm_sRGB => {
271
if is_srgb {
272
TextureFormat::Bc2RgbaUnormSrgb
273
} else {
274
TextureFormat::Bc2RgbaUnorm
275
}
276
}
277
DxgiFormat::BC3_Typeless | DxgiFormat::BC3_UNorm | DxgiFormat::BC3_UNorm_sRGB => {
278
if is_srgb {
279
TextureFormat::Bc3RgbaUnormSrgb
280
} else {
281
TextureFormat::Bc3RgbaUnorm
282
}
283
}
284
DxgiFormat::BC4_Typeless | DxgiFormat::BC4_UNorm => TextureFormat::Bc4RUnorm,
285
DxgiFormat::BC4_SNorm => TextureFormat::Bc4RSnorm,
286
DxgiFormat::BC5_Typeless | DxgiFormat::BC5_UNorm => TextureFormat::Bc5RgUnorm,
287
DxgiFormat::BC5_SNorm => TextureFormat::Bc5RgSnorm,
288
DxgiFormat::B8G8R8A8_UNorm
289
| DxgiFormat::B8G8R8A8_Typeless
290
| DxgiFormat::B8G8R8A8_UNorm_sRGB => {
291
if is_srgb {
292
TextureFormat::Bgra8UnormSrgb
293
} else {
294
TextureFormat::Bgra8Unorm
295
}
296
}
297
298
DxgiFormat::BC6H_Typeless | DxgiFormat::BC6H_UF16 => TextureFormat::Bc6hRgbUfloat,
299
DxgiFormat::BC6H_SF16 => TextureFormat::Bc6hRgbFloat,
300
DxgiFormat::BC7_Typeless | DxgiFormat::BC7_UNorm | DxgiFormat::BC7_UNorm_sRGB => {
301
if is_srgb {
302
TextureFormat::Bc7RgbaUnormSrgb
303
} else {
304
TextureFormat::Bc7RgbaUnorm
305
}
306
}
307
_ => {
308
return Err(TextureError::UnsupportedTextureFormat(format!(
309
"{dxgi_format:?}",
310
)))
311
}
312
}
313
} else {
314
return Err(TextureError::UnsupportedTextureFormat(
315
"unspecified".to_string(),
316
));
317
})
318
}
319
320
#[cfg(test)]
321
mod test {
322
use wgpu_types::{TextureDataOrder, TextureDescriptor, TextureDimension, TextureFormat};
323
324
use crate::CompressedImageFormats;
325
326
use super::dds_buffer_to_image;
327
328
/// `wgpu::create_texture_with_data` that reads from data structure but doesn't actually talk to your GPU
329
fn fake_wgpu_create_texture_with_data(
330
desc: &TextureDescriptor<Option<&'_ str>, &'_ [TextureFormat]>,
331
data: &[u8],
332
) {
333
// Will return None only if it's a combined depth-stencil format
334
// If so, default to 4, validation will fail later anyway since the depth or stencil
335
// aspect needs to be written to individually
336
let block_size = desc.format.block_copy_size(None).unwrap_or(4);
337
let (block_width, block_height) = desc.format.block_dimensions();
338
let layer_iterations = desc.array_layer_count();
339
340
let outer_iteration;
341
let inner_iteration;
342
match TextureDataOrder::default() {
343
TextureDataOrder::LayerMajor => {
344
outer_iteration = layer_iterations;
345
inner_iteration = desc.mip_level_count;
346
}
347
TextureDataOrder::MipMajor => {
348
outer_iteration = desc.mip_level_count;
349
inner_iteration = layer_iterations;
350
}
351
}
352
353
let mut binary_offset = 0;
354
for outer in 0..outer_iteration {
355
for inner in 0..inner_iteration {
356
let (_layer, mip) = match TextureDataOrder::default() {
357
TextureDataOrder::LayerMajor => (outer, inner),
358
TextureDataOrder::MipMajor => (inner, outer),
359
};
360
361
let mut mip_size = desc.mip_level_size(mip).unwrap();
362
// copying layers separately
363
if desc.dimension != TextureDimension::D3 {
364
mip_size.depth_or_array_layers = 1;
365
}
366
367
// When uploading mips of compressed textures and the mip is supposed to be
368
// a size that isn't a multiple of the block size, the mip needs to be uploaded
369
// as its "physical size" which is the size rounded up to the nearest block size.
370
let mip_physical = mip_size.physical_size(desc.format);
371
372
// All these calculations are performed on the physical size as that's the
373
// data that exists in the buffer.
374
let width_blocks = mip_physical.width / block_width;
375
let height_blocks = mip_physical.height / block_height;
376
377
let bytes_per_row = width_blocks * block_size;
378
let data_size = bytes_per_row * height_blocks * mip_size.depth_or_array_layers;
379
380
let end_offset = binary_offset + data_size as usize;
381
382
assert!(binary_offset < data.len());
383
assert!(end_offset <= data.len());
384
// those asserts match how the data will be accessed by wgpu:
385
// data[binary_offset..end_offset])
386
387
binary_offset = end_offset;
388
}
389
}
390
}
391
392
#[test]
393
fn dds_skybox() {
394
let buffer: [u8; 224] = [
395
0x44, 0x44, 0x53, 0x20, 0x7c, 0, 0, 0, 7, 0x10, 0x08, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0x10,
396
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0x47, 0x49, 0x4d, 0x50, 0x2d, 0x44, 0x44, 0x53, 0x5c,
397
0x09, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
398
0, 0, 0, 0, 0, 0, 0, 0x20, 0, 0, 0, 4, 0, 0, 0, 0x44, 0x58, 0x54, 0x35, 0, 0, 0, 0, 0,
399
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x08, 0x10, 0, 0, 0, 0xfe, 0, 0, 0, 0, 0,
400
0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0x49, 0x92, 0x24, 0x49, 0x92, 0x24, 0xda, 0xd6,
401
0x2f, 0x5b, 0x8a, 0, 0xff, 0x55, 0xff, 0xff, 0x49, 0x92, 0x24, 0x49, 0x92, 0x24, 0xd5,
402
0x84, 0x8e, 0x3a, 0xb7, 0, 0xaa, 0x55, 0xff, 0xff, 0x49, 0x92, 0x24, 0x49, 0x92, 0x24,
403
0xf5, 0x94, 0x6f, 0x32, 0x57, 0xb7, 0x8b, 0, 0xff, 0xff, 0x49, 0x92, 0x24, 0x49, 0x92,
404
0x24, 0x2c, 0x3a, 0x49, 0x19, 0x28, 0xf7, 0xd7, 0xbe, 0xff, 0xff, 0x49, 0x92, 0x24,
405
0x49, 0x92, 0x24, 0x16, 0x95, 0xae, 0x42, 0xfc, 0, 0xaa, 0x55, 0xff, 0xff, 0x49, 0x92,
406
0x24, 0x49, 0x92, 0x24, 0xd8, 0xad, 0xae, 0x42, 0xaf, 0x0a, 0xaa, 0x55,
407
];
408
let r = dds_buffer_to_image(&buffer, CompressedImageFormats::BC, true);
409
assert!(r.is_ok());
410
if let Ok(r) = r {
411
fake_wgpu_create_texture_with_data(&r.texture_descriptor, r.data.as_ref().unwrap());
412
}
413
}
414
}
415
416