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

395 lines
13 KiB
Rust
Raw Normal View History

2021-06-21 23:28:52 +00:00
use super::image_texture_conversion::image_to_texture;
2021-06-25 03:04:28 +00:00
use crate::{
render_asset::RenderAsset,
render_resource::{Sampler, Texture, TextureView},
renderer::{RenderDevice, RenderQueue},
};
2021-04-11 20:13:07 +00:00
use bevy_reflect::TypeUuid;
use thiserror::Error;
2021-06-25 03:04:28 +00:00
use wgpu::{
Extent3d, ImageCopyTexture, ImageDataLayout, Origin3d, TextureDimension, TextureFormat,
TextureViewDescriptor,
};
2021-04-11 20:13:07 +00:00
pub const TEXTURE_ASSET_INDEX: u64 = 0;
pub const SAMPLER_ASSET_INDEX: u64 = 1;
#[derive(Debug, Clone, TypeUuid)]
#[uuid = "6ea26da6-6cf8-4ea2-9986-1d7bf6c17d6f"]
2021-06-21 23:28:52 +00:00
pub struct Image {
2021-04-11 20:13:07 +00:00
pub data: Vec<u8>,
2021-06-21 23:28:52 +00:00
// 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>,
2021-04-11 20:13:07 +00:00
}
2021-06-21 23:28:52 +00:00
impl Default for Image {
2021-04-11 20:13:07 +00:00
fn default() -> Self {
2021-06-21 23:28:52 +00:00
Image {
2021-04-11 20:13:07 +00:00
data: Default::default(),
2021-06-21 23:28:52 +00:00
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,
2021-04-11 20:13:07 +00:00
},
2021-06-21 23:28:52 +00:00
sampler_descriptor: wgpu::SamplerDescriptor::default(),
2021-04-11 20:13:07 +00:00
}
}
}
2021-06-21 23:28:52 +00:00
impl Image {
2021-04-11 20:13:07 +00:00
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",
);
2021-07-02 01:05:20 +00:00
let mut image = Self {
data,
..Default::default()
};
2021-06-21 23:28:52 +00:00
image.texture_descriptor.dimension = dimension;
image.texture_descriptor.size = size;
image.texture_descriptor.format = format;
image
2021-04-11 20:13:07 +00:00
}
pub fn new_fill(
size: Extent3d,
dimension: TextureDimension,
pixel: &[u8],
format: TextureFormat,
) -> Self {
2021-06-21 23:28:52 +00:00
let mut value = Image::default();
value.texture_descriptor.format = format;
value.texture_descriptor.dimension = dimension;
2021-04-11 20:13:07 +00:00
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);
2021-04-11 20:13:07 +00:00
}
value
}
pub fn aspect_2d(&self) -> f32 {
2021-06-21 23:28:52 +00:00
self.texture_descriptor.size.height as f32 / self.texture_descriptor.size.width as f32
2021-04-11 20:13:07 +00:00
}
pub fn resize(&mut self, size: Extent3d) {
2021-06-21 23:28:52 +00:00
self.texture_descriptor.size = size;
self.data.resize(
size.volume() * self.texture_descriptor.format.pixel_size(),
0,
);
2021-04-11 20:13:07 +00:00
}
/// 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!(
2021-06-21 23:28:52 +00:00
new_size.volume() == self.texture_descriptor.size.volume(),
2021-04-11 20:13:07 +00:00
"Incompatible sizes: old = {:?} new = {:?}",
2021-06-21 23:28:52 +00:00
self.texture_descriptor.size,
2021-04-11 20:13:07 +00:00
new_size
);
2021-06-21 23:28:52 +00:00
self.texture_descriptor.size = new_size;
2021-04-11 20:13:07 +00:00
}
/// 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.
2021-06-21 23:28:52 +00:00
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);
2021-04-11 20:13:07 +00:00
self.reinterpret_size(Extent3d {
2021-06-21 23:28:52 +00:00
width: self.texture_descriptor.size.width,
height: self.texture_descriptor.size.height / layers,
2021-04-11 20:13:07 +00:00
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`
2021-06-21 23:28:52 +00:00
pub fn from_buffer(buffer: &[u8], image_type: ImageType) -> Result<Image, TextureError> {
2021-04-11 20:13:07 +00:00
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),
}
2021-06-21 23:28:52 +00:00
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,
}
}
}
2021-06-25 03:04:28 +00:00
#[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: if image.texture_descriptor.size.depth_or_array_layers > 1 {
std::num::NonZeroU32::new(image.texture_descriptor.size.height)
} else {
None
},
2021-06-25 03:04:28 +00:00
},
image.texture_descriptor.size,
);
let texture_view = texture.create_view(&TextureViewDescriptor::default());
GpuImage {
texture,
texture_view,
sampler,
}
}
}