bevy/crates/bevy_image/src/compressed_image_saver.rs
Lege19 3978ba9783
Allowed creating uninitialized images (for use as storage textures) (#17760)
# Objective
https://github.com/bevyengine/bevy/issues/17746
## Solution
- Change `Image.data` from being a `Vec<u8>` to a `Option<Vec<u8>>`
- Added functions to help with creating images
## Testing

- Did you test these changes? If so, how?
All current tests pass
Tested a variety of existing examples to make sure they don't crash
(they don't)
- If relevant, what platforms did you test these changes on, and are
there any important ones you can't test?
Linux x86 64-bit NixOS 
---
## Migration Guide
Code that directly access `Image` data will now need to use unwrap or
handle the case where no data is provided.
Behaviour of new_fill slightly changed, but not in a way that is likely
to affect anything. It no longer panics and will fill the whole texture
instead of leaving black pixels if the data provided is not a nice
factor of the size of the image.

---------

Co-authored-by: IceSentry <IceSentry@users.noreply.github.com>
2025-02-10 22:22:07 +00:00

75 lines
2.7 KiB
Rust

use crate::{Image, ImageFormat, ImageFormatSetting, ImageLoader, ImageLoaderSettings};
use bevy_asset::saver::{AssetSaver, SavedAsset};
use futures_lite::AsyncWriteExt;
use thiserror::Error;
pub struct CompressedImageSaver;
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum CompressedImageSaverError {
#[error(transparent)]
Io(#[from] std::io::Error),
#[error("Cannot compress an uninitialized image")]
UninitializedImage,
}
impl AssetSaver for CompressedImageSaver {
type Asset = Image;
type Settings = ();
type OutputLoader = ImageLoader;
type Error = CompressedImageSaverError;
async fn save(
&self,
writer: &mut bevy_asset::io::Writer,
image: SavedAsset<'_, Self::Asset>,
_settings: &Self::Settings,
) -> Result<ImageLoaderSettings, Self::Error> {
let is_srgb = image.texture_descriptor.format.is_srgb();
let compressed_basis_data = {
let mut compressor_params = basis_universal::CompressorParams::new();
compressor_params.set_basis_format(basis_universal::BasisTextureFormat::UASTC4x4);
compressor_params.set_generate_mipmaps(true);
let color_space = if is_srgb {
basis_universal::ColorSpace::Srgb
} else {
basis_universal::ColorSpace::Linear
};
compressor_params.set_color_space(color_space);
compressor_params.set_uastc_quality_level(basis_universal::UASTC_QUALITY_DEFAULT);
let mut source_image = compressor_params.source_image_mut(0);
let size = image.size();
let Some(ref data) = image.data else {
return Err(CompressedImageSaverError::UninitializedImage);
};
source_image.init(data, size.x, size.y, 4);
let mut compressor = basis_universal::Compressor::new(4);
#[expect(
unsafe_code,
reason = "The basis-universal compressor cannot be interacted with except through unsafe functions"
)]
// SAFETY: the CompressorParams are "valid" to the best of our knowledge. The basis-universal
// library bindings note that invalid params might produce undefined behavior.
unsafe {
compressor.init(&compressor_params);
compressor.process().unwrap();
}
compressor.basis_file().to_vec()
};
writer.write_all(&compressed_basis_data).await?;
Ok(ImageLoaderSettings {
format: ImageFormatSetting::Format(ImageFormat::Basis),
is_srgb,
sampler: image.sampler.clone(),
asset_usage: image.asset_usage,
})
}
}