Extend the Texture asset type to support 3D data (#903)

Extend the Texture asset type to support 3D data

Textures are still loaded from images as 2D, but they can be reshaped
according to how the render pipeline would like to use them.

Also add an example of how this can be used with the texture2DArray uniform type.
This commit is contained in:
Duncan 2020-11-22 12:04:47 -08:00 committed by GitHub
parent eb587b2f10
commit 46fac78774
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 279 additions and 51 deletions

View file

@ -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"

Binary file not shown.

After

Width:  |  Height:  |  Size: 460 KiB

View file

@ -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)?,
}),

View file

@ -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)

View file

@ -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,
);

View file

@ -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(())
})

View file

@ -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<u8>,
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<u8>, format: TextureFormat) -> Self {
pub fn new(
size: Extent3d,
dimension: TextureDimension,
data: Vec<u8>,
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(

View file

@ -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,
}

View file

@ -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 {

View file

@ -24,8 +24,8 @@ impl DynamicTextureAtlasBuilder {
texture: &Texture,
) -> Option<u32> {
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();

View file

@ -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();
}
}
}

View file

@ -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),

View file

@ -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| {

View file

@ -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,
));

View file

@ -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,
};
}
}

View file

@ -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 {

View file

@ -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::<MyArrayTexture>()
.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<Texture>,
}
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<Handle<Texture>>);
struct MyPipeline(Handle<PipelineDescriptor>);
fn setup(
commands: &mut Commands,
asset_server: Res<AssetServer>,
mut pipelines: ResMut<Assets<PipelineDescriptor>>,
mut shaders: ResMut<Assets<Shader>>,
mut render_graph: ResMut<RenderGraph>,
) {
// 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::<MyArrayTexture>::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<MyPipeline>,
mut loading_texture: ResMut<LoadingTexture>,
mut textures: ResMut<Assets<Texture>>,
mut meshes: ResMut<Assets<Mesh>>,
mut array_textures: ResMut<Assets<MyArrayTexture>>,
) {
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);
}