use super::image_texture_conversion::image_to_texture; use crate::{ render_asset::RenderAsset, render_resource::{Sampler, Texture, TextureView}, renderer::{RenderDevice, RenderQueue}, }; use bevy_reflect::TypeUuid; use thiserror::Error; use wgpu::{ Extent3d, ImageCopyTexture, ImageDataLayout, Origin3d, TextureDimension, TextureFormat, TextureViewDescriptor, }; pub const TEXTURE_ASSET_INDEX: u64 = 0; pub const SAMPLER_ASSET_INDEX: u64 = 1; #[derive(Debug, Clone, TypeUuid)] #[uuid = "6ea26da6-6cf8-4ea2-9986-1d7bf6c17d6f"] pub struct Image { pub data: Vec, // TODO: this nesting makes accessing Image metadata verbose. Either flatten out descriptor or add accessors pub texture_descriptor: wgpu::TextureDescriptor<'static>, pub sampler_descriptor: wgpu::SamplerDescriptor<'static>, } impl Default for Image { fn default() -> Self { Image { data: Default::default(), texture_descriptor: wgpu::TextureDescriptor { size: wgpu::Extent3d { width: 1, height: 1, depth_or_array_layers: 1, }, format: wgpu::TextureFormat::Rgba8UnormSrgb, dimension: wgpu::TextureDimension::D2, label: None, mip_level_count: 1, sample_count: 1, usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::COPY_DST, }, sampler_descriptor: wgpu::SamplerDescriptor::default(), } } } impl Image { pub fn new( size: Extent3d, dimension: TextureDimension, data: Vec, format: TextureFormat, ) -> Self { debug_assert_eq!( size.volume() * format.pixel_size(), data.len(), "Pixel data, size and format have to match", ); let mut image = Self::default(); image.data = data; image.texture_descriptor.dimension = dimension; image.texture_descriptor.size = size; image.texture_descriptor.format = format; image } pub fn new_fill( size: Extent3d, dimension: TextureDimension, pixel: &[u8], format: TextureFormat, ) -> Self { let mut value = Image::default(); value.texture_descriptor.format = format; value.texture_descriptor.dimension = dimension; 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.texture_descriptor.size.height as f32 / self.texture_descriptor.size.width as f32 } pub fn resize(&mut self, size: Extent3d) { self.texture_descriptor.size = size; self.data.resize( size.volume() * self.texture_descriptor.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.texture_descriptor.size.volume(), "Incompatible sizes: old = {:?} new = {:?}", self.texture_descriptor.size, new_size ); self.texture_descriptor.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.texture_descriptor.dimension == TextureDimension::D2); assert!(self.texture_descriptor.size.depth_or_array_layers == 1); assert_eq!(self.texture_descriptor.size.height % layers, 0); self.reinterpret_size(Extent3d { width: self.texture_descriptor.size.width, height: self.texture_descriptor.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 { 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 { 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), } pub trait Volume { fn volume(&self) -> usize; } impl Volume for Extent3d { fn volume(&self) -> usize { (self.width * self.height * self.depth_or_array_layers) as usize } } pub struct PixelInfo { pub type_size: usize, pub num_components: usize, } pub trait TextureFormatPixelInfo { fn pixel_info(&self) -> PixelInfo; fn pixel_size(&self) -> usize { let info = self.pixel_info(); info.type_size * info.num_components } } impl TextureFormatPixelInfo for TextureFormat { fn pixel_info(&self) -> PixelInfo { let type_size = match self { // 8bit TextureFormat::R8Unorm | TextureFormat::R8Snorm | TextureFormat::R8Uint | TextureFormat::R8Sint | TextureFormat::Rg8Unorm | TextureFormat::Rg8Snorm | TextureFormat::Rg8Uint | TextureFormat::Rg8Sint | TextureFormat::Rgba8Unorm | TextureFormat::Rgba8UnormSrgb | TextureFormat::Rgba8Snorm | TextureFormat::Rgba8Uint | TextureFormat::Rgba8Sint | TextureFormat::Bgra8Unorm | TextureFormat::Bgra8UnormSrgb => 1, // 16bit TextureFormat::R16Uint | TextureFormat::R16Sint | TextureFormat::R16Float | TextureFormat::Rg16Uint | TextureFormat::Rg16Sint | TextureFormat::Rg16Float | TextureFormat::Rgba16Uint | TextureFormat::Rgba16Sint | TextureFormat::Rgba16Float => 2, // 32bit TextureFormat::R32Uint | TextureFormat::R32Sint | TextureFormat::R32Float | TextureFormat::Rg32Uint | TextureFormat::Rg32Sint | TextureFormat::Rg32Float | TextureFormat::Rgba32Uint | TextureFormat::Rgba32Sint | TextureFormat::Rgba32Float | TextureFormat::Depth32Float => 4, // special cases TextureFormat::Rgb10a2Unorm => 4, TextureFormat::Rg11b10Float => 4, TextureFormat::Depth24Plus => 3, // FIXME is this correct? TextureFormat::Depth24PlusStencil8 => 4, // TODO: this is not good! this is a temporary step while porting bevy_render to direct wgpu usage _ => panic!("cannot get pixel info for type"), }; let components = match self { TextureFormat::R8Unorm | TextureFormat::R8Snorm | TextureFormat::R8Uint | TextureFormat::R8Sint | TextureFormat::R16Uint | TextureFormat::R16Sint | TextureFormat::R16Float | TextureFormat::R32Uint | TextureFormat::R32Sint | TextureFormat::R32Float => 1, TextureFormat::Rg8Unorm | TextureFormat::Rg8Snorm | TextureFormat::Rg8Uint | TextureFormat::Rg8Sint | TextureFormat::Rg16Uint | TextureFormat::Rg16Sint | TextureFormat::Rg16Float | TextureFormat::Rg32Uint | TextureFormat::Rg32Sint | TextureFormat::Rg32Float => 2, TextureFormat::Rgba8Unorm | TextureFormat::Rgba8UnormSrgb | TextureFormat::Rgba8Snorm | TextureFormat::Rgba8Uint | TextureFormat::Rgba8Sint | TextureFormat::Bgra8Unorm | TextureFormat::Bgra8UnormSrgb | TextureFormat::Rgba16Uint | TextureFormat::Rgba16Sint | TextureFormat::Rgba16Float | TextureFormat::Rgba32Uint | TextureFormat::Rgba32Sint | TextureFormat::Rgba32Float => 4, // special cases TextureFormat::Rgb10a2Unorm | TextureFormat::Rg11b10Float | TextureFormat::Depth32Float | TextureFormat::Depth24Plus | TextureFormat::Depth24PlusStencil8 => 1, // TODO: this is not good! this is a temporary step while porting bevy_render to direct wgpu usage _ => panic!("cannot get pixel info for type"), }; PixelInfo { type_size, num_components: components, } } } #[derive(Debug, Clone)] pub struct GpuImage { pub texture: Texture, pub texture_view: TextureView, pub sampler: Sampler, } impl RenderAsset for Image { type ExtractedAsset = Image; type PreparedAsset = GpuImage; fn extract_asset(&self) -> Self::ExtractedAsset { self.clone() } fn prepare_asset( image: Self::ExtractedAsset, render_device: &RenderDevice, render_queue: &RenderQueue, ) -> Self::PreparedAsset { let texture = render_device.create_texture(&image.texture_descriptor); let sampler = render_device.create_sampler(&image.sampler_descriptor); let format_size = image.texture_descriptor.format.pixel_size(); render_queue.write_texture( ImageCopyTexture { texture: &texture, mip_level: 0, origin: Origin3d::ZERO, }, &image.data, ImageDataLayout { offset: 0, bytes_per_row: Some( std::num::NonZeroU32::new( image.texture_descriptor.size.width * format_size as u32, ) .unwrap(), ), rows_per_image: None, }, image.texture_descriptor.size, ); let texture_view = texture.create_view(&TextureViewDescriptor::default()); GpuImage { texture, texture_view, sampler, } } }