diff --git a/crates/renderling-derive/src/lib.rs b/crates/renderling-derive/src/lib.rs index 9d9ecae1..236210f4 100644 --- a/crates/renderling-derive/src/lib.rs +++ b/crates/renderling-derive/src/lib.rs @@ -128,14 +128,12 @@ pub fn derive_from_slab(input: proc_macro::TokenStream) -> proc_macro::TokenStre FieldName::Index(i) => Ident::new(&format!("offset_of_{}", i.index), i.span), FieldName::Ident(field) => Ident::new(&format!("offset_of_{}", field), field.span()), }; - offsets.push( - quote!{ - pub fn #ident() -> usize { - #(<#offset_tys as renderling_shader::slab::Slabbed>::slab_size()+)* - 0 - } + offsets.push(quote! { + pub fn #ident() -> usize { + #(<#offset_tys as renderling_shader::slab::Slabbed>::slab_size()+)* + 0 } - ); + }); offset_tys.push(ty.clone()); } diff --git a/crates/renderling-shader/src/array.rs b/crates/renderling-shader/src/array.rs index 73c57dac..6054615d 100644 --- a/crates/renderling-shader/src/array.rs +++ b/crates/renderling-shader/src/array.rs @@ -5,14 +5,25 @@ use crate::id::Id; use crate::slab::Slabbed; #[repr(C)] -#[derive(Clone, Copy)] -pub struct Array { +pub struct Array { index: u32, len: u32, _phantom: PhantomData, } -impl core::fmt::Debug for Array { +impl Clone for Array { + fn clone(&self) -> Self { + Self { + index: self.index, + len: self.len, + _phantom: PhantomData, + } + } +} + +impl Copy for Array {} + +impl core::fmt::Debug for Array { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("Array") .field("index", &self.index) @@ -22,7 +33,7 @@ impl core::fmt::Debug for Array { } } -impl PartialEq for Array { +impl PartialEq for Array { fn eq(&self, other: &Self) -> bool { self.index == other.index && self.len == other.len } @@ -92,4 +103,8 @@ impl Array { Id::new(self.index + (T::slab_size() * index) as u32) } } + + pub fn starting_index(&self) -> usize { + self.index as usize + } } diff --git a/crates/renderling-shader/src/gltf.rs b/crates/renderling-shader/src/gltf.rs new file mode 100644 index 00000000..9187978a --- /dev/null +++ b/crates/renderling-shader/src/gltf.rs @@ -0,0 +1,473 @@ +//! Gltf types that are used in shaders. +use crate::{ + self as renderling_shader, array::Array, id::Id, pbr::PbrMaterial, slab::Slabbed, + stage::GpuTexture, +}; +#[repr(transparent)] +#[derive(Default, Clone, Copy, Slabbed)] +pub struct GltfBuffer(pub Array); + +#[repr(u32)] +#[derive(Default, Clone, Copy)] +pub enum DataType { + I8, + U8, + I16, + U16, + #[default] + U32, + F32, +} + +impl Slabbed for DataType { + fn slab_size() -> usize { + // 1 + u32::slab_size() + } + + fn read_slab(&mut self, index: usize, slab: &[u32]) -> usize { + let original_index = index; + let mut hash = 0u32; + let index = hash.read_slab(index, slab); + match hash { + 0 => *self = DataType::I8, + 1 => *self = DataType::U8, + 2 => *self = DataType::I16, + 3 => *self = DataType::U16, + 4 => *self = DataType::U32, + 5 => *self = DataType::F32, + _ => return original_index, + } + index + } + + fn write_slab(&self, index: usize, slab: &mut [u32]) -> usize { + let hash = *self as u32; + hash.write_slab(index, slab) + } +} + +#[repr(u32)] +#[derive(Default, Clone, Copy)] +pub enum Dimensions { + #[default] + Scalar, + Vec2, + Vec3, + Vec4, + Mat2, + Mat3, + Mat4, +} + +impl Slabbed for Dimensions { + fn slab_size() -> usize { + 1 + } + + fn read_slab(&mut self, index: usize, slab: &[u32]) -> usize { + let original_index = index; + let mut hash = 0u32; + let index = hash.read_slab(index, slab); + match hash { + 0 => *self = Dimensions::Scalar, + 1 => *self = Dimensions::Vec2, + 2 => *self = Dimensions::Vec3, + 3 => *self = Dimensions::Vec4, + 4 => *self = Dimensions::Mat2, + 5 => *self = Dimensions::Mat3, + 6 => *self = Dimensions::Mat4, + _ => return original_index, + } + index + } + + fn write_slab(&self, index: usize, slab: &mut [u32]) -> usize { + match self { + Dimensions::Scalar => 0, + Dimensions::Vec2 => 1, + Dimensions::Vec3 => 2, + Dimensions::Vec4 => 3, + Dimensions::Mat2 => 4, + Dimensions::Mat3 => 5, + Dimensions::Mat4 => 6, + } + .write_slab(index, slab) + } +} + +#[derive(Default, Clone, Copy, Slabbed)] +pub struct GltfAccessor { + // The byte size of each component that this accessor describes. + pub size: u32, + pub buffer: Id, + // Returns the offset relative to the start of the parent buffer view in bytes. + // + // This will be 0 if the corresponding accessor is sparse. + pub view_offset: u32, + // The stride in bytes between vertex attributes or other interleavable data. + pub view_stride: u32, + // The number of components within the buffer view - not to be confused with the + // number of bytes in the buffer view. + pub count: u32, + // The data type of components in the attribute. + pub component_type: DataType, + // Specifies if the attribute is a scalar, vector, or matrix. + pub dimensions: Dimensions, + // Whether or not the attribute is normalized. + pub normalized: bool, +} + +#[derive(Default, Clone, Copy, Slabbed)] +pub struct GltfPrimitive { + pub material: Id, + pub indices: Id, + pub positions: Id, + pub normals: Id, + pub tangents: Id, + pub colors: Id, + pub tex_coords0: Id, + pub tex_coords1: Id, + pub joints: Id, + pub weights: Id, +} + +#[derive(Default, Clone, Copy, Slabbed)] +pub struct GltfMesh { + pub primitives: Array, + pub weights: Array, +} + +#[derive(Clone, Copy)] +pub enum GltfCamera { + Orthographic { + xmag: f32, + ymag: f32, + zfar: f32, + znear: f32, + }, + Perspective { + aspect_ratio: f32, + yfov: f32, + zfar: f32, + znear: f32, + }, +} + +impl GltfCamera { + const ORTHOGRAPHIC_HASH: u32 = 0; + const PERSPECTIVE_HASH: u32 = 1; +} + +impl Default for GltfCamera { + fn default() -> Self { + GltfCamera::Orthographic { + xmag: 0.0, + ymag: 0.0, + zfar: 0.0, + znear: 0.0, + } + } +} + +impl Slabbed for GltfCamera { + fn slab_size() -> usize { + 1 + 4 + } + + fn read_slab(&mut self, index: usize, slab: &[u32]) -> usize { + let original_index = index; + let mut hash = 0u32; + let index = hash.read_slab(index, slab); + match hash { + Self::ORTHOGRAPHIC_HASH => { + let mut xmag = 0.0; + let mut ymag = 0.0; + let mut zfar = 0.0; + let mut znear = 0.0; + let index = xmag.read_slab(index, slab); + let index = ymag.read_slab(index, slab); + let index = zfar.read_slab(index, slab); + let _index = znear.read_slab(index, slab); + *self = Self::Orthographic { + xmag, + ymag, + zfar, + znear, + }; + } + Self::PERSPECTIVE_HASH => { + let mut aspect_ratio = 0.0; + let mut yfov = 0.0; + let mut zfar = 0.0; + let mut znear = 0.0; + let index = aspect_ratio.read_slab(index, slab); + let index = yfov.read_slab(index, slab); + let index = zfar.read_slab(index, slab); + let _index = znear.read_slab(index, slab); + *self = Self::Perspective { + aspect_ratio, + yfov, + zfar, + znear, + }; + } + _ => return index, + } + original_index + Self::slab_size() + } + + fn write_slab(&self, index: usize, slab: &mut [u32]) -> usize { + match self { + Self::Orthographic { + xmag, + ymag, + zfar, + znear, + } => { + let index = Self::ORTHOGRAPHIC_HASH.write_slab(index, slab); + let index = xmag.write_slab(index, slab); + let index = ymag.write_slab(index, slab); + let index = zfar.write_slab(index, slab); + let _ = znear.write_slab(index, slab); + } + Self::Perspective { + aspect_ratio, + yfov, + zfar, + znear, + } => { + let index = Self::PERSPECTIVE_HASH.write_slab(index, slab); + let index = aspect_ratio.write_slab(index, slab); + let index = yfov.write_slab(index, slab); + let index = zfar.write_slab(index, slab); + let _ = znear.write_slab(index, slab); + } + } + index + Self::slab_size() + } +} + +#[derive(Default, Clone, Copy)] +pub enum GltfLightKind { + #[default] + Directional, + Point, + Spot { + inner_cone_angle: f32, + outer_cone_angle: f32, + }, +} + +impl Slabbed for GltfLightKind { + fn slab_size() -> usize { + 1 // hash + + 2 // inner_cone_angle, outer_cone_angle + } + + fn read_slab(&mut self, index: usize, slab: &[u32]) -> usize { + let original_index = index; + let mut hash = 0u32; + let index = hash.read_slab(index, slab); + match hash { + 0 => *self = Self::Directional, + 1 => *self = Self::Point, + 2 => { + let mut inner_cone_angle = 0.0; + let mut outer_cone_angle = 0.0; + let index = inner_cone_angle.read_slab(index, slab); + let _index = outer_cone_angle.read_slab(index, slab); + *self = Self::Spot { + inner_cone_angle, + outer_cone_angle, + }; + } + _ => return index, + } + original_index + Self::slab_size() + } + + fn write_slab(&self, index: usize, slab: &mut [u32]) -> usize { + match self { + Self::Directional => { + let _index = 0.write_slab(index, slab); + } + Self::Point => { + let _index = 1.write_slab(index, slab); + } + Self::Spot { + inner_cone_angle, + outer_cone_angle, + } => { + let index = 2.write_slab(index, slab); + let index = inner_cone_angle.write_slab(index, slab); + let _index = outer_cone_angle.write_slab(index, slab); + } + } + index + Self::slab_size() + } +} + +#[derive(Default, Clone, Copy, Slabbed)] +pub struct GltfLight { + pub color: glam::Vec3, + pub intensity: f32, + // If `range` is f32::MAX, then the light is a directional light. + pub range: f32, + pub kind: GltfLightKind, +} + +#[derive(Default, Clone, Copy, Slabbed)] +pub struct GltfSkin { + pub joints: Array>, + pub inverse_bind_matrices: Id, + pub skeleton: Id, +} + +#[derive(Default, Clone, Copy, Slabbed)] +pub struct GltfNode { + pub camera: Id, + pub children: Array>, + pub mesh: Id, + pub light: Id, + pub skin: Id, + pub weights: Array, + pub translation: glam::Vec3, + pub rotation: glam::Quat, + pub scale: glam::Vec3, +} + +#[repr(u32)] +#[derive(Default, Clone, Copy, PartialEq)] +pub enum GltfInterpolation { + #[default] + Linear, + Step, + CubicSpline, +} + +impl Slabbed for GltfInterpolation { + fn slab_size() -> usize { + 1 + } + + fn read_slab(&mut self, index: usize, slab: &[u32]) -> usize { + let original_index = index; + let mut proxy = 0u32; + let index = proxy.read_slab(index, slab); + match proxy { + 0 => *self = GltfInterpolation::Linear, + 1 => *self = GltfInterpolation::Step, + 2 => *self = GltfInterpolation::CubicSpline, + _ => return original_index, + } + index + } + + fn write_slab(&self, index: usize, slab: &mut [u32]) -> usize { + match self { + GltfInterpolation::Linear => 0, + GltfInterpolation::Step => 1, + GltfInterpolation::CubicSpline => 2, + } + .write_slab(index, slab) + } +} + +#[derive(Default, Clone, Copy, PartialEq, Slabbed)] +pub struct GltfAnimationSampler { + pub input: Id, + pub output: Id, + pub interpolation: GltfInterpolation, +} + +#[repr(u32)] +#[derive(Default, Clone, Copy)] +pub enum GltfTargetProperty { + #[default] + Translation, + Rotation, + Scale, + MorphTargetWeights, +} + +impl Slabbed for GltfTargetProperty { + fn slab_size() -> usize { + 1 + } + + fn read_slab(&mut self, index: usize, slab: &[u32]) -> usize { + let original_index = index; + let mut proxy = 0u32; + let index = proxy.read_slab(index, slab); + match proxy { + 0 => *self = GltfTargetProperty::Translation, + 1 => *self = GltfTargetProperty::Rotation, + 2 => *self = GltfTargetProperty::Scale, + 3 => *self = GltfTargetProperty::MorphTargetWeights, + _ => return original_index, + } + index + } + + fn write_slab(&self, index: usize, slab: &mut [u32]) -> usize { + match self { + GltfTargetProperty::Translation => 0, + GltfTargetProperty::Rotation => 1, + GltfTargetProperty::Scale => 2, + GltfTargetProperty::MorphTargetWeights => 3, + } + .write_slab(index, slab) + } +} + +#[derive(Default, Clone, Copy, Slabbed)] +pub struct GltfTarget { + pub node: Id, + pub property: GltfTargetProperty, +} + +#[derive(Default, Clone, Copy, Slabbed)] +pub struct GltfChannel { + pub sampler: Id, + pub target: GltfTarget, +} + +#[derive(Default, Clone, Copy, Slabbed)] +pub struct GltfAnimation { + pub channels: Array, + pub samplers: Array, +} + +#[derive(Default, Clone, Copy, Slabbed)] +pub struct GltfScene { + pub nodes: Array>, +} + +#[derive(Default, Clone, Copy, Slabbed)] +pub struct GltfBufferView { + pub buffer: Id, + pub offset: u32, + pub length: u32, + pub stride: u32, +} + +/// A document of Gltf data. +/// +/// This tells where certain parts of the Gltf document are stored in the [`Stage`]'s slab. +#[derive(Default, Clone, Copy, Slabbed)] +pub struct GltfDocument { + pub accessors: Array, + pub animations: Array, + pub buffers: Array, + pub cameras: Array, + // TODO: Think about making a `GltfMaterial` + pub materials: Array, + pub meshes: Array, + pub nodes: Array, + pub scenes: Array, + pub skins: Array, + // TODO: Think about making a `GltfTexture` + pub textures: Array, + pub views: Array, +} diff --git a/crates/renderling-shader/src/lib.rs b/crates/renderling-shader/src/lib.rs index b567b566..028b8f44 100644 --- a/crates/renderling-shader/src/lib.rs +++ b/crates/renderling-shader/src/lib.rs @@ -13,6 +13,7 @@ pub mod array; pub mod bits; pub mod convolution; pub mod debug; +pub mod gltf; pub mod id; pub mod math; pub mod pbr; diff --git a/crates/renderling-shader/src/slab.rs b/crates/renderling-shader/src/slab.rs index 9b0ba477..60066330 100644 --- a/crates/renderling-shader/src/slab.rs +++ b/crates/renderling-shader/src/slab.rs @@ -99,6 +99,36 @@ impl Slabbed for f32 { } } +impl Slabbed for Option { + fn slab_size() -> usize { + 1 + T::slab_size() + } + + fn read_slab(&mut self, index: usize, slab: &[u32]) -> usize { + let mut proxy = 0u32; + let index = proxy.read_slab(index, slab); + if proxy == 1 { + let mut t = T::default(); + let index = t.read_slab(index, slab); + *self = Some(t); + index + } else { + *self = None; + index + T::slab_size() + } + } + + fn write_slab(&self, index: usize, slab: &mut [u32]) -> usize { + if let Some(t) = self { + let index = 1u32.write_slab(index, slab); + t.write_slab(index, slab) + } else { + let index = 0u32.write_slab(index, slab); + index + T::slab_size() + } + } +} + impl Slabbed for [T; N] { fn slab_size() -> usize { ::slab_size() * N diff --git a/crates/renderling-shader/src/stage/texture.rs b/crates/renderling-shader/src/stage/texture.rs index 9ec404a6..622179ec 100644 --- a/crates/renderling-shader/src/stage/texture.rs +++ b/crates/renderling-shader/src/stage/texture.rs @@ -36,6 +36,16 @@ impl TextureModes { pub fn set_wrap_t(&mut self, wrap_t: TextureAddressMode) { insert(&mut self.0, Self::BITS_WRAP_T, wrap_t.0) } + + pub fn with_wrap_s(mut self, wrap_s: TextureAddressMode) -> Self { + self.set_wrap_s(wrap_s); + self + } + + pub fn with_wrap_t(mut self, wrap_t: TextureAddressMode) -> Self { + self.set_wrap_t(wrap_t); + self + } } /// A GPU texture. @@ -49,8 +59,8 @@ pub struct GpuTexture { pub size_px: UVec2, // Various toggles of texture modes. pub modes: TextureModes, - - pub padding: u32, + // The index of the image in the atlas. + pub atlas_index: u32, } impl GpuTexture { diff --git a/crates/renderling/src/atlas.rs b/crates/renderling/src/atlas.rs index 94d20514..668e1d23 100644 --- a/crates/renderling/src/atlas.rs +++ b/crates/renderling/src/atlas.rs @@ -26,6 +26,81 @@ pub enum AtlasError { CannotPackTextures { len: usize }, } +/// A texture atlas packing, before it is committed to the GPU. +#[derive(Clone)] +pub enum Packing { + Img { + index: usize, + image: SceneImage, + }, + AtlasImg { + index: usize, + offset_px: UVec2, + size_px: UVec2, + }, +} + +impl Packing { + pub fn width(&self) -> u32 { + match self { + Packing::Img { image, .. } => image.width, + Packing::AtlasImg { size_px, .. } => size_px.x, + } + } + + pub fn height(&self) -> u32 { + match self { + Packing::Img { image, .. } => image.height, + Packing::AtlasImg { size_px, .. } => size_px.y, + } + } + + pub fn index(&self) -> usize { + match self { + Packing::Img { index, .. } => *index, + Packing::AtlasImg { index, .. } => *index, + } + } + + pub fn set_index(&mut self, index: usize) { + match self { + Packing::Img { index: i, .. } => *i = index, + Packing::AtlasImg { index: i, .. } => *i = index, + } + } + + pub fn as_scene_img_mut(&mut self) -> Option<&mut SceneImage> { + match self { + Packing::Img { image, .. } => Some(image), + Packing::AtlasImg { .. } => None, + } + } +} + +/// A preview of the packed atlas. +/// +/// Using a preview of the atlas allows us to access the packed frames +/// without committing the atlas to the GPU. Since the items images +/// are still in CPU memory, we can mutate them and then commit the +/// atlas to the GPU. +#[repr(transparent)] +pub struct RepackPreview { + pub items: crunch::PackedItems, +} + +impl RepackPreview { + pub fn get_frame(&self, index: usize) -> Option<(UVec2, UVec2)> { + self.items.items.get(index).map(|item| { + let rect = item.rect; + gpu_frame_from_rect(rect) + }) + } + + pub fn get_mut(&mut self, index: usize) -> Option<&mut Packing> { + self.items.items.get_mut(index).map(|item| &mut item.data) + } +} + /// A texture atlas, used to store all the textures in a scene. pub struct Atlas { pub texture: crate::Texture, @@ -99,94 +174,256 @@ impl Atlas { Self::new(device, queue, UVec2::new(1, 1)) } - /// Packs the atlas with the list of images. + pub fn is_empty(&self) -> bool { + self.rects.is_empty() + } + + /// Does a dry-run packing of the atlas with the list of images. /// /// Returns a vector of ids that determine the locations of the given images - /// within the atlas. - /// - /// This invalidates any pointers to previous textures in this atlas. - pub fn pack( + /// but doesn't send any data to the GPU. + pub fn pack_preview<'a>( device: &wgpu::Device, - queue: &wgpu::Queue, images: impl IntoIterator, - ) -> Result { - let mut images = images.into_iter().collect::>(); + ) -> Result, AtlasError> { + let images = images.into_iter().collect::>(); let len = images.len(); - let size = device.limits().max_texture_dimension_1d; - let crunch::PackedItems { w, h, items } = crunch::pack_into_po2( - size as usize, - images.iter().enumerate().map(|(i, img)| { + let limit = device.limits().max_texture_dimension_1d; + let items = crunch::pack_into_po2( + limit as usize, + images.into_iter().map(|img| { let w = img.width; let h = img.height; - crunch::Item::new(i, w as usize, h as usize, crunch::Rotation::None) + crunch::Item::new(img, w as usize, h as usize, crunch::Rotation::None) }), ) .ok() .context(CannotPackTexturesSnafu { len })?; + Ok(items) + } + pub fn commit_preview( + device: &wgpu::Device, + queue: &wgpu::Queue, + crunch::PackedItems { w, h, items }: crunch::PackedItems, + ) -> Result { let mut atlas = Atlas::new(device, queue, UVec2::new(w as u32, h as u32)); - atlas.rects = vec![crunch::Rect::default(); len]; - - for crunch::PackedItem { data: i, rect } in items.into_iter() { - let img = &mut images[i]; - let bytes = crate::convert_to_rgba8_bytes( - std::mem::take(&mut img.pixels), - img.format, - img.apply_linear_transfer, - ); - queue.write_texture( - wgpu::ImageCopyTextureBase { - texture: &atlas.texture.texture, - mip_level: 0, - origin: wgpu::Origin3d { - x: rect.x as u32, - y: rect.y as u32, - z: 0, - }, - aspect: wgpu::TextureAspect::All, - }, - &bytes, - wgpu::ImageDataLayout { - offset: 0, - bytes_per_row: Some(4 * img.width), - rows_per_image: Some(img.height), - }, - wgpu::Extent3d { - width: img.width, - height: img.height, - depth_or_array_layers: 1, + atlas.rects = items + .into_iter() + .map( + |crunch::PackedItem { + data: mut img, + rect, + }| { + let bytes = crate::convert_to_rgba8_bytes( + std::mem::take(&mut img.pixels), + img.format, + img.apply_linear_transfer, + ); + queue.write_texture( + wgpu::ImageCopyTextureBase { + texture: &atlas.texture.texture, + mip_level: 0, + origin: wgpu::Origin3d { + x: rect.x as u32, + y: rect.y as u32, + z: 0, + }, + aspect: wgpu::TextureAspect::All, + }, + &bytes, + wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: Some(4 * img.width), + rows_per_image: Some(img.height), + }, + wgpu::Extent3d { + width: img.width, + height: img.height, + depth_or_array_layers: 1, + }, + ); + rect }, - ); + ) + .collect(); - atlas.rects[i] = rect; + Ok(atlas) + } + + pub fn repack_preview( + &self, + device: &wgpu::Device, + images: impl IntoIterator, + ) -> Result { + let mut images = images.into_iter().collect::>(); + let len = images.len() + self.rects.len(); + let items = crunch::pack_into_po2( + device.limits().max_texture_dimension_1d as usize, + self.rects + .iter() + .map(|r| Packing::AtlasImg { + index: 0, + offset_px: UVec2::new(r.x as u32, r.y as u32), + size_px: UVec2::new(r.w as u32, r.y as u32), + }) + .chain( + images + .drain(..) + .map(|image| Packing::Img { index: 0, image }), + ) + .enumerate() + .map(|(i, mut p)| { + let w = p.width() as usize; + let h = p.height() as usize; + p.set_index(i); + crunch::Item::new(p, w, h, crunch::Rotation::None) + }), + ) + .ok() + .context(CannotPackTexturesSnafu { len })?; + debug_assert!(items + .items + .iter() + .zip(0..) + .all(|(item, i)| item.data.index() == i)); + Ok(RepackPreview { items }) + } + + pub fn commit_repack_preview( + &self, + device: &wgpu::Device, + queue: &wgpu::Queue, + RepackPreview { + items: crunch::PackedItems { w, h, items }, + }: RepackPreview, + ) -> Result { + let mut atlas = Atlas::new(device, queue, UVec2::new(w as u32, h as u32)); + atlas.rects = vec![crunch::Rect::default(); items.len()]; + + let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("repack atlas"), + }); + for crunch::PackedItem { data: p, rect } in items.into_iter() { + match p { + Packing::Img { index, mut image } => { + let bytes = crate::convert_to_rgba8_bytes( + std::mem::take(&mut image.pixels), + image.format, + image.apply_linear_transfer, + ); + queue.write_texture( + wgpu::ImageCopyTextureBase { + texture: &atlas.texture.texture, + mip_level: 0, + origin: wgpu::Origin3d { + x: rect.x as u32, + y: rect.y as u32, + z: 0, + }, + aspect: wgpu::TextureAspect::All, + }, + &bytes, + wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: Some(4 * image.width), + rows_per_image: Some(image.height), + }, + wgpu::Extent3d { + width: image.width, + height: image.height, + depth_or_array_layers: 1, + }, + ); + atlas.rects[index] = rect; + } + Packing::AtlasImg { + index, + offset_px, + size_px, + } => { + encoder.copy_texture_to_texture( + wgpu::ImageCopyTexture { + texture: &self.texture.texture, + mip_level: 0, + origin: wgpu::Origin3d { + x: offset_px.x, + y: offset_px.y, + z: 0, + }, + aspect: wgpu::TextureAspect::All, + }, + wgpu::ImageCopyTexture { + texture: &atlas.texture.texture, + mip_level: 0, + origin: wgpu::Origin3d { + x: rect.x as u32, + y: rect.y as u32, + z: 0, + }, + aspect: wgpu::TextureAspect::All, + }, + wgpu::Extent3d { + width: size_px.x, + height: size_px.y, + depth_or_array_layers: 1, + }, + ); + atlas.rects[index] = rect; + } + } } + queue.submit(std::iter::once(encoder.finish())); Ok(atlas) } + /// Packs the atlas with the list of images. + /// + /// Returns a vector of ids that determine the locations of the given images + /// within the atlas. + /// + /// This invalidates any pointers to previous textures in this atlas. + pub fn pack( + device: &wgpu::Device, + queue: &wgpu::Queue, + images: impl IntoIterator, + ) -> Result { + let images = images.into_iter().collect::>(); + let items = Self::pack_preview(device, images)?; + Self::commit_preview(device, queue, items) + } + + pub fn repack( + &self, + device: &wgpu::Device, + queue: &wgpu::Queue, + images: impl IntoIterator, + ) -> Result { + let images = images.into_iter().collect::>(); + let items = self.repack_preview(device, images)?; + self.commit_repack_preview(device, queue, items) + } + pub fn frames(&self) -> impl Iterator + '_ { (0u32..).zip(self.rects.iter().copied().map(gpu_frame_from_rect)) } + /// Return the position and size of the frame at the given index. pub fn get_frame(&self, index: usize) -> Option<(UVec2, UVec2)> { self.rects.get(index).copied().map(gpu_frame_from_rect) } pub fn merge( - self, + &self, device: &wgpu::Device, queue: &wgpu::Queue, - other: Atlas, + other: &Atlas, ) -> Result { - let mut images = self + let images = self .rects - .into_iter() - .zip(std::iter::repeat(self.texture)) - .chain( - other - .rects - .into_iter() - .zip(std::iter::repeat(other.texture)), - ) + .iter() + .zip(std::iter::repeat(&self.texture)) + .chain(other.rects.iter().zip(std::iter::repeat(&other.texture))) .collect::>(); let len = images.len(); let size = device.limits().max_texture_dimension_1d; @@ -276,7 +513,7 @@ mod test { let sandstone = SceneImage::from_path("../../img/sandstone.png").unwrap(); let atlas1 = Atlas::pack(&device, &queue, vec![cheetah, dirt]).unwrap(); let atlas2 = Atlas::pack(&device, &queue, vec![happy_mac, sandstone]).unwrap(); - let atlas3 = atlas1.merge(&device, &queue, atlas2).unwrap(); + let atlas3 = atlas1.merge(&device, &queue, &atlas2).unwrap(); img_diff::assert_img_eq("atlas/merge3.png", atlas3.atlas_img(&device, &queue)); } } diff --git a/crates/renderling/src/scene.rs b/crates/renderling/src/scene.rs index bf5bd0b6..0ddcb7b5 100644 --- a/crates/renderling/src/scene.rs +++ b/crates/renderling/src/scene.rs @@ -1,7 +1,7 @@ //! Build GPU scenes from the CPU. use std::sync::Arc; -use glam::{Mat4, UVec2, Vec3}; +use glam::{Mat4, Vec3}; use moongraph::{Move, View, ViewMut}; use renderling_shader::debug::DebugChannel; use snafu::prelude::*; diff --git a/crates/renderling/src/slab.rs b/crates/renderling/src/slab.rs index e65a74c3..36767ed5 100644 --- a/crates/renderling/src/slab.rs +++ b/crates/renderling/src/slab.rs @@ -10,13 +10,40 @@ use snafu::{ResultExt, Snafu}; pub use renderling_shader::slab::{Slab, Slabbed}; #[derive(Debug, Snafu)] -pub enum SlabError { +pub enum SlabError { #[snafu(display( - "Out of capacity. Tried to write {}(slab size={}) \ - at {} but capacity is {capacity}", - std::any::type_name::(), T::slab_size(), id.index() + "Out of capacity. Tried to write {type_is}(slab size={slab_size}) \ + at {index} but capacity is {capacity}", ))] - Capacity { id: Id, capacity: usize }, + Capacity { + type_is: &'static str, + slab_size: usize, + index: usize, + capacity: usize, + }, + + #[snafu(display( + "Out of capacity. Tried to write an array of {elements} {type_is}\ + (each of slab size={slab_size}) \ + at {index} but capacity is {capacity}", + ))] + ArrayCapacity { + type_is: &'static str, + elements: usize, + slab_size: usize, + index: usize, + capacity: usize, + }, + + #[snafu(display( + "Array({type_is}) length mismatch. Tried to write {data_len} elements \ + into array of length {array_len}", + ))] + ArrayLen { + type_is: &'static str, + array_len: usize, + data_len: usize, + }, #[snafu(display("Async recv error: {source}"))] AsyncRecv { source: async_channel::RecvError }, @@ -80,6 +107,64 @@ impl SlabBuffer { self.capacity.load(std::sync::atomic::Ordering::Relaxed) } + fn maybe_expand_to_fit( + &self, + device: &wgpu::Device, + queue: &wgpu::Queue, + len: usize, + ) { + let size = T::slab_size(); + let capacity = self.capacity(); + //log::trace!( + // "append_slice: {size} * {ts_len} + {len} ({}) >= {capacity}", + // size * ts_len + len + //); + let capacity_needed = self.len() + size * len; + if capacity_needed > capacity { + let mut new_capacity = capacity * 2; + while new_capacity < capacity_needed { + new_capacity *= 2; + } + self.resize(device, queue, new_capacity); + } + } + + /// Preallocate space for one `T` element, but don't write anything to the buffer. + /// + /// This can be used to write later with [`Self::write`]. + /// + /// NOTE: This changes the next available buffer index and may change the buffer capacity. + pub fn allocate(&self, device: &wgpu::Device, queue: &wgpu::Queue) -> Id { + self.maybe_expand_to_fit::(device, queue, 1); + let index = self + .len + .fetch_add(T::slab_size(), std::sync::atomic::Ordering::Relaxed); + Id::from(index) + } + + /// Preallocate space for `len` `T` elements, but don't write to + /// the buffer. + /// + /// This can be used to allocate space for a bunch of elements that get written + /// later with [`Self::write_array`]. + /// + /// NOTE: This changes the length of the buffer and may change the capacity. + pub fn allocate_array( + &self, + device: &wgpu::Device, + queue: &wgpu::Queue, + len: usize, + ) -> Array { + if len == 0 { + return Array::default(); + } + self.maybe_expand_to_fit::(device, queue, len); + let index = self + .len + .fetch_add(T::slab_size() * len, std::sync::atomic::Ordering::Relaxed); + Array::new(index as u32, len as u32) + } + /// Write into the slab buffer, modifying in place. /// /// NOTE: This has no effect on the length of the buffer. @@ -89,7 +174,7 @@ impl SlabBuffer { queue: &wgpu::Queue, id: Id, data: &T, - ) -> Result<(), SlabError> { + ) -> Result<(), SlabError> { let byte_offset = id.index() * std::mem::size_of::(); let size = T::slab_size(); let mut bytes = vec![0u32; size]; @@ -97,7 +182,12 @@ impl SlabBuffer { let capacity = self.capacity(); snafu::ensure!( id.index() + size <= capacity, - CapacitySnafu { id, capacity } + CapacitySnafu { + type_is: std::any::type_name::(), + slab_size: T::slab_size(), + index: id.index(), + capacity + } ); let encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); @@ -111,21 +201,69 @@ impl SlabBuffer { Ok(()) } + /// Write elements into the slab buffer, modifying in place. + /// + /// NOTE: This has no effect on the length of the buffer. + /// + /// ## Errors + /// Errors if the capacity is exceeded. + pub fn write_array( + &self, + device: &wgpu::Device, + queue: &wgpu::Queue, + array: Array, + data: &[T], + ) -> Result<(), SlabError> { + snafu::ensure!( + array.len() == data.len(), + ArrayLenSnafu { + type_is: std::any::type_name::(), + array_len: array.len(), + data_len: data.len() + } + ); + let capacity = self.capacity(); + let size = T::slab_size() * array.len(); + snafu::ensure!( + array.starting_index() + size <= capacity, + ArrayCapacitySnafu { + capacity, + type_is: std::any::type_name::(), + elements: array.len(), + slab_size: T::slab_size(), + index: array.at(0).index() + } + ); + let encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + let mut u32_data = vec![0u32; size]; + let _ = u32_data.write_slice(data, 0); + let byte_offset = array.starting_index() * std::mem::size_of::(); + queue.write_buffer( + // UNWRAP: if we can't lock we want to panic + &self.buffer.read().unwrap(), + byte_offset as u64, + bytemuck::cast_slice(&u32_data), + ); + queue.submit(std::iter::once(encoder.finish())); + Ok(()) + } + /// Read from the slab buffer. /// /// `T` is only for the error message. - pub async fn read_raw( + pub async fn read_raw( &self, device: &wgpu::Device, queue: &wgpu::Queue, start: usize, len: usize, - ) -> Result, SlabError> { + ) -> Result, SlabError> { let byte_offset = start * std::mem::size_of::(); let length = len * std::mem::size_of::(); let output_buffer_size = length as u64; let output_buffer = device.create_buffer(&wgpu::BufferDescriptor { - label: Some(&format!("SlabBuffer::read<{}>", std::any::type_name::())), + label: Some("SlabBuffer::read_raw"), size: output_buffer_size, usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, mapped_at_creation: false, @@ -165,7 +303,7 @@ impl SlabBuffer { device: &wgpu::Device, queue: &wgpu::Queue, id: Id, - ) -> Result> { + ) -> Result { let vec = self .read_raw(device, queue, id.index(), T::slab_size()) .await?; @@ -180,56 +318,24 @@ impl SlabBuffer { queue: &wgpu::Queue, t: &T, ) -> Id { - let len = self.len(); - let capacity = self.capacity(); - if T::slab_size() + len > capacity { - self.resize(device, queue, capacity * 2); - } - let id = Id::::from(len); - // IGNORED: We just checked that there is enough capacity, and added some if not. + let id = self.allocate::(device, queue); + // IGNORED: safe because we just allocated the id let _ = self.write(device, queue, id, t); - self.len - .store(len + T::slab_size(), std::sync::atomic::Ordering::Relaxed); id } - /// Append a slice to the end of the buffer, returning a slab array. - pub fn append_slice( + /// Append a slice to the end of the buffer, resizing if necessary + /// and returning a slabbed array. + pub fn append_array( &self, device: &wgpu::Device, queue: &wgpu::Queue, ts: &[T], ) -> Array { - let ts_len = ts.len(); - let size = T::slab_size(); - let capacity = self.capacity(); - let len = self.len(); - //log::trace!( - // "append_slice: {size} * {ts_len} + {len} ({}) >= {capacity}", - // size * ts_len + len - //); - let capacity_needed = size * ts_len + len; - if capacity_needed >= capacity { - let mut new_capacity = capacity * 2; - while new_capacity < capacity_needed { - new_capacity *= 2; - } - self.resize(device, queue, new_capacity); - } - let starting_index = len as u32; - for (i, t) in ts.iter().enumerate() { - // IGNORED: Safe because we just checked that there is enough capacity, - // and added some if not. - let _ = self.write( - device, - queue, - Id::::from(starting_index + (size * i) as u32), - t, - ); - } - self.len - .store(len + size * ts_len, std::sync::atomic::Ordering::Relaxed); - Array::new(starting_index, ts_len as u32) + let array = self.allocate_array::(device, queue, ts.len()); + // IGNORED: safe because we just allocated the array + let _ = self.write_array(device, queue, array, ts); + array } /// Resize the slab buffer. @@ -306,19 +412,17 @@ mod test { let b = glam::Vec3::new(1.0, 1.0, 1.0); let c = glam::Vec3::new(2.0, 2.0, 2.0); let points = vec![a, b, c]; - let array = slab.append_slice(device, queue, &points); + let array = slab.append_array(device, queue, &points); let slab_u32 = - futures_lite::future::block_on(slab.read_raw::(device, queue, 0, slab.len())) - .unwrap(); + futures_lite::future::block_on(slab.read_raw(device, queue, 0, slab.len())).unwrap(); let points_out = slab_u32.read_vec::(array); assert_eq!(points, points_out); println!("append slice 2"); let points = vec![a, a, a, a, b, b, b, c, c]; - let array = slab.append_slice(device, queue, &points); + let array = slab.append_array(device, queue, &points); let slab_u32 = - futures_lite::future::block_on(slab.read_raw::(device, queue, 0, slab.len())) - .unwrap(); + futures_lite::future::block_on(slab.read_raw(device, queue, 0, slab.len())).unwrap(); let points_out = slab_u32.read_vec::(array); assert_eq!(points, points_out); } diff --git a/crates/renderling/src/stage.rs b/crates/renderling/src/stage.rs index 18bf0505..58cebef2 100644 --- a/crates/renderling/src/stage.rs +++ b/crates/renderling/src/stage.rs @@ -14,28 +14,18 @@ use renderling_shader::{ slab::Slabbed, stage::{GpuLight, RenderUnit, StageLegend}, }; -use snafu::Snafu; use crate::{ bloom::{BloomFilter, BloomResult}, - Atlas, DepthTexture, Device, HdrSurface, Queue, Skybox, SlabBuffer, + Atlas, DepthTexture, Device, HdrSurface, Queue, Skybox, SlabBuffer, SlabError, }; #[cfg(feature = "gltf")] -pub mod gltf_support; +mod gltf_support; pub mod light; -#[derive(Debug, Snafu)] -pub enum StageError { - #[snafu(display("Out of capacity. Tried to write {:?} but capacity is {capacity}"))] - Capacity { id: Id, capacity: usize }, - - #[snafu(display("Async recv error: {source}"))] - AsyncRecv { source: async_channel::RecvError }, - - #[snafu(display("Async error: {source}"))] - Async { source: wgpu::BufferAsyncError }, -} +#[cfg(feature = "gltf")] +pub use gltf_support::*; /// Represents an entire scene worth of rendering data. /// @@ -43,7 +33,7 @@ pub enum StageError { #[derive(Clone)] pub struct Stage { pub(crate) slab: SlabBuffer, - pub(crate) atlas: Arc>, + pub(crate) atlas: Arc>, pub(crate) skybox: Arc>, pub(crate) pipeline: Arc>>>, pub(crate) skybox_pipeline: Arc>>>, @@ -59,11 +49,11 @@ pub struct Stage { impl Stage { /// Create a new stage. - pub fn new(device: Device, queue: Queue, legend: StageLegend) -> Self { + pub fn new(device: Device, queue: Queue) -> Self { let s = Self { slab: SlabBuffer::new(&device, 256), pipeline: Default::default(), - atlas: Arc::new(Mutex::new(Atlas::empty(&device, &queue))), + atlas: Arc::new(RwLock::new(Atlas::empty(&device, &queue))), skybox: Arc::new(Mutex::new(Skybox::empty(&device, &queue))), skybox_pipeline: Default::default(), has_skybox: Arc::new(AtomicBool::new(false)), @@ -75,18 +65,46 @@ impl Stage { device, queue, }; - let _ = s.append(&legend); + let _ = s.append(&StageLegend::default()); s } + /// Allocate some storage for a type on the slab, but don't write it. + pub fn allocate(&self) -> Id { + self.slab.allocate(&self.device, &self.queue) + } + + /// Allocate contiguous storage for `len` elements of a type on the slab, but don't write them. + pub fn allocate_array(&self, len: usize) -> Array { + self.slab.allocate_array(&self.device, &self.queue, len) + } + + /// Write an object to the slab. + pub fn write(&self, id: Id, object: &T) -> Result<(), SlabError> { + let () = self.slab.write(&self.device, &self.queue, id, object)?; + Ok(()) + } + + /// Write many objects to the slab. + pub fn write_array( + &self, + array: Array, + objects: &[T], + ) -> Result<(), SlabError> { + let () = self + .slab + .write_array(&self.device, &self.queue, array, objects)?; + Ok(()) + } + /// Add an object to the slab and return its ID. - pub fn append(&self, object: &T) -> Id { + pub fn append(&self, object: &T) -> Id { self.slab.append(&self.device, &self.queue, object) } /// Add a slice of objects to the slab and return an [`Array`]. - pub fn append_slice(&self, objects: &[T]) -> Array { - self.slab.append_slice(&self.device, &self.queue, objects) + pub fn append_array(&self, objects: &[T]) -> Array { + self.slab.append_array(&self.device, &self.queue, objects) } /// Set the debug mode. @@ -163,7 +181,7 @@ impl Stage { lights: impl IntoIterator>, ) -> Array> { let lights = lights.into_iter().collect::>(); - let light_array = self.append_slice(&lights); + let light_array = self.append_array(&lights); let id = Id::>>::from(StageLegend::offset_of_light_array()); // UNWRAP: safe because we just appended the array, and the light array offset is // guaranteed to be valid. @@ -523,8 +541,8 @@ impl Stage { let b = Arc::new(create_textures_bindgroup( &self.device, &self.get_pipeline(), - // UNWRAP: we can't acquire locks we want to panic - &self.atlas.lock().unwrap(), + // UNWRAP: if we can't acquire locks we want to panic + &self.atlas.read().unwrap(), &self.skybox.lock().unwrap(), )); *bindgroup = Some(b.clone()); @@ -605,7 +623,7 @@ pub(crate) enum StageDrawStrategy { /// Render the stage. pub fn stage_render( (stage, hdr_frame, depth): (ViewMut, View, View), -) -> Result<(BloomResult,), StageError> { +) -> Result<(BloomResult,), SlabError> { let label = Some("stage render"); let pipeline = stage.get_pipeline(); let slab_buffers_bindgroup = stage.get_slab_buffers_bindgroup(); @@ -700,7 +718,7 @@ mod test { .unwrap() .with_background_color(glam::Vec4::splat(1.0)); let (device, queue) = r.get_device_and_queue_owned(); - let stage = Stage::new(device.clone(), queue.clone(), StageLegend::default()) + let stage = Stage::new(device.clone(), queue.clone()) .with_lighting(true) .with_bloom(true); let (projection, view) = default_ortho2d(100.0, 100.0); @@ -710,14 +728,14 @@ mod test { position: Vec3::ZERO, }; let camera_id = stage.append(&camera); - let vertices = stage.append_slice(&right_tri_vertices()); + let vertices = stage.append_array(&right_tri_vertices()); println!("vertices: {vertices:?}"); let _ = stage.draw_unit(&RenderUnit { camera: camera_id, vertices, ..Default::default() }); - let stage_slab = futures_lite::future::block_on(stage.slab.read_raw::( + let stage_slab = futures_lite::future::block_on(stage.slab.read_raw( &stage.device, &stage.queue, 0, diff --git a/crates/renderling/src/stage/gltf_support.rs b/crates/renderling/src/stage/gltf_support.rs index ff0f6b03..4b2cf8df 100644 --- a/crates/renderling/src/stage/gltf_support.rs +++ b/crates/renderling/src/stage/gltf_support.rs @@ -1,122 +1,672 @@ //! Gltf support for the [`Stage`](crate::Stage). use super::*; use crate::{ - scene::TextureParams, - shader::{array::Array, stage::TextureAddressMode}, - slab::Slabbed, + shader::{ + gltf::*, + pbr::PbrMaterial, + stage::{GpuTexture, LightingModel, TextureAddressMode, TextureModes}, + }, SceneImage, }; -use snafu::Snafu; +use glam::{Quat, Vec3, Vec4}; +use snafu::{OptionExt, ResultExt, Snafu}; #[derive(Debug, Snafu)] -pub enum StageGltfError {} - -#[repr(transparent)] -#[derive(Default, Clone, Copy, Slabbed)] -pub struct GltfBuffer(Array); - -/// A legend of Gltf data. -/// -/// This tells where certain parts of the Gltf document are stored in the [`Stage`]'s slab. -#[derive(Default, Clone, Copy, Slabbed)] -pub struct GltfLegend { - pub buffers: Array, +pub enum StageGltfError { + #[snafu(display("{source}"))] + Atlas { source: crate::atlas::AtlasError }, + + #[snafu(display("Missing image at index {index} atlas offset {offset}"))] + MissingImage { offset: usize, index: usize }, + + #[snafu(display("Wrong image at index {index} atlas offset {offset}"))] + WrongImage { offset: usize, index: usize }, + + #[snafu(display("Missing texture at gltf index {index} slab index {tex_id:?}"))] + MissingTexture { + index: usize, + tex_id: Id, + }, + + #[snafu(display("Unsupported primitive mode: {:?}", mode))] + PrimitiveMode { mode: gltf::mesh::Mode }, + + #[snafu(display("No {} attribute for mesh", attribute.to_string()))] + MissingAttribute { attribute: gltf::Semantic }, + + #[snafu(display("No weights array"))] + MissingWeights, + + #[snafu(display("Missing sampler"))] + MissingSampler, + + #[snafu(display("{source}"))] + Slab { source: crate::slab::SlabError }, +} + +impl From for StageGltfError { + fn from(source: crate::slab::SlabError) -> Self { + Self::Slab { source } + } } impl Stage { pub fn load_gltf_document( &self, document: gltf::Document, - buffers: Vec, + buffer_data: Vec, images: Vec, - ) -> GltfLegend { - // Load the buffers into the GPU - let gltf_buffers: Vec = buffers - .into_iter() - .map(|buffer| { - let slice: &[u32] = bytemuck::cast_slice(&buffer); - GltfBuffer(self.append_slice(slice)) + ) -> Result { + log::trace!("Loading buffers into the GPU"); + let buffers = self.allocate_array::(buffer_data.len()); + for (i, buffer) in buffer_data.iter().enumerate() { + log::trace!(" Loading buffer {i} size: {} bytes", buffer.len()); + let slice: &[u32] = bytemuck::cast_slice(&buffer); + let buffer = self.append_array(slice); + self.write(buffers.at(i), &GltfBuffer(buffer))?; + } + + log::trace!("Loading views into the GPU"); + let views = self.allocate_array(document.views().len()); + for view in document.views() { + let buffer = buffers.at(view.buffer().index()); + let offset = view.offset() as u32; + let length = view.length() as u32; + let stride = view.stride().unwrap_or_default() as u32; + self.write( + views.at(view.index()), + &GltfBufferView { + buffer, + offset, + length, + stride, + }, + )?; + } + + log::trace!("Loading accessors into the GPU"); + let accessors = document + .accessors() + .map(|accessor| { + let size = accessor.size() as u32; + let buffer_view = accessor.view().unwrap(); + let view_buffer = buffer_view.buffer(); + let buffer_index = view_buffer.index(); + let buffer = buffers.at(buffer_index); + let count = accessor.count() as u32; + let view_offset = buffer_view.offset() as u32; + let view_stride = buffer_view.stride().unwrap_or(0) as u32; + let component_type = match accessor.data_type() { + gltf::accessor::DataType::I8 => DataType::I8, + gltf::accessor::DataType::U8 => DataType::U8, + gltf::accessor::DataType::I16 => DataType::I16, + gltf::accessor::DataType::U16 => DataType::U16, + gltf::accessor::DataType::U32 => DataType::U32, + gltf::accessor::DataType::F32 => DataType::F32, + }; + let dimensions = match accessor.dimensions() { + gltf::accessor::Dimensions::Scalar => Dimensions::Scalar, + gltf::accessor::Dimensions::Vec2 => Dimensions::Vec2, + gltf::accessor::Dimensions::Vec3 => Dimensions::Vec3, + gltf::accessor::Dimensions::Vec4 => Dimensions::Vec4, + gltf::accessor::Dimensions::Mat2 => Dimensions::Mat2, + gltf::accessor::Dimensions::Mat3 => Dimensions::Mat3, + gltf::accessor::Dimensions::Mat4 => Dimensions::Mat4, + }; + let normalized = accessor.normalized(); + let accessor = GltfAccessor { + size, + count, + buffer, + view_offset, + view_stride, + component_type, + dimensions, + normalized, + }; + accessor + }) + .collect::>(); + let accessors = self.append_array(&accessors); + + log::trace!("Loading cameras into the GPU"); + let cameras = document + .cameras() + .map(|camera| match camera.projection() { + gltf::camera::Projection::Perspective(perspective) => { + let aspect_ratio = perspective.aspect_ratio().unwrap_or(1.0); + let yfov = perspective.yfov(); + let zfar = perspective.zfar().unwrap_or(f32::MAX); + let znear = perspective.znear(); + let camera = GltfCamera::Perspective { + aspect_ratio, + yfov, + zfar, + znear, + }; + camera + } + gltf::camera::Projection::Orthographic(orthographic) => { + let xmag = orthographic.xmag(); + let ymag = orthographic.ymag(); + let zfar = orthographic.zfar(); + let znear = orthographic.znear(); + let camera = GltfCamera::Orthographic { + xmag, + ymag, + zfar, + znear, + }; + camera + } }) .collect::>(); - let buffers = self.append_slice(&gltf_buffers); - - // Create the images in the atlas - let images = images.into_iter().map(SceneImage::from).collect::>(); - //let texture_params = document - // .textures() - // .map(|texture| { - // let index = texture.index(); - // let name = texture.name().map(String::from); - // let image_index = texture.source().index(); - - // fn mode(mode: gltf::texture::WrappingMode) -> TextureAddressMode { - // match mode { - // gltf::texture::WrappingMode::ClampToEdge => { - // TextureAddressMode::CLAMP_TO_EDGE - // } - // gltf::texture::WrappingMode::MirroredRepeat => { - // TextureAddressMode::MIRRORED_REPEAT - // } - // gltf::texture::WrappingMode::Repeat => TextureAddressMode::REPEAT, - // } - // } - // let mode_s = mode(texture.sampler().wrap_s()); - // let mode_t = mode(texture.sampler().wrap_t()); - // let params = TextureParams { - // image_index, - // mode_s, - // mode_t, - // }; - - // let texture_id = builder.add_texture(params); - // log::trace!( - // "adding texture index:{index} name:{name:?} id:{texture_id:?} with wrapping \ - // s:{mode_s} t:{mode_t}" - // ); - // let _ = self.textures.insert(index, name, texture_id); - // Ok(texture_id) - // }) - // .collect::>(); - - GltfLegend { buffers } + let cameras = self.append_array(&cameras); + + // We need the (re)packing of the atlas before we marshal the images into the GPU + // because we need their frames for textures and materials, but we need to know + // if the materials are require us to apply a linear transfer. So we'll get the + // preview repacking first, then update the frames in the textures. + let (mut repacking, atlas_offset) = { + // UNWRAP: if we can't lock the atlas, we want to panic. + let atlas = self.atlas.read().unwrap(); + let atlas_offset = atlas.rects.len(); + ( + atlas + .repack_preview(&self.device, images.into_iter().map(SceneImage::from)) + .context(AtlasSnafu)?, + atlas_offset, + ) + }; + + log::trace!("Creating GPU textures"); + let mut gpu_textures = vec![]; + for texture in document.textures() { + let image_index = texture.source().index(); + + fn mode(mode: gltf::texture::WrappingMode) -> TextureAddressMode { + match mode { + gltf::texture::WrappingMode::ClampToEdge => TextureAddressMode::CLAMP_TO_EDGE, + gltf::texture::WrappingMode::MirroredRepeat => { + TextureAddressMode::MIRRORED_REPEAT + } + gltf::texture::WrappingMode::Repeat => TextureAddressMode::REPEAT, + } + } + + let mode_s = mode(texture.sampler().wrap_s()); + let mode_t = mode(texture.sampler().wrap_t()); + let (offset_px, size_px) = + repacking + .get_frame(image_index + atlas_offset) + .context(MissingImageSnafu { + index: image_index, + offset: atlas_offset, + })?; + gpu_textures.push(GpuTexture { + offset_px, + size_px, + modes: TextureModes::default() + .with_wrap_s(mode_s) + .with_wrap_t(mode_t), + atlas_index: (image_index + atlas_offset) as u32, + }); + } + let gpu_textures = gpu_textures; + let textures = self.append_array(&gpu_textures); + + log::trace!("Creating materials"); + let mut gpu_materials = vec![]; + for material in document.materials() { + let index = material.index(); + let name = material.name().map(String::from); + log::trace!("loading material {index:?} {name:?}"); + let pbr = material.pbr_metallic_roughness(); + let material = if material.unlit() { + log::trace!(" is unlit"); + let (albedo_texture, albedo_tex_coord) = + if let Some(info) = pbr.base_color_texture() { + let texture = info.texture(); + let index = texture.index(); + let tex_id = textures.at(index); + // The index of the image in the original gltf document + let image_index = texture.source().index(); + let image = repacking + .get_mut(atlas_offset + image_index) + .context(MissingImageSnafu { + index: image_index, + offset: atlas_offset, + })? + .as_scene_img_mut() + .context(WrongImageSnafu { + index: image_index, + offset: atlas_offset, + })?; + image.apply_linear_transfer = true; + (tex_id, info.tex_coord()) + } else { + (Id::NONE, 0) + }; + + PbrMaterial { + albedo_texture, + albedo_tex_coord, + albedo_factor: pbr.base_color_factor().into(), + ..Default::default() + } + } else { + log::trace!(" is pbr"); + let albedo_factor: Vec4 = pbr.base_color_factor().into(); + let (albedo_texture, albedo_tex_coord) = + if let Some(info) = pbr.base_color_texture() { + let texture = info.texture(); + let index = texture.index(); + let tex_id = textures.at(index); + let image_index = texture.source().index(); + let image = repacking + .get_mut(image_index + atlas_offset) + .context(MissingImageSnafu { + index: image_index, + offset: atlas_offset, + })? + .as_scene_img_mut() + .context(WrongImageSnafu { + index: image_index, + offset: atlas_offset, + })?; + image.apply_linear_transfer = true; + (tex_id, info.tex_coord()) + } else { + (Id::NONE, 0) + }; + + let ( + metallic_factor, + roughness_factor, + metallic_roughness_texture, + metallic_roughness_tex_coord, + ) = if let Some(info) = pbr.metallic_roughness_texture() { + let index = info.texture().index(); + let tex_id = textures.at(index); + (1.0, 1.0, tex_id, info.tex_coord()) + } else { + (pbr.metallic_factor(), pbr.roughness_factor(), Id::NONE, 0) + }; + + let (normal_texture, normal_tex_coord) = + if let Some(norm_tex) = material.normal_texture() { + let tex_id = textures.at(norm_tex.texture().index()); + (tex_id, norm_tex.tex_coord()) + } else { + (Id::NONE, 0) + }; + + let (ao_strength, ao_texture, ao_tex_coord) = + if let Some(occlusion_tex) = material.occlusion_texture() { + let tex_id = textures.at(occlusion_tex.texture().index()); + (occlusion_tex.strength(), tex_id, occlusion_tex.tex_coord()) + } else { + (0.0, Id::NONE, 0) + }; + + let (emissive_texture, emissive_tex_coord) = + if let Some(emissive_tex) = material.emissive_texture() { + let texture = emissive_tex.texture(); + let index = texture.index(); + let tex_id = textures.at(index); + let image_index = texture.source().index(); + let image = repacking + .get_mut(image_index + atlas_offset) + .context(MissingImageSnafu { + index: image_index, + offset: atlas_offset, + })? + .as_scene_img_mut() + .context(WrongImageSnafu { + index: image_index, + offset: atlas_offset, + })?; + image.apply_linear_transfer = true; + (tex_id, emissive_tex.tex_coord()) + } else { + (Id::NONE, 0) + }; + let emissive_factor = Vec3::from(material.emissive_factor()) + .extend(material.emissive_strength().unwrap_or(1.0)); + + PbrMaterial { + albedo_factor, + metallic_factor, + roughness_factor, + albedo_texture, + metallic_roughness_texture, + normal_texture, + ao_texture, + albedo_tex_coord, + metallic_roughness_tex_coord, + normal_tex_coord, + ao_tex_coord, + ao_strength, + emissive_factor, + emissive_texture, + emissive_tex_coord, + lighting_model: LightingModel::PBR_LIGHTING, + ..Default::default() + } + }; + gpu_materials.push(material); + } + let gpu_materials = gpu_materials; + let materials = self.append_array(&gpu_materials); + + log::trace!("Packing the atlas"); + { + // UNWRAP: if we can't lock the atlas, we want to panic. + let mut atlas = self.atlas.write().unwrap(); + let new_atlas = atlas + .commit_repack_preview(&self.device, &self.queue, repacking) + .context(AtlasSnafu)?; + *atlas = new_atlas; + } + + log::trace!("Loading meshes"); + let meshes = self.allocate_array::(document.meshes().len()); + for mesh in document.meshes() { + let primitives = self.allocate_array::(mesh.primitives().len()); + for (j, primitive) in mesh.primitives().enumerate() { + debug_assert_eq!(j, primitive.index()); + let material = primitive + .material() + .index() + .map(|i| materials.at(i)) + .unwrap_or(Id::NONE); + let indices = primitive + .indices() + .map(|acc| accessors.at(acc.index())) + .unwrap_or_default(); + let positions = primitive + .get(&gltf::Semantic::Positions) + .map(|acc| accessors.at(acc.index())) + .unwrap_or_default(); + let normals = primitive + .get(&gltf::Semantic::Normals) + .map(|acc| accessors.at(acc.index())) + .unwrap_or_default(); + let tangents = primitive + .get(&gltf::Semantic::Tangents) + .map(|acc| accessors.at(acc.index())) + .unwrap_or_default(); + let colors = primitive + .get(&gltf::Semantic::Colors(0)) + .map(|acc| accessors.at(acc.index())) + .unwrap_or_default(); + let tex_coords0 = primitive + .get(&gltf::Semantic::TexCoords(0)) + .map(|acc| accessors.at(acc.index())) + .unwrap_or_default(); + let tex_coords1 = primitive + .get(&gltf::Semantic::TexCoords(1)) + .map(|acc| accessors.at(acc.index())) + .unwrap_or_default(); + let joints = primitive + .get(&gltf::Semantic::Joints(0)) + .map(|acc| accessors.at(acc.index())) + .unwrap_or_default(); + let weights = primitive + .get(&gltf::Semantic::Weights(0)) + .map(|acc| accessors.at(acc.index())) + .unwrap_or_default(); + + self.write( + primitives.at(primitive.index()), + &GltfPrimitive { + material, + indices, + positions, + normals, + tangents, + colors, + tex_coords0, + tex_coords1, + joints, + weights, + }, + )?; + } + let weights = mesh.weights().unwrap_or(&[]); + let weights = self.append_array(weights); + self.write( + meshes.at(mesh.index()), + &GltfMesh { + primitives, + weights, + }, + )?; + } + log::trace!("Loading lights"); + let lights_array = self.allocate_array::( + document + .lights() + .map(|lights| lights.len()) + .unwrap_or_default(), + ); + if let Some(lights) = document.lights() { + for light in lights { + let light_index = light.index(); + let color = Vec3::from(light.color()); + let range = light.range().unwrap_or(f32::MAX); + let intensity = light.intensity(); + let kind = match light.kind() { + gltf::khr_lights_punctual::Kind::Directional => GltfLightKind::Directional, + gltf::khr_lights_punctual::Kind::Point => GltfLightKind::Point, + gltf::khr_lights_punctual::Kind::Spot { + inner_cone_angle, + outer_cone_angle, + } => GltfLightKind::Spot { + inner_cone_angle, + outer_cone_angle, + }, + }; + self.write( + lights_array.at(light_index), + &GltfLight { + color, + range, + intensity, + kind, + }, + )?; + } + } + let lights = lights_array; + + // Preallocate nodes and skins so we can reference their ids before + // we write them to the slab. + let nodes = self.allocate_array::(document.nodes().len()); + let skins = self.allocate_array::(document.skins().len()); + + log::trace!("Loading nodes"); + for (i, node) in document.nodes().enumerate() { + let node_index = node.index(); + debug_assert_eq!(i, node_index); + let children = node + .children() + .map(|node| nodes.at(node.index())) + .collect::>(); + let children = self.append_array(&children); + let (translation, rotation, scale) = node.transform().decomposed(); + let translation = Vec3::from(translation); + let rotation = Quat::from_array(rotation); + let scale = Vec3::from(scale); + let mesh = node + .mesh() + .map(|mesh| meshes.at(mesh.index())) + .unwrap_or_default(); + let skin = node + .skin() + .map(|skin| skins.at(skin.index())) + .unwrap_or_default(); + let camera = node + .camera() + .map(|camera| cameras.at(camera.index())) + .unwrap_or_default(); + let light = node + .light() + .map(|light| lights.at(light.index())) + .unwrap_or_default(); + let weights = if let Some(weights) = node.weights() { + self.append_array(weights) + } else { + Array::default() + }; + self.write( + nodes.at(node_index), + &GltfNode { + children, + translation, + rotation, + scale, + mesh, + camera, + weights, + light, + skin, + }, + )?; + } + + log::trace!("Loading skins"); + for skin in document.skins() { + let skin_index = skin.index(); + let joints = skin + .joints() + .map(|node| nodes.at(node.index())) + .collect::>(); + let joints = self.append_array(&joints); + let inverse_bind_matrices = skin + .inverse_bind_matrices() + .map(|acc| accessors.at(acc.index())) + .unwrap_or_default(); + let skeleton = skin + .skeleton() + .map(|node| nodes.at(node.index())) + .unwrap_or_default(); + let _ = self.write( + skins.at(skin_index), + &GltfSkin { + joints, + inverse_bind_matrices, + skeleton, + }, + ); + } + + log::trace!("Loading animations"); + let animations = self.allocate_array::(document.animations().count()); + for animation in document.animations() { + let samplers = + self.allocate_array::(animation.samplers().count()); + fn create_sampler( + accessors: Array, + sampler: gltf::animation::Sampler<'_>, + ) -> GltfAnimationSampler { + let interpolation = match sampler.interpolation() { + gltf::animation::Interpolation::Linear => GltfInterpolation::Linear, + gltf::animation::Interpolation::Step => GltfInterpolation::Step, + gltf::animation::Interpolation::CubicSpline => GltfInterpolation::CubicSpline, + }; + let input = accessors.at(sampler.input().index()); + let output = accessors.at(sampler.output().index()); + GltfAnimationSampler { + interpolation, + input, + output, + } + } + let mut stored_samplers = vec![]; + for (i, sampler) in animation.samplers().enumerate() { + let sampler = create_sampler(accessors, sampler); + self.write(samplers.at(i), &sampler)?; + // Store it later so we can figure out the index of the sampler + // used by the channel. + stored_samplers.push(sampler); + } + let channels = self.allocate_array::(animation.channels().count()); + for (i, channel) in animation.channels().enumerate() { + let target = channel.target(); + let node = nodes.at(target.node().index()); + let property = match target.property() { + gltf::animation::Property::Translation => GltfTargetProperty::Translation, + gltf::animation::Property::Rotation => GltfTargetProperty::Rotation, + gltf::animation::Property::Scale => GltfTargetProperty::Scale, + gltf::animation::Property::MorphTargetWeights => { + GltfTargetProperty::MorphTargetWeights + } + }; + let target = GltfTarget { node, property }; + let sampler = create_sampler(accessors, channel.sampler()); + let index = stored_samplers + .iter() + .position(|s| s == &sampler) + .context(MissingSamplerSnafu)?; + let sampler = samplers.at(index); + self.write(channels.at(i), &GltfChannel { target, sampler })?; + } + self.write( + animations.at(animation.index()), + &GltfAnimation { channels, samplers }, + )?; + } + + log::trace!("Loading scenes"); + let scenes = self.allocate_array::(document.scenes().len()); + for scene in document.scenes() { + let nodes = scene + .nodes() + .map(|node| nodes.at(node.index())) + .collect::>(); + let nodes = self.append_array(&nodes); + self.write(scenes.at(scene.index()), &GltfScene { nodes })?; + } + + log::trace!("Done loading gltf"); + + Ok(GltfDocument { + accessors, + animations, + buffers, + cameras, + materials, + meshes, + nodes, + scenes, + skins, + textures, + views, + }) } } #[cfg(test)] mod test { - //use glam::Vec3; - - //use crate::Renderling; - - //#[test] - //fn normal_mapping_brick_sphere() { - // let size = 600; - // let mut r = Renderling::headless(size, size) - // .unwrap() - // .with_background_color(Vec3::splat(1.0).extend(1.0)); - // let (device, queue) = r.get_device_and_queue_owned(); - - // let stage = Stage::new(device, queue, StageLegend::default()); - // let (document, buffers, images) = - // gltf::import(path).map_err(|source| gltf_support::GltfLoaderError::Gltf { - // source, - // cwd: std::env::current_dir() - // .map(|p| format!("{}", p.display())) - // .unwrap_or("?".to_string()), - // })?; - // let loader = builder.gltf_load("../../gltf/red_brick_03_1k.glb").unwrap(); - // let (projection, view) = loader.cameras.get(0).copied().unwrap(); - // builder.set_camera(projection, view); - - // let scene = builder.build().unwrap(); - // r.setup_render_graph(RenderGraphConfig { - // scene: Some(scene), - // with_screen_capture: true, - // ..Default::default() - // }); - - // let img = r.render_image().unwrap(); - // println!("saving frame"); - // img_diff::assert_img_eq("gltf_normal_mapping_brick_sphere.png", img); - //} + use glam::Vec3; + + use crate::{Renderling, Stage}; + + #[test] + fn stage_normal_mapping_brick_sphere() { + crate::init_logging(); + let size = 600; + let mut r = Renderling::headless(size, size) + .unwrap() + .with_background_color(Vec3::splat(1.0).extend(1.0)); + let (device, queue) = r.get_device_and_queue_owned(); + let stage = Stage::new(device, queue); + stage.configure_graph(&mut r, true); + + log::trace!("Reading gltf"); + let (document, buffers, images) = gltf::import("../../gltf/red_brick_03_1k.glb").unwrap(); + log::trace!("Loading gltf"); + let _doc = stage.load_gltf_document(document, buffers, images).unwrap(); + } } diff --git a/crates/renderling/src/tutorial.rs b/crates/renderling/src/tutorial.rs index e230ee5c..bb817691 100644 --- a/crates/renderling/src/tutorial.rs +++ b/crates/renderling/src/tutorial.rs @@ -8,10 +8,8 @@ mod test { frame::FrameTextureView, graph::{graph, Graph, GraphError, View}, shader::{ - self, array::Array, id::Id, - slab::{Slab, Slabbed}, stage::{Camera, RenderUnit, Vertex}, }, DepthTexture, Device, Queue, Renderling, @@ -128,7 +126,7 @@ mod test { // Create our geometry on the slab. // Don't worry too much about capacity, it can grow. let slab = crate::slab::SlabBuffer::new(&device, 16); - let vertices = slab.append_slice( + let vertices = slab.append_array( &device, &queue, &[ @@ -332,7 +330,7 @@ mod test { ..Default::default() }, ]; - let vertices = slab.append_slice(&device, &queue, &geometry); + let vertices = slab.append_array(&device, &queue, &geometry); let vertices_id = slab.append(&device, &queue, &vertices); // Create a bindgroup for the slab so our shader can read out the types. @@ -521,7 +519,7 @@ mod test { ..Default::default() }, ]; - let vertices = slab.append_slice(&device, &queue, &geometry); + let vertices = slab.append_array(&device, &queue, &geometry); let unit = RenderUnit { vertices, ..Default::default() @@ -714,7 +712,7 @@ mod test { ..Default::default() }, ]; - let vertices = slab.append_slice(&device, &queue, &geometry); + let vertices = slab.append_array(&device, &queue, &geometry); let (projection, view) = crate::default_ortho2d(100.0, 100.0); let camera_id = slab.append( &device, diff --git a/rustfmt.toml b/rustfmt.toml index fcb4fe46..e8294147 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,4 +1,7 @@ -wrap_comments = true +blank_lines_upper_bound = 2 +combine_control_expr = false format_code_in_doc_comments = true -normalize_comments = true format_strings = true +max_width = 100 +imports_granularity= "crate" +wrap_comments = true \ No newline at end of file