diff --git a/Cargo.toml b/Cargo.toml index 0e43e7daba87a..26ff837bfc3e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -266,6 +266,10 @@ path = "examples/shader/mesh_custom_attribute.rs" name = "shader_custom_material" path = "examples/shader/shader_custom_material.rs" +[[example]] +name = "array_texture" +path = "examples/shader/array_texture.rs" + [[example]] name = "shader_defs" path = "examples/shader/shader_defs.rs" diff --git a/assets/textures/array_texture.png b/assets/textures/array_texture.png new file mode 100644 index 0000000000000..ab2c144e11247 Binary files /dev/null and b/assets/textures/array_texture.png differ diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index a6b7e1384951f..b637ddea37576 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -11,7 +11,9 @@ use bevy_render::{ pipeline::PrimitiveTopology, prelude::{Color, Texture}, render_graph::base, - texture::{AddressMode, FilterMode, SamplerDescriptor, TextureFormat}, + texture::{ + AddressMode, Extent3d, FilterMode, SamplerDescriptor, TextureDimension, TextureFormat, + }, }; use bevy_scene::Scene; use bevy_transform::{ @@ -136,7 +138,8 @@ async fn load_gltf<'a, 'b>( &texture_label, LoadedAsset::new(Texture { data: image.clone().into_vec(), - size: bevy_math::f32::vec2(size.0 as f32, size.1 as f32), + size: Extent3d::new(size.0, size.1, 1), + dimension: TextureDimension::D2, format: TextureFormat::Rgba8Unorm, sampler: texture_sampler(&texture)?, }), diff --git a/crates/bevy_render/src/render_graph/nodes/texture_copy_node.rs b/crates/bevy_render/src/render_graph/nodes/texture_copy_node.rs index d68a6729ec217..f2f0d7500429c 100644 --- a/crates/bevy_render/src/render_graph/nodes/texture_copy_node.rs +++ b/crates/bevy_render/src/render_graph/nodes/texture_copy_node.rs @@ -34,13 +34,17 @@ impl Node for TextureCopyNode { } let texture_descriptor: TextureDescriptor = texture.into(); - let width = texture.size.x as usize; - let aligned_width = render_context - .resources() - .get_aligned_texture_size(texture.size.x as usize); + let width = texture.size.width as usize; + let aligned_width = + render_context.resources().get_aligned_texture_size(width); let format_size = texture.format.pixel_size(); - let mut aligned_data = - vec![0; format_size * aligned_width * texture.size.y as usize]; + let mut aligned_data = vec![ + 0; + format_size + * aligned_width + * texture.size.height as usize + * texture.size.depth as usize + ]; texture .data .chunks_exact(format_size * width) diff --git a/crates/bevy_render/src/texture/hdr_texture_loader.rs b/crates/bevy_render/src/texture/hdr_texture_loader.rs index f3929892e64cd..da03340ef67ea 100644 --- a/crates/bevy_render/src/texture/hdr_texture_loader.rs +++ b/crates/bevy_render/src/texture/hdr_texture_loader.rs @@ -1,7 +1,6 @@ -use super::{Texture, TextureFormat}; +use super::{Extent3d, Texture, TextureDimension, TextureFormat}; use anyhow::Result; use bevy_asset::{AssetLoader, LoadContext, LoadedAsset}; -use bevy_math::Vec2; use bevy_utils::BoxedFuture; /// Loads HDR textures as Texture assets @@ -37,7 +36,8 @@ impl AssetLoader for HdrTextureLoader { } let texture = Texture::new( - Vec2::new(info.width as f32, info.height as f32), + Extent3d::new(info.width, info.height, 1), + TextureDimension::D2, rgba_data, format, ); diff --git a/crates/bevy_render/src/texture/image_texture_loader.rs b/crates/bevy_render/src/texture/image_texture_loader.rs index 2def1cf6389f0..ef0ed28eb71ec 100644 --- a/crates/bevy_render/src/texture/image_texture_loader.rs +++ b/crates/bevy_render/src/texture/image_texture_loader.rs @@ -1,7 +1,6 @@ -use super::{Texture, TextureFormat}; +use super::{Extent3d, Texture, TextureDimension, TextureFormat}; use anyhow::Result; use bevy_asset::{AssetLoader, LoadContext, LoadedAsset}; -use bevy_math::Vec2; use bevy_utils::BoxedFuture; /// Loader for images that can be read by the `image` crate. @@ -148,7 +147,12 @@ impl AssetLoader for ImageTextureLoader { } } - let texture = Texture::new(Vec2::new(width as f32, height as f32), data, format); + let texture = Texture::new( + Extent3d::new(width, height, 1), + TextureDimension::D2, + data, + format, + ); load_context.set_default_asset(LoadedAsset::new(texture)); Ok(()) }) diff --git a/crates/bevy_render/src/texture/texture.rs b/crates/bevy_render/src/texture/texture.rs index 4a39f365b4c2f..ed7eda398fdea 100644 --- a/crates/bevy_render/src/texture/texture.rs +++ b/crates/bevy_render/src/texture/texture.rs @@ -1,11 +1,10 @@ -use super::{SamplerDescriptor, TextureDescriptor, TextureFormat}; +use super::{Extent3d, SamplerDescriptor, TextureDescriptor, TextureDimension, TextureFormat}; use crate::renderer::{ RenderResource, RenderResourceContext, RenderResourceId, RenderResourceType, }; use bevy_app::prelude::{EventReader, Events}; use bevy_asset::{AssetEvent, Assets, Handle}; use bevy_ecs::{Res, ResMut}; -use bevy_math::Vec2; use bevy_type_registry::TypeUuid; use bevy_utils::HashSet; @@ -16,8 +15,9 @@ pub const SAMPLER_ASSET_INDEX: u64 = 1; #[uuid = "6ea26da6-6cf8-4ea2-9986-1d7bf6c17d6f"] pub struct Texture { pub data: Vec, - pub size: Vec2, + pub size: Extent3d, pub format: TextureFormat, + pub dimension: TextureDimension, pub sampler: SamplerDescriptor, } @@ -25,31 +25,48 @@ impl Default for Texture { fn default() -> Self { Texture { data: Default::default(), - size: Default::default(), + size: Extent3d { + width: 1, + height: 1, + depth: 1, + }, format: TextureFormat::Rgba8UnormSrgb, + dimension: TextureDimension::D2, sampler: Default::default(), } } } impl Texture { - pub fn new(size: Vec2, data: Vec, format: TextureFormat) -> Self { + pub fn new( + size: Extent3d, + dimension: TextureDimension, + data: Vec, + format: TextureFormat, + ) -> Self { debug_assert_eq!( - size.x as usize * size.y as usize * format.pixel_size(), + size.volume() * format.pixel_size(), data.len(), "Pixel data, size and format have to match", ); Self { data, size, + dimension, format, ..Default::default() } } - pub fn new_fill(size: Vec2, pixel: &[u8], format: TextureFormat) -> Self { + pub fn new_fill( + size: Extent3d, + dimension: TextureDimension, + pixel: &[u8], + format: TextureFormat, + ) -> Self { let mut value = Texture { format, + dimension, ..Default::default() }; value.resize(size); @@ -70,16 +87,42 @@ impl Texture { value } - pub fn aspect(&self) -> f32 { - self.size.y / self.size.x + pub fn aspect_2d(&self) -> f32 { + self.size.height as f32 / self.size.width as f32 } - pub fn resize(&mut self, size: Vec2) { + pub fn resize(&mut self, size: Extent3d) { self.size = size; - let width = size.x as usize; - let height = size.y as usize; self.data - .resize(width * height * self.format.pixel_size(), 0); + .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 == 1); + assert_eq!(self.size.height % layers, 0); + + self.reinterpret_size(Extent3d { + width: self.size.width, + height: self.size.height / layers, + depth: layers, + }); } pub fn texture_resource_system( diff --git a/crates/bevy_render/src/texture/texture_descriptor.rs b/crates/bevy_render/src/texture/texture_descriptor.rs index 82515d4f89176..15ab341ccda56 100644 --- a/crates/bevy_render/src/texture/texture_descriptor.rs +++ b/crates/bevy_render/src/texture/texture_descriptor.rs @@ -14,14 +14,10 @@ pub struct TextureDescriptor { impl From<&Texture> for TextureDescriptor { fn from(texture: &Texture) -> Self { TextureDescriptor { - size: Extent3d { - width: texture.size.x as u32, - height: texture.size.y as u32, - depth: 1, - }, + size: texture.size, mip_level_count: 1, sample_count: 1, - dimension: TextureDimension::D2, + dimension: texture.dimension, format: texture.format, usage: TextureUsage::SAMPLED | TextureUsage::COPY_DST, } diff --git a/crates/bevy_render/src/texture/texture_dimension.rs b/crates/bevy_render/src/texture/texture_dimension.rs index 77e8b42262b42..d230be2303c7e 100644 --- a/crates/bevy_render/src/texture/texture_dimension.rs +++ b/crates/bevy_render/src/texture/texture_dimension.rs @@ -1,5 +1,7 @@ // NOTE: These are currently just copies of the wgpu types, but they might change in the future +use bevy_math::Vec3; + /// Dimensions of a particular texture view. #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] pub enum TextureViewDimension { @@ -27,6 +29,24 @@ pub struct Extent3d { pub depth: u32, } +impl Extent3d { + pub fn new(width: u32, height: u32, depth: u32) -> Self { + Self { + width, + height, + depth, + } + } + + pub fn volume(&self) -> usize { + (self.width * self.height * self.depth) as usize + } + + pub fn as_vec3(&self) -> Vec3 { + Vec3::new(self.width as f32, self.height as f32, self.depth as f32) + } +} + /// Type of data shaders will read from a texture. #[derive(Copy, Hash, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] pub enum TextureComponentType { diff --git a/crates/bevy_sprite/src/dynamic_texture_atlas_builder.rs b/crates/bevy_sprite/src/dynamic_texture_atlas_builder.rs index be5cdd2c27c95..8fc4827523c77 100644 --- a/crates/bevy_sprite/src/dynamic_texture_atlas_builder.rs +++ b/crates/bevy_sprite/src/dynamic_texture_atlas_builder.rs @@ -24,8 +24,8 @@ impl DynamicTextureAtlasBuilder { texture: &Texture, ) -> Option { let allocation = self.atlas_allocator.allocate(size2( - texture.size.x as i32 + self.padding, - texture.size.y as i32 + self.padding, + texture.size.width as i32 + self.padding, + texture.size.height as i32 + self.padding, )); if let Some(allocation) = allocation { let atlas_texture = textures.get_mut(&texture_atlas.texture).unwrap(); @@ -72,7 +72,7 @@ impl DynamicTextureAtlasBuilder { let mut rect = allocation.rectangle; rect.max.x -= self.padding; rect.max.y -= self.padding; - let atlas_width = atlas_texture.size.x as usize; + let atlas_width = atlas_texture.size.width as usize; let rect_width = rect.width() as usize; let format_size = atlas_texture.format.pixel_size(); diff --git a/crates/bevy_sprite/src/sprite.rs b/crates/bevy_sprite/src/sprite.rs index 9efc960dc6a9d..772ee4c3354a3 100644 --- a/crates/bevy_sprite/src/sprite.rs +++ b/crates/bevy_sprite/src/sprite.rs @@ -48,7 +48,7 @@ pub fn sprite_system( let material = materials.get(handle).unwrap(); if let Some(ref texture_handle) = material.texture { if let Some(texture) = textures.get(texture_handle) { - sprite.size = texture.size; + sprite.size = texture.size.as_vec3().truncate(); } } } diff --git a/crates/bevy_sprite/src/texture_atlas_builder.rs b/crates/bevy_sprite/src/texture_atlas_builder.rs index 262667a8d65ed..9515837c86ceb 100644 --- a/crates/bevy_sprite/src/texture_atlas_builder.rs +++ b/crates/bevy_sprite/src/texture_atlas_builder.rs @@ -1,7 +1,7 @@ use crate::{Rect, TextureAtlas}; use bevy_asset::{Assets, Handle}; use bevy_math::Vec2; -use bevy_render::texture::{Texture, TextureFormat}; +use bevy_render::texture::{Extent3d, Texture, TextureDimension, TextureFormat}; use bevy_utils::HashMap; use rectangle_pack::{ contains_smallest_box, pack_rects, volume_heuristic, GroupedRectsToPlace, PackedLocation, @@ -43,7 +43,7 @@ impl TextureAtlasBuilder { self.rects_to_place.push_rect( texture_handle, None, - RectToInsert::new(texture.size.x as u32, texture.size.y as u32, 1), + RectToInsert::new(texture.size.width, texture.size.height, 1), ) } @@ -57,7 +57,7 @@ impl TextureAtlasBuilder { 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.size.x as usize; + let atlas_width = atlas_texture.size.width as usize; let format_size = atlas_texture.format.pixel_size(); for (texture_y, bound_y) in (rect_y..rect_y + rect_height).enumerate() { @@ -93,7 +93,6 @@ impl TextureAtlasBuilder { let mut target_bins = std::collections::BTreeMap::new(); target_bins.insert(0, TargetBin::new(current_width, current_height, 1)); - rect_placements = match pack_rects( &self.rects_to_place, target_bins, @@ -102,7 +101,8 @@ impl TextureAtlasBuilder { ) { Ok(rect_placements) => { atlas_texture = Texture::new_fill( - Vec2::new(current_width as f32, current_height as f32), + Extent3d::new(current_width, current_height, 1), + TextureDimension::D2, &[0, 0, 0, 0], TextureFormat::Rgba8UnormSrgb, ); @@ -137,7 +137,7 @@ impl TextureAtlasBuilder { self.place_texture(&mut atlas_texture, texture, packed_location); } Ok(TextureAtlas { - size: atlas_texture.size, + size: atlas_texture.size.as_vec3().truncate(), texture: textures.add(atlas_texture), textures: texture_rects, texture_handles: Some(texture_handles), diff --git a/crates/bevy_text/src/font.rs b/crates/bevy_text/src/font.rs index f701bff87c413..c50c695aa4637 100644 --- a/crates/bevy_text/src/font.rs +++ b/crates/bevy_text/src/font.rs @@ -1,8 +1,7 @@ use ab_glyph::{FontArc, FontVec, InvalidFont, OutlinedGlyph}; -use bevy_math::Vec2; use bevy_render::{ color::Color, - texture::{Texture, TextureFormat}, + texture::{Extent3d, Texture, TextureDimension, TextureFormat}, }; use bevy_type_registry::TypeUuid; @@ -36,7 +35,8 @@ impl Font { (color.b() * 255.0) as u8, ]; Texture::new( - Vec2::new(width as f32, height as f32), + Extent3d::new(width as u32, height as u32, 1), + TextureDimension::D2, alpha .iter() .map(|a| { diff --git a/crates/bevy_text/src/font_atlas.rs b/crates/bevy_text/src/font_atlas.rs index bd6d7c6a16453..0120d70ea3682 100644 --- a/crates/bevy_text/src/font_atlas.rs +++ b/crates/bevy_text/src/font_atlas.rs @@ -1,7 +1,7 @@ use ab_glyph::GlyphId; use bevy_asset::{Assets, Handle}; use bevy_math::Vec2; -use bevy_render::texture::{Texture, TextureFormat}; +use bevy_render::texture::{Extent3d, Texture, TextureDimension, TextureFormat}; use bevy_sprite::{DynamicTextureAtlasBuilder, TextureAtlas}; use bevy_utils::HashMap; @@ -18,7 +18,8 @@ impl FontAtlas { size: Vec2, ) -> FontAtlas { let atlas_texture = textures.add(Texture::new_fill( - size, + Extent3d::new(size.x as u32, size.y as u32, 1), + TextureDimension::D2, &[0, 0, 0, 0], TextureFormat::Rgba8UnormSrgb, )); diff --git a/crates/bevy_ui/src/widget/image.rs b/crates/bevy_ui/src/widget/image.rs index cde64fe0a320c..835d7a520b857 100644 --- a/crates/bevy_ui/src/widget/image.rs +++ b/crates/bevy_ui/src/widget/image.rs @@ -28,8 +28,8 @@ pub fn image_node_system( .and_then(|texture_handle| textures.get(texture_handle)) { calculated_size.size = Size { - width: texture.size.x, - height: texture.size.y, + width: texture.size.width as f32, + height: texture.size.height as f32, }; } } diff --git a/crates/bevy_wgpu/src/renderer/wgpu_render_resource_context.rs b/crates/bevy_wgpu/src/renderer/wgpu_render_resource_context.rs index a2a7ae5d20df2..136eaa51a064e 100644 --- a/crates/bevy_wgpu/src/renderer/wgpu_render_resource_context.rs +++ b/crates/bevy_wgpu/src/renderer/wgpu_render_resource_context.rs @@ -88,7 +88,7 @@ impl WgpuRenderResourceContext { layout: wgpu::TextureDataLayout { offset: source_offset, bytes_per_row: source_bytes_per_row, - rows_per_image: 0, // NOTE: Example sets this to 0, but should it be height? + rows_per_image: size.height, }, }, wgpu::TextureCopyView { diff --git a/examples/shader/array_texture.rs b/examples/shader/array_texture.rs new file mode 100644 index 0000000000000..2285fb7ea2d97 --- /dev/null +++ b/examples/shader/array_texture.rs @@ -0,0 +1,153 @@ +use bevy::{ + prelude::*, + render::{ + mesh::shape, + pipeline::{PipelineDescriptor, RenderPipeline}, + render_graph::{base, AssetRenderResourcesNode, RenderGraph}, + renderer::RenderResources, + shader::{ShaderStage, ShaderStages}, + }, + type_registry::TypeUuid, +}; + +/// This example illustrates how to create a texture for use with a texture2DArray shader uniform variable. +fn main() { + App::build() + .add_plugins(DefaultPlugins) + .add_asset::() + .add_startup_system(setup.system()) + .add_system(create_array_texture.system()) + .run(); +} + +#[derive(RenderResources, Default, TypeUuid)] +#[uuid = "93fb26fc-6c05-489b-9029-601edf703b6b"] +struct MyArrayTexture { + pub texture: Handle, +} + +const VERTEX_SHADER: &str = r#" +#version 450 + +layout(location = 0) in vec3 Vertex_Position; +layout(location = 0) out vec4 v_Position; + +layout(set = 0, binding = 0) uniform Camera { + mat4 ViewProj; +}; +layout(set = 1, binding = 0) uniform Transform { + mat4 Model; +}; + +void main() { + v_Position = ViewProj * Model * vec4(Vertex_Position, 1.0); + gl_Position = v_Position; +} +"#; + +const FRAGMENT_SHADER: &str = r#" +#version 450 + +layout(location = 0) in vec4 v_Position; +layout(location = 0) out vec4 o_Target; + +layout(set = 1, binding = 1) uniform texture2DArray MyArrayTexture_texture; +layout(set = 1, binding = 2) uniform sampler MyArrayTexture_texture_sampler; + +void main() { + // Screen-space coordinates determine which layer of the array texture we sample. + vec2 ss = v_Position.xy / v_Position.w; + float layer = 0.0; + if (ss.x > 0.0 && ss.y > 0.0) { + layer = 0.0; + } else if (ss.x < 0.0 && ss.y > 0.0) { + layer = 1.0; + } else if (ss.x > 0.0 && ss.y < 0.0) { + layer = 2.0; + } else { + layer = 3.0; + } + + // Convert to texture coordinates. + vec2 uv = (ss + vec2(1.0)) / 2.0; + + o_Target = texture(sampler2DArray(MyArrayTexture_texture, MyArrayTexture_texture_sampler), vec3(uv, layer)); +} +"#; + +struct LoadingTexture(Option>); + +struct MyPipeline(Handle); + +fn setup( + commands: &mut Commands, + asset_server: Res, + mut pipelines: ResMut>, + mut shaders: ResMut>, + mut render_graph: ResMut, +) { + // Start loading the texture. + commands.insert_resource(LoadingTexture(Some( + asset_server.load("textures/array_texture.png"), + ))); + + // Create a new shader pipeline. + let pipeline_handle = pipelines.add(PipelineDescriptor::default_config(ShaderStages { + vertex: shaders.add(Shader::from_glsl(ShaderStage::Vertex, VERTEX_SHADER)), + fragment: Some(shaders.add(Shader::from_glsl(ShaderStage::Fragment, FRAGMENT_SHADER))), + })); + commands.insert_resource(MyPipeline(pipeline_handle)); + + // Add an AssetRenderResourcesNode to our Render Graph. This will bind MyArrayTexture resources to our shader. + render_graph.add_system_node( + "my_array_texture", + AssetRenderResourcesNode::::new(true), + ); + // Add a Render Graph edge connecting our new "my_array_texture" node to the main pass node. This ensures "my_array_texture" + // runs before the main pass. + render_graph + .add_node_edge("my_array_texture", base::node::MAIN_PASS) + .unwrap(); + + commands.spawn(Camera3dBundle { + transform: Transform::from_translation(Vec3::new(2.0, 2.0, 2.0)) + .looking_at(Vec3::default(), Vec3::unit_y()), + ..Default::default() + }); +} + +fn create_array_texture( + commands: &mut Commands, + my_pipeline: Res, + mut loading_texture: ResMut, + mut textures: ResMut>, + mut meshes: ResMut>, + mut array_textures: ResMut>, +) { + let (handle, texture) = match loading_texture.0.as_ref() { + Some(handle) => { + if let Some(texture) = textures.get_mut(handle) { + (loading_texture.0.take().unwrap(), texture) + } else { + return; + } + } + None => return, + }; + + // Create a new array texture asset from the loaded texture. + let array_layers = 4; + texture.reinterpret_stacked_2d_as_array(array_layers); + let array_texture = array_textures.add(MyArrayTexture { texture: handle }); + + // Spawn a cube that's shaded using the array texture. + commands + .spawn(MeshBundle { + mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + render_pipelines: RenderPipelines::from_pipelines(vec![RenderPipeline::new( + my_pipeline.0.clone(), + )]), + ..Default::default() + }) + .with(array_texture); +}