diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 15fa70b81e8df..04825c3a3d03f 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -23,7 +23,7 @@ use bevy_render::{ render_resource::*, renderer::{RenderDevice, RenderQueue}, texture::{ - BevyDefault, DefaultImageSampler, GpuImage, Image, ImageSampler, TextureFormatPixelInfo, + BevyDefault, DefaultImageSampler, Extent3dDimensions, GpuImage, Image, ImageSampler, }, view::{ComputedVisibility, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms}, Extract, RenderApp, RenderStage, @@ -446,7 +446,6 @@ impl FromWorld for MeshPipeline { ImageSampler::Descriptor(descriptor) => render_device.create_sampler(&descriptor), }; - let format_size = image.texture_descriptor.format.pixel_size(); render_queue.write_texture( ImageCopyTexture { texture: &texture, @@ -457,12 +456,7 @@ impl FromWorld for MeshPipeline { &image.data, ImageDataLayout { offset: 0, - bytes_per_row: Some( - std::num::NonZeroU32::new( - image.texture_descriptor.size.width * format_size as u32, - ) - .unwrap(), - ), + bytes_per_row: Some(image.texture_descriptor.bytes_per_row()), rows_per_image: None, }, image.texture_descriptor.size, diff --git a/crates/bevy_render/src/texture/hdr_texture_loader.rs b/crates/bevy_render/src/texture/hdr_texture_loader.rs index 81c539a061664..537485c83ff35 100644 --- a/crates/bevy_render/src/texture/hdr_texture_loader.rs +++ b/crates/bevy_render/src/texture/hdr_texture_loader.rs @@ -1,4 +1,4 @@ -use crate::texture::{Image, TextureFormatPixelInfo}; +use crate::texture::Image; use anyhow::Result; use bevy_asset::{AssetLoader, LoadContext, LoadedAsset}; use bevy_utils::BoxedFuture; @@ -16,16 +16,12 @@ impl AssetLoader for HdrTextureLoader { ) -> BoxedFuture<'a, Result<()>> { Box::pin(async move { let format = TextureFormat::Rgba32Float; - debug_assert_eq!( - format.pixel_size(), - 4 * 4, - "Format should have 32bit x 4 size" - ); let decoder = image::codecs::hdr::HdrDecoder::new(bytes)?; let info = decoder.metadata(); let rgb_data = decoder.read_image_hdr()?; - let mut rgba_data = Vec::with_capacity(rgb_data.len() * format.pixel_size()); + let mut rgba_data = + Vec::with_capacity(rgb_data.len() * format.describe().block_size as usize); for rgb in rgb_data { let alpha = 1.0f32; diff --git a/crates/bevy_render/src/texture/image.rs b/crates/bevy_render/src/texture/image.rs index 69340449744ed..3bb53868a4e55 100644 --- a/crates/bevy_render/src/texture/image.rs +++ b/crates/bevy_render/src/texture/image.rs @@ -16,11 +16,11 @@ use bevy_derive::{Deref, DerefMut}; use bevy_ecs::system::{lifetimeless::SRes, Resource, SystemParamItem}; use bevy_math::Vec2; use bevy_reflect::{FromReflect, Reflect, TypeUuid}; -use std::hash::Hash; +use std::{hash::Hash, num::NonZeroU32}; use thiserror::Error; use wgpu::{ - Extent3d, ImageCopyTexture, ImageDataLayout, Origin3d, TextureDimension, TextureFormat, - TextureViewDescriptor, + Extent3d, ImageCopyTexture, ImageDataLayout, Origin3d, TextureDescriptor, TextureDimension, + TextureFormat, TextureViewDescriptor, }; pub const TEXTURE_ASSET_INDEX: u64 = 0; @@ -172,7 +172,8 @@ pub struct DefaultImageSampler(pub(crate) Sampler); impl Default for Image { fn default() -> Self { let format = wgpu::TextureFormat::bevy_default(); - let data = vec![255; format.pixel_size()]; + // TODO: check if this breaks if bevy_default is a compressed texture + let data = vec![255; format.describe().block_size as usize]; Image { data, texture_descriptor: wgpu::TextureDescriptor { @@ -207,7 +208,7 @@ impl Image { format: TextureFormat, ) -> Self { debug_assert_eq!( - size.volume() * format.pixel_size(), + total_bytes(&size, &format) as usize, data.len(), "Pixel data, size and format have to match", ); @@ -239,7 +240,7 @@ impl Image { value.resize(size); debug_assert_eq!( - pixel.len() % format.pixel_size(), + pixel.len() % format.describe().block_size as usize, 0, "Must not have incomplete pixel data." ); @@ -271,20 +272,19 @@ impl Image { /// Does not properly resize the contents of the image, but only its internal `data` buffer. pub fn resize(&mut self, size: Extent3d) { self.texture_descriptor.size = size; - self.data.resize( - size.volume() * self.texture_descriptor.format.pixel_size(), - 0, - ); + self.data + .resize(self.texture_descriptor.total_bytes() as usize, 0); } /// Changes the `size`, asserting that the total number of data elements (pixels) remains the /// same. /// /// # Panics - /// Panics if the `new_size` does not have the same volume as to old one. + /// Panics if the `new_size` does not have the same number of bytes as the old one. pub fn reinterpret_size(&mut self, new_size: Extent3d) { assert!( - new_size.volume() == self.texture_descriptor.size.volume(), + total_bytes(&new_size, &self.texture_descriptor.format) + == self.texture_descriptor.total_bytes(), "Incompatible sizes: old = {:?} new = {:?}", self.texture_descriptor.size, new_size @@ -455,148 +455,48 @@ impl<'a> ImageType<'a> { } } -/// Used to calculate the volume of an item. -pub trait Volume { - fn volume(&self) -> usize; +/// Extends Extent3d with some convenience methods to calculate some useful values related to the dimensions of a texture +pub trait Extent3dDimensions { + /// Calculates the bytes per row in a texture + fn bytes_per_row(&self) -> NonZeroU32; + /// Calculates the rows in an image + fn rows_per_image(&self) -> u32; + /// Calculates the total bytes for a texture + fn total_bytes(&self) -> u32; } -impl Volume for Extent3d { - /// Calculates the volume of the [`Extent3d`]. - fn volume(&self) -> usize { - (self.width * self.height * self.depth_or_array_layers) as usize +impl<'a> Extent3dDimensions for TextureDescriptor<'a> { + fn bytes_per_row(&self) -> NonZeroU32 { + bytes_per_row(self.size.physical_size(self.format).width, &self.format) } -} -/// Information about the pixel size in bytes and the number of different components. -pub struct PixelInfo { - /// The size of a component of a pixel in bytes. - pub type_size: usize, - /// The amount of different components (color channels). - pub num_components: usize, -} + fn rows_per_image(&self) -> u32 { + rows_per_image(self.size.physical_size(self.format).height, &self.format) + } -/// Extends the wgpu [`TextureFormat`] with information about the pixel. -pub trait TextureFormatPixelInfo { - /// Returns the pixel information of the format. - fn pixel_info(&self) -> PixelInfo; - /// Returns the size of a pixel of the format. - fn pixel_size(&self) -> usize { - let info = self.pixel_info(); - info.type_size * info.num_components + fn total_bytes(&self) -> u32 { + total_bytes(&self.size, &self.format) } } -impl TextureFormatPixelInfo for TextureFormat { - #[allow(clippy::match_same_arms)] - 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::R16Unorm - | TextureFormat::Rg16Uint - | TextureFormat::Rg16Sint - | TextureFormat::Rg16Unorm - | 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::Rgb9e5Ufloat => 4, - 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"), - }; +fn bytes_per_row(physical_width: u32, format: &TextureFormat) -> NonZeroU32 { + let info = format.describe(); + NonZeroU32::new(physical_width * info.block_size as u32 / info.block_dimensions.0 as u32) + .unwrap() +} - let components = match self { - TextureFormat::R8Unorm - | TextureFormat::R8Snorm - | TextureFormat::R8Uint - | TextureFormat::R8Sint - | TextureFormat::R16Uint - | TextureFormat::R16Sint - | TextureFormat::R16Unorm - | TextureFormat::R16Float - | TextureFormat::R32Uint - | TextureFormat::R32Sint - | TextureFormat::R32Float => 1, - - TextureFormat::Rg8Unorm - | TextureFormat::Rg8Snorm - | TextureFormat::Rg8Uint - | TextureFormat::Rg8Sint - | TextureFormat::Rg16Uint - | TextureFormat::Rg16Sint - | TextureFormat::Rg16Unorm - | 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::Rgb9e5Ufloat - | 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"), - }; +fn rows_per_image(physical_height: u32, format: &TextureFormat) -> u32 { + let info = format.describe(); + physical_height / info.block_dimensions.1 as u32 +} - PixelInfo { - type_size, - num_components: components, - } - } +/// Calculate the total bytes needed to hold an image with `size` and `format`. If you have a [`TextureDescriptor`] +/// consider using [`Extent3dDimensions::total_bytes`] instead. +pub fn total_bytes(size: &Extent3d, format: &TextureFormat) -> u32 { + let physical_size = size.physical_size(*format); + let bytes_per_row = u32::from(bytes_per_row(physical_size.width, format)); + let rows_per_image = rows_per_image(physical_size.height, format); + bytes_per_row * rows_per_image * size.depth_or_array_layers } /// The GPU-representation of an [`Image`]. @@ -637,7 +537,6 @@ impl RenderAsset for Image { ) } else { let texture = render_device.create_texture(&image.texture_descriptor); - let format_size = image.texture_descriptor.format.pixel_size(); render_queue.write_texture( ImageCopyTexture { texture: &texture, @@ -648,14 +547,9 @@ impl RenderAsset for Image { &image.data, ImageDataLayout { offset: 0, - bytes_per_row: Some( - std::num::NonZeroU32::new( - image.texture_descriptor.size.width * format_size as u32, - ) - .unwrap(), - ), + bytes_per_row: Some(image.texture_descriptor.bytes_per_row()), rows_per_image: if image.texture_descriptor.size.depth_or_array_layers > 1 { - std::num::NonZeroU32::new(image.texture_descriptor.size.height) + std::num::NonZeroU32::new(image.texture_descriptor.rows_per_image()) } else { None }, diff --git a/crates/bevy_render/src/texture/image_texture_conversion.rs b/crates/bevy_render/src/texture/image_texture_conversion.rs index 45ae5bbc22582..0ead0afc0b078 100644 --- a/crates/bevy_render/src/texture/image_texture_conversion.rs +++ b/crates/bevy_render/src/texture/image_texture_conversion.rs @@ -1,4 +1,4 @@ -use crate::texture::{Image, TextureFormatPixelInfo}; +use crate::texture::Image; use anyhow::anyhow; use image::{DynamicImage, ImageBuffer}; use wgpu::{Extent3d, TextureDimension, TextureFormat}; @@ -84,8 +84,9 @@ impl Image { height = image.height(); format = TextureFormat::Rgba16Uint; - let mut local_data = - Vec::with_capacity(width as usize * height as usize * format.pixel_size()); + let mut local_data = Vec::with_capacity( + width as usize * height as usize * format.describe().block_size as usize, + ); for pixel in image.into_raw().chunks_exact(3) { // TODO: use the array_chunks method once stabilised @@ -117,8 +118,9 @@ impl Image { height = image.height(); format = TextureFormat::Rgba32Float; - let mut local_data = - Vec::with_capacity(width as usize * height as usize * format.pixel_size()); + let mut local_data = Vec::with_capacity( + width as usize * height as usize * format.describe().block_size as usize, + ); for pixel in image.into_raw().chunks_exact(3) { // TODO: use the array_chunks method once stabilised diff --git a/crates/bevy_sprite/src/dynamic_texture_atlas_builder.rs b/crates/bevy_sprite/src/dynamic_texture_atlas_builder.rs index 1155e0c5e23ce..6a1094528896c 100644 --- a/crates/bevy_sprite/src/dynamic_texture_atlas_builder.rs +++ b/crates/bevy_sprite/src/dynamic_texture_atlas_builder.rs @@ -1,7 +1,7 @@ use crate::TextureAtlas; use bevy_asset::Assets; use bevy_math::{IVec2, Rect, Vec2}; -use bevy_render::texture::{Image, TextureFormatPixelInfo}; +use bevy_render::texture::Image; use guillotiere::{size2, Allocation, AtlasAllocator}; pub struct DynamicTextureAtlasBuilder { @@ -67,12 +67,26 @@ impl DynamicTextureAtlasBuilder { allocation: Allocation, texture: &Image, ) { + debug_assert_eq!( + atlas_texture + .texture_descriptor + .format + .describe() + .block_dimensions, + (1, 1), + "Compressed textures are unsupported" + ); + let mut rect = allocation.rectangle; rect.max.x -= self.padding; rect.max.y -= self.padding; let atlas_width = atlas_texture.texture_descriptor.size.width as usize; let rect_width = rect.width() as usize; - let format_size = atlas_texture.texture_descriptor.format.pixel_size(); + let format_size = atlas_texture + .texture_descriptor + .format + .describe() + .block_size as usize; for (texture_y, bound_y) in (rect.min.y..rect.max.y).map(|i| i as usize).enumerate() { let begin = (bound_y * atlas_width + rect.min.x as usize) * format_size; diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index 0910463f26c9f..77cc64c6a0f56 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -15,7 +15,7 @@ use bevy_render::{ render_resource::*, renderer::{RenderDevice, RenderQueue}, texture::{ - BevyDefault, DefaultImageSampler, GpuImage, Image, ImageSampler, TextureFormatPixelInfo, + BevyDefault, DefaultImageSampler, Extent3dDimensions, GpuImage, Image, ImageSampler, }, view::{ ComputedVisibility, ExtractedView, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms, @@ -218,7 +218,6 @@ impl FromWorld for Mesh2dPipeline { ImageSampler::Descriptor(descriptor) => render_device.create_sampler(&descriptor), }; - let format_size = image.texture_descriptor.format.pixel_size(); let render_queue = world.resource_mut::(); render_queue.write_texture( ImageCopyTexture { @@ -230,12 +229,7 @@ impl FromWorld for Mesh2dPipeline { &image.data, ImageDataLayout { offset: 0, - bytes_per_row: Some( - std::num::NonZeroU32::new( - image.texture_descriptor.size.width * format_size as u32, - ) - .unwrap(), - ), + bytes_per_row: Some(image.texture_descriptor.bytes_per_row()), rows_per_image: None, }, image.texture_descriptor.size, diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index 1cdc6d93782d8..0de24e102d4ff 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -22,7 +22,7 @@ use bevy_render::{ render_resource::*, renderer::{RenderDevice, RenderQueue}, texture::{ - BevyDefault, DefaultImageSampler, GpuImage, Image, ImageSampler, TextureFormatPixelInfo, + BevyDefault, DefaultImageSampler, Extent3dDimensions, GpuImage, Image, ImageSampler, }, view::{ ComputedVisibility, ExtractedView, Msaa, ViewTarget, ViewUniform, ViewUniformOffset, @@ -100,7 +100,6 @@ impl FromWorld for SpritePipeline { ImageSampler::Descriptor(descriptor) => render_device.create_sampler(&descriptor), }; - let format_size = image.texture_descriptor.format.pixel_size(); render_queue.write_texture( ImageCopyTexture { texture: &texture, @@ -111,12 +110,7 @@ impl FromWorld for SpritePipeline { &image.data, ImageDataLayout { offset: 0, - bytes_per_row: Some( - std::num::NonZeroU32::new( - image.texture_descriptor.size.width * format_size as u32, - ) - .unwrap(), - ), + bytes_per_row: Some(image.texture_descriptor.bytes_per_row()), rows_per_image: None, }, image.texture_descriptor.size, diff --git a/crates/bevy_sprite/src/texture_atlas_builder.rs b/crates/bevy_sprite/src/texture_atlas_builder.rs index f77e0cce90c31..f847459fe87ab 100644 --- a/crates/bevy_sprite/src/texture_atlas_builder.rs +++ b/crates/bevy_sprite/src/texture_atlas_builder.rs @@ -3,7 +3,7 @@ use bevy_log::{debug, error, warn}; use bevy_math::{Rect, Vec2}; use bevy_render::{ render_resource::{Extent3d, TextureDimension, TextureFormat}, - texture::{Image, TextureFormatPixelInfo}, + texture::Image, }; use bevy_utils::HashMap; use rectangle_pack::{ @@ -97,12 +97,26 @@ impl TextureAtlasBuilder { texture: &Image, packed_location: &PackedLocation, ) { + debug_assert_eq!( + atlas_texture + .texture_descriptor + .format + .describe() + .block_dimensions, + (1, 1), + "Compressed textures are unsupported" + ); + let rect_width = packed_location.width() as usize; let rect_height = packed_location.height() as usize; let rect_x = packed_location.x() as usize; let rect_y = packed_location.y() as usize; let atlas_width = atlas_texture.texture_descriptor.size.width as usize; - let format_size = atlas_texture.texture_descriptor.format.pixel_size(); + let format_size = atlas_texture + .texture_descriptor + .format + .describe() + .block_size as usize; for (texture_y, bound_y) in (rect_y..rect_y + rect_height).enumerate() { let begin = (bound_y * atlas_width + rect_x) * format_size; @@ -161,6 +175,16 @@ impl TextureAtlasBuilder { let mut rect_placements = None; let mut atlas_texture = Image::default(); + debug_assert_eq!( + atlas_texture + .texture_descriptor + .format + .describe() + .block_dimensions, + (1, 1), + "Compressed textures are unsupported" + ); + while rect_placements.is_none() { if current_width > max_width || current_height > max_height { break; @@ -186,7 +210,8 @@ impl TextureAtlasBuilder { TextureDimension::D2, vec![ 0; - self.format.pixel_size() * (current_width * current_height) as usize + self.format.describe().block_size as usize + * (current_width * current_height) as usize ], self.format, );