rafx_assets/assets/image/
assets.rs

1use rafx_api::{RafxResourceType, RafxResult};
2use rafx_framework::upload::GpuImageDataColorSpace;
3use rafx_framework::{ImageResource, ImageViewResource, ResourceArc};
4use serde::{Deserialize, Serialize};
5use type_uuid::*;
6
7//NOTE: This is serialized in image asset options, so may require asset schema change if modifying it
8#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
9pub enum ImageAssetColorSpaceConfig {
10    Srgb,
11    Linear,
12}
13
14//NOTE: This is serialized in image asset options, so may require asset schema change if modifying it
15#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
16pub enum ImageAssetMipGeneration {
17    NoMips,
18    Precomupted,
19    Runtime,
20}
21
22impl Into<GpuImageDataColorSpace> for ImageAssetColorSpaceConfig {
23    fn into(self) -> GpuImageDataColorSpace {
24        match self {
25            ImageAssetColorSpaceConfig::Srgb => GpuImageDataColorSpace::Srgb,
26            ImageAssetColorSpaceConfig::Linear => GpuImageDataColorSpace::Linear,
27        }
28    }
29}
30
31//NOTE: This is serialized in image asset options, so may require asset schema change if modifying it
32#[derive(Serialize, Deserialize, Clone, Copy, Debug)]
33pub enum ImageAssetBasisCompressionType {
34    Etc1S,
35    Uastc,
36}
37
38#[cfg(feature = "basis-universal")]
39impl Into<basis_universal::BasisTextureFormat> for ImageAssetBasisCompressionType {
40    fn into(self) -> basis_universal::BasisTextureFormat {
41        match self {
42            ImageAssetBasisCompressionType::Etc1S => basis_universal::BasisTextureFormat::ETC1S,
43            ImageAssetBasisCompressionType::Uastc => basis_universal::BasisTextureFormat::UASTC4x4,
44        }
45    }
46}
47
48//NOTE: This is serialized in image asset options, so may require asset schema change if modifying it
49#[derive(Serialize, Deserialize, Clone, Copy, Debug)]
50pub struct ImageAssetBasisCompressionSettings {
51    pub compression_type: ImageAssetBasisCompressionType,
52    pub quality: u32,
53}
54
55#[cfg(feature = "basis-universal")]
56impl ImageAssetBasisCompressionSettings {
57    pub fn default_uastc() -> Self {
58        ImageAssetBasisCompressionSettings {
59            compression_type: ImageAssetBasisCompressionType::Uastc,
60            quality: basis_universal::UASTC_QUALITY_DEFAULT,
61        }
62    }
63
64    pub fn default_etc1s() -> Self {
65        ImageAssetBasisCompressionSettings {
66            compression_type: ImageAssetBasisCompressionType::Etc1S,
67            quality: basis_universal::ETC1S_QUALITY_DEFAULT,
68        }
69    }
70}
71
72#[derive(Serialize, Deserialize, Clone, Copy, Debug)]
73#[allow(non_camel_case_types)]
74pub enum ImageAssetDataFormat {
75    RGBA32_Linear,
76    RGBA32_Srgb,
77    Basis_Linear,
78    Basis_Srgb,
79    BC1_UNorm_Linear,
80    BC1_UNorm_Srgb,
81    BC2_UNorm_Linear,
82    BC2_UNorm_Srgb,
83    BC3_UNorm_Linear,
84    BC3_UNorm_Srgb,
85    BC4_UNorm,
86    BC4_SNorm,
87    BC5_UNorm,
88    BC5_SNorm,
89    BC6H_UFloat,
90    BC6H_SFloat,
91    BC7_Unorm_Linear,
92    BC7_Unorm_Srgb,
93}
94
95//NOTE: This is serialized in image asset options, so may require asset schema change if modifying it
96#[derive(Serialize, Deserialize, Clone, Copy, Debug)]
97pub enum ImageAssetDataFormatConfig {
98    Uncompressed,
99    BasisCompressed(ImageAssetBasisCompressionSettings),
100}
101
102#[derive(Serialize, Deserialize, Clone)]
103pub struct ImageAssetDataMipLevel {
104    pub width: u32,
105    pub height: u32,
106    #[serde(with = "serde_bytes")]
107    pub bytes: Vec<u8>,
108}
109
110#[derive(Serialize, Deserialize, Clone)]
111pub struct ImageAssetDataLayer {
112    pub mip_levels: Vec<ImageAssetDataMipLevel>,
113}
114
115#[derive(Serialize, Deserialize, Clone)]
116pub struct ImageAssetDataPayloadSubresources {
117    pub layers: Vec<ImageAssetDataLayer>,
118}
119
120#[derive(Serialize, Deserialize, Clone)]
121pub struct ImageAssetDataPayloadSingleBuffer {
122    #[serde(with = "serde_bytes")]
123    pub buffer: Vec<u8>,
124}
125
126#[derive(Serialize, Deserialize, Clone)]
127pub enum ImageAssetDataPayload {
128    Subresources(ImageAssetDataPayloadSubresources),
129    // Special case intended for things like basis where we don't unpack subresources until runtime
130    SingleBuffer(ImageAssetDataPayloadSingleBuffer),
131}
132
133#[derive(TypeUuid, Serialize, Deserialize, Clone)]
134#[uuid = "e6166902-8716-401b-9d2e-8b01701c5626"]
135pub struct ImageAssetData {
136    pub width: u32,
137    pub height: u32,
138    pub format: ImageAssetDataFormat,
139    pub resource_type: RafxResourceType,
140    pub generate_mips_at_runtime: bool,
141    pub data: ImageAssetDataPayload,
142}
143
144impl std::fmt::Debug for ImageAssetData {
145    fn fmt(
146        &self,
147        f: &mut std::fmt::Formatter<'_>,
148    ) -> std::fmt::Result {
149        f.debug_struct("Point")
150            .field("width", &self.width)
151            .field("width", &self.height)
152            .field("format", &self.format)
153            .finish()
154    }
155}
156
157impl ImageAssetData {
158    // Temporary - off by default because encoding textures is very slow
159    pub fn default_format_and_mip_generation(
160    ) -> (ImageAssetDataFormatConfig, ImageAssetMipGeneration) {
161        let compress_textures = false;
162        if compress_textures {
163            #[cfg(feature = "basis-universal")]
164            {
165                let basis_settings = ImageAssetBasisCompressionSettings::default_uastc();
166                let format_config = ImageAssetDataFormatConfig::BasisCompressed(basis_settings);
167                let mipmap_generation = ImageAssetMipGeneration::Precomupted;
168                (format_config, mipmap_generation)
169            }
170
171            #[cfg(not(feature = "basis-universal"))]
172            {
173                unimplemented!("Not built with basis-universal feature")
174            }
175        } else {
176            let format_config = ImageAssetDataFormatConfig::Uncompressed;
177            let mipmap_generation = ImageAssetMipGeneration::Runtime;
178            (format_config, mipmap_generation)
179        }
180    }
181
182    pub fn from_raw_rgba32(
183        width: u32,
184        height: u32,
185        color_space: ImageAssetColorSpaceConfig,
186        format_config: ImageAssetDataFormatConfig,
187        mip_generation: ImageAssetMipGeneration,
188        resource_type: RafxResourceType,
189        raw_rgba32: &[u8],
190    ) -> RafxResult<ImageAssetData> {
191        match format_config {
192            ImageAssetDataFormatConfig::Uncompressed => {
193                let generate_mips_at_runtime = match mip_generation {
194                    ImageAssetMipGeneration::NoMips => false,
195                    ImageAssetMipGeneration::Precomupted => Err(
196                        "Uncompressed ImageAssetDataFormatConfig cannot store precomputed mipmaps",
197                    )?,
198                    ImageAssetMipGeneration::Runtime => true,
199                };
200
201                let mip = ImageAssetDataMipLevel {
202                    width,
203                    height,
204                    bytes: raw_rgba32.to_vec(),
205                };
206
207                let layer = ImageAssetDataLayer {
208                    mip_levels: vec![mip],
209                };
210
211                let format = match color_space {
212                    ImageAssetColorSpaceConfig::Linear => ImageAssetDataFormat::RGBA32_Linear,
213                    ImageAssetColorSpaceConfig::Srgb => ImageAssetDataFormat::RGBA32_Srgb,
214                };
215
216                Ok(ImageAssetData {
217                    width,
218                    height,
219                    format,
220                    generate_mips_at_runtime,
221                    resource_type,
222                    data: ImageAssetDataPayload::Subresources(ImageAssetDataPayloadSubresources {
223                        layers: vec![layer],
224                    }),
225                })
226            }
227            #[cfg(not(feature = "basis-universal"))]
228            ImageAssetDataFormatConfig::BasisCompressed(_) => {
229                unimplemented!("crate not built with basis-universal feature");
230            }
231            #[cfg(feature = "basis-universal")]
232            ImageAssetDataFormatConfig::BasisCompressed(settings) => {
233                let generate_mips_at_runtime = match mip_generation {
234                    ImageAssetMipGeneration::NoMips => false,
235                    ImageAssetMipGeneration::Precomupted => false,
236                    ImageAssetMipGeneration::Runtime => true,
237                };
238
239                let basis_color_space = match color_space {
240                    ImageAssetColorSpaceConfig::Srgb => basis_universal::ColorSpace::Srgb,
241                    ImageAssetColorSpaceConfig::Linear => basis_universal::ColorSpace::Linear,
242                };
243
244                let mut compressor_params = basis_universal::CompressorParams::new();
245                compressor_params.set_basis_format(settings.compression_type.into());
246                compressor_params
247                    .set_generate_mipmaps(mip_generation == ImageAssetMipGeneration::Precomupted);
248                compressor_params.set_color_space(basis_color_space);
249
250                match settings.compression_type {
251                    ImageAssetBasisCompressionType::Etc1S => {
252                        let quality_level = settings.quality.clamp(
253                            basis_universal::ETC1S_QUALITY_MIN,
254                            basis_universal::ETC1S_QUALITY_MAX,
255                        );
256                        compressor_params.set_etc1s_quality_level(quality_level)
257                    }
258                    ImageAssetBasisCompressionType::Uastc => {
259                        let quality_level = settings.quality.clamp(
260                            basis_universal::ETC1S_QUALITY_MIN,
261                            basis_universal::ETC1S_QUALITY_MAX,
262                        );
263                        compressor_params.set_uastc_quality_level(quality_level)
264                    }
265                }
266
267                let mut source_image = compressor_params.source_image_mut(0);
268                source_image.init(raw_rgba32, width, height, 4);
269
270                let mut compressor = basis_universal::Compressor::new(4);
271                unsafe {
272                    compressor.init(&compressor_params);
273                    log::debug!("Compressing texture");
274                    compressor.process().unwrap();
275                    log::debug!("Compressed texture");
276                }
277                let compressed_basis_data = compressor.basis_file();
278
279                let format = match color_space {
280                    ImageAssetColorSpaceConfig::Linear => ImageAssetDataFormat::Basis_Linear,
281                    ImageAssetColorSpaceConfig::Srgb => ImageAssetDataFormat::Basis_Srgb,
282                };
283
284                Ok(ImageAssetData {
285                    width,
286                    height,
287                    format,
288                    generate_mips_at_runtime,
289                    resource_type,
290                    data: ImageAssetDataPayload::SingleBuffer(ImageAssetDataPayloadSingleBuffer {
291                        buffer: compressed_basis_data.to_vec(),
292                    }),
293                })
294            }
295        }
296    }
297}
298
299#[derive(TypeUuid, Clone)]
300#[uuid = "7a67b850-17f9-4877-8a6e-293a1589bbd8"]
301pub struct ImageAsset {
302    pub image: ResourceArc<ImageResource>,
303    pub image_view: ResourceArc<ImageViewResource>,
304}