bevy/pipelined/bevy_render2/src/texture/texture.rs

208 lines
6.9 KiB
Rust
Raw Normal View History

2021-04-11 20:13:07 +00:00
use super::{
image_texture_conversion::image_to_texture, Extent3d, SamplerDescriptor, TextureDimension,
TextureFormat,
};
2021-06-02 02:59:17 +00:00
use crate::render_resource::{SamplerId, TextureId, TextureViewId};
2021-04-11 20:13:07 +00:00
use bevy_reflect::TypeUuid;
use thiserror::Error;
pub const TEXTURE_ASSET_INDEX: u64 = 0;
pub const SAMPLER_ASSET_INDEX: u64 = 1;
// TODO: this shouldn't live in the Texture type
#[derive(Debug, Clone)]
2021-06-02 02:59:17 +00:00
pub struct TextureGpuData {
pub texture: TextureId,
pub texture_view: TextureViewId,
pub sampler: SamplerId,
2021-04-11 20:13:07 +00:00
}
#[derive(Debug, Clone, TypeUuid)]
#[uuid = "6ea26da6-6cf8-4ea2-9986-1d7bf6c17d6f"]
pub struct Texture {
pub data: Vec<u8>,
2021-06-02 02:59:17 +00:00
pub gpu_data: Option<TextureGpuData>,
2021-04-11 20:13:07 +00:00
pub size: Extent3d,
pub format: TextureFormat,
pub dimension: TextureDimension,
pub sampler: SamplerDescriptor,
}
impl Default for Texture {
fn default() -> Self {
Texture {
data: Default::default(),
gpu_data: None,
size: Extent3d {
width: 1,
height: 1,
depth_or_array_layers: 1,
},
format: TextureFormat::Rgba8UnormSrgb,
dimension: TextureDimension::D2,
sampler: Default::default(),
}
}
}
impl Texture {
pub fn new(
size: Extent3d,
dimension: TextureDimension,
data: Vec<u8>,
format: TextureFormat,
) -> Self {
debug_assert_eq!(
size.volume() * format.pixel_size(),
data.len(),
"Pixel data, size and format have to match",
);
Self {
data,
size,
format,
dimension,
..Default::default()
}
}
pub fn new_fill(
size: Extent3d,
dimension: TextureDimension,
pixel: &[u8],
format: TextureFormat,
) -> Self {
let mut value = Texture {
format,
dimension,
..Default::default()
};
value.resize(size);
debug_assert_eq!(
pixel.len() % format.pixel_size(),
0,
"Must not have incomplete pixel data."
);
debug_assert!(
pixel.len() <= value.data.len(),
"Fill data must fit within pixel buffer."
);
for current_pixel in value.data.chunks_exact_mut(pixel.len()) {
current_pixel.copy_from_slice(&pixel);
}
value
}
pub fn aspect_2d(&self) -> f32 {
self.size.height as f32 / self.size.width as f32
}
pub fn resize(&mut self, size: Extent3d) {
self.size = size;
self.data
.resize(size.volume() * self.format.pixel_size(), 0);
}
/// Changes the `size`, asserting that the total number of data elements (pixels) remains the
/// same.
pub fn reinterpret_size(&mut self, new_size: Extent3d) {
assert!(
new_size.volume() == self.size.volume(),
"Incompatible sizes: old = {:?} new = {:?}",
self.size,
new_size
);
self.size = new_size;
}
/// Takes a 2D texture containing vertically stacked images of the same size, and reinterprets
/// it as a 2D array texture, where each of the stacked images becomes one layer of the
/// array. This is primarily for use with the `texture2DArray` shader uniform type.
pub fn reinterpret_stacked_2d_as_array(&mut self, layers: u32) {
// Must be a stacked image, and the height must be divisible by layers.
assert!(self.dimension == TextureDimension::D2);
assert!(self.size.depth_or_array_layers == 1);
assert_eq!(self.size.height % layers, 0);
self.reinterpret_size(Extent3d {
width: self.size.width,
height: self.size.height / layers,
depth_or_array_layers: layers,
});
}
/// Convert a texture from a format to another
/// Only a few formats are supported as input and output:
/// - `TextureFormat::R8Unorm`
/// - `TextureFormat::Rg8Unorm`
/// - `TextureFormat::Rgba8UnormSrgb`
/// - `TextureFormat::Bgra8UnormSrgb`
pub fn convert(&self, new_format: TextureFormat) -> Option<Self> {
super::image_texture_conversion::texture_to_image(self)
.and_then(|img| match new_format {
TextureFormat::R8Unorm => Some(image::DynamicImage::ImageLuma8(img.into_luma8())),
TextureFormat::Rg8Unorm => {
Some(image::DynamicImage::ImageLumaA8(img.into_luma_alpha8()))
}
TextureFormat::Rgba8UnormSrgb => {
Some(image::DynamicImage::ImageRgba8(img.into_rgba8()))
}
TextureFormat::Bgra8UnormSrgb => {
Some(image::DynamicImage::ImageBgra8(img.into_bgra8()))
}
_ => None,
})
.map(super::image_texture_conversion::image_to_texture)
}
/// Load a bytes buffer in a [`Texture`], according to type `image_type`, using the `image`
/// crate`
pub fn from_buffer(buffer: &[u8], image_type: ImageType) -> Result<Texture, TextureError> {
let format = match image_type {
ImageType::MimeType(mime_type) => match mime_type {
"image/png" => Ok(image::ImageFormat::Png),
"image/vnd-ms.dds" => Ok(image::ImageFormat::Dds),
"image/x-targa" => Ok(image::ImageFormat::Tga),
"image/x-tga" => Ok(image::ImageFormat::Tga),
"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.
// Some formats like PNG allow for R or RG textures too, so the texture
// format needs to be determined. For RGB textures an alpha channel
// needs to be added, so the image data needs to be converted in those
// cases.
let dyn_img = image::load_from_memory_with_format(buffer, format)?;
Ok(image_to_texture(dyn_img))
}
}
/// An error that occurs when loading a texture
#[derive(Error, Debug)]
pub enum TextureError {
#[error("invalid image mime type")]
InvalidImageMimeType(String),
#[error("invalid image extension")]
InvalidImageExtension(String),
#[error("failed to load an image: {0}")]
ImageError(#[from] image::ImageError),
}
/// Type of a raw image buffer
pub enum ImageType<'a> {
/// Mime type of an image, for example `"image/png"`
MimeType(&'a str),
/// Extension of an image file, for example `"png"`
Extension(&'a str),
}