From f2b51c121ed3deac47d58f46905c7c8d180a6e9c Mon Sep 17 00:00:00 2001 From: chronicl Date: Thu, 22 May 2025 06:07:54 +0200 Subject: [PATCH 01/32] MVP type layout 2 --- examples/api_showcase/src/main.rs | 2 +- .../hello_triangles/src/util/shame_glam.rs | 6 +- examples/shame_wgpu/src/conversion.rs | 14 +- examples/type_layout/Cargo.toml | 7 + examples/type_layout/src/main.rs | 112 +++ shame/src/common/po2.rs | 38 +- shame/src/common/proc_macro_reexports.rs | 10 +- shame/src/common/proc_macro_utils.rs | 5 +- shame/src/frontend/any/render_io.rs | 17 +- shame/src/frontend/encoding/buffer.rs | 21 +- shame/src/frontend/encoding/io_iter.rs | 8 +- shame/src/frontend/rust_types/array.rs | 27 +- shame/src/frontend/rust_types/atomic.rs | 21 +- .../src/frontend/rust_types/layout_traits.rs | 95 +- shame/src/frontend/rust_types/mat.rs | 29 +- shame/src/frontend/rust_types/mod.rs | 2 +- shame/src/frontend/rust_types/packed_vec.rs | 34 +- shame/src/frontend/rust_types/struct_.rs | 18 +- shame/src/frontend/rust_types/type_layout.rs | 906 ------------------ .../type_layout/cpu_shareable/align_size.rs | 298 ++++++ .../type_layout/cpu_shareable/builder.rs | 165 ++++ .../type_layout/cpu_shareable/mod.rs | 493 ++++++++++ .../src/frontend/rust_types/type_layout/eq.rs | 452 +++++++++ .../frontend/rust_types/type_layout/mod.rs | 616 ++++++++++++ .../rust_types/type_layout/storage_uniform.rs | 475 +++++++++ .../frontend/rust_types/type_layout/vertex.rs | 100 ++ shame/src/frontend/rust_types/type_traits.rs | 21 +- shame/src/frontend/rust_types/vec.rs | 37 +- shame/src/ir/ir_type/align_size.rs | 4 +- shame/src/ir/ir_type/layout_constraints.rs | 6 +- shame/src/ir/ir_type/tensor.rs | 78 +- shame/src/ir/pipeline/wip_pipeline.rs | 17 +- shame/src/lib.rs | 11 +- shame/tests/test_layout.rs | 4 +- shame_derive/src/derive_layout.rs | 105 +- shame_derive/src/util.rs | 20 +- 36 files changed, 3163 insertions(+), 1111 deletions(-) create mode 100644 examples/type_layout/Cargo.toml create mode 100644 examples/type_layout/src/main.rs delete mode 100644 shame/src/frontend/rust_types/type_layout.rs create mode 100644 shame/src/frontend/rust_types/type_layout/cpu_shareable/align_size.rs create mode 100644 shame/src/frontend/rust_types/type_layout/cpu_shareable/builder.rs create mode 100644 shame/src/frontend/rust_types/type_layout/cpu_shareable/mod.rs create mode 100644 shame/src/frontend/rust_types/type_layout/eq.rs create mode 100644 shame/src/frontend/rust_types/type_layout/mod.rs create mode 100644 shame/src/frontend/rust_types/type_layout/storage_uniform.rs create mode 100644 shame/src/frontend/rust_types/type_layout/vertex.rs diff --git a/examples/api_showcase/src/main.rs b/examples/api_showcase/src/main.rs index a1b3eb8..f8e2d88 100644 --- a/examples/api_showcase/src/main.rs +++ b/examples/api_showcase/src/main.rs @@ -509,4 +509,4 @@ impl sm::CpuLayout for Mat4 { // // tell `shame` about the layout semantics of `glam` types // impl MyCpuLayoutTrait for glam::Mat4 { // fn layout() -> shame::TypeLayout { sm::f32x4x4::layout() } -// } +// } \ No newline at end of file diff --git a/examples/hello_triangles/src/util/shame_glam.rs b/examples/hello_triangles/src/util/shame_glam.rs index 7f85092..6cf3441 100644 --- a/examples/hello_triangles/src/util/shame_glam.rs +++ b/examples/hello_triangles/src/util/shame_glam.rs @@ -14,14 +14,14 @@ pub trait CpuLayoutExt { // glam::Vec4 matches sm::f32x4 in size and alignment impl CpuLayoutExt for glam::Vec4 { - fn cpu_layout() -> sm::TypeLayout { sm::f32x4::gpu_layout() } + fn cpu_layout() -> sm::TypeLayout { sm::f32x4::gpu_layout().into() } } // glam::Vec2 only matches sm::f32x2 if it has 8 byte alignment impl CpuLayoutExt for glam::Vec2 { fn cpu_layout() -> sm::TypeLayout { if align_of::() == 8 { - sm::f32x2::gpu_layout() + sm::f32x2::gpu_layout().into() } else { panic!("glam needs to use the `cuda` crate feature for Vec2 to be 8 byte aligned"); } @@ -30,5 +30,5 @@ impl CpuLayoutExt for glam::Vec2 { // glam::Mat4 matches sm::f32x4x4 in size and alignment impl CpuLayoutExt for glam::Mat4 { - fn cpu_layout() -> sm::TypeLayout { sm::f32x4x4::gpu_layout() } + fn cpu_layout() -> sm::TypeLayout { sm::f32x4x4::gpu_layout().into() } } diff --git a/examples/shame_wgpu/src/conversion.rs b/examples/shame_wgpu/src/conversion.rs index 16c7ad6..05b6d73 100644 --- a/examples/shame_wgpu/src/conversion.rs +++ b/examples/shame_wgpu/src/conversion.rs @@ -101,7 +101,7 @@ pub fn sample_type(st: sm::TextureSampleUsageType) -> wgpu::TextureSampleType { } } -/// converts `tf` into a `wgpu::TextureFormat` if supported. +/// converts `tf` into a `wgpu::TextureFormat` if supported. /// If `tf` is `ExtraTextureFormats::SurfaceFormat`, then the provided `surface_format` argument /// is returned if it is `Some`. Otherwise an error is returned. #[rustfmt::skip] @@ -194,7 +194,7 @@ pub fn texture_format(tf: &dyn sm::TextureFormatId, surface_format: Option wgpu::TextureFormat::EacR11Snorm, SmTf::EacRg11Unorm => wgpu::TextureFormat::EacRg11Unorm, SmTf::EacRg11Snorm => wgpu::TextureFormat::EacRg11Snorm, - SmTf::Astc { block, channel } => wgpu::TextureFormat::Astc { + SmTf::Astc { block, channel } => wgpu::TextureFormat::Astc { block: match block { SmASTCb::B4x4 => wgpu::AstcBlock::B4x4, SmASTCb::B5x4 => wgpu::AstcBlock::B5x4, @@ -210,12 +210,12 @@ pub fn texture_format(tf: &dyn sm::TextureFormatId, surface_format: Option wgpu::AstcBlock::B10x10, SmASTCb::B12x10 => wgpu::AstcBlock::B12x10, SmASTCb::B12x12 => wgpu::AstcBlock::B12x12, - }, + }, channel: match channel { SmASTCc::Unorm => wgpu::AstcChannel::Unorm, SmASTCc::UnormSrgb => wgpu::AstcChannel::UnormSrgb, SmASTCc::Hdr => wgpu::AstcChannel::Hdr, - } + } }, }; Ok(wtf) @@ -449,7 +449,7 @@ fn color_writes(write_mask: smr::ChannelWrites) -> wgpu::ColorWrites { #[rustfmt::skip] fn vertex_format(format: smr::VertexAttribFormat) -> Result { - use smr::ScalarType as S; + use shame::cpu_shareable::ScalarType as S; use smr::Len as L; use wgpu::VertexFormat as W; let unsupported = Err(ShameToWgpuError::UnsupportedVertexAttribFormat(format)); @@ -479,10 +479,8 @@ fn vertex_format(format: smr::VertexAttribFormat) -> Result W::Sint32x2, (S::I32, L::X3) => W::Sint32x3, (S::I32, L::X4) => W::Sint32x4, - - (S::Bool, _) => return unsupported, }, - + smr::VertexAttribFormat::Coarse(p) => { use smr::PackedScalarType as PS; use smr::PackedFloat as Norm; diff --git a/examples/type_layout/Cargo.toml b/examples/type_layout/Cargo.toml new file mode 100644 index 0000000..9d53846 --- /dev/null +++ b/examples/type_layout/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "type_layout2" +version = "0.1.0" +edition = "2024" + +[dependencies] +shame = { path = "../../shame/" } diff --git a/examples/type_layout/src/main.rs b/examples/type_layout/src/main.rs new file mode 100644 index 0000000..2b37fdf --- /dev/null +++ b/examples/type_layout/src/main.rs @@ -0,0 +1,112 @@ +#![allow(dead_code, unused)] +//! Demonstration of the TypeLayout and TypeLayout Builder API. + +use shame::{ + any::{self, U32PowerOf2}, + boolx1, + cpu_shareable::{self as cs, BinaryReprSized}, + f32x1, f32x2, f32x3, f32x4, + type_layout::*, + Array, GpuLayout, GpuSized, VertexAttribute, VertexLayout, +}; + +fn main() { + // We'll start by building a `TypeLayout`, which can be used for ... nothing really + #[derive(GpuLayout)] + struct Vertex { + position: f32x3, + normal: f32x3, + uv: f32x1, + } + + // TypeLayout::vertex_builder immediately takes the first field of the struct, because + // structs need to have at least one field. + let mut builder = + TypeLayout::vertex_builder("Vertex", "position", f32x4::vertex_attrib_format(), cs::Repr::Storage) + .extend("normal", f32x3::vertex_attrib_format()) + .extend("uv", f32x1::vertex_attrib_format()) + .finish(); + + + // Now we'll replicate the layout of this struct + #[derive(GpuLayout)] + struct A { + a: f32x4, + b: f32x3, + c: Array, + } + + // Be default structs are #[gpu_repr(Storage)], which means that it follows + // the wgsl storage layout rules (std430). To obtain a corresponding TypeLayout + // we first need to build a `CpuShareableType`, in our case an `UnsizedStruct`. + let unsized_struct = cs::UnsizedStruct { + name: "A".into(), + sized_fields: vec![ + cs::SizedField::new("a", cs::Vector::new(cs::ScalarType::F32, cs::Len::X4)), + cs::SizedField::new("b", f32x3::layout_type_sized()), + ], + last_unsized: cs::RuntimeSizedArrayField::new("c", None, f32x1::layout_type_sized()), + }; + // And now we can get the `TypeLayout`. + let s_layout = TypeLayout::new_storage_layout_for(unsized_struct.clone()); + // For now `TypeLayout::::new_layout_for` only accepts sized types, + // however `TypeLayout::::new_layout_for_unchecked` allows to obtain the + // the uniform layout of an unsized cpu-shareable. Using that layout with wgsl as your + // target language will cause an error. + let u_layout = TypeLayout::new_uniform_layout_for(unsized_struct); + // This struct's field offsets are different for storage and uniform layout rules. The array + // has an alignment of 4 with storage alignment and an alignment of 16 with uniform alignment. + assert_ne!(s_layout, u_layout); + + #[derive(GpuLayout)] + struct B { + a: f32x4, + b: f32x3, + c: f32x1, + } + + // Sized structs require a builder to ensure it always contains at least one field. + let mut sized_struct = cs::SizedStruct::new("B", "b", f32x4::layout_type_sized()) + .extend("b", f32x3::layout_type_sized()) + .extend("c", f32x1::layout_type_sized()); + // Since this struct is sized we can use TypeLayout::::new_layout_for. + let u_layout = TypeLayout::new_uniform_layout_for(sized_struct.clone()); + let s_layout = TypeLayout::new_storage_layout_for(sized_struct); + // And this time they are equal, despite different layout rules. + assert_eq!(s_layout, u_layout); + // Using `TryFrom::try_from` we can check whether the storage type layout also follows + // uniform layout rules despite not being `TypeLayout`, + // which in this case will succeed, but if it doesn't we get a very nice error message about + // why the layout is not compatible with the uniform layout rules. + let u_layout = TypeLayout::::try_from(&s_layout).unwrap(); + + // Let's replicate a more complex example with explicit field size and align. + #[derive(shame::GpuLayout)] + struct C { + a: f32x2, + #[size(16)] + b: f32x1, + #[align(16)] + c: f32x2, + } + + let mut sized_struct = cs::SizedStruct::new("C", "a", f32x3::layout_type_sized()) + .extend(FieldOptions::new("b", None, Some(16)), f32x3::layout_type_sized()) + .extend( + FieldOptions::new("c", Some(U32PowerOf2::_16), None), + f32x1::layout_type_sized(), + ); + let layout = TypeLayout::new_storage_layout_for(sized_struct); + assert!(layout.byte_align.as_u32() == 16); + + // Let's end on a pretty error message + let mut sized_struct = cs::SizedStruct::new("D", "a", f32x2::layout_type_sized()) + // This has align of 4 for storage and align of 16 for uniform. + .extend("b", Array::>::layout_type_sized()); + let s_layout = TypeLayout::new_storage_layout_for(sized_struct); + let result = TypeLayout::::try_from(&s_layout); + match result { + Err(e) => println!("This error is a showcase:\n{}", e), + Ok(u_layout) => println!("It unexpectedly worked, ohh no."), + } +} diff --git a/shame/src/common/po2.rs b/shame/src/common/po2.rs index 846e6c0..accf6a2 100644 --- a/shame/src/common/po2.rs +++ b/shame/src/common/po2.rs @@ -38,10 +38,11 @@ pub enum U32PowerOf2 { _2147483648, } -impl From for u32 { +impl U32PowerOf2 { + /// Returns the corresponding u32. #[rustfmt::skip] - fn from(value: U32PowerOf2) -> Self { - match value { + pub const fn as_u32(self) -> u32 { + match self { U32PowerOf2::_1 => 1_u32 , U32PowerOf2::_2 => 2_u32 , U32PowerOf2::_4 => 4_u32 , @@ -76,6 +77,18 @@ impl From for u32 { U32PowerOf2::_2147483648 => 2147483648_u32, } } + + /// Returns the corresponding u64. + pub const fn as_u64(self) -> u64 { self.as_u32() as u64 } +} + +impl From for u32 { + fn from(value: U32PowerOf2) -> Self { value.as_u32() } +} + +impl U32PowerOf2 { + /// Returns the maximum between `self` and `other`. + pub const fn max(self, other: Self) -> Self { if self as u32 > other as u32 { self } else { other } } } #[derive(Debug)] @@ -89,11 +102,10 @@ impl Display for NotAU32PowerOf2 { impl std::error::Error for NotAU32PowerOf2 {} -impl TryFrom for U32PowerOf2 { - type Error = NotAU32PowerOf2; - - fn try_from(value: u32) -> Result { - Ok(match value { +impl U32PowerOf2 { + /// Tries to convert a u32 to U32PowerOf2. + pub const fn try_from_u32(value: u32) -> Option { + Some(match value { 1 => U32PowerOf2::_1, 2 => U32PowerOf2::_2, 4 => U32PowerOf2::_4, @@ -126,11 +138,19 @@ impl TryFrom for U32PowerOf2 { 536870912 => U32PowerOf2::_536870912, 1073741824 => U32PowerOf2::_1073741824, 2147483648 => U32PowerOf2::_2147483648, - n => return Err(NotAU32PowerOf2(n)), + n => return None, }) } } +impl TryFrom for U32PowerOf2 { + type Error = NotAU32PowerOf2; + + fn try_from(value: u32) -> Result { + U32PowerOf2::try_from_u32(value).ok_or(NotAU32PowerOf2(value)) + } +} + impl From for u64 { fn from(value: U32PowerOf2) -> Self { u32::from(value) as u64 } } diff --git a/shame/src/common/proc_macro_reexports.rs b/shame/src/common/proc_macro_reexports.rs index 30170bf..86d2f64 100644 --- a/shame/src/common/proc_macro_reexports.rs +++ b/shame/src/common/proc_macro_reexports.rs @@ -25,8 +25,16 @@ pub use crate::frontend::rust_types::struct_::BufferFields; pub use crate::frontend::rust_types::struct_::SizedFields; pub use crate::frontend::rust_types::type_layout::FieldLayout; pub use crate::frontend::rust_types::type_layout::FieldLayoutWithOffset; +pub use crate::frontend::rust_types::type_layout::FieldOptions; pub use crate::frontend::rust_types::type_layout::StructLayout; -pub use crate::frontend::rust_types::type_layout::StructLayoutError; +pub use crate::frontend::rust_types::type_layout::cpu_shareable::SizedStruct; +pub use crate::frontend::rust_types::type_layout::cpu_shareable::BinaryRepr; +pub use crate::frontend::rust_types::type_layout::cpu_shareable::BinaryReprSized; +pub use crate::frontend::rust_types::type_layout::cpu_shareable::LayoutType; +pub use crate::frontend::rust_types::type_layout::cpu_shareable::SizedType; +pub use crate::frontend::rust_types::type_layout::cpu_shareable::SizedOrArray; +pub use crate::frontend::rust_types::type_layout::cpu_shareable::Repr; +pub use crate::frontend::rust_types::type_layout::cpu_shareable::StructFromPartsError; pub use crate::frontend::rust_types::type_layout::TypeLayout; pub use crate::frontend::rust_types::type_layout::TypeLayoutRules; pub use crate::frontend::rust_types::type_layout::TypeLayoutSemantics; diff --git a/shame/src/common/proc_macro_utils.rs b/shame/src/common/proc_macro_utils.rs index f392681..7e22a09 100644 --- a/shame/src/common/proc_macro_utils.rs +++ b/shame/src/common/proc_macro_utils.rs @@ -95,9 +95,11 @@ pub fn repr_c_struct_layout( Some(repr_c_align) => max_alignment.max(repr_c_align), None => max_alignment, }; + let struct_alignment = U32PowerOf2::try_from(struct_alignment as u32).unwrap(); let last_field_size = last_field_size.map(|s| s as u64); - let total_struct_size = last_field_size.map(|last_size| round_up(struct_alignment, last_field_offset + last_size)); + let total_struct_size = + last_field_size.map(|last_size| round_up(struct_alignment.as_u64(), last_field_offset + last_size)); let mut fields = first_fields_with_offsets_and_sizes .iter() @@ -132,6 +134,7 @@ pub fn repr_c_struct_layout( name: struct_name.into(), fields, })), + None, )) } diff --git a/shame/src/frontend/any/render_io.rs b/shame/src/frontend/any/render_io.rs index 5640f27..a963506 100644 --- a/shame/src/frontend/any/render_io.rs +++ b/shame/src/frontend/any/render_io.rs @@ -2,8 +2,10 @@ use std::{fmt::Display, rc::Rc}; use thiserror::Error; +use crate::cpu_shareable; use crate::frontend::any::Any; use crate::frontend::rust_types::type_layout::TypeLayout; +use crate::type_layout::cpu_shareable::array_stride; use crate::{ call_info, common::iterator_ext::try_collect, @@ -108,7 +110,7 @@ impl Any { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum VertexAttribFormat { /// regular [`crate::vec`] types - Fine(Len, ScalarType), + Fine(Len, cpu_shareable::ScalarType), /// packed [`crate::packed::PackedVec`] types Coarse(PackedVector), } @@ -238,7 +240,7 @@ impl VertexAttribFormat { #[allow(missing_docs)] pub fn type_in_shader(self) -> SizedType { match self { - VertexAttribFormat::Fine(l, t) => SizedType::Vector(l, t), + VertexAttribFormat::Fine(l, t) => SizedType::Vector(l, t.into()), VertexAttribFormat::Coarse(coarse) => coarse.decompressed_ty(), } } @@ -251,16 +253,16 @@ impl Attrib { ) -> Option<(Box<[Attrib]>, u64)> { let stride = { let size = layout.byte_size()?; - stride_of_array_from_element_align_size(layout.align(), size) + array_stride(layout.byte_align(), size) }; use TypeLayoutSemantics as TLS; let attribs: Box<[Attrib]> = match &layout.kind { - TLS::Matrix(..) | TLS::Array(..) | TLS::Vector(_, ScalarType::Bool) => return None, - TLS::Vector(len, non_bool) => [Attrib { + TLS::Matrix(..) | TLS::Array(..) => return None, + TLS::Vector(v) => [Attrib { offset: 0, location: location_counter.next(), - format: VertexAttribFormat::Fine(*len, *non_bool), + format: VertexAttribFormat::Fine(v.len, v.scalar), }] .into(), TLS::PackedVector(packed_vector) => [Attrib { @@ -274,8 +276,7 @@ impl Attrib { offset: f.rel_byte_offset, location: location_counter.next(), format: match f.field.ty.kind { - TLS::Vector(_, ScalarType::Bool) => return None, - TLS::Vector(len, non_bool) => Some(VertexAttribFormat::Fine(len, non_bool)), + TLS::Vector(v) => Some(VertexAttribFormat::Fine(v.len, v.scalar)), TLS::PackedVector(packed_vector) => Some(VertexAttribFormat::Coarse(packed_vector)), TLS::Matrix(..) | TLS::Array(..) | TLS::Structure(..) => None, }?, diff --git a/shame/src/frontend/encoding/buffer.rs b/shame/src/frontend/encoding/buffer.rs index 50ad927..ee857c4 100644 --- a/shame/src/frontend/encoding/buffer.rs +++ b/shame/src/frontend/encoding/buffer.rs @@ -17,7 +17,8 @@ use crate::frontend::rust_types::{reference::AccessModeReadable, scalar_type::Sc use crate::ir::pipeline::StageMask; use crate::ir::recording::{Context, MemoryRegion}; use crate::ir::Type; -use crate::{self as shame, call_info, ir}; +use crate::type_layout::cpu_shareable::BinaryReprSized; +use crate::{self as shame, call_info, ir, GpuLayout}; use std::borrow::Borrow; use std::marker::PhantomData; @@ -97,7 +98,7 @@ where impl Buffer where - T: GpuStore + NoHandles + NoAtomics + NoBools, + T: GpuStore + NoHandles + NoAtomics + NoBools + GpuLayout, AS: BufferAddressSpace, { #[track_caller] @@ -114,7 +115,7 @@ where impl BufferRef where - T: GpuStore + NoHandles + NoBools, + T: GpuStore + NoHandles + NoBools + GpuLayout, AS: BufferAddressSpace, AM: AccessModeReadable, { @@ -294,7 +295,7 @@ impl BufferRefInner #[rustfmt::skip] impl Binding for Buffer where - T: GpuSized + T: GpuSized+ GpuLayout { fn binding_type() -> BindingType { BufferInner::::binding_type(DYN_OFFSET) } #[track_caller] @@ -316,9 +317,9 @@ fn store_type_from_impl_category(category: GpuStoreImplCategory) -> ir::StoreTyp } #[rustfmt::skip] impl -Binding for Buffer, AS, DYN_OFFSET> -where - T: GpuType + GpuSized +Binding for Buffer, AS, DYN_OFFSET> +where + T: GpuType + GpuSized + GpuLayout + BinaryReprSized { fn binding_type() -> BindingType { BufferInner::::binding_type(DYN_OFFSET) } #[track_caller] @@ -464,7 +465,7 @@ where /// /// // field access returns references /// let world: sm::Ref = buffer.world; -/// +/// /// // get fields via `.get()` /// let matrix: f32x4x4 = buffer.world.get(); /// @@ -488,7 +489,7 @@ where pub(crate) inner: BufferRefInner, } -#[rustfmt::skip] impl +#[rustfmt::skip] impl Binding for BufferRef where AS: BufferAddressSpace + SupportsAccess, @@ -497,7 +498,7 @@ where fn binding_type() -> BindingType { BufferRefInner::::binding_type(DYN_OFFSET) } #[track_caller] fn new_binding(args: Result) -> Self { BufferRef::new(args) } - + fn store_ty() -> ir::StoreType { store_type_from_impl_category(T::impl_category()) } diff --git a/shame/src/frontend/encoding/io_iter.rs b/shame/src/frontend/encoding/io_iter.rs index 67a8511..e3e20af 100644 --- a/shame/src/frontend/encoding/io_iter.rs +++ b/shame/src/frontend/encoding/io_iter.rs @@ -107,7 +107,7 @@ impl VertexBuffer<'_, T> { let call_info = call_info!(); let attribs_and_stride = Context::try_with(call_info, |ctx| { let skip_stride_check = false; // it is implied that T is in an array, the strides must match - let gpu_layout = get_layout_compare_with_cpu_push_error::(ctx, skip_stride_check); + let gpu_layout = get_layout_compare_with_cpu_push_error::(ctx, skip_stride_check).into_plain(); let attribs_and_stride = Attrib::get_attribs_and_stride(&gpu_layout, &location_counter).ok_or_else(|| { ctx.push_error(FrontendError::MalformedVertexBufferLayout(gpu_layout).into()); @@ -454,13 +454,13 @@ impl BindingIter<'_> { /// let texarr: sm::TextureArray = bind_group.next(); /// let texarr: sm::TextureArray, 4> = bind_group.next(); /// ``` - /// --- + /// --- /// ## storage textures /// ``` /// let texsto: sm::StorageTexture = bind_group.next(); /// let texsto: sm::StorageTexture = bind_group.next(); /// ``` - /// --- + /// --- /// ## Arrays of storage textures /// ``` /// let texstoarr: sm::StorageTextureArray = bind_group.next(); @@ -558,7 +558,7 @@ impl PushConstants<'_> { #[track_caller] pub fn get(self) -> T where - T: GpuStore + GpuSized + NoAtomics + NoBools, + T: GpuStore + GpuSized + NoAtomics + NoBools + GpuLayout, { let _caller_scope = Context::call_info_scope(); diff --git a/shame/src/frontend/rust_types/array.rs b/shame/src/frontend/rust_types/array.rs index 853e6e1..644d10a 100644 --- a/shame/src/frontend/rust_types/array.rs +++ b/shame/src/frontend/rust_types/array.rs @@ -13,6 +13,7 @@ use super::vec::{ToInteger, ToVec}; use super::{AsAny, GpuType}; use super::{To, ToGpuType}; use crate::common::small_vec::SmallVec; +use crate::cpu_shareable::{BinaryRepr, BinaryReprSized}; use crate::frontend::any::shared_io::{BindPath, BindingType}; use crate::frontend::any::Any; use crate::frontend::any::InvalidReason; @@ -24,7 +25,7 @@ use crate::frontend::rust_types::vec::vec; use crate::ir::ir_type::stride_of_array_from_element_align_size; use crate::ir::pipeline::StageMask; use crate::ir::recording::Context; -use crate::{call_info, for_count, ir}; +use crate::{call_info, cpu_shareable, for_count, ir}; use std::borrow::Cow; use std::marker::PhantomData; use std::num::NonZeroU32; @@ -155,19 +156,30 @@ impl GpuSized for Array> { #[rustfmt::skip] impl NoHandles for Array {} #[rustfmt::skip] impl NoAtomics for Array {} #[rustfmt::skip] impl NoBools for Array {} +impl BinaryReprSized for Array> { + fn layout_type_sized() -> cpu_shareable::SizedType { + cpu_shareable::SizedArray::new(T::layout_type_sized(), Size::::nonzero()).into() + } +} +impl BinaryRepr for Array { + fn layout_type() -> cpu_shareable::LayoutType { + match N::LEN { + Some(n) => cpu_shareable::SizedArray::new(T::layout_type_sized(), n).into(), + None => cpu_shareable::RuntimeSizedArray::new(T::layout_type_sized()).into(), + } + } +} impl ToGpuType for Array { type Gpu = Self; - - fn to_gpu(&self) -> Self::Gpu { self.clone() } fn as_gpu_type_ref(&self) -> Option<&Self::Gpu> { Some(self) } } -impl GpuLayout for Array { - fn gpu_layout() -> TypeLayout { TypeLayout::from_array(TypeLayoutRules::Wgsl, &T::sized_ty(), N::LEN) } +impl GpuLayout for Array { + fn gpu_repr() -> cpu_shareable::Repr { cpu_shareable::Repr::Storage } fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { let (t_cpu_name, t_cpu_layout) = match T::cpu_type_name_and_layout()? { @@ -187,14 +199,15 @@ impl GpuLayout for Array { name.into(), TypeLayout::new( N::LEN.map(|n| n.get() as u64 * t_cpu_size), - t_cpu_layout.align(), + t_cpu_layout.byte_align(), TypeLayoutSemantics::Array( Rc::new(ElementLayout { - byte_stride: stride_of_array_from_element_align_size(t_cpu_layout.align(), t_cpu_size), + byte_stride: cpu_shareable::array_stride(t_cpu_layout.byte_align(), t_cpu_size), ty: t_cpu_layout, }), N::LEN.map(NonZeroU32::get), ), + None, ), ); diff --git a/shame/src/frontend/rust_types/atomic.rs b/shame/src/frontend/rust_types/atomic.rs index e391b6b..cb239b8 100644 --- a/shame/src/frontend/rust_types/atomic.rs +++ b/shame/src/frontend/rust_types/atomic.rs @@ -6,7 +6,7 @@ use super::{ mem::{AddressSpace, AddressSpaceAtomic}, reference::{AccessMode, AccessModeReadable, ReadWrite}, scalar_type::{ScalarType, ScalarTypeInteger}, - type_layout::{TypeLayout, TypeLayoutRules}, + type_layout::{cpu_shareable::BinaryReprSized, TypeLayout, TypeLayoutRules}, type_traits::{ BindingArgs, EmptyRefFields, GpuAligned, GpuSized, GpuStore, GpuStoreImplCategory, NoAtomics, NoBools, NoHandles, @@ -14,7 +14,10 @@ use super::{ vec::vec, AsAny, GpuType, To, ToGpuType, }; -use crate::frontend::rust_types::reference::Ref; +use crate::{ + cpu_shareable::{self, BinaryRepr}, + frontend::rust_types::reference::Ref, +}; use crate::{ boolx1, frontend::{ @@ -129,8 +132,20 @@ impl GetAllFields for Atomic { fn fields_as_anys_unchecked(self_as_any: Any) -> impl std::borrow::Borrow<[Any]> { [] } } +impl BinaryReprSized for Atomic { + fn layout_type_sized() -> cpu_shareable::SizedType { + cpu_shareable::Atomic { + scalar: T::SCALAR_TYPE_INTEGER, + } + .into() + } +} +impl BinaryRepr for Atomic { + fn layout_type() -> cpu_shareable::LayoutType { Self::layout_type_sized().into() } +} + impl GpuLayout for Atomic { - fn gpu_layout() -> TypeLayout { TypeLayout::from_sized_ty(TypeLayoutRules::Wgsl, &::sized_ty()) } + fn gpu_repr() -> cpu_shareable::Repr { cpu_shareable::Repr::Storage } fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { diff --git a/shame/src/frontend/rust_types/layout_traits.rs b/shame/src/frontend/rust_types/layout_traits.rs index 96e9373..0b10946 100644 --- a/shame/src/frontend/rust_types/layout_traits.rs +++ b/shame/src/frontend/rust_types/layout_traits.rs @@ -1,6 +1,7 @@ use crate::call_info; use crate::common::po2::U32PowerOf2; use crate::common::proc_macro_utils::{self, repr_c_struct_layout, ReprCError, ReprCField}; +use crate::cpu_shareable::{BinaryRepr, BinaryReprSized, Repr}; use crate::frontend::any::render_io::{ Attrib, VertexBufferLookupIndex, Location, VertexAttribFormat, VertexBufferLayout, VertexLayoutError, }; @@ -21,9 +22,11 @@ use super::error::FrontendError; use super::mem::AddressSpace; use super::reference::{AccessMode, AccessModeReadable}; use super::struct_::{BufferFields, SizedFields, Struct}; +use super::type_layout::constraint::TypeConstraint; +use super::type_layout::cpu_shareable::{self, array_stride, Vector}; use super::type_layout::{ - ElementLayout, FieldLayout, FieldLayoutWithOffset, StructLayout, TypeLayout, TypeLayoutError, TypeLayoutRules, - TypeLayoutSemantics, + self, constraint, ElementLayout, FieldLayout, FieldLayoutWithOffset, StructLayout, TypeLayout, TypeLayoutError, + TypeLayoutRules, TypeLayoutSemantics, }; use super::type_traits::{ BindingArgs, GpuAligned, GpuSized, GpuStore, GpuStoreImplCategory, NoAtomics, NoBools, NoHandles, VertexAttribute, @@ -114,7 +117,7 @@ use std::rc::Rc; /// # Layout comparison of different types /// /// The layouts of different [`GpuLayout`]/[`CpuLayout`] types can be compared -/// by comparing [`TypeLayout`] objects returned by `.gpu_layout()`/`.cpu_layout()` +/// by comparing [`TypeLayout`] objects returned by `.gpu_layout()`/`.cpu_layout()` /// ``` /// use shame as sm; /// use sm::{ GpuLayout, CpuLayout }; @@ -135,14 +138,14 @@ use std::rc::Rc; /// [`Texture`]: crate::Texture /// [`StorageTexture`]: crate::StorageTexture /// -pub trait GpuLayout { +pub trait GpuLayout: BinaryRepr { /// returns a [`TypeLayout`] object that can be used to inspect the layout /// of a type on the gpu. /// /// # Layout comparison of different types /// /// The layouts of different [`GpuLayout`]/[`CpuLayout`] types can be compared - /// by comparing [`TypeLayout`] objects returned by `.gpu_layout()`/`.cpu_layout()` + /// by comparing [`TypeLayout`] objects returned by `.gpu_layout()`/`.cpu_layout()` /// ``` /// use shame as sm; /// use sm::{ GpuLayout, CpuLayout }; @@ -156,7 +159,10 @@ pub trait GpuLayout { /// println!("OnGpu:\n{}\n", OnGpu::gpu_layout()); /// println!("OnCpu:\n{}\n", OnCpu::cpu_layout()); /// ``` - fn gpu_layout() -> TypeLayout; + fn gpu_layout() -> TypeLayout { TypeLayout::new_layout_for(Self::layout_type(), Self::gpu_repr()) } + + /// TODO(chronicl) docs + fn gpu_repr() -> Repr; /// the `#[cpu(...)]` in `#[derive(GpuLayout)]` allows the definition of a /// corresponding Cpu type to the Gpu type that the derive macro is used on. @@ -201,15 +207,15 @@ pub(crate) fn get_layout_compare_with_cpu_push_error( gpu_layout } -pub(crate) fn check_layout_push_error( +pub(crate) fn check_layout_push_error( ctx: &Context, cpu_name: &str, - cpu_layout: &TypeLayout, - gpu_layout: &TypeLayout, + cpu_layout: &TypeLayout, + gpu_layout: &TypeLayout, skip_stride_check: bool, comment_on_mismatch_error: &str, ) -> Result<(), InvalidReason> { - TypeLayout::check_eq(("cpu", cpu_layout), ("gpu", gpu_layout)) + type_layout::check_eq(("cpu", cpu_layout), ("gpu", gpu_layout)) .map_err(|e| LayoutError::LayoutMismatch(e, Some(comment_on_mismatch_error.to_string()))) .and_then(|_| { if skip_stride_check { @@ -222,8 +228,8 @@ pub(crate) fn check_layout_push_error( name: gpu_layout.short_name(), }), (Some(cpu_size), Some(gpu_size)) => { - let cpu_stride = stride_of_array_from_element_align_size(cpu_layout.align(), cpu_size); - let gpu_stride = stride_of_array_from_element_align_size(gpu_layout.align(), gpu_size); + let cpu_stride = array_stride(cpu_layout.byte_align(), cpu_size); + let gpu_stride = array_stride(gpu_layout.byte_align(), gpu_size); if cpu_stride != gpu_stride { Err(LayoutError::StrideMismatch { @@ -348,6 +354,7 @@ pub(crate) fn from_single_any(mut anys: impl Iterator) -> Any { pub trait VertexLayout: GpuLayout + FromAnys {} impl VertexLayout for T {} +// TODO(chronicl) uncomment // #[derive(GpuLayout)] // TODO(release) remove this type. This is an example impl for figuring out how the derive macro should work #[derive(Clone)] @@ -533,28 +540,15 @@ where } } +impl BinaryReprSized for GpuT { + fn layout_type_sized() -> cpu_shareable::SizedType { todo!() } +} +impl BinaryRepr for GpuT { + fn layout_type() -> cpu_shareable::LayoutType { todo!() } +} + impl GpuLayout for GpuT { - fn gpu_layout() -> TypeLayout { - use crate::__private::proc_macro_reexports as rx; - // compiler_error! if the struct has zero fields! - let is_packed = false; - let result = rx::TypeLayout::struct_from_parts( - rx::TypeLayoutRules::Wgsl, - is_packed, - std::stringify!(GpuT).into(), - [FieldLayout { - name: unreachable!(), - custom_min_size: unreachable!(), - custom_min_align: unreachable!(), - ty: as rx::GpuLayout>::gpu_layout(), - }] - .into_iter(), - ); - match result { - Ok(layout) => layout, - Err(e @ rx::StructLayoutError::UnsizedFieldMustBeLast { .. }) => unreachable!(), - } - } + fn gpu_repr() -> Repr { todo!() } fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { Some(Ok(( @@ -701,7 +695,10 @@ where impl CpuLayout for f32 { fn cpu_layout() -> TypeLayout { - TypeLayout::from_rust_sized::(TypeLayoutSemantics::Vector(ir::Len::X1, Self::SCALAR_TYPE)) + TypeLayout::from_rust_sized::(TypeLayoutSemantics::Vector(Vector::new( + cpu_shareable::ScalarType::F32, + cpu_shareable::Len::X1, + ))) } // fn gpu_type_layout() -> Option> { // Some(Ok(vec::::gpu_layout())) @@ -710,29 +707,29 @@ impl CpuLayout for f32 { impl CpuLayout for f64 { fn cpu_layout() -> TypeLayout { - TypeLayout::from_rust_sized::(TypeLayoutSemantics::Vector(ir::Len::X1, Self::SCALAR_TYPE)) + TypeLayout::from_rust_sized::(TypeLayoutSemantics::Vector(Vector::new( + cpu_shareable::ScalarType::F64, + cpu_shareable::Len::X1, + ))) } - // fn gpu_type_layout() -> Option> { - // Some(Ok(vec::::gpu_layout())) - // } } impl CpuLayout for u32 { fn cpu_layout() -> TypeLayout { - TypeLayout::from_rust_sized::(TypeLayoutSemantics::Vector(ir::Len::X1, Self::SCALAR_TYPE)) + TypeLayout::from_rust_sized::(TypeLayoutSemantics::Vector(Vector::new( + cpu_shareable::ScalarType::U32, + cpu_shareable::Len::X1, + ))) } - // fn gpu_type_layout() -> Option> { - // Some(Ok(vec::::gpu_layout())) - // } } impl CpuLayout for i32 { fn cpu_layout() -> TypeLayout { - TypeLayout::from_rust_sized::(TypeLayoutSemantics::Vector(ir::Len::X1, Self::SCALAR_TYPE)) + TypeLayout::from_rust_sized::(TypeLayoutSemantics::Vector(Vector::new( + cpu_shareable::ScalarType::I32, + cpu_shareable::Len::X1, + ))) } - // fn gpu_type_layout() -> Option> { - // Some(Ok(vec::::gpu_layout())) - // } } /// (no documentation yet) @@ -760,7 +757,7 @@ impl CpuAligned for [T] { impl CpuLayout for [T; N] { fn cpu_layout() -> TypeLayout { - let align = ::alignment() as u64; + let align = U32PowerOf2::try_from(::alignment() as u32).unwrap(); TypeLayout::new( Some(std::mem::size_of::() as u64), @@ -772,6 +769,7 @@ impl CpuLayout for [T; N] { }), Some(u32::try_from(N).expect("arrays larger than u32::MAX elements are not supported by WGSL")), ), + None, ) } @@ -803,7 +801,7 @@ impl CpuLayout for [T; N] { impl CpuLayout for [T] { fn cpu_layout() -> TypeLayout { - let align = ::alignment() as u64; + let align = U32PowerOf2::try_from(::alignment() as u32).unwrap(); TypeLayout::new( None, @@ -815,6 +813,7 @@ impl CpuLayout for [T] { }), None, ), + None, ) } diff --git a/shame/src/frontend/rust_types/mat.rs b/shame/src/frontend/rust_types/mat.rs index 08c72ab..ded204c 100644 --- a/shame/src/frontend/rust_types/mat.rs +++ b/shame/src/frontend/rust_types/mat.rs @@ -7,7 +7,10 @@ use super::{ mem::AddressSpace, reference::{AccessMode, AccessModeReadable}, scalar_type::{ScalarType, ScalarTypeFp}, - type_layout::{TypeLayout, TypeLayoutRules}, + type_layout::{ + cpu_shareable::{self, BinaryReprSized}, + TypeLayout, TypeLayoutRules, + }, type_traits::{ BindingArgs, EmptyRefFields, GpuAligned, GpuSized, GpuStore, GpuStoreImplCategory, NoAtomics, NoBools, NoHandles, @@ -15,7 +18,7 @@ use super::{ vec::{scalar, vec, ToInteger}, AsAny, GpuType, To, ToGpuType, }; -use crate::{frontend::rust_types::reference::Ref, ir::recording::CallInfoScope}; +use crate::{cpu_shareable::BinaryRepr, frontend::rust_types::reference::Ref, ir::recording::CallInfoScope}; use crate::{ call_info, frontend::{ @@ -50,8 +53,22 @@ impl Default for mat { } } +impl BinaryReprSized for mat { + fn layout_type_sized() -> cpu_shareable::SizedType { + cpu_shareable::Matrix { + columns: C::LEN2, + rows: R::LEN2, + scalar: T::SCALAR_TYPE_FP, + } + .into() + } +} +impl BinaryRepr for mat { + fn layout_type() -> cpu_shareable::LayoutType { Self::layout_type_sized().into() } +} + impl GpuLayout for mat { - fn gpu_layout() -> TypeLayout { TypeLayout::from_sized_ty(TypeLayoutRules::Wgsl, &::sized_ty()) } + fn gpu_repr() -> cpu_shareable::Repr { cpu_shareable::Repr::Storage } fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { None } } @@ -181,7 +198,7 @@ impl mat { /// sm::vec!(2.0, 5.0) // row 3 /// ]) /// - /// let m: f32x3x2 = sm::mat::new([ + /// let m: f32x3x2 = sm::mat::new([ /// 0.0, 3.0 // column 0, becomes row 0 /// 1.0, 4.0 // column 1, becomes row 1 /// 2.0, 5.0 // column 2, becomes row 2 @@ -235,7 +252,7 @@ impl mat { /// sm::vec!(2.0, 5.0) // row 3 /// ]) /// - /// let m: f32x3x2 = sm::mat::new([ + /// let m: f32x3x2 = sm::mat::new([ /// 0.0, 3.0 // column 0, becomes row 0 /// 1.0, 4.0 // column 1, becomes row 1 /// 2.0, 5.0 // column 2, becomes row 2 @@ -496,7 +513,7 @@ impl mat { /// sm::vec!(2.0, 5.0) // row 3 /// ]) /// - /// let m: f32x3x2 = sm::mat::new([ + /// let m: f32x3x2 = sm::mat::new([ /// 0.0, 3.0 // column 0, becomes row 0 /// 1.0, 4.0 // column 1, becomes row 1 /// 2.0, 5.0 // column 2, becomes row 2 diff --git a/shame/src/frontend/rust_types/mod.rs b/shame/src/frontend/rust_types/mod.rs index 8a79162..5d6da72 100644 --- a/shame/src/frontend/rust_types/mod.rs +++ b/shame/src/frontend/rust_types/mod.rs @@ -51,7 +51,7 @@ pub mod vec_range_traits; //[old-doc] - `StorageTexture<…>` /// (no documentation yet) /// -pub trait GpuType: ToGpuType + From + AsAny + Clone + GpuLayout { +pub trait GpuType: ToGpuType + From + AsAny + Clone { /// (no documentation yet) #[doc(hidden)] // returns a type from the `any` api fn ty() -> ir::Type; diff --git a/shame/src/frontend/rust_types/packed_vec.rs b/shame/src/frontend/rust_types/packed_vec.rs index 7331f5f..148c98e 100644 --- a/shame/src/frontend/rust_types/packed_vec.rs +++ b/shame/src/frontend/rust_types/packed_vec.rs @@ -7,6 +7,7 @@ use std::{ use crate::{ any::{AsAny, DataPackingFn}, common::floating_point::f16, + cpu_shareable::{BinaryRepr, BinaryReprSized}, f32x2, f32x4, i32x4, u32x1, u32x4, }; use crate::frontend::rust_types::len::{x1, x2, x3, x4}; @@ -22,7 +23,7 @@ use super::{ layout_traits::{from_single_any, ArrayElementsUnsizedError, FromAnys, GpuLayout}, len::LenEven, scalar_type::ScalarType, - type_layout::{TypeLayout, TypeLayoutRules, TypeLayoutSemantics}, + type_layout::{cpu_shareable, type_layout_internal, TypeLayout, TypeLayoutRules, TypeLayoutSemantics}, type_traits::{GpuAligned, GpuSized, NoAtomics, NoBools, NoHandles, VertexAttribute}, vec::IsVec, GpuType, @@ -104,7 +105,7 @@ impl PackedVec { } } -fn get_type_description() -> PackedVector { +pub(crate) fn get_type_description() -> PackedVector { PackedVector { len: L::LEN_EVEN, bits_per_component: T::BITS_PER_COMPONENT, @@ -130,22 +131,29 @@ impl GpuAligned for PackedVec { impl NoBools for PackedVec {} impl NoHandles for PackedVec {} impl NoAtomics for PackedVec {} +impl BinaryReprSized for PackedVec { + fn layout_type_sized() -> cpu_shareable::SizedType { + cpu_shareable::PackedVector { + scalar_type: T::SCALAR_TYPE, + bits_per_component: T::BITS_PER_COMPONENT, + len: L::LEN_EVEN, + } + .into() + } +} +impl BinaryRepr for PackedVec { + fn layout_type() -> cpu_shareable::LayoutType { Self::layout_type_sized().into() } +} impl GpuLayout for PackedVec { - fn gpu_layout() -> TypeLayout { - let packed_vec = get_type_description::(); - TypeLayout::new( - Some(u8::from(packed_vec.byte_size()) as u64), - packed_vec.align(), - TypeLayoutSemantics::PackedVector(get_type_description::()), - ) - } + fn gpu_repr() -> cpu_shareable::Repr { cpu_shareable::Repr::Storage } fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { let sized_ty = Self::sized_ty_equivalent(); let name = sized_ty.to_string().into(); - let layout = TypeLayout::from_sized_ty(TypeLayoutRules::Wgsl, &sized_ty); - Some(Ok((name, layout))) + let sized_ty: cpu_shareable::SizedType = sized_ty.try_into().expect("PackedVec is NoBools and NoHandles"); + let layout = TypeLayout::new_storage_layout_for(sized_ty); + Some(Ok((name, layout.into()))) } } @@ -162,7 +170,7 @@ impl From for PackedVec { let inner = Context::try_with(call_info!(), |ctx| { let err = |ty| { ctx.push_error_get_invalid_any( - FrontendError::InvalidDowncastToNonShaderType(ty, Self::gpu_layout()).into(), + FrontendError::InvalidDowncastToNonShaderType(ty, Self::gpu_layout().into()).into(), ) }; match any.ty() { diff --git a/shame/src/frontend/rust_types/struct_.rs b/shame/src/frontend/rust_types/struct_.rs index f5615dc..4fec66f 100644 --- a/shame/src/frontend/rust_types/struct_.rs +++ b/shame/src/frontend/rust_types/struct_.rs @@ -1,4 +1,5 @@ use crate::common::small_vec::SmallVec; +use crate::cpu_shareable::{self, BinaryRepr, BinaryReprSized}; use crate::frontend::any::shared_io::{BindPath, BindingType}; use crate::frontend::any::{Any, InvalidReason}; use crate::frontend::encoding::buffer::BufferRefInner; @@ -22,7 +23,7 @@ use std::{ }; use super::layout_traits::{GetAllFields, GpuLayout}; -use super::type_layout::TypeLayout; +use super::type_layout::{TypeLayout, TypeLayoutRules}; use super::type_traits::{GpuAligned, GpuSized, GpuStore, GpuStoreImplCategory, NoBools}; use super::{ error::FrontendError, @@ -134,8 +135,19 @@ impl Deref for Struct { fn deref(&self) -> &Self::Target { &self.fields } } -impl GpuLayout for Struct { - fn gpu_layout() -> TypeLayout { T::gpu_layout() } +impl BinaryReprSized for Struct { + fn layout_type_sized() -> cpu_shareable::SizedType { + cpu_shareable::SizedStruct::try_from(T::get_sizedstruct_type()) + .expect("no bools") + .into() + } +} +impl BinaryRepr for Struct { + fn layout_type() -> cpu_shareable::LayoutType { Self::layout_type_sized().into() } +} + +impl GpuLayout for Struct { + fn gpu_repr() -> cpu_shareable::Repr { cpu_shareable::Repr::Storage } fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { T::cpu_type_name_and_layout().map(|x| x.map(|(name, l)| (format!("Struct<{name}>").into(), l))) diff --git a/shame/src/frontend/rust_types/type_layout.rs b/shame/src/frontend/rust_types/type_layout.rs deleted file mode 100644 index 14811ce..0000000 --- a/shame/src/frontend/rust_types/type_layout.rs +++ /dev/null @@ -1,906 +0,0 @@ -use std::{ - fmt::{Debug, Display, Write}, - num::NonZeroU32, - ops::Deref, - rc::Rc, -}; - -use crate::{ - call_info, - common::{ - ignore_eq::{IgnoreInEqOrdHash, InEqOrd}, - prettify::set_color, - }, - ir::{ - self, - ir_type::{ - align_of_array, byte_size_of_array, round_up, stride_of_array, AlignedType, CanonName, LenEven, - PackedVectorByteSize, ScalarTypeFp, ScalarTypeInteger, - }, - recording::Context, - Len, SizedType, Type, - }, -}; -use thiserror::Error; - -/// The type contained in the bytes of a `TypeLayout` -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum TypeLayoutSemantics { - /// `vec` - Vector(ir::Len, ir::ScalarType), - /// special compressed vectors for vertex attribute types - /// - /// see the [`crate::packed`] module - PackedVector(ir::PackedVector), - /// `mat`, first `Len2` is cols, 2nd `Len2` is rows - Matrix(ir::Len2, ir::Len2, ScalarTypeFp), - /// `Array` and `Array>` - Array(Rc, Option), // not NonZeroU32, since for rust `CpuLayout`s the array size may be 0. - /// structures which may be empty and may have an unsized last field - Structure(Rc), -} - -/// The memory layout of a type. -/// -/// This models only the layout, not other characteristics of the types. -/// For example an `Atomic>` is treated like a regular `vec` layout wise. -/// -/// The `PartialEq + Eq` implementation of `TypeLayout` is designed to answer the question -/// "do these two types have the same layout" so that uploading a type to the gpu -/// will result in no memory errors. -/// -/// a layout comparison looks like this: -/// ``` -/// assert!(f32::cpu_layout() == vec::gpu_layout()); -/// // or, more explicitly -/// assert_eq!( -/// ::cpu_layout(), -/// as GpuLayout>::gpu_layout(), -/// ); -/// ``` -/// -#[derive(Clone, PartialEq, Eq, Hash)] -pub struct TypeLayout { - /// size in bytes (Some), or unsized (None) - pub byte_size: Option, - /// the byte alignment - /// - /// top level alignment is not considered relevant in some checks, but relevant in others (vertex array elements) - pub byte_align: IgnoreInEqOrdHash, - /// the type contained in the bytes of this type layout - pub kind: TypeLayoutSemantics, -} - -impl TypeLayout { - pub(crate) fn new(byte_size: Option, byte_align: u64, kind: TypeLayoutSemantics) -> Self { - Self { - byte_size, - byte_align: byte_align.into(), - kind, - } - } - - pub(crate) fn from_rust_sized(kind: TypeLayoutSemantics) -> Self { - Self::new(Some(size_of::() as u64), align_of::() as u64, kind) - } - - fn first_line_of_display_with_ellipsis(&self) -> String { - let string = format!("{}", self); - string.split_once('\n').map(|(s, _)| format!("{s}…")).unwrap_or(string) - } - - /// a short name for this `TypeLayout`, useful for printing inline - pub fn short_name(&self) -> String { - match &self.kind { - TypeLayoutSemantics::Vector { .. } | - TypeLayoutSemantics::PackedVector { .. } | - TypeLayoutSemantics::Matrix { .. } => format!("{}", self), - TypeLayoutSemantics::Array(element_layout, n) => match n { - Some(n) => format!("array<{}, {n}>", element_layout.ty.short_name()), - None => format!("array<{}, runtime-sized>", element_layout.ty.short_name()), - }, - TypeLayoutSemantics::Structure(s) => s.name.to_string(), - } - } -} - -/// a sized or unsized struct type with 0 or more fields -#[allow(missing_docs)] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct StructLayout { - pub name: IgnoreInEqOrdHash, - pub fields: Vec, -} - -impl StructLayout { - /// this exists, because if in the future a refactor happens that separates - /// fields into sized and unsized fields, the intention of this function is - /// clear - fn all_fields(&self) -> &[FieldLayoutWithOffset] { &self.fields } -} - -#[allow(missing_docs)] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct FieldLayoutWithOffset { - pub field: FieldLayout, - pub rel_byte_offset: u64, // this being relative is used in TypeLayout::byte_size -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct ElementLayout { - pub byte_stride: u64, - pub ty: TypeLayout, -} - -/// the layout rules used when calculating the byte offsets and alignment of a type -#[derive(Debug, Clone, Copy)] -pub enum TypeLayoutRules { - /// wgsl type layout rules, see https://www.w3.org/TR/WGSL/#memory-layouts - Wgsl, - // reprC, - // Std140, - // Std430, - // Scalar, -} - -#[allow(missing_docs)] -#[derive(Error, Debug, Clone)] -pub enum TypeLayoutError { - #[error("An array cannot contain elements of an unsized type {elements}")] - ArrayOfUnsizedElements { elements: TypeLayout }, - #[error("the type `{0}` has no defined {1:?} layout in shaders")] - LayoutUndefined(Type, TypeLayoutRules), - #[error("in `{parent_name}` at field `{field_name}`: {error}")] - AtField { - parent_name: CanonName, - field_name: CanonName, - error: Rc, - }, - #[error("in element of array: {0}")] - InArrayElement(Rc), -} - -#[allow(missing_docs)] // TODO(docs) low priority docs, add after release -impl TypeLayout { - pub fn from_ty(rules: TypeLayoutRules, ty: &ir::Type) -> Result { - match ty { - Type::Unit | Type::Ptr(_, _, _) | Type::Ref(_, _, _) => { - Err(TypeLayoutError::LayoutUndefined(ty.clone(), rules)) - } - Type::Store(ty) => Self::from_store_ty(rules, ty), - } - } - - pub fn from_store_ty(rules: TypeLayoutRules, ty: &ir::StoreType) -> Result { - match ty { - ir::StoreType::Sized(sized) => Ok(TypeLayout::from_sized_ty(rules, sized)), - ir::StoreType::Handle(handle) => Err(TypeLayoutError::LayoutUndefined(ir::Type::Store(ty.clone()), rules)), - ir::StoreType::RuntimeSizedArray(element) => Ok(Self::from_array(rules, element, None)), - ir::StoreType::BufferBlock(s) => Ok(Self::from_struct(rules, s)), - } - } - - pub fn from_sized_ty(rules: TypeLayoutRules, ty: &ir::SizedType) -> TypeLayout { - pub use TypeLayoutSemantics as Sem; - let size = ty.byte_size(); - let align = ty.align(); - match ty { - ir::SizedType::Vector(l, t) => - // we treat bool as a type that has a layout to allow for an - // `Eq` operator on `TypeLayout` that behaves intuitively. - // The layout of `bool`s is not actually observable in any part of the api. - { - TypeLayout::new(Some(size), align, Sem::Vector(*l, *t)) - } - ir::SizedType::Matrix(c, r, t) => TypeLayout::new(Some(size), align, Sem::Matrix(*c, *r, *t)), - ir::SizedType::Array(sized, l) => Self::from_array(rules, sized, Some(*l)), - ir::SizedType::Atomic(t) => Self::from_sized_ty(rules, &ir::SizedType::Vector(ir::Len::X1, (*t).into())), - ir::SizedType::Structure(s) => Self::from_struct(rules, s), - } - } - - pub fn from_array(rules: TypeLayoutRules, element: &ir::SizedType, len: Option) -> TypeLayout { - TypeLayout::new( - len.map(|n| byte_size_of_array(element, n)), - align_of_array(element), - TypeLayoutSemantics::Array( - Rc::new(ElementLayout { - byte_stride: match rules { - TypeLayoutRules::Wgsl => stride_of_array(element), - }, - ty: Self::from_sized_ty(rules, element), - }), - len.map(NonZeroU32::get), - ), - ) - } - - pub fn from_struct(rules: TypeLayoutRules, s: &ir::Struct) -> TypeLayout { - let (size, align, struct_layout) = StructLayout::from_ir_struct(rules, s); - TypeLayout::new(size, align, TypeLayoutSemantics::Structure(Rc::new(struct_layout))) - } - - pub fn struct_from_parts( - rules: TypeLayoutRules, - packed: bool, - name: CanonName, - fields: impl ExactSizeIterator, - ) -> Result { - let (byte_size, byte_align, struct_) = StructLayout::new(rules, packed, name, fields)?; - let layout = TypeLayout::new(byte_size, byte_align, TypeLayoutSemantics::Structure(Rc::new(struct_))); - Ok(layout) - } - - pub fn from_aligned_type(rules: TypeLayoutRules, ty: &AlignedType) -> TypeLayout { - match ty { - AlignedType::Sized(sized) => Self::from_sized_ty(rules, sized), - AlignedType::RuntimeSizedArray(element) => Self::from_array(rules, element, None), - } - } - - pub(crate) fn writeln(&self, indent: &str, colored: bool, f: &mut W) -> std::fmt::Result { - self.write(indent, colored, f)?; - writeln!(f) - } - - //TODO(low prio) try to figure out a cleaner way of writing these. - pub(crate) fn write(&self, indent: &str, colored: bool, f: &mut W) -> std::fmt::Result { - let tab = " "; - let use_256_color_mode = false; - let color = |f_: &mut W, hex| match colored { - true => set_color(f_, Some(hex), use_256_color_mode), - false => Ok(()), - }; - let reset = |f_: &mut W| match colored { - true => set_color(f_, None, use_256_color_mode), - false => Ok(()), - }; - - use TypeLayoutSemantics as Sem; - - match &self.kind { - Sem::Vector(l, t) => match l { - Len::X1 => write!(f, "{t}")?, - l => write!(f, "{t}x{}", u64::from(*l))?, - }, - Sem::PackedVector(c) => write!(f, "{}", c)?, - Sem::Matrix(c, r, t) => write!(f, "{}", ir::SizedType::Matrix(*c, *r, *t))?, - Sem::Array(t, n) => { - let stride = t.byte_stride; - write!(f, "array<")?; - t.ty.write(&(indent.to_string() + tab), colored, f)?; - if let Some(n) = n { - write!(f, ", {n}")?; - } - write!(f, "> stride={stride}")?; - } - Sem::Structure(s) => { - writeln!(f, "struct {} {{", s.name)?; - { - let indent = indent.to_string() + tab; - for field in &s.fields { - let offset = field.rel_byte_offset; - let field = &field.field; - write!(f, "{indent}{offset:3} {}: ", field.name)?; - field.ty.write(&(indent.to_string() + tab), colored, f)?; - if let Some(size) = field.ty.byte_size { - let size = size.max(field.custom_min_size.unwrap_or(0)); - write!(f, " size={size}")?; - } else { - write!(f, " size=?")?; - } - writeln!(f, ",")?; - } - } - write!(f, "{indent}}}")?; - write!(f, " align={}", self.byte_align)?; - if let Some(size) = self.byte_size { - write!(f, " size={size}")?; - } else { - write!(f, " size=?")?; - } - } - }; - Ok(()) - } - - pub fn align(&self) -> u64 { *self.byte_align } - - pub fn byte_size(&self) -> Option { self.byte_size } -} - -#[allow(missing_docs)] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct FieldLayout { - pub name: CanonName, - pub custom_min_size: IgnoreInEqOrdHash>, // whether size/align is custom doesn't matter for the layout equality. - pub custom_min_align: IgnoreInEqOrdHash>, - pub ty: TypeLayout, -} - -impl FieldLayout { - fn byte_size(&self) -> Option { - self.ty - .byte_size() - .map(|byte_size| byte_size.max(self.custom_min_size.unwrap_or(0))) - } - fn align(&self) -> u64 { self.ty.align().max(self.custom_min_align.map(u64::from).unwrap_or(1)) } -} - -#[allow(missing_docs)] -#[derive(Error, Debug)] -pub enum StructLayoutError { - #[error( - "field #{unsized_field_index} in struct `{struct_name}` with {num_fields} is unsized. Only the last field may be unsized." - )] - UnsizedFieldMustBeLast { - struct_name: CanonName, - unsized_field_index: usize, - num_fields: usize, - }, -} - -impl StructLayout { - /// returns a `(byte_size, byte_alignment, struct_layout)` tuple or an error - /// - /// this was created for the `#[derive(GpuLayout)]` macro to support the - /// non-GpuType `PackedVec` for gpu_repr(packed) and non-packed. - /// - // TODO(low prio) find a way to merge all struct layout calculation functions in this codebase. This is very redundand. - pub(crate) fn new( - rules: TypeLayoutRules, - packed: bool, - name: CanonName, - fields: impl ExactSizeIterator, - ) -> Result<(Option, u64, StructLayout), StructLayoutError> { - let mut total_byte_size = None; - let mut total_align = 1; - let num_fields = fields.len(); - let struct_layout = StructLayout { - name: name.clone().into(), - fields: { - let mut offset_so_far = 0; - let mut fields_with_offset = Vec::new(); - for (i, field) in fields.enumerate() { - let is_last = i + 1 == num_fields; - fields_with_offset.push(FieldLayoutWithOffset { - field: field.clone(), - rel_byte_offset: match rules { - TypeLayoutRules::Wgsl => { - let field_offset = match (packed, *field.custom_min_align) { - (true, None) => offset_so_far, - (true, Some(custom_align)) => round_up(custom_align, offset_so_far), - (false, _) => round_up(field.align(), offset_so_far), - }; - match (field.byte_size(), is_last) { - (Some(field_size), _) => { - offset_so_far = field_offset + field_size; - Ok(()) - } - (None, true) => Ok(()), - (None, false) => Err(StructLayoutError::UnsizedFieldMustBeLast { - struct_name: name.clone(), - unsized_field_index: i, - num_fields, - }), - }?; - field_offset - } - }, - }); - total_align = total_align.max(field.align()); - if is_last { - // wgsl spec: - // roundUp(AlignOf(S), justPastLastMember) - // where justPastLastMember = OffsetOfMember(S,N) + SizeOfMember(S,N) - - // if the last field size is None (= unsized), just_past_last is None (= unsized) - let just_past_last = field.byte_size().map(|_| offset_so_far); - total_byte_size = just_past_last.map(|just_past_last| round_up(total_align, just_past_last)); - } - } - fields_with_offset - }, - }; - Ok((total_byte_size, total_align, struct_layout)) - } - - /// returns a `(byte_size, byte_alignment, struct_layout)` tuple - #[doc(hidden)] - pub fn from_ir_struct(rules: TypeLayoutRules, s: &ir::Struct) -> (Option, u64, StructLayout) { - let mut total_byte_size = None; - let struct_layout = StructLayout { - name: s.name().clone().into(), - fields: { - let mut offset = 0; - let mut fields = Vec::new(); - for field in s.sized_fields() { - fields.push(FieldLayoutWithOffset { - field: FieldLayout { - name: field.name.clone(), - ty: TypeLayout::from_sized_ty(rules, &field.ty), - custom_min_size: field.custom_min_size.into(), - custom_min_align: field.custom_min_align.map(u64::from).into(), - }, - rel_byte_offset: match rules { - TypeLayoutRules::Wgsl => { - let rel_byte_offset = round_up(field.align(), offset); - offset = rel_byte_offset + field.byte_size(); - rel_byte_offset - } - }, - }) - } - if let Some(unsized_array) = s.last_unsized_field() { - fields.push(FieldLayoutWithOffset { - field: FieldLayout { - name: unsized_array.name.clone(), - custom_min_align: unsized_array.custom_min_align.map(u64::from).into(), - custom_min_size: None.into(), - ty: TypeLayout::from_array(rules, &unsized_array.element_ty, None), - }, - rel_byte_offset: round_up(unsized_array.align(), offset), - }) - } else { - total_byte_size = Some(s.min_byte_size()); - } - fields - }, - }; - (total_byte_size, s.align(), struct_layout) - } -} - -impl Display for TypeLayout { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let colored = Context::try_with(call_info!(), |ctx| ctx.settings().colored_error_messages).unwrap_or(false); - self.write("", colored, f) - } -} - -impl Debug for TypeLayout { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.write("", false, f) } -} - -#[derive(Clone)] -pub struct LayoutMismatch { - /// 2 (name, layout) pairs - layouts: [(String, TypeLayout); 2], - colored_error: bool, -} - - -impl std::fmt::Debug for LayoutMismatch { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self) // use Display - } -} - -impl LayoutMismatch { - fn pad_width(name_a: &str, name_b: &str) -> usize { name_a.chars().count().max(name_b.chars().count()) + SEP.len() } -} - -impl Display for LayoutMismatch { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let colored = self.colored_error; - let [(a_name, a), (b_name, b)] = &self.layouts; - write!(f, "{:width$}", ' ', width = Self::pad_width(a_name, b_name))?; - let layouts = [(a_name.as_str(), a), (b_name.as_str(), b)]; - match LayoutMismatch::write("", layouts, colored, f) { - Err(MismatchWasFound) => Ok(()), - Ok(KeepWriting) => { - writeln!( - f, - "" - )?; - writeln!(f, "the full type layouts in question are:")?; - for (name, layout) in layouts { - writeln!(f, "`{}`:", name)?; - writeln!(f, "{}", layout)?; - } - Ok(()) - } - } - } -} - -/// layout mismatch diff name separator -/// used in the display impl of LayoutMismatch to show the actual place where the layouts mismatch -/// ``` -/// layout mismatch: -/// cpu{SEP} f32 -/// gpu{SEP} i32 -/// ``` -const SEP: &str = ": "; - -/// whether the mismatching part of the TypeLayouts in a LayoutMismatch was already expressed via writes. -/// indicates that the `write` function should stop writing. -pub(crate) struct MismatchWasFound; -pub(crate) struct KeepWriting; - -impl LayoutMismatch { - //TODO(low prio) try to figure out a cleaner way of writing these. - - /// this function uses the `Err(MismatchWasFound)` to halt traversing the typelayout. - /// It does not constitute an error of this function, it is just so the ? operator can be used to propagate the abort. - #[allow(clippy::needless_return)] - pub(crate) fn write( - indent: &str, - layouts: [(&str, &TypeLayout); 2], - colored: bool, - f: &mut W, - ) -> Result { - let tab = " "; - let [(a_name, a), (b_name, b)] = layouts; - - if a == b { - a.write(indent, colored, f); - return Ok(KeepWriting); - } - - let use_256_color_mode = false; - let hex_color = |f_: &mut W, hex| match colored { - true => set_color(f_, Some(hex), use_256_color_mode), - false => Ok(()), - }; - let color_reset = |f_: &mut W| match colored { - true => set_color(f_, None, use_256_color_mode), - false => Ok(()), - }; - - let color_a_hex = "#DF5853"; - let color_b_hex = "#9A639C"; - let color_a = |f| hex_color(f, color_a_hex); - let color_b = |f| hex_color(f, color_b_hex); - - let pad_width = Self::pad_width(a_name, b_name); - - use TypeLayoutSemantics as S; - match (&a.kind, &b.kind) { - (S::Structure(sa), S::Structure(sb)) => { - let max_fields = sa.all_fields().len().max(sb.all_fields().len()); - { - write!(f, "struct "); - hex_color(f, color_a_hex); - write!(f, "{}", sa.name); - color_reset(f); - write!(f, " / "); - hex_color(f, color_b_hex); - write!(f, "{}", sb.name); - color_reset(f); - writeln!(f, " {{"); - } - - let mut sa_fields = sa.all_fields().iter(); - let mut sb_fields = sb.all_fields().iter(); - - loop { - //TODO(low prio) get a hold of the code duplication here - match (sa_fields.next(), sb_fields.next()) { - (Some(a_field), Some(b_field)) => { - let offsets_match = a_field.rel_byte_offset == b_field.rel_byte_offset; - let types_match = a_field.field.ty == b_field.field.ty; - if !offsets_match && types_match { - // only write this mismatch if the types are also the same, otherwise display the detailed type mismatch further below - let a_ty_string = a_field.field.ty.first_line_of_display_with_ellipsis(); - let b_ty_string = b_field.field.ty.first_line_of_display_with_ellipsis(); - color_a(f); - writeln!( - f, - "{a_name}{SEP}{indent}{:3} {}: {a_ty_string} align={}", - a_field.rel_byte_offset, - a_field.field.name, - a_field.field.align() - ); - color_b(f); - writeln!( - f, - "{b_name}{SEP}{indent}{:3} {}: {b_ty_string} align={}", - b_field.rel_byte_offset, - b_field.field.name, - b_field.field.align() - ); - color_reset(f); - writeln!( - f, - "field offset is different on {a_name} ({}) and {b_name} ({}).", - a_field.rel_byte_offset, b_field.rel_byte_offset - ); - return Err(MismatchWasFound); - } - let offset = a_field.rel_byte_offset; - let a_field = &a_field.field; - let b_field = &b_field.field; - let field = &a_field; - - if offsets_match { - write!(f, "{:width$}{indent}{offset:3} ", ' ', width = pad_width); - } else { - write!(f, "{:width$}{indent} ? ", ' ', width = pad_width); - } - if a_field.name != b_field.name { - writeln!(f); - color_a(f); - writeln!(f, "{a_name}{SEP}{indent} {}: …", a_field.name); - color_b(f); - writeln!(f, "{b_name}{SEP}{indent} {}: …", b_field.name); - color_reset(f); - writeln!( - f, - "identifier mismatch, either\nfield '{}' is missing on {a_name}, or\nfield '{}' is missing on {b_name}.", - b_field.name, a_field.name - ); - return Err(MismatchWasFound); - } - write!(f, "{}: ", field.name); - if a_field.ty != b_field.ty { - Self::write( - &format!("{indent}{tab}"), - [(a_name, &a_field.ty), (b_name, &b_field.ty)], - colored, - f, - )?; - return Err(MismatchWasFound); - } - write!(f, "{}", field.ty.first_line_of_display_with_ellipsis()); - if a_field.byte_size() != b_field.byte_size() { - writeln!(f); - color_a(f); - writeln!( - f, - "{a_name}{SEP}{indent} size={}", - a_field - .byte_size() - .as_ref() - .map(|x| x as &dyn Display) - .unwrap_or(&"?" as _) - ); - color_b(f); - writeln!( - f, - "{b_name}{SEP}{indent} size={}", - b_field - .byte_size() - .as_ref() - .map(|x| x as &dyn Display) - .unwrap_or(&"?" as _) - ); - color_reset(f); - return Err(MismatchWasFound); - } - - write!( - f, - " size={}", - field - .byte_size() - .as_ref() - .map(|x| x as &dyn Display) - .unwrap_or(&"?" as _) - ); - writeln!(f, ","); - } - (Some(a_field), None) => { - let offset = a_field.rel_byte_offset; - let a_field = &a_field.field; - let field = &a_field; - color_a(f); - write!(f, "{a_name}{SEP}{indent}{offset:3} "); - write!(f, "{}: ", field.name); - write!(f, "{}", field.ty.first_line_of_display_with_ellipsis()); - write!( - f, - " size={}", - field - .byte_size() - .as_ref() - .map(|x| x as &dyn Display) - .unwrap_or(&"?" as _) - ); - writeln!(f, ","); - color_b(f); - writeln!(f, "{b_name}{SEP}{indent}", a_field.name); - color_reset(f); - return Err(MismatchWasFound); - } - (None, Some(b_field)) => { - let offset = b_field.rel_byte_offset; - let b_field = &b_field.field; - color_a(f); - writeln!(f, "{a_name}{SEP}{indent}", b_field.name); - let field = &b_field; - color_b(f); - write!(f, "{b_name}{SEP}{indent}{offset:3} "); - write!(f, "{}: ", field.name); - write!(f, "{}", field.ty.first_line_of_display_with_ellipsis()); - write!( - f, - " size={}", - field - .byte_size() - .as_ref() - .map(|x| x as &dyn Display) - .unwrap_or(&"?" as _) - ); - writeln!(f, ","); - color_reset(f); - return Err(MismatchWasFound); - } - (None, None) => break, - } - } - - write!(f, "{:width$}{indent}}}", ' ', width = pad_width); - let align_matches = a.align() == b.align(); - let size_matches = a.byte_size() == b.byte_size(); - if !align_matches && size_matches { - writeln!(f); - color_a(f); - writeln!(f, "{a_name}{SEP}{indent}align={}", a.align()); - color_b(f); - writeln!(f, "{b_name}{SEP}{indent}align={}", b.align()); - color_reset(f); - return Err(MismatchWasFound); - } else { - match align_matches { - true => write!(f, " align={}", a.align()), - false => write!(f, " align=?"), - }; - } - if !size_matches { - writeln!(f); - color_a(f); - writeln!( - f, - "{a_name}{SEP}{indent}size={}", - a.byte_size().as_ref().map(|x| x as &dyn Display).unwrap_or(&"?" as _) - ); - color_b(f); - writeln!( - f, - "{b_name}{SEP}{indent}size={}", - b.byte_size().as_ref().map(|x| x as &dyn Display).unwrap_or(&"?" as _) - ); - color_reset(f); - return Err(MismatchWasFound); - } else { - match a.byte_size() { - Some(size) => write!(f, " size={size}"), - None => write!(f, " size=?"), - }; - } - // this should never happen, returning Ok(KeepWriting) will trigger the internal error in the Display impl - return Ok(KeepWriting); - } - (S::Array(ta, na), S::Array(tb, nb)) => { - if na != nb { - writeln!(f); - color_a(f); - write!(f, "{a_name}{SEP}"); - - write!( - f, - "array<…, {}>", - match na { - Some(n) => n as &dyn Display, - None => (&"runtime-sized") as &dyn Display, - } - ); - - //a.writeln(indent, colored, f); - writeln!(f); - color_b(f); - write!(f, "{b_name}{SEP}"); - - write!( - f, - "array<…, {}>", - match nb { - Some(n) => n as &dyn Display, - None => (&"runtime-sized") as &dyn Display, - } - ); - - //b.writeln(indent, colored, f); - color_reset(f); - Err(MismatchWasFound) - } else { - write!(f, "array<"); - //ta.ty.write(&(indent.to_string() + tab), colored, f); - - Self::write( - &format!("{indent}{tab}"), - [(a_name, &ta.ty), (b_name, &tb.ty)], - colored, - f, - )?; - - if let Some(na) = na { - write!(f, ", {na}"); - } - write!(f, ">"); - - if ta.byte_stride != tb.byte_stride { - writeln!(f); - color_a(f); - writeln!(f, "{a_name}{SEP}{indent}> stride={}", ta.byte_stride); - color_b(f); - writeln!(f, "{b_name}{SEP}{indent}> stride={}", tb.byte_stride); - color_reset(f); - Err(MismatchWasFound) - } else { - // this should never happen, returning Ok(KeepWriting) will trigger the internal error in the Display impl - write!(f, "> stride={}", ta.byte_stride); - return Ok(KeepWriting); - } - } - } - (S::Vector(na, ta), S::Vector(nb, tb)) => { - writeln!(f); - color_a(f); - write!(f, "{a_name}{SEP}"); - a.writeln(indent, colored, f); - color_b(f); - write!(f, "{b_name}{SEP}"); - b.writeln(indent, colored, f); - color_reset(f); - Err(MismatchWasFound) - } - (S::Matrix(c, r, t), S::Matrix(c1, r1, t1)) => { - writeln!(f); - color_a(f); - write!(f, "{a_name}{SEP}"); - a.writeln(indent, colored, f); - color_b(f); - write!(f, "{b_name}{SEP}"); - b.writeln(indent, colored, f); - color_reset(f); - Err(MismatchWasFound) - } - (S::PackedVector(p), S::PackedVector(p1)) => { - writeln!(f); - color_a(f); - write!(f, "{a_name}{SEP}"); - a.writeln(indent, colored, f); - color_b(f); - write!(f, "{b_name}{SEP}"); - b.writeln(indent, colored, f); - color_reset(f); - Err(MismatchWasFound) - } - ( - // its written like this so that exhaustiveness checks lead us to this match statement if a type is added - S::Structure { .. } | S::Array { .. } | S::Vector { .. } | S::Matrix { .. } | S::PackedVector { .. }, - _, - ) => { - // TypeLayoutSemantics mismatch - writeln!(f); - color_a(f); - write!(f, "{a_name}{SEP}"); - a.writeln(indent, colored, f); - color_b(f); - write!(f, "{b_name}{SEP}"); - b.writeln(indent, colored, f); - color_reset(f); - Err(MismatchWasFound) - } - } - } -} - -impl TypeLayout { - /// takes two pairs of `(debug_name, layout)` and compares them for equality. - /// - /// if the two layouts are not equal it uses the debug names in the returned - /// error to tell the two layouts apart. - pub(crate) fn check_eq(a: (&str, &TypeLayout), b: (&str, &TypeLayout)) -> Result<(), LayoutMismatch> { - match a.1 == b.1 { - true => Ok(()), - false => Err(LayoutMismatch { - layouts: [(a.0.into(), a.1.clone()), (b.0.into(), b.1.clone())], - colored_error: Context::try_with(call_info!(), |ctx| ctx.settings().colored_error_messages) - .unwrap_or(false), - }), - } - } -} diff --git a/shame/src/frontend/rust_types/type_layout/cpu_shareable/align_size.rs b/shame/src/frontend/rust_types/type_layout/cpu_shareable/align_size.rs new file mode 100644 index 0000000..9f075d4 --- /dev/null +++ b/shame/src/frontend/rust_types/type_layout/cpu_shareable/align_size.rs @@ -0,0 +1,298 @@ +use super::super::LayoutCalculator; +use super::*; + +// Size and align of host-shareable types // +// https://www.w3.org/TR/WGSL/#address-space-layout-constraints // + +#[derive(Debug, Clone, Copy)] +pub enum Repr { + /// Wgsl storage address space layout / OpenGL std430 + /// https://www.w3.org/TR/WGSL/#address-space-layout-constraints + Storage, + /// Wgsl uniform address space layout / OpenGL std140 + /// https://www.w3.org/TR/WGSL/#address-space-layout-constraints + Uniform, + /// Packed layout. Vertex buffer only. + Packed, +} + +impl SizedType { + /// This is expensive for structs. Prefer `byte_size_and_align` if you also need the align. + pub fn byte_size(&self, repr: Repr) -> u64 { + match self { + SizedType::Array(a) => a.byte_size(repr), + SizedType::Vector(v) => v.byte_size(), + SizedType::Matrix(m) => m.byte_size(Major::Row), + SizedType::Atomic(a) => a.byte_size(), + SizedType::PackedVec(v) => u8::from(v.byte_size()) as u64, + SizedType::Struct(s) => s.byte_size_and_align(repr).0, + } + } + + /// This is expensive for structs. Prefer `byte_size_and_align` if you also need the size. + pub fn byte_align(&self, repr: Repr) -> U32PowerOf2 { + match self { + SizedType::Array(a) => a.align(repr), + SizedType::Vector(v) => v.align(), + SizedType::Matrix(m) => m.align(repr, Major::Row), + SizedType::Atomic(a) => a.align(), + SizedType::PackedVec(v) => v.align(), + SizedType::Struct(s) => s.byte_size_and_align(repr).1, + } + } + + /// This is expensive for structs. + pub fn byte_size_and_align(&self, repr: Repr) -> (u64, U32PowerOf2) { + match self { + SizedType::Struct(s) => s.byte_size_and_align(repr), + non_struct => (non_struct.byte_size(repr), non_struct.byte_align(repr)), + } + } +} + + +impl SizedStruct { + pub fn field_offsets(&self, repr: Repr) -> FieldOffsets { + FieldOffsets { + fields: &self.fields, + field_index: 0, + calc: LayoutCalculator::new(matches!(repr, Repr::Packed)), + repr, + } + } + + /// Returns (byte_size, byte_align) + /// + /// ### Careful! + /// This is an expensive operation as it calculates byte size and align from scratch. + /// If you also need field offsets, use [`SizedStruct::field_offsets`] instead and + /// read the documentation of [`FieldOffsets`] on how to obtain the byte size and align from it. + pub fn byte_size_and_align(&self, layout: Repr) -> (u64, U32PowerOf2) { + let mut field_offsets = self.field_offsets(layout); + (&mut field_offsets).count(); // &mut so it doesn't consume + (field_offsets.byte_size(), field_offsets.align()) + } +} + +/// An iterator over the field offsets of a `SizedStruct`. +/// +/// `FieldOffsets::byte_size` and `FieldOffsets::byte_align` can be used to query the struct's +/// `byte_size` and `byte_align`, but only takes into account the fields that have been iterated over. +pub struct FieldOffsets<'a> { + fields: &'a [SizedField], + field_index: usize, + calc: LayoutCalculator, + repr: Repr, +} + +impl Iterator for FieldOffsets<'_> { + type Item = u64; + + fn next(&mut self) -> Option { + self.field_index += 1; + self.fields.get(self.field_index - 1).map(|field| { + let (size, align) = match &field.ty { + SizedType::Struct(s) => { + let (size, align) = s.byte_size_and_align(self.repr); + match self.repr { + // Packedness is ensured by the `LayoutCalculator`. + Repr::Storage | Repr::Packed => (size, align), + // https://www.w3.org/TR/WGSL/#address-space-layout-constraints + // The uniform address space requires that: + // - struct S align: roundUp(16, AlignOf(S)) + // - If a structure member itself has a structure type S, then the number of + // bytes between the start of that member and the start of any following + // member must be at least roundUp(16, SizeOf(S)). + // -> We adjust size too. + Repr::Uniform => (round_up(16, size), round_up_align(U32PowerOf2::_16, align)), + } + } + non_struct => non_struct.byte_size_and_align(self.repr), + }; + + self.calc + .extend(size, align, field.custom_min_size, field.custom_min_align) + }) + } +} + +impl FieldOffsets<'_> { + /// Returns the byte size of the struct, but ONLY with the fields that have been iterated over so far. + pub const fn byte_size(&self) -> u64 { self.calc.byte_size() } + /// Returns the byte align of the struct, but ONLY with the fields that have been iterated over so far. + pub const fn align(&self) -> U32PowerOf2 { struct_align(self.calc.align(), self.repr) } +} + + +impl UnsizedStruct { + pub fn sized_field_offsets(&self, repr: Repr) -> FieldOffsets { + FieldOffsets { + fields: &self.sized_fields, + field_index: 0, + calc: LayoutCalculator::new(matches!(repr, Repr::Packed)), + repr, + } + } + + /// Returns (last field offset, byte align of unsized struct) + /// + /// `sized_field_offsets` must be from the same `UnsizedStruct`, otherwise the returned values + /// are not accurate. + pub fn last_field_offset_and_struct_align(&self, sized_field_offsets: FieldOffsets) -> (u64, U32PowerOf2) { + let mut offsets = sized_field_offsets; + // Iterating over any remaining field offsets to update the layout calculator. + (&mut offsets).count(); + + let array_align = self.last_unsized.array.byte_align(offsets.repr); + let custom_min_align = self.last_unsized.custom_min_align; + let (offset, align) = offsets.calc.extend_unsized(array_align, custom_min_align); + (offset, struct_align(align, offsets.repr)) + } + + /// This is expensive as it calculates the byte align from scratch. + pub fn byte_align(&self, repr: Repr) -> U32PowerOf2 { + let offsets = self.sized_field_offsets(repr); + self.last_field_offset_and_struct_align(offsets).1 + } +} + +const fn struct_align(align: U32PowerOf2, repr: Repr) -> U32PowerOf2 { + match repr { + // Packedness is ensured by the `LayoutCalculator`. + Repr::Storage | Repr::Packed => align, + Repr::Uniform => round_up_align(U32PowerOf2::_16, align), + } +} + +impl Vector { + pub const fn new(scalar: ScalarType, len: Len) -> Self { Self { scalar, len } } + + pub const fn byte_size(&self) -> u64 { self.len.as_u64() * self.scalar.byte_size() } + + pub const fn align(&self) -> U32PowerOf2 { + let len = match self.len { + Len::X1 | Len::X2 | Len::X4 => self.len.as_u32(), + Len::X3 => 4, + }; + + // len * self.scalar.align() = power of 2 * power of 2 = power of 2 + U32PowerOf2::try_from_u32(len * self.scalar.align().as_u32()).unwrap() + } +} + +impl ScalarType { + pub const fn byte_size(&self) -> u64 { + match self { + ScalarType::F16 => 2, + ScalarType::F32 | ScalarType::U32 | ScalarType::I32 => 4, + ScalarType::F64 => 8, + } + } + + pub const fn align(&self) -> U32PowerOf2 { + match self { + ScalarType::F16 => U32PowerOf2::_2, + ScalarType::F32 | ScalarType::U32 | ScalarType::I32 => U32PowerOf2::_4, + ScalarType::F64 => U32PowerOf2::_8, + } + } +} + +#[derive(Debug, Clone, Copy)] +pub enum Major { + Row, + Column, +} + +impl Matrix { + pub const fn byte_size(&self, major: Major) -> u64 { + let (vec, array_len) = self.as_vector_array(major); + let array_stride = array_stride(vec.align(), vec.byte_size()); + array_size(array_stride, array_len) + } + + pub const fn align(&self, repr: Repr, major: Major) -> U32PowerOf2 { + let (vec, _) = self.as_vector_array(major); + array_align(vec.align(), repr) + } + + const fn as_vector_array(&self, major: Major) -> (Vector, NonZeroU32) { + let (vec_len, array_len): (Len, NonZeroU32) = match major { + Major::Row => (self.rows.as_len(), self.columns.as_non_zero_u32()), + Major::Column => (self.columns.as_len(), self.rows.as_non_zero_u32()), + }; + ( + Vector { + len: vec_len, + scalar: self.scalar.as_scalar_type(), + }, + array_len, + ) + } +} + +impl Atomic { + pub const fn byte_size(&self) -> u64 { self.scalar.as_scalar_type().byte_size() } + pub const fn align(&self) -> U32PowerOf2 { self.scalar.as_scalar_type().align() } +} + +impl SizedArray { + pub fn byte_size(&self, repr: Repr) -> u64 { array_size(self.byte_stride(repr), self.len) } + + pub fn align(&self, repr: Repr) -> U32PowerOf2 { array_align(self.element.byte_align(repr), repr) } + + pub fn byte_stride(&self, repr: Repr) -> u64 { + let (element_size, element_align) = self.element.byte_size_and_align(repr); + array_stride(element_align, element_size) + } +} + +pub const fn array_size(array_stride: u64, len: NonZeroU32) -> u64 { array_stride * len.get() as u64 } + +pub const fn array_align(element_align: U32PowerOf2, layout: Repr) -> U32PowerOf2 { + match layout { + // Packedness is ensured by the `LayoutCalculator`. + Repr::Storage | Repr::Packed => element_align, + Repr::Uniform => U32PowerOf2::try_from_u32(round_up(16, element_align.as_u64()) as u32).unwrap(), + } +} + +pub const fn array_stride(element_align: U32PowerOf2, element_size: u64) -> u64 { + // Arrays of element type T must have an element stride that is a multiple of the + // RequiredAlignOf(T, C) for the address space C: + round_up(element_align.as_u64(), element_size) +} + +impl RuntimeSizedArray { + pub fn byte_align(&self, repr: Repr) -> U32PowerOf2 { array_align(self.element.byte_align(repr), repr) } + + pub fn byte_stride(&self, repr: Repr) -> u64 { array_stride(self.byte_align(repr), self.element.byte_size(repr)) } +} + +impl SizedField { + pub fn byte_size(&self, repr: Repr) -> u64 { self.ty.byte_size(repr) } + pub fn byte_align(&self, repr: Repr) -> U32PowerOf2 { self.ty.byte_align(repr) } +} + +impl RuntimeSizedArrayField { + pub fn byte_align(&self, repr: Repr) -> U32PowerOf2 { self.array.byte_align(repr) } +} + +pub const fn round_up(multiple_of: u64, n: u64) -> u64 { + match multiple_of { + 0 => match n { + 0 => 0, + _ => panic!("cannot round up n to a multiple of 0"), + }, + k @ 1.. => n.div_ceil(k) * k, + } +} + +pub const fn round_up_align(multiple_of: U32PowerOf2, n: U32PowerOf2) -> U32PowerOf2 { + let rounded_up = round_up(multiple_of.as_u64(), n.as_u64()); + // n <= multiple_of -> rounded_up = multiple_of + // n > multiple_of -> rounded_up = n, since both are powers of 2, n must already + // be a multiple of multiple_of + // In both cases rounded_up is a power of 2 + U32PowerOf2::try_from_u32(rounded_up as u32).unwrap() +} diff --git a/shame/src/frontend/rust_types/type_layout/cpu_shareable/builder.rs b/shame/src/frontend/rust_types/type_layout/cpu_shareable/builder.rs new file mode 100644 index 0000000..c60e3fc --- /dev/null +++ b/shame/src/frontend/rust_types/type_layout/cpu_shareable/builder.rs @@ -0,0 +1,165 @@ +use super::*; + +impl LayoutType { + pub fn struct_from_parts( + struct_name: impl Into, + fields: impl IntoIterator, + ) -> Result { + use StructFromPartsError::*; + + enum Field { + Sized(SizedField), + Unsized(RuntimeSizedArrayField), + } + + let mut fields = fields + .into_iter() + .map(|(options, ty)| { + Ok(match ty { + LayoutType::Sized(s) => Field::Sized(SizedField::new(options, s)), + LayoutType::RuntimeSizedArray(a) => Field::Unsized(RuntimeSizedArrayField::new( + options.name, + options.custom_min_align, + a.element, + )), + LayoutType::UnsizedStruct(_) => return Err(MustNotHaveUnsizedStructField), + }) + }) + .peekable(); + + let mut sized_fields = Vec::new(); + let mut last_unsized = None; + while let Some(field) = fields.next() { + let field = field?; + match field { + Field::Sized(sized) => sized_fields.push(sized), + Field::Unsized(a) => { + last_unsized = Some(a); + if fields.peek().is_some() { + return Err(OnlyLastFieldMayBeUnsized); + } + } + } + } + + let field_count = sized_fields.len() + last_unsized.is_some() as usize; + if field_count == 0 { + return Err(MustHaveAtLeastOneField); + } + + if let Some(last_unsized) = last_unsized { + Ok(UnsizedStruct { + name: struct_name.into(), + sized_fields, + last_unsized, + } + .into()) + } else { + Ok(SizedStruct::from_parts(struct_name, sized_fields).into()) + } + } +} + +#[derive(thiserror::Error, Debug)] +pub enum StructFromPartsError { + #[error("Struct must have at least one field.")] + MustHaveAtLeastOneField, + #[error("Only the last field of a struct may be unsized.")] + OnlyLastFieldMayBeUnsized, + #[error("A field of the struct is an unsized struct, which isn't allowed.")] + MustNotHaveUnsizedStructField, +} + +impl SizedStruct { + /// Creates a new `SizedStruct` with one field. + /// + /// To add additional fields to it, use [`SizedStruct::extend`] or [`SizedStruct::extend_unsized`]. + pub fn new(name: impl Into, field_options: impl Into, ty: impl Into) -> Self { + Self { + name: name.into(), + fields: vec![SizedField::new(field_options, ty)], + } + } + + pub fn from_parts(name: impl Into, fields: Vec) -> Self { + Self { + name: name.into(), + fields, + } + } + + /// Adds a sized field to the struct. + pub fn extend(mut self, field_options: impl Into, ty: impl Into) -> Self { + self.fields.push(SizedField::new(field_options, ty)); + self + } + + /// Adds a runtime sized array field to the struct. This can only be the last + /// field of a struct, which is ensured by transitioning to an UnsizedStruct. + pub fn extend_unsized( + self, + name: impl Into, + custom_min_align: Option, + element_ty: impl Into, + ) -> UnsizedStruct { + UnsizedStruct { + name: self.name, + sized_fields: self.fields, + last_unsized: RuntimeSizedArrayField::new(name, custom_min_align, element_ty), + } + } + + /// Adds either a `SizedType` or a `RuntimeSizedArray` field to the struct. + /// + /// Returns a `HostshareableType`, because the `Self` may either stay + /// a `SizedStruct` or become an `UnsizedStruct` depending on the field's type. + pub fn extend_sized_or_array(self, field_options: impl Into, field: SizedOrArray) -> LayoutType { + let options = field_options.into(); + match field { + SizedOrArray::Sized(ty) => self.extend(options, ty).into(), + SizedOrArray::RuntimeSizedArray(a) => self + .extend_unsized(options.name, options.custom_min_align, a.element) + .into(), + } + } + + pub fn new_sized_or_array( + struct_name: impl Into, + field_options: impl Into, + field: SizedOrArray, + ) -> LayoutType { + let options = field_options.into(); + match field { + SizedOrArray::Sized(ty) => Self::new(struct_name, options, ty).into(), + SizedOrArray::RuntimeSizedArray(a) => UnsizedStruct { + name: struct_name.into(), + sized_fields: Vec::new(), + last_unsized: RuntimeSizedArrayField::new(options.name, options.custom_min_align, a.element), + } + .into(), + } + } + + pub fn fields(&self) -> &[SizedField] { &self.fields } +} + +impl> From<(T, SizedType)> for SizedField { + fn from(value: (T, SizedType)) -> Self { + Self { + name: value.0.into(), + ty: value.1, + custom_min_size: None, + custom_min_align: None, + } + } +} + +impl> From<(T, SizedType)> for RuntimeSizedArrayField { + fn from(value: (T, SizedType)) -> Self { + Self { + name: value.0.into(), + array: RuntimeSizedArray { element: value.1 }, + custom_min_align: None, + } + } +} diff --git a/shame/src/frontend/rust_types/type_layout/cpu_shareable/mod.rs b/shame/src/frontend/rust_types/type_layout/cpu_shareable/mod.rs new file mode 100644 index 0000000..42f6a5e --- /dev/null +++ b/shame/src/frontend/rust_types/type_layout/cpu_shareable/mod.rs @@ -0,0 +1,493 @@ +#![allow(missing_docs)] +use std::{num::NonZeroU32, rc::Rc}; + +use crate::{ + any::U32PowerOf2, + ir::{self, ir_type::BufferBlockDefinitionError, StructureFieldNamesMustBeUnique}, + GpuAligned, GpuSized, GpuStore, GpuType, NoBools, NoHandles, +}; + +// TODO(chronicl) +// - Consider moving this module into ir_type? +// - We borrow these types from `StoreType` currently. Maybe it would be better the other +// way around - `StoreType` should borrow from `HostShareableType`. +pub use crate::ir::{Len, Len2, LenEven, PackedVector, ScalarTypeFp, ScalarTypeInteger, ir_type::CanonName}; +use super::{constraint, FieldOptions}; + +mod align_size; +mod builder; + +pub use align_size::*; +pub use builder::*; + +// TODO(chronicl) rewrite +/// This reprsents a wgsl spec compliant host-shareable type with the addition +/// that f64 is a supported scalar type. +/// +/// https://www.w3.org/TR/WGSL/#host-shareable-types +#[derive(Debug, Clone)] +pub enum LayoutType { + Sized(SizedType), + UnsizedStruct(UnsizedStruct), + RuntimeSizedArray(RuntimeSizedArray), +} + +#[derive(Debug, Clone)] +pub enum SizedType { + Vector(Vector), + Matrix(Matrix), + Array(SizedArray), + Atomic(Atomic), + PackedVec(PackedVector), + Struct(SizedStruct), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct Vector { + pub scalar: ScalarType, + pub len: Len, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct Matrix { + pub scalar: ScalarTypeFp, + pub columns: Len2, + pub rows: Len2, +} + +#[derive(Debug, Clone)] +pub struct SizedArray { + pub element: Rc, + pub len: NonZeroU32, +} + +#[derive(Debug, Clone, Copy)] +pub struct Atomic { + pub scalar: ScalarTypeInteger, +} + +#[derive(Debug, Clone)] +pub struct RuntimeSizedArray { + pub element: SizedType, +} + +/// Same as `ir::ScalarType`, but without `ScalarType::Bool`. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ScalarType { + F16, + F32, + U32, + I32, + F64, +} + +#[derive(Debug, Clone)] +pub struct SizedStruct { + pub name: CanonName, + // This is private to ensure a `SizedStruct` always has at least one field. + fields: Vec, +} + +#[derive(Debug, Clone)] +pub struct UnsizedStruct { + pub name: CanonName, + pub sized_fields: Vec, + pub last_unsized: RuntimeSizedArrayField, +} + +#[derive(Debug, Clone)] +pub struct SizedField { + pub name: CanonName, + pub custom_min_size: Option, + pub custom_min_align: Option, + pub ty: SizedType, +} + +#[derive(Debug, Clone)] +pub struct RuntimeSizedArrayField { + pub name: CanonName, + pub custom_min_align: Option, + pub array: RuntimeSizedArray, +} + +impl SizedField { + pub fn new(options: impl Into, ty: impl Into) -> Self { + let options = options.into(); + Self { + name: options.name, + custom_min_size: options.custom_min_size, + custom_min_align: options.custom_min_align, + ty: ty.into(), + } + } +} + +impl RuntimeSizedArrayField { + pub fn new( + name: impl Into, + custom_min_align: Option, + element_ty: impl Into, + ) -> Self { + Self { + name: name.into(), + custom_min_align, + array: RuntimeSizedArray { + element: element_ty.into(), + }, + } + } +} + +impl SizedArray { + pub fn new(element_ty: impl Into, len: NonZeroU32) -> Self { + Self { + element: Rc::new(element_ty.into()), + len, + } + } +} + +impl RuntimeSizedArray { + pub fn new(element_ty: impl Into) -> Self { + RuntimeSizedArray { + element: element_ty.into(), + } + } +} + +pub enum SizedOrArray { + Sized(SizedType), + RuntimeSizedArray(RuntimeSizedArray), +} + +#[derive(thiserror::Error, Debug)] +#[error("`LayoutType` is `UnsizedStruct`, which is not a variant of `SizedOrArray`")] +pub struct IsUnsizedStruct; +impl TryFrom for SizedOrArray { + type Error = IsUnsizedStruct; + + fn try_from(value: LayoutType) -> Result { + match value { + LayoutType::Sized(sized) => Ok(SizedOrArray::Sized(sized)), + LayoutType::RuntimeSizedArray(array) => Ok(SizedOrArray::RuntimeSizedArray(array)), + LayoutType::UnsizedStruct(_) => Err(IsUnsizedStruct), + } + } +} + +// TODO(chronicl) documentation +pub trait BinaryRepr { + fn layout_type() -> LayoutType; +} +pub trait BinaryReprSized: BinaryRepr { + fn layout_type_sized() -> SizedType; +} + + +impl std::fmt::Display for ScalarType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + ScalarType::F16 => "f16", + ScalarType::F32 => "f32", + ScalarType::F64 => "f64", + ScalarType::U32 => "u32", + ScalarType::I32 => "i32", + }) + } +} + +// Conversions to ScalarType, SizedType and HostshareableType // + +macro_rules! impl_into_sized_type { + ($($ty:ident -> $variant:path),*) => { + $( + impl $ty { + /// Const conversion to [`SizedType`] + pub const fn into_sized_type(self) -> SizedType { $variant(self) } + /// Const conversion to [`HostshareableType`] + pub const fn into_cpu_shareable(self) -> LayoutType { + LayoutType::Sized(self.into_sized_type()) + } + } + + impl From<$ty> for SizedType { + fn from(v: $ty) -> Self { v.into_sized_type() } + } + )* + }; +} +impl_into_sized_type!( + Vector -> SizedType::Vector, + Matrix -> SizedType::Matrix, + SizedArray -> SizedType::Array, + Atomic -> SizedType::Atomic, + SizedStruct -> SizedType::Struct, + PackedVector -> SizedType::PackedVec +); + +impl From for LayoutType +where + SizedType: From, +{ + fn from(value: T) -> Self { LayoutType::Sized(SizedType::from(value)) } +} + +impl From for LayoutType { + fn from(s: UnsizedStruct) -> Self { LayoutType::UnsizedStruct(s) } +} +impl From for LayoutType { + fn from(a: RuntimeSizedArray) -> Self { LayoutType::RuntimeSizedArray(a) } +} + +impl ScalarTypeInteger { + pub const fn as_scalar_type(self) -> ScalarType { + match self { + ScalarTypeInteger::I32 => ScalarType::I32, + ScalarTypeInteger::U32 => ScalarType::U32, + } + } +} +impl From for ScalarType { + fn from(int: ScalarTypeInteger) -> Self { int.as_scalar_type() } +} +impl ScalarTypeFp { + pub const fn as_scalar_type(self) -> ScalarType { + match self { + ScalarTypeFp::F16 => ScalarType::F16, + ScalarTypeFp::F32 => ScalarType::F32, + ScalarTypeFp::F64 => ScalarType::F64, + } + } +} +impl From for ScalarType { + fn from(int: ScalarTypeFp) -> Self { int.as_scalar_type() } +} + + +// Conversions to ir types // + +impl From for ir::StoreType { + fn from(host: LayoutType) -> Self { + match host { + LayoutType::Sized(s) => ir::StoreType::Sized(s.into()), + LayoutType::RuntimeSizedArray(s) => ir::StoreType::RuntimeSizedArray(s.element.into()), + LayoutType::UnsizedStruct(s) => ir::StoreType::BufferBlock(s.into()), + } + } +} + +impl From for ir::SizedType { + fn from(host: SizedType) -> Self { + match host { + SizedType::Vector(v) => ir::SizedType::Vector(v.len, v.scalar.into()), + SizedType::Matrix(m) => ir::SizedType::Matrix(m.columns, m.rows, m.scalar), + SizedType::Array(a) => ir::SizedType::Array(Rc::new(Rc::unwrap_or_clone(a.element).into()), a.len), + SizedType::Atomic(i) => ir::SizedType::Atomic(i.scalar), + // TODO(chronicl) check if this should be decompressed + SizedType::PackedVec(v) => v.decompressed_ty(), + SizedType::Struct(s) => ir::SizedType::Structure(s.into()), + } + } +} + +impl From for ir::ScalarType { + fn from(scalar_type: ScalarType) -> Self { + match scalar_type { + ScalarType::F16 => ir::ScalarType::F16, + ScalarType::F32 => ir::ScalarType::F32, + ScalarType::F64 => ir::ScalarType::F64, + ScalarType::U32 => ir::ScalarType::U32, + ScalarType::I32 => ir::ScalarType::I32, + } + } +} + +impl From for ir::ir_type::SizedStruct { + fn from(host: SizedStruct) -> Self { + let mut fields: Vec = host.fields.into_iter().map(Into::into).collect(); + // has at least one field + let last_field = fields.pop().unwrap(); + + // Note: This might throw an error in real usage if the fields aren't valid + // We're assuming they're valid in the conversion + match ir::ir_type::SizedStruct::new_nonempty(host.name, fields, last_field) { + Ok(s) => s, + Err(StructureFieldNamesMustBeUnique) => { + // TODO(chronicl) this isn't true + unreachable!("field names are unique for `LayoutType`") + } + } + } +} + +impl From for ir::ir_type::BufferBlock { + fn from(host: UnsizedStruct) -> Self { + let sized_fields: Vec = host.sized_fields.into_iter().map(Into::into).collect(); + + let last_unsized = host.last_unsized.into(); + + // TODO(chronicl) + // Note: This might throw an error in real usage if the struct isn't valid + // We're assuming it's valid in the conversion + match ir::ir_type::BufferBlock::new(host.name, sized_fields, Some(last_unsized)) { + Ok(b) => b, + Err(BufferBlockDefinitionError::FieldNamesMustBeUnique) => { + // TODO(chronicl) this isn't true + unreachable!("field names are unique for `UnsizedStruct`") + } + Err(BufferBlockDefinitionError::MustHaveAtLeastOneField) => { + unreachable!("`UnsizedStruct` has at least one field.") + } + } + } +} + +impl From for ir::StoreType { + fn from(array: RuntimeSizedArray) -> Self { ir::StoreType::RuntimeSizedArray(array.element.into()) } +} + +impl From for ir::ir_type::SizedField { + fn from(f: SizedField) -> Self { ir::SizedField::new(f.name, f.custom_min_size, f.custom_min_align, f.ty.into()) } +} + +impl From for ir::ir_type::RuntimeSizedArrayField { + fn from(f: RuntimeSizedArrayField) -> Self { + ir::RuntimeSizedArrayField::new(f.name, f.custom_min_align, f.array.element.into()) + } +} + + +// Conversions from ir types // + +#[derive(thiserror::Error, Debug)] +#[error("Type contains bools, which isn't cpu shareable.")] +pub struct ContainsBools; + +impl TryFrom for ScalarType { + type Error = ContainsBools; + + fn try_from(value: ir::ScalarType) -> Result { + Ok(match value { + ir::ScalarType::F16 => ScalarType::F16, + ir::ScalarType::F32 => ScalarType::F32, + ir::ScalarType::F64 => ScalarType::F64, + ir::ScalarType::U32 => ScalarType::U32, + ir::ScalarType::I32 => ScalarType::I32, + ir::ScalarType::Bool => return Err(ContainsBools), + }) + } +} + +impl ir::ScalarType { + // TODO(chronicl) remove + pub fn as_host_shareable_unchecked(self) -> ScalarType { self.try_into().unwrap() } +} + +impl TryFrom for SizedType { + type Error = ContainsBools; + + fn try_from(value: ir::SizedType) -> Result { + Ok(match value { + ir::SizedType::Vector(len, scalar) => SizedType::Vector(Vector { + scalar: scalar.try_into()?, + len, + }), + ir::SizedType::Matrix(columns, rows, scalar) => SizedType::Matrix(Matrix { scalar, columns, rows }), + ir::SizedType::Array(element, len) => SizedType::Array(SizedArray { + element: Rc::new((*element).clone().try_into()?), + len, + }), + ir::SizedType::Atomic(scalar_type) => SizedType::Atomic(Atomic { scalar: scalar_type }), + ir::SizedType::Structure(structure) => SizedType::Struct(structure.try_into()?), + }) + } +} + +impl TryFrom for SizedStruct { + type Error = ContainsBools; + + fn try_from(structure: ir::ir_type::SizedStruct) -> Result { + let mut fields = Vec::new(); + + for field in structure.sized_fields() { + fields.push(SizedField { + name: field.name.clone(), + custom_min_size: field.custom_min_size, + custom_min_align: field.custom_min_align, + ty: field.ty.clone().try_into()?, + }); + } + + Ok(SizedStruct { + name: structure.name().clone(), + fields, + }) + } +} + +#[derive(thiserror::Error, Debug)] +pub enum CpuShareableConversionError { + #[error("Type contains bools, which isn't cpu shareable.")] + ContainsBool, + #[error("Type is a handle, which isn't cpu shareable.")] + IsHandle, +} + +impl From for CpuShareableConversionError { + fn from(_: ContainsBools) -> Self { Self::ContainsBool } +} + +impl TryFrom for LayoutType { + type Error = CpuShareableConversionError; + + fn try_from(value: ir::StoreType) -> Result { + Ok(match value { + ir::StoreType::Sized(sized_type) => LayoutType::Sized(sized_type.try_into()?), + ir::StoreType::RuntimeSizedArray(element) => LayoutType::RuntimeSizedArray(RuntimeSizedArray { + element: element.try_into()?, + }), + ir::StoreType::BufferBlock(buffer_block) => buffer_block.try_into()?, + ir::StoreType::Handle(_) => return Err(CpuShareableConversionError::IsHandle), + }) + } +} + +impl TryFrom for LayoutType { + type Error = ContainsBools; + + fn try_from(buffer_block: ir::ir_type::BufferBlock) -> Result { + let mut sized_fields = Vec::new(); + + for field in buffer_block.sized_fields() { + sized_fields.push(SizedField { + name: field.name.clone(), + custom_min_size: field.custom_min_size, + custom_min_align: field.custom_min_align, + ty: field.ty.clone().try_into()?, + }); + } + + let last_unsized = if let Some(last_field) = buffer_block.last_unsized_field() { + RuntimeSizedArrayField { + name: last_field.name.clone(), + custom_min_align: last_field.custom_min_align, + array: RuntimeSizedArray { + element: last_field.element_ty.clone().try_into()?, + }, + } + } else { + return Ok(SizedStruct { + name: buffer_block.name().clone(), + fields: sized_fields, + } + .into()); + }; + + Ok(UnsizedStruct { + name: buffer_block.name().clone(), + sized_fields, + last_unsized, + } + .into()) + } +} diff --git a/shame/src/frontend/rust_types/type_layout/eq.rs b/shame/src/frontend/rust_types/type_layout/eq.rs new file mode 100644 index 0000000..bc43f74 --- /dev/null +++ b/shame/src/frontend/rust_types/type_layout/eq.rs @@ -0,0 +1,452 @@ +use super::*; + +/// Error of two layouts mismatching. Implements Display for a visualization of the mismatch. +#[derive(Clone)] +pub struct LayoutMismatch { + /// 2 (name, layout) pairs + layouts: [(String, TypeLayout); 2], + colored_error: bool, +} + + +impl std::fmt::Debug for LayoutMismatch { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self) // use Display + } +} + +impl LayoutMismatch { + fn pad_width(name_a: &str, name_b: &str) -> usize { name_a.chars().count().max(name_b.chars().count()) + SEP.len() } +} + +impl Display for LayoutMismatch { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let colored = self.colored_error; + let [(a_name, a), (b_name, b)] = &self.layouts; + write!(f, "{:width$}", ' ', width = Self::pad_width(a_name, b_name))?; + let layouts = [(a_name.as_str(), a), (b_name.as_str(), b)]; + match LayoutMismatch::write("", layouts, colored, f) { + Err(MismatchWasFound) => Ok(()), + Ok(KeepWriting) => { + writeln!( + f, + "" + )?; + writeln!(f, "the full type layouts in question are:")?; + for (name, layout) in layouts { + writeln!(f, "`{}`:", name)?; + writeln!(f, "{}", layout)?; + } + Ok(()) + } + } + } +} + +/// layout mismatch diff name separator +/// used in the display impl of LayoutMismatch to show the actual place where the layouts mismatch +/// ``` +/// layout mismatch: +/// cpu{SEP} f32 +/// gpu{SEP} i32 +/// ``` +const SEP: &str = ": "; + +/// whether the mismatching part of the TypeLayouts in a LayoutMismatch was already expressed via writes. +/// indicates that the `write` function should stop writing. +pub(crate) struct MismatchWasFound; +pub(crate) struct KeepWriting; + +impl LayoutMismatch { + //TODO(low prio) try to figure out a cleaner way of writing these. + + /// this function uses the `Err(MismatchWasFound)` to halt traversing the typelayout. + /// It does not constitute an error of this function, it is just so the ? operator can be used to propagate the abort. + #[allow(clippy::needless_return)] + pub(crate) fn write( + indent: &str, + layouts: [(&str, &TypeLayout); 2], + colored: bool, + f: &mut W, + ) -> Result { + let tab = " "; + let [(a_name, a), (b_name, b)] = layouts; + + if a == b { + a.write(indent, colored, f); + return Ok(KeepWriting); + } + + let use_256_color_mode = false; + let hex_color = |f_: &mut W, hex| match colored { + true => set_color(f_, Some(hex), use_256_color_mode), + false => Ok(()), + }; + let color_reset = |f_: &mut W| match colored { + true => set_color(f_, None, use_256_color_mode), + false => Ok(()), + }; + + let color_a_hex = "#DF5853"; + let color_b_hex = "#9A639C"; + let color_a = |f| hex_color(f, color_a_hex); + let color_b = |f| hex_color(f, color_b_hex); + + let pad_width = Self::pad_width(a_name, b_name); + + use TypeLayoutSemantics as S; + match (&a.kind, &b.kind) { + (S::Structure(sa), S::Structure(sb)) => { + let max_fields = sa.all_fields().len().max(sb.all_fields().len()); + { + write!(f, "struct "); + hex_color(f, color_a_hex); + write!(f, "{}", sa.name); + color_reset(f); + write!(f, " / "); + hex_color(f, color_b_hex); + write!(f, "{}", sb.name); + color_reset(f); + writeln!(f, " {{"); + } + + let mut sa_fields = sa.all_fields().iter(); + let mut sb_fields = sb.all_fields().iter(); + + loop { + //TODO(low prio) get a hold of the code duplication here + match (sa_fields.next(), sb_fields.next()) { + (Some(a_field), Some(b_field)) => { + let offsets_match = a_field.rel_byte_offset == b_field.rel_byte_offset; + let types_match = a_field.field.ty == b_field.field.ty; + if !offsets_match && types_match { + // only write this mismatch if the types are also the same, otherwise display the detailed type mismatch further below + let a_ty_string = a_field.field.ty.first_line_of_display_with_ellipsis(); + let b_ty_string = b_field.field.ty.first_line_of_display_with_ellipsis(); + color_a(f); + writeln!( + f, + "{a_name}{SEP}{indent}{:3} {}: {a_ty_string} align={}", + a_field.rel_byte_offset, + a_field.field.name, + a_field.field.byte_align().as_u32() + ); + color_b(f); + writeln!( + f, + "{b_name}{SEP}{indent}{:3} {}: {b_ty_string} align={}", + b_field.rel_byte_offset, + b_field.field.name, + b_field.field.byte_align().as_u32() + ); + color_reset(f); + writeln!( + f, + "field offset is different on {a_name} ({}) and {b_name} ({}).", + a_field.rel_byte_offset, b_field.rel_byte_offset + ); + return Err(MismatchWasFound); + } + let offset = a_field.rel_byte_offset; + let a_field = &a_field.field; + let b_field = &b_field.field; + let field = &a_field; + + if offsets_match { + write!(f, "{:width$}{indent}{offset:3} ", ' ', width = pad_width); + } else { + write!(f, "{:width$}{indent} ? ", ' ', width = pad_width); + } + if a_field.name != b_field.name { + writeln!(f); + color_a(f); + writeln!(f, "{a_name}{SEP}{indent} {}: …", a_field.name); + color_b(f); + writeln!(f, "{b_name}{SEP}{indent} {}: …", b_field.name); + color_reset(f); + writeln!( + f, + "identifier mismatch, either\nfield '{}' is missing on {a_name}, or\nfield '{}' is missing on {b_name}.", + b_field.name, a_field.name + ); + return Err(MismatchWasFound); + } + write!(f, "{}: ", field.name); + if a_field.ty != b_field.ty { + Self::write( + &format!("{indent}{tab}"), + [(a_name, &a_field.ty), (b_name, &b_field.ty)], + colored, + f, + )?; + return Err(MismatchWasFound); + } + write!(f, "{}", field.ty.first_line_of_display_with_ellipsis()); + if a_field.byte_size() != b_field.byte_size() { + writeln!(f); + color_a(f); + writeln!( + f, + "{a_name}{SEP}{indent} size={}", + a_field + .byte_size() + .as_ref() + .map(|x| x as &dyn Display) + .unwrap_or(&"?" as _) + ); + color_b(f); + writeln!( + f, + "{b_name}{SEP}{indent} size={}", + b_field + .byte_size() + .as_ref() + .map(|x| x as &dyn Display) + .unwrap_or(&"?" as _) + ); + color_reset(f); + return Err(MismatchWasFound); + } + + write!( + f, + " size={}", + field + .byte_size() + .as_ref() + .map(|x| x as &dyn Display) + .unwrap_or(&"?" as _) + ); + writeln!(f, ","); + } + (Some(a_field), None) => { + let offset = a_field.rel_byte_offset; + let a_field = &a_field.field; + let field = &a_field; + color_a(f); + write!(f, "{a_name}{SEP}{indent}{offset:3} "); + write!(f, "{}: ", field.name); + write!(f, "{}", field.ty.first_line_of_display_with_ellipsis()); + write!( + f, + " size={}", + field + .byte_size() + .as_ref() + .map(|x| x as &dyn Display) + .unwrap_or(&"?" as _) + ); + writeln!(f, ","); + color_b(f); + writeln!(f, "{b_name}{SEP}{indent}", a_field.name); + color_reset(f); + return Err(MismatchWasFound); + } + (None, Some(b_field)) => { + let offset = b_field.rel_byte_offset; + let b_field = &b_field.field; + color_a(f); + writeln!(f, "{a_name}{SEP}{indent}", b_field.name); + let field = &b_field; + color_b(f); + write!(f, "{b_name}{SEP}{indent}{offset:3} "); + write!(f, "{}: ", field.name); + write!(f, "{}", field.ty.first_line_of_display_with_ellipsis()); + write!( + f, + " size={}", + field + .byte_size() + .as_ref() + .map(|x| x as &dyn Display) + .unwrap_or(&"?" as _) + ); + writeln!(f, ","); + color_reset(f); + return Err(MismatchWasFound); + } + (None, None) => break, + } + } + + write!(f, "{:width$}{indent}}}", ' ', width = pad_width); + let align_matches = a.byte_align() == b.byte_align(); + let size_matches = a.byte_size() == b.byte_size(); + if !align_matches && size_matches { + writeln!(f); + color_a(f); + writeln!(f, "{a_name}{SEP}{indent}align={}", a.byte_align().as_u32()); + color_b(f); + writeln!(f, "{b_name}{SEP}{indent}align={}", b.byte_align().as_u32()); + color_reset(f); + return Err(MismatchWasFound); + } else { + match align_matches { + true => write!(f, " align={}", a.byte_align().as_u32()), + false => write!(f, " align=?"), + }; + } + if !size_matches { + writeln!(f); + color_a(f); + writeln!( + f, + "{a_name}{SEP}{indent}size={}", + a.byte_size().as_ref().map(|x| x as &dyn Display).unwrap_or(&"?" as _) + ); + color_b(f); + writeln!( + f, + "{b_name}{SEP}{indent}size={}", + b.byte_size().as_ref().map(|x| x as &dyn Display).unwrap_or(&"?" as _) + ); + color_reset(f); + return Err(MismatchWasFound); + } else { + match a.byte_size() { + Some(size) => write!(f, " size={size}"), + None => write!(f, " size=?"), + }; + } + // this should never happen, returning Ok(KeepWriting) will trigger the internal error in the Display impl + return Ok(KeepWriting); + } + (S::Array(ta, na), S::Array(tb, nb)) => { + if na != nb { + writeln!(f); + color_a(f); + write!(f, "{a_name}{SEP}"); + + write!( + f, + "array<…, {}>", + match na { + Some(n) => n as &dyn Display, + None => (&"runtime-sized") as &dyn Display, + } + ); + + //a.writeln(indent, colored, f); + writeln!(f); + color_b(f); + write!(f, "{b_name}{SEP}"); + + write!( + f, + "array<…, {}>", + match nb { + Some(n) => n as &dyn Display, + None => (&"runtime-sized") as &dyn Display, + } + ); + + //b.writeln(indent, colored, f); + color_reset(f); + Err(MismatchWasFound) + } else { + write!(f, "array<"); + //ta.ty.write(&(indent.to_string() + tab), colored, f); + + Self::write( + &format!("{indent}{tab}"), + [(a_name, &ta.ty), (b_name, &tb.ty)], + colored, + f, + )?; + + if let Some(na) = na { + write!(f, ", {na}"); + } + write!(f, ">"); + + if ta.byte_stride != tb.byte_stride { + writeln!(f); + color_a(f); + writeln!(f, "{a_name}{SEP}{indent}> stride={}", ta.byte_stride); + color_b(f); + writeln!(f, "{b_name}{SEP}{indent}> stride={}", tb.byte_stride); + color_reset(f); + Err(MismatchWasFound) + } else { + // this should never happen, returning Ok(KeepWriting) will trigger the internal error in the Display impl + write!(f, "> stride={}", ta.byte_stride); + return Ok(KeepWriting); + } + } + } + (S::Vector(_), S::Vector(_)) => { + writeln!(f); + color_a(f); + write!(f, "{a_name}{SEP}"); + a.writeln(indent, colored, f); + color_b(f); + write!(f, "{b_name}{SEP}"); + b.writeln(indent, colored, f); + color_reset(f); + Err(MismatchWasFound) + } + (S::Matrix(_), S::Matrix(_)) => { + writeln!(f); + color_a(f); + write!(f, "{a_name}{SEP}"); + a.writeln(indent, colored, f); + color_b(f); + write!(f, "{b_name}{SEP}"); + b.writeln(indent, colored, f); + color_reset(f); + Err(MismatchWasFound) + } + (S::PackedVector(p), S::PackedVector(p1)) => { + writeln!(f); + color_a(f); + write!(f, "{a_name}{SEP}"); + a.writeln(indent, colored, f); + color_b(f); + write!(f, "{b_name}{SEP}"); + b.writeln(indent, colored, f); + color_reset(f); + Err(MismatchWasFound) + } + ( + // its written like this so that exhaustiveness checks lead us to this match statement if a type is added + S::Structure { .. } | S::Array { .. } | S::Vector { .. } | S::Matrix { .. } | S::PackedVector { .. }, + _, + ) => { + // TypeLayoutSemantics mismatch + writeln!(f); + color_a(f); + write!(f, "{a_name}{SEP}"); + a.writeln(indent, colored, f); + color_b(f); + write!(f, "{b_name}{SEP}"); + b.writeln(indent, colored, f); + color_reset(f); + Err(MismatchWasFound) + } + } + } +} + +/// takes two pairs of `(debug_name, layout)` and compares them for equality. +/// +/// if the two layouts are not equal it uses the debug names in the returned +/// error to tell the two layouts apart. +pub(crate) fn check_eq( + a: (&str, &TypeLayout), + b: (&str, &TypeLayout), +) -> Result<(), LayoutMismatch> +where + TypeLayout: PartialEq>, +{ + match a.1 == b.1 { + true => Ok(()), + false => Err(LayoutMismatch { + layouts: [ + (a.0.into(), a.1.to_owned().into_plain()), + (b.0.into(), b.1.to_owned().into_plain()), + ], + colored_error: Context::try_with(call_info!(), |ctx| ctx.settings().colored_error_messages) + .unwrap_or(false), + }), + } +} diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs new file mode 100644 index 0000000..bc4a432 --- /dev/null +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -0,0 +1,616 @@ +#![allow(missing_docs)] // TODO(chronicl) remove +//! Everything related to type layouts. + + + +use std::{ + fmt::{Debug, Display, Write}, + hash::Hash, + marker::PhantomData, + rc::Rc, +}; + +use crate::{ + any::U32PowerOf2, + call_info, + common::{ignore_eq::IgnoreInEqOrdHash, prettify::set_color}, + ir::{ + self, + ir_type::{round_up, CanonName, ScalarTypeFp}, + recording::Context, + Len, SizedType, Type, + }, +}; +use cpu_shareable::{LayoutType, Matrix, Vector}; +use thiserror::Error; + +pub mod cpu_shareable; +mod eq; +mod storage_uniform; +mod vertex; + +pub use vertex::*; +pub use storage_uniform::*; +pub(crate) use eq::*; + +/// The type contained in the bytes of a `TypeLayout`. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum TypeLayoutSemantics { + // TODO(chronicl) consider replacing this scalar type with the host shareable version. + /// `vec` + Vector(Vector), + /// special compressed vectors for vertex attribute types + /// + /// see the [`crate::packed`] module + PackedVector(ir::PackedVector), + /// `mat`, first `Len2` is cols, 2nd `Len2` is rows + Matrix(Matrix), + /// `Array` and `Array>` + Array(Rc, Option), // not NonZeroU32, since for rust `CpuLayout`s the array size may be 0. + /// structures which may be empty and may have an unsized last field + Structure(Rc), +} + +/// The memory layout of a type. +/// +/// This models only the layout, not other characteristics of the types. +/// For example an `Atomic>` is treated like a regular `vec` layout wise. +/// +/// ### Constraint generic +/// +/// `TypeLayout` has a generic `T: TypeConstraint`, which guarantees that the layout +/// follows certain layout rules. The following layout rules exist and are captured by +/// the [`Repr`] enum: +/// ``` +/// pub enum Repr { +/// Storage, /// wgsl storage address space layout / OpenGL std430 +/// Uniform, /// wgsl uniform address space layout / OpenGL std140 +/// Packed, /// packed layout, vertex buffer only +/// } +/// ``` +/// +/// More information on the exact details of these layout rules is available here +/// +/// https://www.w3.org/TR/WGSL/#address-space-layout-constraints +/// +/// The following types implementing `TypeConstraint` exist and can be found in [`constraint`]: +/// +/// ``` +/// struct Plain; /// May not follow any layout rules. +/// struct Storage; /// Follows `Repr::Storage` layout rules. +/// struct Uniform; /// Follows `Repr::Uniform` layout rules. +/// struct Vertex; /// Follows either `Repr::Packed` or both +/// /// `Repr::Storage` and `Repr::Uniform`. +/// ``` +/// `Vertex` also guarantees that it is either the layout of a [`VertexAttribute`] or the layout +/// of a struct with fields that are all `VertexAttribute`s, which is why `Repr::Storage` +/// and `Repr::Uniform` are equivalent for it. +/// +/// `Storage` and `Uniform` are always based on a [`CpuShareableType`], which can be accessed +/// using [`TypeLayout::cpu_shareable`]. They also guarantee, that all nested `TypeLayout`s +/// (for example struct fields) are also based on a `CpuShareableType`, which can only be accessed +/// through `TypeLayout::get_cpu_shareable`. +/// +/// TODO(chronicl) make sure these are caught: +/// There is some target language specific constraints that `TypeLayout` does not capture: +/// - `TypeLayout` may be unsized, which is not allowed for wgsl. +/// - `custom_min_align` and `custom_min_size` are not supported for glsl. +/// +/// ### Layout comparison +/// +/// The `PartialEq + Eq` implementation of `TypeLayout` is designed to answer the question +/// "do these two types have the same layout" so that uploading a type to the gpu +/// will result in no memory errors. +/// +/// A layout comparison looks like this: +/// ``` +/// assert!(f32::cpu_layout() == vec::gpu_layout().into_unconstraint()); +/// // or, more explicitly +/// assert_eq!( +/// ::cpu_layout(), +/// as GpuLayout>::gpu_layout(), +/// ); +/// ``` +/// +#[derive(Clone)] +pub struct TypeLayout { + /// size in bytes (Some), or unsized (None) + pub byte_size: Option, + /// the byte alignment + /// + /// top level alignment is not considered relevant in some checks, but relevant in others (vertex array elements) + pub byte_align: U32PowerOf2, + /// the type contained in the bytes of this type layout + pub kind: TypeLayoutSemantics, + + /// Is some for `constraint::Storage` and `constraint::Uniform`. Can be converted to a `StoreType`. + pub(crate) cpu_shareable: Option, + _phantom: PhantomData, +} + +// PartialEq, Eq, Hash for TypeLayout +impl PartialEq> for TypeLayout { + fn eq(&self, other: &TypeLayout) -> bool { self.byte_size() == other.byte_size() && self.kind == other.kind } +} +impl Eq for TypeLayout {} +impl Hash for TypeLayout { + fn hash(&self, state: &mut H) { + self.byte_size.hash(state); + self.kind.hash(state); + } +} + +use constraint::TypeConstraint; +/// Module for all restrictions on `TypeLayout`. +pub mod constraint { + use super::*; + + /// Type restriction of a `TypeLayout`. This allows type layouts + /// to have guarantees based on their type restriction. For example a + /// `TypeLayout can be used in vertex buffers. For more + /// details see the documentation of [`TypeLayout`]. + pub trait TypeConstraint: Clone + PartialEq + Eq {} + + macro_rules! type_restriction { + ($($constraint:ident),*) => { + $( + /// A type restriction for `TypeLayout`. + /// See [`TypeRestriction`] documentation for TODO(chronicl) + #[derive(Clone, PartialEq, Eq, Hash)] + pub struct $constraint; + impl TypeConstraint for $constraint {} + )* + }; + } + + type_restriction!(Plain, Storage, Uniform, Vertex); + + macro_rules! impl_from_into { + ($from:ident -> $($into:ident),*) => { + $( + impl From> for TypeLayout<$into> { + fn from(layout: TypeLayout<$from>) -> Self { type_layout_internal::cast_unchecked(layout) } + } + )* + }; + } + + impl_from_into!(Storage -> Plain); + impl_from_into!(Uniform -> Plain, Storage); + impl_from_into!(Vertex -> Plain); +} + +impl TypeLayout { + pub(crate) fn new( + byte_size: Option, + byte_align: U32PowerOf2, + kind: TypeLayoutSemantics, + hostshareable: Option, + ) -> Self { + TypeLayout { + byte_size, + byte_align, + kind, + cpu_shareable: hostshareable, + _phantom: PhantomData, + } + } +} + +/// This module offers helper methods that do not adhere to the restriction of `TypeLayout. +/// The caller must uphold these restrictions themselves. +/// The main purpose of this module is to avoid repetition. +pub(in super::super::rust_types) mod type_layout_internal { + use super::*; + + pub fn cast_unchecked(layout: TypeLayout) -> TypeLayout { + TypeLayout { + byte_size: layout.byte_size, + byte_align: layout.byte_align, + kind: layout.kind, + cpu_shareable: layout.cpu_shareable, + _phantom: PhantomData, + } + } +} + +/// `LayoutCalculator` helps calculate the size, align and the field offsets of a gpu struct. +/// +/// If `LayoutCalculator` is created with `packed = true`, provided `field_align`s +/// are ignored and the field is directly after the previous field. However, +/// a `custom_min_align` that is `Some` overwrites the `packedness` of the field. +#[derive(Debug, Clone)] +pub struct LayoutCalculator { + next_offset_min: u64, + align: U32PowerOf2, + packed: bool, +} + +impl LayoutCalculator { + /// Creates a new `LayoutCalculator`, which calculates the size, align and + /// the field offsets of a gpu struct. + pub const fn new(packed: bool) -> Self { + Self { + next_offset_min: 0, + align: U32PowerOf2::_1, + packed, + } + } + + /// Extends the layout by a field given it's size and align. + /// + /// Returns the field's offset. + pub const fn extend( + &mut self, + field_size: u64, + field_align: U32PowerOf2, + custom_min_size: Option, + custom_min_align: Option, + ) -> u64 { + let size = FieldLayout::calculate_byte_size(field_size, custom_min_size); + let align = FieldLayout::calculate_align(field_align, custom_min_align); + + let offset = self.next_field_offset(align, custom_min_align); + self.next_offset_min = offset + size; + self.align = self.align.max(align); + + offset + } + + /// Extends the layout by a field given it's size and align. If the field + /// is unsized, pass `None` as it's size. + /// + /// Returns (byte size, byte align, last field offset). + /// + /// `self` is consumed, so that no further fields may be extended, because + /// only the last field may be unsized. + pub const fn extend_maybe_unsized( + mut self, + field_size: Option, + field_align: U32PowerOf2, + custom_min_size: Option, + custom_min_align: Option, + ) -> (Option, U32PowerOf2, u64) { + if let Some(size) = field_size { + let offset = self.extend(size, field_align, custom_min_size, custom_min_align); + (Some(self.byte_size()), self.align(), offset) + } else { + let (offset, align) = self.extend_unsized(field_align, custom_min_align); + (None, align, offset) + } + } + + + /// Extends the layout by an unsized field given it's align. + /// + /// Returns (last field offset, align) + /// + /// `self` is consumed, so that no further fields may be extended, because + /// only the last field may be unsized. + pub const fn extend_unsized( + mut self, + field_align: U32PowerOf2, + custom_min_align: Option, + ) -> (u64, U32PowerOf2) { + let align = FieldLayout::calculate_align(field_align, custom_min_align); + + let offset = self.next_field_offset(align, custom_min_align); + self.align = self.align.max(align); + + (offset, self.align) + } + + /// Returns the byte size of the struct. + // wgsl spec: + // roundUp(AlignOf(S), justPastLastMember) + // where justPastLastMember = OffsetOfMember(S,N) + SizeOfMember(S,N) + // + // self.next_offset_min is justPastLastMember already. + pub const fn byte_size(&self) -> u64 { round_up(self.align.as_u64(), self.next_offset_min) } + + /// Returns the align of the struct. + pub const fn align(&self) -> U32PowerOf2 { self.align } + + /// field_align should already respect field_custom_min_align. + /// field_custom_min_align is used to overwrite packing if self is packed. + const fn next_field_offset(&self, field_align: U32PowerOf2, field_custom_min_align: Option) -> u64 { + match (self.packed, field_custom_min_align) { + (true, None) => self.next_offset_min, + (true, Some(custom_align)) => round_up(custom_align.as_u64(), self.next_offset_min), + (false, _) => round_up(field_align.as_u64(), self.next_offset_min), + } + } +} + +/// a sized or unsized struct type with 0 or more fields +#[allow(missing_docs)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct StructLayout { + pub name: IgnoreInEqOrdHash, + pub fields: Vec, +} + +impl StructLayout { + /// this exists, because if in the future a refactor happens that separates + /// fields into sized and unsized fields, the intention of this function is + /// clear + fn all_fields(&self) -> &[FieldLayoutWithOffset] { &self.fields } +} + +#[allow(missing_docs)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct FieldLayoutWithOffset { + pub field: FieldLayout, + pub rel_byte_offset: u64, // this being relative is used in TypeLayout::byte_size +} + +impl std::ops::Deref for FieldLayoutWithOffset { + type Target = FieldLayout; + fn deref(&self) -> &Self::Target { &self.field } +} + +/// Describes the layout of the elements of an array. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct ElementLayout { + /// Stride of the elements + pub byte_stride: u64, + /// The elment layout must be constraint::Plain, because it's shared by all constraints. + // `ElementLayout` could possibly be made generic too, but it would complicate a lot. + pub ty: TypeLayout, +} + +/// the layout rules used when calculating the byte offsets and alignment of a type +#[derive(Debug, Clone, Copy)] +pub enum TypeLayoutRules { + /// wgsl type layout rules, see https://www.w3.org/TR/WGSL/#memory-layouts + Wgsl, + // reprC, + // Std140, + // Std430, + // Scalar, +} + +#[allow(missing_docs)] +#[derive(Error, Debug, Clone)] +pub enum TypeLayoutError { + #[error("An array cannot contain elements of an unsized type {elements}")] + ArrayOfUnsizedElements { elements: TypeLayout }, + #[error("the type `{0}` has no defined {1:?} layout in shaders")] + LayoutUndefined(Type, TypeLayoutRules), + #[error("in `{parent_name}` at field `{field_name}`: {error}")] + AtField { + parent_name: CanonName, + field_name: CanonName, + error: Rc, + }, + #[error("in element of array: {0}")] + InArrayElement(Rc), +} + +impl TypeLayout { + /// Byte size of the represented type. + pub fn byte_size(&self) -> Option { self.byte_size } + + /// Align of the represented type. + pub fn byte_align(&self) -> U32PowerOf2 { self.byte_align } + + /// Although all TypeLayout always implement Into, this method + /// is offered to avoid having to declare that as a bound when handling generic TypeLayout. + pub fn into_plain(self) -> TypeLayout { type_layout_internal::cast_unchecked(self) } + + /// Get the `CpuShareableType` this layout is based on. + /// + /// This may be `None` for type layouts + /// that aren't `TypeLayout` or `TypeLayout`. Those two also offer direct + /// access via [`TypeLayout::cpu_shareable`]. + pub fn get_cpu_shareable(&self) -> Option<&LayoutType> { self.cpu_shareable.as_ref() } + + /// a short name for this `TypeLayout`, useful for printing inline + pub fn short_name(&self) -> String { + match &self.kind { + TypeLayoutSemantics::Vector { .. } | + TypeLayoutSemantics::PackedVector { .. } | + TypeLayoutSemantics::Matrix { .. } => format!("{}", self), + TypeLayoutSemantics::Array(element_layout, n) => match n { + Some(n) => format!("array<{}, {n}>", element_layout.ty.short_name()), + None => format!("array<{}, runtime-sized>", element_layout.ty.short_name()), + }, + TypeLayoutSemantics::Structure(s) => s.name.to_string(), + } + } + + pub(crate) fn first_line_of_display_with_ellipsis(&self) -> String { + let string = format!("{}", self); + string.split_once('\n').map(|(s, _)| format!("{s}…")).unwrap_or(string) + } + + pub(crate) fn writeln(&self, indent: &str, colored: bool, f: &mut W) -> std::fmt::Result { + self.write(indent, colored, f)?; + writeln!(f) + } + + //TODO(low prio) try to figure out a cleaner way of writing these. + pub(crate) fn write(&self, indent: &str, colored: bool, f: &mut W) -> std::fmt::Result { + let tab = " "; + let use_256_color_mode = false; + let color = |f_: &mut W, hex| match colored { + true => set_color(f_, Some(hex), use_256_color_mode), + false => Ok(()), + }; + let reset = |f_: &mut W| match colored { + true => set_color(f_, None, use_256_color_mode), + false => Ok(()), + }; + + use TypeLayoutSemantics as Sem; + + match &self.kind { + Sem::Vector(Vector { len: l, scalar: t }) => match l { + Len::X1 => write!(f, "{t}")?, + l => write!(f, "{t}x{}", u64::from(*l))?, + }, + Sem::PackedVector(c) => write!(f, "{}", c)?, + Sem::Matrix(m) => write!(f, "{}", ir::SizedType::Matrix(m.columns, m.rows, m.scalar))?, + Sem::Array(t, n) => { + let stride = t.byte_stride; + write!(f, "array<")?; + t.ty.write(&(indent.to_string() + tab), colored, f)?; + if let Some(n) = n { + write!(f, ", {n}")?; + } + write!(f, "> stride={stride}")?; + } + Sem::Structure(s) => { + writeln!(f, "struct {} {{", s.name)?; + { + let indent = indent.to_string() + tab; + for field in &s.fields { + let offset = field.rel_byte_offset; + let field = &field.field; + write!(f, "{indent}{offset:3} {}: ", field.name)?; + field.ty.write(&(indent.to_string() + tab), colored, f)?; + if let Some(size) = field.ty.byte_size { + let size = size.max(field.custom_min_size.unwrap_or(0)); + write!(f, " size={size}")?; + } else { + write!(f, " size=?")?; + } + writeln!(f, ",")?; + } + } + write!(f, "{indent}}}")?; + write!(f, " align={}", self.byte_align.as_u64())?; + if let Some(size) = self.byte_size { + write!(f, " size={size}")?; + } else { + write!(f, " size=?")?; + } + } + }; + Ok(()) + } +} + +impl TypeLayout { + pub(crate) fn from_rust_sized(kind: TypeLayoutSemantics) -> Self { + Self::new( + Some(size_of::() as u64), + // align is always a power of 2: + // https://doc.rust-lang.org/reference/type-layout.html#r-layout.properties.align + U32PowerOf2::try_from(align_of::() as u32).unwrap(), + kind, + None, + ) + } + + // TODO(chronicl) this should be removed with improved any api for storage/uniform bindings + pub(crate) fn from_store_ty(store_type: ir::StoreType) -> Result { + let t: cpu_shareable::LayoutType = store_type.try_into()?; + Ok(TypeLayout::new_storage_layout_for(t).into()) + } +} + +#[allow(missing_docs)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct FieldLayout { + pub name: CanonName, + // whether size/align is custom doesn't matter for the layout equality. + pub custom_min_size: IgnoreInEqOrdHash>, + pub custom_min_align: IgnoreInEqOrdHash>, + /// The fields layout must be constraint::Plain, + /// because that's the most it can be while supporting all possible constraints. + pub ty: TypeLayout, +} + +impl FieldLayout { + pub fn new( + name: CanonName, + custom_min_size: Option, + custom_min_align: Option, + ty: TypeLayout, + ) -> Self { + Self { + name, + custom_min_size: custom_min_size.into(), + custom_min_align: custom_min_align.into(), + ty, + } + } + + fn byte_size(&self) -> Option { + self.ty + .byte_size() + .map(|byte_size| Self::calculate_byte_size(byte_size, self.custom_min_size.0)) + } + + /// The alignment of the field with `custom_min_align` taken into account. + fn byte_align(&self) -> U32PowerOf2 { Self::calculate_align(self.ty.byte_align(), self.custom_min_align.0) } + + const fn calculate_byte_size(byte_size: u64, custom_min_size: Option) -> u64 { + // const byte_size.max(custom_min_size.unwrap_or(0)) + if let Some(min_size) = custom_min_size { + if min_size > byte_size { + return min_size; + } + } + byte_size + } + + const fn calculate_align(align: U32PowerOf2, custom_min_align: Option) -> U32PowerOf2 { + // const align.max(custom_min_align.unwrap_or(U32PowerOf2::_1) as u32 as u64) + if let Some(min_align) = custom_min_align { + align.max(min_align) + } else { + align + } + } +} + +/// Options for the field of a struct. +/// +/// If you only want to customize the field's name, you can convert most string types +/// to `FieldOptions` using `Into::into`, but most methods take `impl Into`, +/// meaning you can just pass the string type directly. +#[derive(Debug, Clone)] +pub struct FieldOptions { + /// Name of the field + pub name: CanonName, + /// Custom minimum align of the field. + pub custom_min_align: Option, + /// Custom mininum size of the field. + pub custom_min_size: Option, +} + +impl FieldOptions { + /// Creates new `FieldOptions`. + /// + /// If you only want to customize the field's name, you can convert most string types + /// to `FieldOptions` using `Into::into`, but most methods take `impl Into`, + /// meaning you can just pass the string type directly. + pub fn new( + name: impl Into, + custom_min_align: Option, + custom_min_size: Option, + ) -> Self { + Self { + name: name.into(), + custom_min_align, + custom_min_size, + } + } +} + +impl> From for FieldOptions { + fn from(name: T) -> Self { Self::new(name, None, None) } +} + +impl Display for TypeLayout { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let colored = Context::try_with(call_info!(), |ctx| ctx.settings().colored_error_messages).unwrap_or(false); + self.write("", colored, f) + } +} + +impl Debug for TypeLayout { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.write("", false, f) } +} diff --git a/shame/src/frontend/rust_types/type_layout/storage_uniform.rs b/shame/src/frontend/rust_types/type_layout/storage_uniform.rs new file mode 100644 index 0000000..d788a71 --- /dev/null +++ b/shame/src/frontend/rust_types/type_layout/storage_uniform.rs @@ -0,0 +1,475 @@ +use crate::{ + __private::SmallVec, + cpu_shareable::{self as cs, Major, Repr, SizedField, UnsizedStruct}, + ir::{ir_type::max_u64_po2_dividing, StoreType}, +}; +use super::*; +use TypeLayoutSemantics as TLS; + +impl TypeLayout { + /// Takes a `SizedType`, because wgsl only supports sized uniform buffers. + /// Use `new_layout_for_unchecked`to obtain the the uniform layout of an unsized host-shareable. /// Using the unsized layout with wgsl as your target language will cause an error. + pub fn new_uniform_layout_for(ty: impl Into) -> Self { + type_layout_internal::cast_unchecked(TypeLayout::new_layout_for(ty, Repr::Uniform)) + } + + /// Get the `CpuShareableType` this layout is based on. + pub fn cpu_shareable(&self) -> &LayoutType { + self.cpu_shareable + .as_ref() + .expect("constraint::Uniform always contains a cpu-shareable") + } +} + +impl TypeLayout { + pub fn new_storage_layout_for(ty: impl Into) -> Self { + type_layout_internal::cast_unchecked(TypeLayout::new_layout_for(ty, Repr::Storage)) + } + + /// Get the `CpuShareableType` this layout is based on. + pub fn cpu_shareable(&self) -> &LayoutType { + self.cpu_shareable + .as_ref() + .expect("constraint::Storage always contains a cpu-shareable") + } +} + +impl TypeLayout { + pub fn new_layout_for(ty: impl Into, repr: Repr) -> Self { + match ty.into() { + LayoutType::Sized(ty) => Self::from_sized_type(ty, repr), + LayoutType::UnsizedStruct(ty) => Self::from_unsized_struct(ty, repr), + LayoutType::RuntimeSizedArray(ty) => Self::from_runtime_sized_array(ty, repr), + } + } + + fn from_sized_type(ty: cs::SizedType, repr: Repr) -> Self { + let (size, align, tls) = match &ty { + cs::SizedType::Vector(v) => (v.byte_size(), v.align(), TLS::Vector(*v)), + cs::SizedType::Atomic(a) => ( + a.byte_size(), + a.align(), + TLS::Vector(Vector::new(a.scalar.into(), ir::Len::X1)), + ), + cs::SizedType::Matrix(m) => (m.byte_size(Major::Row), m.align(repr, Major::Row), TLS::Matrix(*m)), + cs::SizedType::Array(a) => ( + a.byte_size(repr), + a.align(repr), + TLS::Array( + Rc::new(ElementLayout { + byte_stride: a.byte_stride(repr), + ty: Self::from_sized_type((*a.element).clone(), repr), + }), + Some(a.len.get()), + ), + ), + cs::SizedType::PackedVec(v) => (v.byte_size().as_u64(), v.align(), TLS::PackedVector(*v)), + cs::SizedType::Struct(s) => { + let mut field_offsets = s.field_offsets(repr); + let fields = (&mut field_offsets) + .zip(s.fields()) + .map(|(offset, field)| sized_field_to_field_layout(field, offset, repr)) + .collect(); + + ( + field_offsets.byte_size(), + field_offsets.align(), + TLS::Structure(Rc::new(StructLayout { + name: s.name.clone().into(), + fields, + })), + ) + } + }; + + TypeLayout::new(Some(size), align, tls, Some(ty.into())) + } + + fn from_unsized_struct(s: UnsizedStruct, repr: Repr) -> Self { + let mut field_offsets = s.sized_field_offsets(repr); + let mut fields = (&mut field_offsets) + .zip(s.sized_fields.iter()) + .map(|(offset, field)| sized_field_to_field_layout(field, offset, repr)) + .collect::>(); + + let (field_offset, align) = s.last_field_offset_and_struct_align(field_offsets); + fields.push(FieldLayoutWithOffset { + rel_byte_offset: field_offset, + field: FieldLayout { + name: s.last_unsized.name.clone(), + custom_min_size: None.into(), + custom_min_align: s.last_unsized.custom_min_align.into(), + ty: Self::from_runtime_sized_array(s.last_unsized.array.clone(), repr), + }, + }); + + TypeLayout::new( + None, + align, + TLS::Structure(Rc::new(StructLayout { + name: s.name.clone().into(), + fields, + })), + Some(s.into()), + ) + } + + + fn from_runtime_sized_array(ty: cs::RuntimeSizedArray, repr: Repr) -> Self { + Self::new( + None, + ty.byte_align(repr), + TLS::Array( + Rc::new(ElementLayout { + byte_stride: ty.byte_stride(repr), + ty: Self::from_sized_type(ty.element.clone(), repr), + }), + None, + ), + Some(ty.into()), + ) + } +} + +fn sized_field_to_field_layout(field: &SizedField, offset: u64, repr: Repr) -> FieldLayoutWithOffset { + FieldLayoutWithOffset { + rel_byte_offset: offset, + field: FieldLayout { + name: field.name.clone(), + custom_min_size: field.custom_min_size.into(), + custom_min_align: field.custom_min_align.into(), + ty: TypeLayout::from_sized_type(field.ty.clone(), repr), + }, + } +} + +impl<'a> TryFrom<&'a TypeLayout> for TypeLayout { + type Error = UniformLayoutError; + + fn try_from(s_layout: &'a TypeLayout) -> Result { + let cpu_shareable = s_layout.cpu_shareable().clone(); + + // Checking whether it's sized + let sized = match cpu_shareable { + LayoutType::Sized(sized) => sized, + _ => { + return Err(UniformLayoutError::MustBeSized("wgsl", cpu_shareable.into())); + } + }; + + let u_layout = TypeLayout::new_uniform_layout_for(sized); + + // Checking fields + fn check_layout( + s_layout: &TypeLayout, + u_layout: &TypeLayout, + is_top_level: bool, + ) -> Result<(), Mismatch> { + // kinds are the same, because the type layouts are based on the same cpu shareable + match (&s_layout.kind, &u_layout.kind) { + (TLS::Structure(s), TLS::Structure(u)) => { + for (i, (s_field, u_field)) in s.fields.iter().zip(u.fields.iter()).enumerate() { + // Checking field offset + if s_field.rel_byte_offset != u_field.rel_byte_offset { + let (sized_fields, last_unsized) = match s_layout.get_cpu_shareable().unwrap() { + LayoutType::Sized(cs::SizedType::Struct(s)) => (s.fields().to_vec(), None), + LayoutType::UnsizedStruct(s) => (s.sized_fields.clone(), Some(s.last_unsized.clone())), + _ => unreachable!("is struct, because tls is struct"), + }; + + return Err(Mismatch::StructureFieldOffset(StructureFieldOffsetError { + struct_layout: (**s).clone(), + sized_fields, + last_unsized, + field_name: s_field.field.name.clone(), + field_index: i, + actual_offset: s_field.rel_byte_offset, + expected_alignment: u_field.field.byte_align(), + is_top_level, + })); + } + + check_layout(&s_field.field.ty, &u_field.field.ty, false)?; + } + } + (TLS::Array(s_ele, _), TLS::Array(u_ele, _)) => { + // As long as the strides are the same, the size must be the same and + // the element align must divide the stride (uniform requirement), because + // u_layout is a valid uniform layout. + if s_ele.byte_stride != u_ele.byte_stride { + let element_ty = match u_ele.ty.get_cpu_shareable().unwrap() { + LayoutType::Sized(s) => s.clone(), + _ => { + unreachable!("elements of an array are always sized for TypeLayout") + } + }; + return Err(Mismatch::ArrayStride(ArrayStrideError { + expected: u_ele.byte_stride, + actual: s_ele.byte_stride, + element_ty, + })); + } + + check_layout(&s_ele.ty, &u_ele.ty, false)?; + } + _ => {} + } + + Ok(()) + } + + + match check_layout(s_layout, &u_layout, true) { + Ok(()) => Ok(u_layout), + Err(e) => { + let ctx = LayoutErrorContext { + s_layout: s_layout.clone(), + u_layout, + // TODO(chronicl) default shouldn't be true? + use_color: Context::try_with(call_info!(), |ctx| ctx.settings().colored_error_messages) + .unwrap_or(true), + }; + + use UniformLayoutError as U; + let e = match e { + Mismatch::ArrayStride(e) => U::ArrayStride(WithContext::new(ctx, e)), + Mismatch::StructureFieldOffset(e) => U::StructureFieldOffset(WithContext::new(ctx, e)), + }; + Err(e) + } + } + } +} + +type DepthIndex = SmallVec; + +#[derive(thiserror::Error, Debug, Clone)] +pub enum UniformLayoutError { + #[error("{0}")] + ArrayStride(WithContext), + #[error("{0}")] + StructureFieldOffset(WithContext), + // TODO(chronicl) consider using CpuShareableType and implementing Display for it instead + // of converting to StoreType + #[error( + "The size of `{1}` on the gpu is not known at compile time. `{0}` \ + requires that the size of uniform buffers on the gpu is known at compile time." + )] + MustBeSized(&'static str, StoreType), +} + +#[derive(Debug, Clone)] +pub enum Mismatch { + ArrayStride(ArrayStrideError), + StructureFieldOffset(StructureFieldOffsetError), +} + + +#[derive(Debug, Clone)] +pub struct LayoutErrorContext { + s_layout: TypeLayout, + u_layout: TypeLayout, + use_color: bool, +} + +#[derive(Debug, Clone)] +pub struct WithContext { + ctx: LayoutErrorContext, + inner: T, +} + +impl std::ops::Deref for WithContext { + type Target = T; + fn deref(&self) -> &Self::Target { &self.inner } +} + +impl WithContext { + fn new(ctx: LayoutErrorContext, inner: T) -> Self { Self { ctx, inner } } +} + + +#[allow(missing_docs)] +#[derive(Debug, Clone)] +pub struct ArrayStrideError { + expected: u64, + actual: u64, + element_ty: cs::SizedType, +} + +impl std::error::Error for WithContext {} +impl Display for WithContext { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let top_level: StoreType = self.ctx.s_layout.cpu_shareable().clone().into(); + writeln!( + f, + "array elements within type `{}` do not satisfy uniform layout requirements.", + top_level + )?; + writeln!( + f, + "The array with `{}` elements requires stride {}, but has stride {}.", + Into::::into(self.element_ty.clone()), + self.expected, + self.actual + )?; + writeln!(f, "The full layout of `{}` is:", top_level)?; + self.ctx.s_layout.write("", self.ctx.use_color, f)?; + writeln!(f)?; + writeln!( + f, + "\nfor more information on the layout rules, see https://www.w3.org/TR/WGSL/#address-space-layout-constraints", + )?; + Ok(()) + } +} + + +#[derive(Debug, Clone)] +pub struct StructureFieldOffsetError { + pub struct_layout: StructLayout, + pub sized_fields: Vec, + pub last_unsized: Option, + pub field_name: CanonName, + pub field_index: usize, + pub actual_offset: u64, + pub expected_alignment: U32PowerOf2, + pub is_top_level: bool, +} + +impl std::error::Error for WithContext {} +impl Display for WithContext { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // TODO(chronicl) remove conversion here if CpuShareableType implements Display + let top_level_type: StoreType = self.ctx.s_layout.cpu_shareable().clone().into(); + writeln!( + f, + "The type `{top_level_type}` cannot be used as a uniform buffer binding." + )?; + + match self.is_top_level { + true => write!(f, "Struct `{}`", &*self.struct_layout.name)?, + false => write!(f, "It contains a struct `{}`, which", &*self.struct_layout.name)?, + } + writeln!(f, " does not satisfy the uniform memory layout requirements.",)?; + writeln!(f)?; + // TODO(chronicl) structure_def_location + // if let Some(call_info) = structure_def_location { + // writeln!(f, "Definition at {call_info}")?; + // } + + write_struct_layout( + &self.struct_layout, + &self.sized_fields, + self.last_unsized.as_ref(), + self.ctx.use_color, + Some(self.field_index), + f, + )?; + + let actual_align = max_u64_po2_dividing(self.actual_offset); + let expected_alignment = self.expected_alignment.as_u32(); + writeln!(f)?; + set_color(f, Some("#508EE3"), false)?; + writeln!( + f, + "Field `{}` needs to be {} byte aligned, but has a byte-offset of {} which is only {actual_align} byte aligned.", + self.field_name, expected_alignment, self.actual_offset + )?; + set_color(f, None, false)?; + writeln!(f)?; + + writeln!(f, "Potential solutions include:"); + writeln!( + f, + "- add an #[align({})] attribute to the definition of `{}` (not supported by OpenGL/GLSL pipelines)", + expected_alignment, self.field_name + ); + writeln!(f, "- use a storage binding instead of a uniform binding"); + writeln!( + f, + "- increase the offset of `{}` until it is divisible by {} by making previous fields larger or adding fields before it", + self.field_name, expected_alignment + ); + writeln!(f)?; + + writeln!( + f, + "In the uniform address space, structs, arrays and array elements must be at least 16 byte aligned.\nMore info about the uniform address space layout can be found at https://www.w3.org/TR/WGSL/#address-space-layout-constraints" + )?; + Ok(()) + } +} + +/// Panics if s is not the layout of a struct and doesn't contain a cpu-shareable. +fn write_struct_layout( + struct_layout: &StructLayout, + sized_fields: &[cs::SizedField], + last_unsized: Option<&cs::RuntimeSizedArrayField>, + colored: bool, + highlight_field: Option, + f: &mut F, +) -> std::fmt::Result +where + F: Write, +{ + let use_256_color_mode = false; + let color = |f_: &mut F, hex| match colored { + true => set_color(f_, Some(hex), use_256_color_mode), + false => Ok(()), + }; + let reset = |f_: &mut F| match colored { + true => set_color(f_, None, use_256_color_mode), + false => Ok(()), + }; + + let struct_name = &*struct_layout.name; + + let indent = " "; + let field_decl_line = |field: &cs::SizedField| { + let sized: ir::SizedType = field.ty.clone().into(); + format!("{indent}{}: {},", field.name, sized) + }; + let header = format!("struct {} {{", struct_name); + let table_start_column = 1 + sized_fields + .iter() + .map(field_decl_line) + .map(|s| s.len()) + .max() + .unwrap_or(0) + .max(header.chars().count()); + f.write_str(&header)?; + for i in header.len()..table_start_column { + f.write_char(' ')? + } + writeln!(f, "offset align size")?; + for (i, (field, layout)) in sized_fields.iter().zip(struct_layout.fields.iter()).enumerate() { + if Some(i) == highlight_field { + color(f, "#508EE3")?; + } + let (align, size) = (layout.ty.byte_align(), layout.ty.byte_size().expect("is sized field")); + let decl_line = field_decl_line(field); + f.write_str(&decl_line)?; + // write spaces to table on the right + for _ in decl_line.len()..table_start_column { + f.write_char(' ')? + } + writeln!(f, "{:6} {:5} {:4}", layout.rel_byte_offset, align.as_u32(), size)?; + if Some(i) == highlight_field { + reset(f)?; + } + } + if let Some(last_field) = last_unsized { + let layout = struct_layout.fields.last().expect("structs have at least one field"); + + let store_type: ir::StoreType = last_field.array.clone().into(); + let decl_line = format!("{indent}{}: {},", last_field.name, store_type); + f.write_str(&decl_line)?; + // write spaces to table on the right + for _ in decl_line.len()..table_start_column { + f.write_char(' ')? + } + write!(f, "{:6} {:5}", layout.rel_byte_offset, layout.ty.byte_align().as_u32())?; + } + writeln!(f, "}}")?; + Ok(()) +} diff --git a/shame/src/frontend/rust_types/type_layout/vertex.rs b/shame/src/frontend/rust_types/type_layout/vertex.rs new file mode 100644 index 0000000..3fe905e --- /dev/null +++ b/shame/src/frontend/rust_types/type_layout/vertex.rs @@ -0,0 +1,100 @@ +use crate::{any::VertexAttribFormat, cpu_shareable::Repr}; +use super::{*}; + +impl TypeLayout { + pub fn from_vertex_attribute(attribute: VertexAttribFormat) -> TypeLayout { + let (size, align, kind) = match attribute { + VertexAttribFormat::Fine(len, scalar) => { + let sized = cpu_shareable::Vector::new(scalar, len); + (sized.byte_size(), sized.align(), TypeLayoutSemantics::Vector(sized)) + } + VertexAttribFormat::Coarse(packed) => ( + u8::from(packed.byte_size()) as u64, + U32PowerOf2::try_from(packed.align() as u32).unwrap(), + TypeLayoutSemantics::PackedVector(packed), + ), + }; + + type_layout_internal::cast_unchecked(TypeLayout::new(Some(size), align, kind, None)) + } + + /// Creates a new builder for a `TypeLayout`. Takes the first + /// attribute immediately, because at least one attribut needs to exist. + /// + /// `rules` determines whether the layout is packed or not. The `Uniform` and `Storage` + /// are equivalent for vertex layouts. + pub fn vertex_builder( + struct_name: impl Into, + field_options: impl Into, + attribute: VertexAttribFormat, + rules: Repr, + ) -> VertexLayoutBuilder { + VertexLayoutBuilder::new(struct_name, field_options, attribute, rules) + } +} + +pub struct VertexLayoutBuilder { + name: CanonName, + attributes: Vec, + rules: Repr, +} + +impl VertexLayoutBuilder { + /// Creates a new builder for a `TypeLayout`. Takes the first + /// attribute immediately, because at least one attribut needs to exist. + /// + /// `rules` determines whether the layout is packed or not. The `Uniform` and `Storage` + /// are equivalent for vertex layouts. + pub fn new( + struct_name: impl Into, + field_options: impl Into, + attribute: VertexAttribFormat, + rules: Repr, + ) -> Self { + let this = VertexLayoutBuilder { + name: struct_name.into(), + attributes: Vec::new(), + rules, + }; + this.extend(field_options, attribute) + } + + pub fn extend(mut self, field_options: impl Into, attribute: VertexAttribFormat) -> Self { + let layout = TypeLayout::from_vertex_attribute(attribute); + let options = field_options.into(); + self.attributes.push(FieldLayout::new( + options.name, + options.custom_min_size, + options.custom_min_align, + layout.into(), + )); + self + } + + pub fn finish(self) -> TypeLayout { + let mut calc = LayoutCalculator::new(matches!(self.rules, Repr::Packed)); + let fields = self + .attributes + .into_iter() + .map(|field| { + let rel_byte_offset = calc.extend( + field.byte_size().unwrap(), // attributes are always sized + field.byte_align(), + *field.custom_min_size, + *field.custom_min_align, + ); + FieldLayoutWithOffset { field, rel_byte_offset } + }) + .collect::>(); + + type_layout_internal::cast_unchecked(TypeLayout::new( + Some(calc.byte_size()), + calc.align(), + TypeLayoutSemantics::Structure(Rc::new(StructLayout { + name: self.name.into(), + fields, + })), + None, + )) + } +} diff --git a/shame/src/frontend/rust_types/type_traits.rs b/shame/src/frontend/rust_types/type_traits.rs index 0157f57..7d01d4e 100644 --- a/shame/src/frontend/rust_types/type_traits.rs +++ b/shame/src/frontend/rust_types/type_traits.rs @@ -3,9 +3,14 @@ use super::{ layout_traits::{FromAnys, GetAllFields, GpuLayout}, mem::{self, AddressSpace}, reference::{AccessMode, AccessModeReadable}, + type_layout::{self, type_layout_internal}, AsAny, GpuType, ToGpuType, }; -use crate::frontend::any::shared_io::{BindPath, BindingType}; +use crate::{ + frontend::any::shared_io::{BindPath, BindingType}, + cpu_shareable::{LayoutType}, + TypeLayout, +}; use crate::{ call_info, common::proc_macro_utils::push_wrong_amount_of_args_error, @@ -60,7 +65,7 @@ pub struct BindingArgs { //[old-doc] //[old-doc] corresponds to WGSL "Storable type" https://www.w3.org/TR/WGSL/#storable-types /// (no documentation yet) -pub trait GpuStore: GpuAligned + GetAllFields + FromAnys { +pub trait GpuStore: GetAllFields + FromAnys { /// the type whose public immutable interface is exposed by [`shame::Ref`]: /// /// ` as std::ops::Deref>::Target` @@ -168,7 +173,7 @@ pub trait GpuSized: GpuAligned { )] /// ## known byte-alignment on the gpu /// types that have a byte-alignment on the graphics device that is known at rust compile-time -pub trait GpuAligned: GpuLayout { +pub trait GpuAligned { #[doc(hidden)] // runtime api fn aligned_ty() -> AlignedType where @@ -186,7 +191,7 @@ pub trait GpuAligned: GpuLayout { /// /// boolean types do not have a defined size on gpus. /// You may want to use unsigned integers for transferring boolean data instead. -pub trait NoBools: GpuLayout {} +pub trait NoBools {} /// (no documentation yet) #[diagnostic::on_unimplemented( @@ -197,7 +202,7 @@ pub trait NoBools: GpuLayout {} // error message isn't misleading for user provided types `T`. Those types will show // the base trait diagnostic, instead of "`T` contains `XYZ`" which it doesn't. /// types that don't contain atomics at any nesting level -pub trait NoAtomics: GpuLayout {} +pub trait NoAtomics {} // implementor note: // NoXYZ traits must require GpuLayout or some other base trait, so that the @@ -207,13 +212,15 @@ pub trait NoAtomics: GpuLayout {} message = "`{Self}` may be or contain a handle type such as `Texture`, `Sampler`, `StorageTexture`." )] /// Implemented by types that aren't/contain no textures, storage textures, their array variants or samplers -pub trait NoHandles: GpuLayout {} +pub trait NoHandles {} /// this trait is only implemented by: /// /// * `sm::vec`s of non-boolean type (e.g. `sm::f32x4`) /// * `sm::packed::PackedVec`s (e.g. `sm::packed::unorm8x4`) -pub trait VertexAttribute: GpuLayout + FromAnys { +// Is at most 16 bytes according to https://www.w3.org/TR/WGSL/#input-output-locations +// and thus GpuSized. +pub trait VertexAttribute: GpuLayout + FromAnys + GpuSized { #[doc(hidden)] // runtime api fn vertex_attrib_format() -> VertexAttribFormat; } diff --git a/shame/src/frontend/rust_types/vec.rs b/shame/src/frontend/rust_types/vec.rs index 977c2e4..b25587e 100644 --- a/shame/src/frontend/rust_types/vec.rs +++ b/shame/src/frontend/rust_types/vec.rs @@ -7,7 +7,10 @@ use super::{ mem::AddressSpace, reference::{AccessMode, AccessModeReadable}, scalar_type::{dtype_as_scalar_from_f64, ScalarType, ScalarTypeInteger, ScalarTypeNumber}, - type_layout::TypeLayoutRules, + type_layout::{ + cpu_shareable::{self, BinaryReprSized}, + TypeLayoutRules, + }, type_traits::{BindingArgs, GpuAligned, GpuStoreImplCategory, NoAtomics, NoHandles, VertexAttribute}, AsAny, GpuType, To, ToGpuType, }; @@ -17,6 +20,7 @@ use crate::{ proc_macro_utils::{collect_into_array_exact, push_wrong_amount_of_args_error}, small_vec::SmallVec, }, + cpu_shareable::BinaryRepr, frontend::encoding::{ buffer::{BufferAddressSpace, BufferInner, BufferRefInner}, rasterizer::Gradient, @@ -68,12 +72,12 @@ pub type scalar = vec; /// let my_vec3 = sm::vec!(1.0, 2.0, 3.0); /// let my_vec4 = sm::vec!(my_vec3, 0.0); // component concatenation, like usual in shaders /// let my_vec4 = my_vec3.extend(0.0); // or like this -/// +/// /// let my_normal = sm::vec!(1.0, 1.0, 0.0).normalize(); /// let rgb = my_normal.remap(-1.0..=1.0, 0.0..=1.0); // remap linear ranges (instead of " * 0.5 + 0.5") /// /// let alpha = 0.4.to_gpu(); // convert from rust to `shame` types (also works for arrays and structs) -/// let smooth: f32x1 = alpha.smoothstep(0.4..0.8); +/// let smooth: f32x1 = alpha.smootcstep(0.4..0.8); /// /// // clamp as generalized min, max, clamp via half open ranges /// let upper = alpha.clamp(..=0.8); @@ -572,8 +576,27 @@ impl GpuStore for vec { fn impl_category() -> GpuStoreImplCategory { GpuStoreImplCategory::GpuType(Self::store_ty()) } } -impl GpuLayout for vec { - fn gpu_layout() -> TypeLayout { TypeLayout::from_sized_ty(TypeLayoutRules::Wgsl, &::sized_ty()) } + +impl BinaryReprSized for vec +where + vec: NoBools, +{ + fn layout_type_sized() -> cpu_shareable::SizedType { + cpu_shareable::Vector::new(T::SCALAR_TYPE.try_into().expect("no bools"), L::LEN).into() + } +} +impl BinaryRepr for vec +where + vec: NoBools, +{ + fn layout_type() -> cpu_shareable::LayoutType { Self::layout_type_sized().into() } +} + +impl GpuLayout for vec +where + vec: NoBools, +{ + fn gpu_repr() -> cpu_shareable::Repr { cpu_shareable::Repr::Storage } fn cpu_type_name_and_layout() -> Option, TypeLayout), super::layout_traits::ArrayElementsUnsizedError>> @@ -1095,7 +1118,9 @@ impl VertexAttribute for vec where Self: NoBools, { - fn vertex_attrib_format() -> VertexAttribFormat { VertexAttribFormat::Fine(L::LEN, T::SCALAR_TYPE) } + fn vertex_attrib_format() -> VertexAttribFormat { + VertexAttribFormat::Fine(L::LEN, T::SCALAR_TYPE.as_host_shareable_unchecked()) + } } impl FromAnys for vec { diff --git a/shame/src/ir/ir_type/align_size.rs b/shame/src/ir/ir_type/align_size.rs index 3916e09..1ed703a 100644 --- a/shame/src/ir/ir_type/align_size.rs +++ b/shame/src/ir/ir_type/align_size.rs @@ -8,11 +8,11 @@ use thiserror::Error; use super::{CanonName, Len2, ScalarType, ScalarTypeFp, SizedType, StoreType}; use super::{Len, Std}; -pub fn round_up(multiple_of: u64, n: u64) -> u64 { +pub const fn round_up(multiple_of: u64, n: u64) -> u64 { match multiple_of { 0 => match n { 0 => 0, - n => panic!("cannot round up {n} to a multiple of 0"), + n => panic!("cannot round up n to a multiple of 0"), }, k @ 1.. => n.div_ceil(k) * k, } diff --git a/shame/src/ir/ir_type/layout_constraints.rs b/shame/src/ir/ir_type/layout_constraints.rs index 8d7b80f..5af9ed9 100644 --- a/shame/src/ir/ir_type/layout_constraints.rs +++ b/shame/src/ir/ir_type/layout_constraints.rs @@ -507,7 +507,7 @@ impl Display for ArrayStrideAlignmentError { "The array with `{}` elements requires that every element is {expected_align}-byte aligned, but the array has a stride of {actual_stride} bytes, which means subsequent elements are not {expected_align}-byte aligned.", self.element_ty ); - if let Ok(layout) = TypeLayout::from_store_ty(TypeLayoutRules::Wgsl, &self.ctx.top_level_type) { + if let Ok(layout) = TypeLayout::from_store_ty(self.ctx.top_level_type.clone()) { writeln!(f, "The full layout of `{}` is:", self.ctx.top_level_type); layout.write("", self.ctx.use_color, f)?; writeln!(f); @@ -542,7 +542,7 @@ impl Display for ArrayStrideError { "The array with `{}` elements requires stride {}, but has stride {}.", self.element_ty, self.expected, self.actual ); - if let Ok(layout) = TypeLayout::from_store_ty(TypeLayoutRules::Wgsl, &self.ctx.top_level_type) { + if let Ok(layout) = TypeLayout::from_store_ty(self.ctx.top_level_type.clone()) { writeln!(f, "The full layout of `{}` is:", self.ctx.top_level_type); layout.write("", self.ctx.use_color, f)?; writeln!(f); @@ -577,7 +577,7 @@ impl Display for ArrayAlignmentError { "The array with `{}` elements requires alignment {}, but has alignment {}.", self.element_ty, self.expected, self.actual ); - if let Ok(layout) = TypeLayout::from_store_ty(TypeLayoutRules::Wgsl, &self.ctx.top_level_type) { + if let Ok(layout) = TypeLayout::from_store_ty(self.ctx.top_level_type.clone()) { writeln!(f, "The full layout of `{}` is:", self.ctx.top_level_type); layout.write("", self.ctx.use_color, f)?; writeln!(f); diff --git a/shame/src/ir/ir_type/tensor.rs b/shame/src/ir/ir_type/tensor.rs index 9933b87..64e7282 100644 --- a/shame/src/ir/ir_type/tensor.rs +++ b/shame/src/ir/ir_type/tensor.rs @@ -1,6 +1,7 @@ use std::{fmt::Display, num::NonZeroU32}; use crate::{ + any::U32PowerOf2, common::floating_point::{f16, f32_eq_where_nans_are_equal, f64_eq_where_nans_are_equal}, ir::Comp4, }; @@ -73,23 +74,11 @@ impl PartialEq for Len { } impl From for u64 { - fn from(value: Len2) -> Self { - match value { - Len2::X2 => 2, - Len2::X3 => 3, - Len2::X4 => 4, - } - } + fn from(value: Len2) -> Self { value.as_u64() } } impl From for NonZeroU32 { - fn from(value: Len2) -> Self { - match value { - Len2::X2 => NonZeroU32::new(2).unwrap(), - Len2::X3 => NonZeroU32::new(3).unwrap(), - Len2::X4 => NonZeroU32::new(4).unwrap(), - } - } + fn from(value: Len2) -> Self { value.as_non_zero_u32() } } impl PartialEq for Len2 { @@ -102,6 +91,54 @@ impl Len { use Comp4::*; [X, Y, Z, W].into_iter().take(self.into()) } + + /// as u8 + pub const fn as_u8(self) -> u8 { + match self { + Len::X1 => 1, + Len::X2 => 2, + Len::X3 => 3, + Len::X4 => 4, + } + } + /// as u32 + pub const fn as_u32(self) -> u32 { self.as_u8() as u32 } + /// as u64 + pub const fn as_u64(self) -> u64 { self.as_u8() as u64 } + /// as `NonZeroU32` + pub const fn as_non_zero_u32(self) -> NonZeroU32 { + match self { + Len::X1 => NonZeroU32::new(1).unwrap(), + Len::X2 => NonZeroU32::new(2).unwrap(), + Len::X3 => NonZeroU32::new(3).unwrap(), + Len::X4 => NonZeroU32::new(4).unwrap(), + } + } +} + +impl Len2 { + /// as u8 + pub const fn as_u8(self) -> u8 { + match self { + Len2::X2 => 2, + Len2::X3 => 3, + Len2::X4 => 4, + } + } + /// as u32 + pub const fn as_u32(self) -> u32 { self.as_u8() as u32 } + /// as u64 + pub const fn as_u64(self) -> u64 { self.as_u8() as u64 } + /// as `NonZeroU32` + pub const fn as_non_zero_u32(self) -> NonZeroU32 { self.as_len().as_non_zero_u32() } + /// as [`Len`] + pub const fn as_len(self) -> Len { + match self { + Len2::X2 => Len::X2, + Len2::X3 => Len::X3, + Len2::X4 => Len::X4, + } + } } /// (no documentation yet) @@ -458,6 +495,12 @@ impl From for u8 { } } +impl PackedVectorByteSize { + pub fn as_u32(self) -> u32 { u8::from(self) as u32 } + + pub fn as_u64(self) -> u64 { u8::from(self) as u64 } +} + impl Display for PackedVector { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let stype = match self.scalar_type { @@ -485,12 +528,13 @@ impl PackedVector { } } - pub fn align(&self) -> u64 { - match self.byte_size() { + pub fn align(&self) -> U32PowerOf2 { + let align = match self.byte_size() { PackedVectorByteSize::_2 => SizedType::Vector(Len::X1, ScalarType::F16).align(), PackedVectorByteSize::_4 => SizedType::Vector(Len::X1, ScalarType::U32).align(), PackedVectorByteSize::_8 => SizedType::Vector(Len::X2, ScalarType::U32).align(), - } + }; + U32PowerOf2::try_from(align as u32).expect("the above all have power of 2 align") } } diff --git a/shame/src/ir/pipeline/wip_pipeline.rs b/shame/src/ir/pipeline/wip_pipeline.rs index 4e9e1f0..57e21fd 100644 --- a/shame/src/ir/pipeline/wip_pipeline.rs +++ b/shame/src/ir/pipeline/wip_pipeline.rs @@ -16,6 +16,7 @@ use crate::{ po2::U32PowerOf2, pool::{Key, PoolRef}, }, + cpu_shareable, frontend::{ any::{ render_io::{Attrib, ColorTarget, Location, VertexBufferLayout}, @@ -43,7 +44,8 @@ use crate::{ StructureFieldNamesMustBeUnique, TextureFormatWrapper, Type, }, results::DepthStencilState, - BindingIter, DepthLhs, StencilMasking, Test, + type_layout::TypeLayoutSemantics, + BindingIter, DepthLhs, StencilMasking, Test, TypeLayout, }; @@ -71,7 +73,7 @@ macro_rules! stringify_checked { #[derive(Error, Debug, Clone)] pub enum PipelineError { - #[error("Missing pipeline specialization. Use either the `{}` or `{}` method to start a compute- or render-pipeline encoding.", + #[error("Missing pipeline specialization. Use either the `{}` or `{}` method to start a compute- or render-pipeline encoding.", stringify_checked!(expr: EncodingGuard::new_compute_pipeline::<3>).replace("3", "_"), stringify_checked!(expr: EncodingGuard::new_render_pipeline), )] @@ -352,7 +354,14 @@ impl WipPushConstantsField { let byte_size = sized_struct.byte_size(); // TODO(release) the `.expect()` calls here can be removed by building a `std::alloc::Layout`-like builder for struct layouts. - let (_, _, layout) = StructLayout::from_ir_struct(TypeLayoutRules::Wgsl, &sized_struct); + let sized_struct: cpu_shareable::SizedStruct = sized_struct + .try_into() + .expect("push constants are NoBools and NoHandles"); + let layout = TypeLayout::new_storage_layout_for(sized_struct); + let layout = match &layout.kind { + TypeLayoutSemantics::Structure(layout) => &**layout, + _ => unreachable!("expected struct layout for type layout of struct"), + }; let mut ranges = ByteRangesPerStage::default(); @@ -408,7 +417,7 @@ impl WipPushConstantsField { // here we have to allocate unique name strings for each field, // so we don't fail the name uniqueness check, even though we don't need those names. - SizedStruct::new_nonempty("PushConstants".into(), + SizedStruct::new_nonempty("PushConstants".into(), fields.iter().map(&mut to_sized_field).collect(), to_sized_field(last) ).map_err(|err| match err { diff --git a/shame/src/lib.rs b/shame/src/lib.rs index 9d316df..b54475b 100644 --- a/shame/src/lib.rs +++ b/shame/src/lib.rs @@ -184,7 +184,7 @@ pub use frontend::rust_types::vec_range_traits::VecRangeBoundsInclusive; pub mod aliases { use crate::frontend::rust_types::aliases; - #[rustfmt::skip] + #[rustfmt::skip] pub use aliases::rust_simd::{ f16x1, f32x1, f64x1, u32x1, i32x1, boolx1, f16x2, f32x2, f64x2, u32x2, i32x2, boolx2, @@ -192,16 +192,16 @@ pub mod aliases { f16x4, f32x4, f64x4, u32x4, i32x4, boolx4, }; - #[rustfmt::skip] + #[rustfmt::skip] pub use aliases::rust_simd::{ f16x2x2, f32x2x2, f64x2x2, f16x2x3, f32x2x3, f64x2x3, f16x2x4, f32x2x4, f64x2x4, - + f16x3x2, f32x3x2, f64x3x2, f16x3x3, f32x3x3, f64x3x3, f16x3x4, f32x3x4, f64x3x4, - + f16x4x2, f32x4x2, f64x4x2, f16x4x3, f32x4x3, f64x4x3, f16x4x4, f32x4x4, f64x4x4, @@ -316,8 +316,10 @@ pub use shame_derive::CpuLayout; pub use shame_derive::GpuLayout; pub use frontend::rust_types::layout_traits::GpuLayout; pub use frontend::rust_types::layout_traits::CpuLayout; +pub use frontend::rust_types::type_layout; pub use frontend::rust_types::type_layout::TypeLayout; pub use frontend::rust_types::type_layout::TypeLayoutError; +pub use frontend::rust_types::type_layout::cpu_shareable; pub use frontend::rust_types::layout_traits::ArrayElementsUnsizedError; // derived traits @@ -331,6 +333,7 @@ pub use frontend::rust_types::struct_::SizedFields; pub use frontend::rust_types::type_traits::NoBools; pub use frontend::rust_types::type_traits::NoAtomics; pub use frontend::rust_types::type_traits::NoHandles; +pub use frontend::rust_types::type_traits::VertexAttribute; pub use frontend::rust_types::layout_traits::VertexLayout; diff --git a/shame/tests/test_layout.rs b/shame/tests/test_layout.rs index b137a23..855d8a9 100644 --- a/shame/tests/test_layout.rs +++ b/shame/tests/test_layout.rs @@ -248,9 +248,9 @@ fn unsized_struct_vec3_align_layout_eq() { } assert_ne!(OnGpu::gpu_layout(), OnCpu::cpu_layout()); assert!(OnGpu::gpu_layout().byte_size() == Some(16)); - assert!(OnGpu::gpu_layout().align() == 16); + assert!(OnGpu::gpu_layout().byte_align().as_u32() == 16); assert!(OnCpu::cpu_layout().byte_size() == Some(12)); - assert!(OnCpu::cpu_layout().align() == 4); + assert!(OnCpu::cpu_layout().byte_align().as_u32() == 4); } #[test] diff --git a/shame_derive/src/derive_layout.rs b/shame_derive/src/derive_layout.rs index c9fe351..45a1446 100644 --- a/shame_derive/src/derive_layout.rs +++ b/shame_derive/src/derive_layout.rs @@ -8,6 +8,7 @@ use syn::LitInt; use syn::{DataStruct, DeriveInput, FieldsNamed}; use crate::util; +use crate::util::Repr; macro_rules! bail { ($span: expr, $display: expr) => {return Err(syn::Error::new($span, $display,))}; @@ -81,12 +82,17 @@ pub fn impl_for_struct( .into_iter(); let none_if_no_cpu_equivalent_type = cpu_attr.is_none().then_some(quote! { None }).into_iter(); - // #[gpu_repr(packed)] - let gpu_repr_packed = util::find_gpu_repr_packed(&input.attrs)?; - if let (Some(span), WhichDerive::CpuLayout) = (&gpu_repr_packed, &which_derive) { + // #[gpu_repr(packed | storage | uniform)] + let gpu_repr = util::determine_gpu_repr(&input.attrs)?; + if let (Some((span, _)), WhichDerive::CpuLayout) = (&gpu_repr, &which_derive) { bail!(*span, "`gpu_repr` attribute is only supported by `derive(GpuLayout)`") } - let is_gpu_repr_packed = gpu_repr_packed.is_some(); + let gpu_repr = gpu_repr.map(|(_, repr)| repr).unwrap_or(util::Repr::Storage); + let gpu_repr_shame = match gpu_repr { + Repr::Packed => quote!( #re::Repr::Packed ), + Repr::Storage => quote!( #re::Repr::Storage ), + Repr::Uniform => quote!( #re::Repr::Uniform ), + }; // #[repr(...)] let repr_c_attr = util::try_parse_repr(&input.attrs)?; @@ -194,35 +200,69 @@ pub fn impl_for_struct( match which_derive { WhichDerive::GpuLayout => { - let impl_gpu_layout = quote! { - impl<#generics_decl> #re::GpuLayout for #derive_struct_ident<#(#idents_of_generics),*> + let layout_type_fn = quote! { + let result = #re::LayoutType::struct_from_parts( + std::stringify!(#derive_struct_ident), + [ + #(( + #re::FieldOptions::new( + std::stringify!(#field_ident), + #field_align.map(|align: u32| TryFrom::try_from(align).expect("power of two validated during codegen")).into(), + #field_size.into(), + ), + <#field_type as #re::BinaryRepr>::layout_type() + ),)* + ] + ); + + match result { + Ok(layout_type) => layout_type, + Err(#re::StructFromPartsError::MustHaveAtLeastOneField) => unreachable!("checked above"), + Err(#re::StructFromPartsError::OnlyLastFieldMayBeUnsized) => unreachable!("ensured by field trait bounds"), + // GpuType is not implemented for derived structs directly, so they can't be used + // as the field of another struct, instead shame::Struct has to be used, which + // only accepts sized structs. + Err(#re::StructFromPartsError::MustNotHaveUnsizedStructField) => unreachable!("GpuType bound for fields makes this impossible"), + } + }; + + let impl_binary_repr = quote! { + impl<#generics_decl> #re::BinaryRepr for #derive_struct_ident<#(#idents_of_generics),*> where - #(#first_fields_type: #re::GpuSized,)* - #last_field_type: #re::GpuAligned, + // These NoBools and NoHandle bounds are only for better diagnostics, BinaryRepr already implies them + #(#first_fields_type: #re::NoBools + #re::NoHandles + #re::BinaryReprSized,)* + #last_field_type: #re::NoBools + #re::NoHandles + #re::BinaryRepr, #where_clause_predicates { + fn layout_type() -> #re::LayoutType { + #layout_type_fn + } + } - fn gpu_layout() -> #re::TypeLayout { - let result = #re::TypeLayout::struct_from_parts( - #re::TypeLayoutRules::Wgsl, - #is_gpu_repr_packed, - std::stringify!(#derive_struct_ident).into(), - [ - #( - #re::FieldLayout { - name: std::stringify!(#field_ident).into(), - ty: <#field_type as #re::GpuLayout>::gpu_layout(), - custom_min_align: #field_align.map(|align: u32| TryFrom::try_from(align).expect("power of two validated during codegen")).into(), - custom_min_size: #field_size.into(), - }, - )* - ].into_iter() - ); - match result { - Ok(layout) => layout, - Err(e @ #re::StructLayoutError::UnsizedFieldMustBeLast { .. }) => unreachable!("all fields except last require bound `GpuSized`. {e}"), + impl<#generics_decl> #re::BinaryReprSized for #derive_struct_ident<#(#idents_of_generics),*> + where + #(#field_type: #re::NoBools + #re::NoHandles + #triv #re::BinaryReprSized,)* + #where_clause_predicates + { + fn layout_type_sized() -> #re::SizedType { + match { #layout_type_fn } { + #re::LayoutType::Sized(s) => s, + _ => unreachable!("ensured by BinaryReprSized field trait bounds above") } } + } + }; + + let impl_gpu_layout = quote! { + impl<#generics_decl> #re::GpuLayout for #derive_struct_ident<#(#idents_of_generics),*> + where + #(#first_fields_type: #re::BinaryReprSized,)* + #last_field_type: #re::BinaryRepr, + #where_clause_predicates + { + fn gpu_repr() -> #re::Repr { + #gpu_repr_shame + } fn cpu_type_name_and_layout() -> Option, #re::TypeLayout), #re::ArrayElementsUnsizedError>> { use #re::CpuLayout as _; @@ -243,8 +283,7 @@ pub fn impl_for_struct( where #(#triv #field_type: #re::VertexAttribute,)* #where_clause_predicates - { - } + { } }; @@ -322,10 +361,11 @@ pub fn impl_for_struct( } }; - if gpu_repr_packed.is_some() { + if matches!(gpu_repr, Repr::Packed) { // this is basically only for vertex buffers, so // we only implement `GpuLayout` and `VertexLayout`, as well as their implied traits Ok(quote! { + #impl_binary_repr #impl_gpu_layout #impl_vertex_buffer_layout #impl_fake_auto_traits @@ -334,12 +374,13 @@ pub fn impl_for_struct( } else { // non gpu_repr(packed) let struct_ref_doc = format!( - r#"This struct was generated by `#[derive(shame::GpuLayout)]` - as a version of `{derive_struct_ident}` which holds references to its fields. It is used as + r#"This struct was generated by `#[derive(shame::GpuLayout)]` + as a version of `{derive_struct_ident}` which holds references to its fields. It is used as the `std::ops::Deref` target of `shame::Ref<{derive_struct_ident}>`"# ); Ok(quote! { + #impl_binary_repr #impl_gpu_layout #impl_vertex_buffer_layout #impl_fake_auto_traits diff --git a/shame_derive/src/util.rs b/shame_derive/src/util.rs index afa6930..0e085ed 100644 --- a/shame_derive/src/util.rs +++ b/shame_derive/src/util.rs @@ -27,16 +27,32 @@ pub fn find_literal_list_attr( Ok(None) } -pub fn find_gpu_repr_packed(attribs: &[syn::Attribute]) -> Result> { +pub enum Repr { + Packed, + Storage, + Uniform, +} + +pub fn determine_gpu_repr(attribs: &[syn::Attribute]) -> Result> { + let mut repr = Repr::Storage; for a in attribs { if a.path().is_ident("gpu_repr") { a.parse_nested_meta(|meta| { if meta.path.is_ident("packed") { + repr = Repr::Packed; + return Ok(()); + } else if meta.path.is_ident("storage") { + repr = Repr::Storage; + return Ok(()); + } else if meta.path.is_ident("uniform") { + repr = Repr::Uniform; return Ok(()); } + Err(meta.error("unrecognized `gpu_repr`. Did you mean `gpu_repr(packed)`?")) })?; - return Ok(Some(a.span())); + + return Ok(Some((a.span(), repr))); } } Ok(None) From 4b7e4a4564db08738036115bf6a8e491c86a35c8 Mon Sep 17 00:00:00 2001 From: chronicl Date: Thu, 22 May 2025 06:20:11 +0200 Subject: [PATCH 02/32] Add gpu_repr(uniform) to layout example --- examples/hello_triangles/src/util/shame_glam.rs | 6 +++--- examples/type_layout/src/main.rs | 13 ++++++++++++- shame/src/frontend/rust_types/type_layout/mod.rs | 10 +++++----- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/examples/hello_triangles/src/util/shame_glam.rs b/examples/hello_triangles/src/util/shame_glam.rs index 6cf3441..7f85092 100644 --- a/examples/hello_triangles/src/util/shame_glam.rs +++ b/examples/hello_triangles/src/util/shame_glam.rs @@ -14,14 +14,14 @@ pub trait CpuLayoutExt { // glam::Vec4 matches sm::f32x4 in size and alignment impl CpuLayoutExt for glam::Vec4 { - fn cpu_layout() -> sm::TypeLayout { sm::f32x4::gpu_layout().into() } + fn cpu_layout() -> sm::TypeLayout { sm::f32x4::gpu_layout() } } // glam::Vec2 only matches sm::f32x2 if it has 8 byte alignment impl CpuLayoutExt for glam::Vec2 { fn cpu_layout() -> sm::TypeLayout { if align_of::() == 8 { - sm::f32x2::gpu_layout().into() + sm::f32x2::gpu_layout() } else { panic!("glam needs to use the `cuda` crate feature for Vec2 to be 8 byte aligned"); } @@ -30,5 +30,5 @@ impl CpuLayoutExt for glam::Vec2 { // glam::Mat4 matches sm::f32x4x4 in size and alignment impl CpuLayoutExt for glam::Mat4 { - fn cpu_layout() -> sm::TypeLayout { sm::f32x4x4::gpu_layout().into() } + fn cpu_layout() -> sm::TypeLayout { sm::f32x4x4::gpu_layout() } } diff --git a/examples/type_layout/src/main.rs b/examples/type_layout/src/main.rs index 2b37fdf..ea0f64a 100644 --- a/examples/type_layout/src/main.rs +++ b/examples/type_layout/src/main.rs @@ -97,7 +97,18 @@ fn main() { f32x1::layout_type_sized(), ); let layout = TypeLayout::new_storage_layout_for(sized_struct); - assert!(layout.byte_align.as_u32() == 16); + assert!(layout.align.as_u32() == 16); + + // gpu_repr(uniform) and gpu_repr(storage), which is the default, are now supported + #[derive(shame::GpuLayout)] + #[gpu_repr(uniform)] + struct D { + a: f32x2, + b: Array, + } + + // this would be 8 for storage layout rules + assert!(D::gpu_layout().align.as_u32() == 16); // Let's end on a pretty error message let mut sized_struct = cs::SizedStruct::new("D", "a", f32x2::layout_type_sized()) diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index bc4a432..35c6f26 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -119,7 +119,7 @@ pub struct TypeLayout { /// the byte alignment /// /// top level alignment is not considered relevant in some checks, but relevant in others (vertex array elements) - pub byte_align: U32PowerOf2, + pub align: U32PowerOf2, /// the type contained in the bytes of this type layout pub kind: TypeLayoutSemantics, @@ -189,7 +189,7 @@ impl TypeLayout { ) -> Self { TypeLayout { byte_size, - byte_align, + align: byte_align, kind, cpu_shareable: hostshareable, _phantom: PhantomData, @@ -206,7 +206,7 @@ pub(in super::super::rust_types) mod type_layout_internal { pub fn cast_unchecked(layout: TypeLayout) -> TypeLayout { TypeLayout { byte_size: layout.byte_size, - byte_align: layout.byte_align, + align: layout.align, kind: layout.kind, cpu_shareable: layout.cpu_shareable, _phantom: PhantomData, @@ -392,7 +392,7 @@ impl TypeLayout { pub fn byte_size(&self) -> Option { self.byte_size } /// Align of the represented type. - pub fn byte_align(&self) -> U32PowerOf2 { self.byte_align } + pub fn byte_align(&self) -> U32PowerOf2 { self.align } /// Although all TypeLayout always implement Into, this method /// is offered to avoid having to declare that as a bound when handling generic TypeLayout. @@ -479,7 +479,7 @@ impl TypeLayout { } } write!(f, "{indent}}}")?; - write!(f, " align={}", self.byte_align.as_u64())?; + write!(f, " align={}", self.align.as_u64())?; if let Some(size) = self.byte_size { write!(f, " size={size}")?; } else { From 2a9a4ed96efa10789c10115e94d2e0239d7abe10 Mon Sep 17 00:00:00 2001 From: chronicl Date: Thu, 22 May 2025 06:44:59 +0200 Subject: [PATCH 03/32] Add GpuStore: GpuAligned back --- shame/src/frontend/rust_types/type_traits.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shame/src/frontend/rust_types/type_traits.rs b/shame/src/frontend/rust_types/type_traits.rs index 7d01d4e..78ff297 100644 --- a/shame/src/frontend/rust_types/type_traits.rs +++ b/shame/src/frontend/rust_types/type_traits.rs @@ -65,7 +65,7 @@ pub struct BindingArgs { //[old-doc] //[old-doc] corresponds to WGSL "Storable type" https://www.w3.org/TR/WGSL/#storable-types /// (no documentation yet) -pub trait GpuStore: GetAllFields + FromAnys { +pub trait GpuStore: GpuAligned + GetAllFields + FromAnys { /// the type whose public immutable interface is exposed by [`shame::Ref`]: /// /// ` as std::ops::Deref>::Target` From 4619d603539a2bd604422f91951d3bca7ae401c7 Mon Sep 17 00:00:00 2001 From: chronicl Date: Thu, 22 May 2025 07:04:40 +0200 Subject: [PATCH 04/32] Cleanup type layout Vertex --- examples/type_layout/src/main.rs | 27 +++-- .../{storage_uniform.rs => builder.rs} | 19 +++- .../frontend/rust_types/type_layout/mod.rs | 10 +- .../frontend/rust_types/type_layout/vertex.rs | 100 ------------------ 4 files changed, 38 insertions(+), 118 deletions(-) rename shame/src/frontend/rust_types/type_layout/{storage_uniform.rs => builder.rs} (95%) delete mode 100644 shame/src/frontend/rust_types/type_layout/vertex.rs diff --git a/examples/type_layout/src/main.rs b/examples/type_layout/src/main.rs index ea0f64a..7dda41d 100644 --- a/examples/type_layout/src/main.rs +++ b/examples/type_layout/src/main.rs @@ -6,26 +6,35 @@ use shame::{ boolx1, cpu_shareable::{self as cs, BinaryReprSized}, f32x1, f32x2, f32x3, f32x4, - type_layout::*, + type_layout::{ + constraint::{Packed, Plain, Storage}, + *, + }, Array, GpuLayout, GpuSized, VertexAttribute, VertexLayout, }; fn main() { - // We'll start by building a `TypeLayout`, which can be used for ... nothing really + // We'll start by building a `TypeLayout`, which for this Vertex type. #[derive(GpuLayout)] struct Vertex { position: f32x3, normal: f32x3, - uv: f32x1, + uv: f32x2, } - // TypeLayout::vertex_builder immediately takes the first field of the struct, because + // SizedStruct::new immediately takes the first field of the struct, because // structs need to have at least one field. - let mut builder = - TypeLayout::vertex_builder("Vertex", "position", f32x4::vertex_attrib_format(), cs::Repr::Storage) - .extend("normal", f32x3::vertex_attrib_format()) - .extend("uv", f32x1::vertex_attrib_format()) - .finish(); + let sized_struct = cs::SizedStruct::new("Vertex", "position", f32x3::layout_type_sized()) + .extend("normal", f32x4::layout_type_sized()) + .extend("uv", f32x1::layout_type_sized()); + // A layout that follows the storage layout rules + let layout: TypeLayout = TypeLayout::new_layout_for(sized_struct.clone(), cs::Repr::Storage); + // Or we can get a TypeLayout, which guarantees the storage layout rules. + let layout_storage: TypeLayout = TypeLayout::new_storage_layout_for(sized_struct.clone()); + assert_eq!(layout, layout_storage); + // Or we get it as a packed layout + let layout_packed: TypeLayout = TypeLayout::new_packed_layout_for(sized_struct); + assert_ne!(layout_storage, layout_packed); // Now we'll replicate the layout of this struct diff --git a/shame/src/frontend/rust_types/type_layout/storage_uniform.rs b/shame/src/frontend/rust_types/type_layout/builder.rs similarity index 95% rename from shame/src/frontend/rust_types/type_layout/storage_uniform.rs rename to shame/src/frontend/rust_types/type_layout/builder.rs index d788a71..0af94d4 100644 --- a/shame/src/frontend/rust_types/type_layout/storage_uniform.rs +++ b/shame/src/frontend/rust_types/type_layout/builder.rs @@ -6,6 +6,19 @@ use crate::{ use super::*; use TypeLayoutSemantics as TLS; +impl TypeLayout { + pub fn new_storage_layout_for(ty: impl Into) -> Self { + type_layout_internal::cast_unchecked(TypeLayout::new_layout_for(ty, Repr::Storage)) + } + + /// Get the `CpuShareableType` this layout is based on. + pub fn cpu_shareable(&self) -> &LayoutType { + self.cpu_shareable + .as_ref() + .expect("constraint::Storage always contains a cpu-shareable") + } +} + impl TypeLayout { /// Takes a `SizedType`, because wgsl only supports sized uniform buffers. /// Use `new_layout_for_unchecked`to obtain the the uniform layout of an unsized host-shareable. /// Using the unsized layout with wgsl as your target language will cause an error. @@ -21,9 +34,9 @@ impl TypeLayout { } } -impl TypeLayout { - pub fn new_storage_layout_for(ty: impl Into) -> Self { - type_layout_internal::cast_unchecked(TypeLayout::new_layout_for(ty, Repr::Storage)) +impl TypeLayout { + pub fn new_packed_layout_for(ty: impl Into) -> Self { + type_layout_internal::cast_unchecked(TypeLayout::new_layout_for(ty, Repr::Packed)) } /// Get the `CpuShareableType` this layout is based on. diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index 35c6f26..8c49ad0 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -24,13 +24,11 @@ use crate::{ use cpu_shareable::{LayoutType, Matrix, Vector}; use thiserror::Error; +mod builder; pub mod cpu_shareable; mod eq; -mod storage_uniform; -mod vertex; -pub use vertex::*; -pub use storage_uniform::*; +pub use builder::*; pub(crate) use eq::*; /// The type contained in the bytes of a `TypeLayout`. @@ -163,7 +161,7 @@ pub mod constraint { }; } - type_restriction!(Plain, Storage, Uniform, Vertex); + type_restriction!(Plain, Storage, Uniform, Packed); macro_rules! impl_from_into { ($from:ident -> $($into:ident),*) => { @@ -177,7 +175,7 @@ pub mod constraint { impl_from_into!(Storage -> Plain); impl_from_into!(Uniform -> Plain, Storage); - impl_from_into!(Vertex -> Plain); + impl_from_into!(Packed -> Plain); } impl TypeLayout { diff --git a/shame/src/frontend/rust_types/type_layout/vertex.rs b/shame/src/frontend/rust_types/type_layout/vertex.rs deleted file mode 100644 index 3fe905e..0000000 --- a/shame/src/frontend/rust_types/type_layout/vertex.rs +++ /dev/null @@ -1,100 +0,0 @@ -use crate::{any::VertexAttribFormat, cpu_shareable::Repr}; -use super::{*}; - -impl TypeLayout { - pub fn from_vertex_attribute(attribute: VertexAttribFormat) -> TypeLayout { - let (size, align, kind) = match attribute { - VertexAttribFormat::Fine(len, scalar) => { - let sized = cpu_shareable::Vector::new(scalar, len); - (sized.byte_size(), sized.align(), TypeLayoutSemantics::Vector(sized)) - } - VertexAttribFormat::Coarse(packed) => ( - u8::from(packed.byte_size()) as u64, - U32PowerOf2::try_from(packed.align() as u32).unwrap(), - TypeLayoutSemantics::PackedVector(packed), - ), - }; - - type_layout_internal::cast_unchecked(TypeLayout::new(Some(size), align, kind, None)) - } - - /// Creates a new builder for a `TypeLayout`. Takes the first - /// attribute immediately, because at least one attribut needs to exist. - /// - /// `rules` determines whether the layout is packed or not. The `Uniform` and `Storage` - /// are equivalent for vertex layouts. - pub fn vertex_builder( - struct_name: impl Into, - field_options: impl Into, - attribute: VertexAttribFormat, - rules: Repr, - ) -> VertexLayoutBuilder { - VertexLayoutBuilder::new(struct_name, field_options, attribute, rules) - } -} - -pub struct VertexLayoutBuilder { - name: CanonName, - attributes: Vec, - rules: Repr, -} - -impl VertexLayoutBuilder { - /// Creates a new builder for a `TypeLayout`. Takes the first - /// attribute immediately, because at least one attribut needs to exist. - /// - /// `rules` determines whether the layout is packed or not. The `Uniform` and `Storage` - /// are equivalent for vertex layouts. - pub fn new( - struct_name: impl Into, - field_options: impl Into, - attribute: VertexAttribFormat, - rules: Repr, - ) -> Self { - let this = VertexLayoutBuilder { - name: struct_name.into(), - attributes: Vec::new(), - rules, - }; - this.extend(field_options, attribute) - } - - pub fn extend(mut self, field_options: impl Into, attribute: VertexAttribFormat) -> Self { - let layout = TypeLayout::from_vertex_attribute(attribute); - let options = field_options.into(); - self.attributes.push(FieldLayout::new( - options.name, - options.custom_min_size, - options.custom_min_align, - layout.into(), - )); - self - } - - pub fn finish(self) -> TypeLayout { - let mut calc = LayoutCalculator::new(matches!(self.rules, Repr::Packed)); - let fields = self - .attributes - .into_iter() - .map(|field| { - let rel_byte_offset = calc.extend( - field.byte_size().unwrap(), // attributes are always sized - field.byte_align(), - *field.custom_min_size, - *field.custom_min_align, - ); - FieldLayoutWithOffset { field, rel_byte_offset } - }) - .collect::>(); - - type_layout_internal::cast_unchecked(TypeLayout::new( - Some(calc.byte_size()), - calc.align(), - TypeLayoutSemantics::Structure(Rc::new(StructLayout { - name: self.name.into(), - fields, - })), - None, - )) - } -} From fb24c94070ca58921fa3e919bef4a32ffa526ae3 Mon Sep 17 00:00:00 2001 From: chronicl Date: Thu, 22 May 2025 07:07:30 +0200 Subject: [PATCH 05/32] Fix example --- examples/type_layout/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/type_layout/src/main.rs b/examples/type_layout/src/main.rs index 7dda41d..d07064e 100644 --- a/examples/type_layout/src/main.rs +++ b/examples/type_layout/src/main.rs @@ -25,7 +25,7 @@ fn main() { // SizedStruct::new immediately takes the first field of the struct, because // structs need to have at least one field. let sized_struct = cs::SizedStruct::new("Vertex", "position", f32x3::layout_type_sized()) - .extend("normal", f32x4::layout_type_sized()) + .extend("normal", f32x3::layout_type_sized()) .extend("uv", f32x1::layout_type_sized()); // A layout that follows the storage layout rules let layout: TypeLayout = TypeLayout::new_layout_for(sized_struct.clone(), cs::Repr::Storage); From 82228640d0a47a1762bbb0ff92e1f8504fdf8c84 Mon Sep 17 00:00:00 2001 From: chronicl Date: Fri, 23 May 2025 03:40:50 +0200 Subject: [PATCH 06/32] Rebase --- examples/shame_wgpu/src/conversion.rs | 2 +- examples/type_layout/src/main.rs | 59 ++--- shame/src/common/proc_macro_reexports.rs | 17 +- shame/src/common/proc_macro_utils.rs | 4 +- shame/src/frontend/any/render_io.rs | 7 +- shame/src/frontend/encoding/buffer.rs | 4 +- shame/src/frontend/rust_types/array.rs | 28 +-- shame/src/frontend/rust_types/atomic.rs | 23 +- .../src/frontend/rust_types/layout_traits.rs | 48 ++-- shame/src/frontend/rust_types/mat.rs | 19 +- shame/src/frontend/rust_types/packed_vec.rs | 24 +- shame/src/frontend/rust_types/struct_.rs | 16 +- .../rust_types/type_layout/builder.rs | 181 +++++++------- .../src/frontend/rust_types/type_layout/eq.rs | 14 +- .../align_size.rs | 63 +++-- .../{cpu_shareable => layoutable}/builder.rs | 71 +++--- .../{cpu_shareable => layoutable}/mod.rs | 203 +++++++++------- .../frontend/rust_types/type_layout/mod.rs | 227 +++++++----------- shame/src/frontend/rust_types/type_traits.rs | 1 - shame/src/frontend/rust_types/vec.rs | 20 +- shame/src/ir/ir_type/layout_constraints.rs | 6 +- shame/src/ir/ir_type/tensor.rs | 1 + shame/src/ir/ir_type/ty.rs | 2 +- shame/src/ir/pipeline/wip_pipeline.rs | 8 +- shame/src/lib.rs | 59 ++++- shame/tests/test_layout.rs | 4 +- shame_derive/src/derive_layout.rs | 42 ++-- 27 files changed, 617 insertions(+), 536 deletions(-) rename shame/src/frontend/rust_types/type_layout/{cpu_shareable => layoutable}/align_size.rs (81%) rename shame/src/frontend/rust_types/type_layout/{cpu_shareable => layoutable}/builder.rs (70%) rename shame/src/frontend/rust_types/type_layout/{cpu_shareable => layoutable}/mod.rs (64%) diff --git a/examples/shame_wgpu/src/conversion.rs b/examples/shame_wgpu/src/conversion.rs index 05b6d73..3bb0ff7 100644 --- a/examples/shame_wgpu/src/conversion.rs +++ b/examples/shame_wgpu/src/conversion.rs @@ -449,7 +449,7 @@ fn color_writes(write_mask: smr::ChannelWrites) -> wgpu::ColorWrites { #[rustfmt::skip] fn vertex_format(format: smr::VertexAttribFormat) -> Result { - use shame::cpu_shareable::ScalarType as S; + use shame::any::layout::ScalarType as S; use smr::Len as L; use wgpu::VertexFormat as W; let unsupported = Err(ShameToWgpuError::UnsupportedVertexAttribFormat(format)); diff --git a/examples/type_layout/src/main.rs b/examples/type_layout/src/main.rs index d07064e..56bfbc0 100644 --- a/examples/type_layout/src/main.rs +++ b/examples/type_layout/src/main.rs @@ -1,16 +1,17 @@ #![allow(dead_code, unused)] //! Demonstration of the TypeLayout and TypeLayout Builder API. +use layout::{repr, Repr, SizedStruct}; use shame::{ - any::{self, U32PowerOf2}, - boolx1, - cpu_shareable::{self as cs, BinaryReprSized}, - f32x1, f32x2, f32x3, f32x4, - type_layout::{ - constraint::{Packed, Plain, Storage}, - *, + any::{ + self, + layout::{ + self, LayoutableSized, UnsizedStruct, SizedField, SizedType, ScalarType, Len, RuntimeSizedArrayField, + FieldOptions, Vector, + }, + U32PowerOf2, }, - Array, GpuLayout, GpuSized, VertexAttribute, VertexLayout, + boolx1, f32x1, f32x2, f32x3, f32x4, Array, GpuLayout, GpuSized, TypeLayout, VertexAttribute, VertexLayout, }; fn main() { @@ -24,16 +25,16 @@ fn main() { // SizedStruct::new immediately takes the first field of the struct, because // structs need to have at least one field. - let sized_struct = cs::SizedStruct::new("Vertex", "position", f32x3::layout_type_sized()) - .extend("normal", f32x3::layout_type_sized()) - .extend("uv", f32x1::layout_type_sized()); + let sized_struct = SizedStruct::new("Vertex", "position", f32x3::layoutable_type_sized()) + .extend("normal", f32x3::layoutable_type_sized()) + .extend("uv", f32x1::layoutable_type_sized()); // A layout that follows the storage layout rules - let layout: TypeLayout = TypeLayout::new_layout_for(sized_struct.clone(), cs::Repr::Storage); + let layout: TypeLayout = TypeLayout::new_layout_for(sized_struct.clone(), Repr::Storage); // Or we can get a TypeLayout, which guarantees the storage layout rules. - let layout_storage: TypeLayout = TypeLayout::new_storage_layout_for(sized_struct.clone()); + let layout_storage: TypeLayout = TypeLayout::new_storage_layout_for(sized_struct.clone()); assert_eq!(layout, layout_storage); // Or we get it as a packed layout - let layout_packed: TypeLayout = TypeLayout::new_packed_layout_for(sized_struct); + let layout_packed: TypeLayout = TypeLayout::new_packed_layout_for(sized_struct); assert_ne!(layout_storage, layout_packed); @@ -45,16 +46,16 @@ fn main() { c: Array, } - // Be default structs are #[gpu_repr(Storage)], which means that it follows + // By default structs are #[gpu_repr(Storage)], which means that it follows // the wgsl storage layout rules (std430). To obtain a corresponding TypeLayout // we first need to build a `CpuShareableType`, in our case an `UnsizedStruct`. - let unsized_struct = cs::UnsizedStruct { + let unsized_struct = UnsizedStruct { name: "A".into(), sized_fields: vec![ - cs::SizedField::new("a", cs::Vector::new(cs::ScalarType::F32, cs::Len::X4)), - cs::SizedField::new("b", f32x3::layout_type_sized()), + SizedField::new("a", Vector::new(ScalarType::F32, Len::X4)), + SizedField::new("b", f32x3::layoutable_type_sized()), ], - last_unsized: cs::RuntimeSizedArrayField::new("c", None, f32x1::layout_type_sized()), + last_unsized: RuntimeSizedArrayField::new("c", None, f32x1::layoutable_type_sized()), }; // And now we can get the `TypeLayout`. let s_layout = TypeLayout::new_storage_layout_for(unsized_struct.clone()); @@ -75,9 +76,9 @@ fn main() { } // Sized structs require a builder to ensure it always contains at least one field. - let mut sized_struct = cs::SizedStruct::new("B", "b", f32x4::layout_type_sized()) - .extend("b", f32x3::layout_type_sized()) - .extend("c", f32x1::layout_type_sized()); + let mut sized_struct = SizedStruct::new("B", "b", f32x4::layoutable_type_sized()) + .extend("b", f32x3::layoutable_type_sized()) + .extend("c", f32x1::layoutable_type_sized()); // Since this struct is sized we can use TypeLayout::::new_layout_for. let u_layout = TypeLayout::new_uniform_layout_for(sized_struct.clone()); let s_layout = TypeLayout::new_storage_layout_for(sized_struct); @@ -87,7 +88,7 @@ fn main() { // uniform layout rules despite not being `TypeLayout`, // which in this case will succeed, but if it doesn't we get a very nice error message about // why the layout is not compatible with the uniform layout rules. - let u_layout = TypeLayout::::try_from(&s_layout).unwrap(); + let u_layout = TypeLayout::::try_from(&s_layout).unwrap(); // Let's replicate a more complex example with explicit field size and align. #[derive(shame::GpuLayout)] @@ -99,11 +100,11 @@ fn main() { c: f32x2, } - let mut sized_struct = cs::SizedStruct::new("C", "a", f32x3::layout_type_sized()) - .extend(FieldOptions::new("b", None, Some(16)), f32x3::layout_type_sized()) + let mut sized_struct = SizedStruct::new("C", "a", f32x3::layoutable_type_sized()) + .extend(FieldOptions::new("b", None, Some(16)), f32x3::layoutable_type_sized()) .extend( FieldOptions::new("c", Some(U32PowerOf2::_16), None), - f32x1::layout_type_sized(), + f32x1::layoutable_type_sized(), ); let layout = TypeLayout::new_storage_layout_for(sized_struct); assert!(layout.align.as_u32() == 16); @@ -120,11 +121,11 @@ fn main() { assert!(D::gpu_layout().align.as_u32() == 16); // Let's end on a pretty error message - let mut sized_struct = cs::SizedStruct::new("D", "a", f32x2::layout_type_sized()) + let mut sized_struct = SizedStruct::new("D", "a", f32x2::layoutable_type_sized()) // This has align of 4 for storage and align of 16 for uniform. - .extend("b", Array::>::layout_type_sized()); + .extend("b", Array::>::layoutable_type_sized()); let s_layout = TypeLayout::new_storage_layout_for(sized_struct); - let result = TypeLayout::::try_from(&s_layout); + let result = TypeLayout::::try_from(&s_layout); match result { Err(e) => println!("This error is a showcase:\n{}", e), Ok(u_layout) => println!("It unexpectedly worked, ohh no."), diff --git a/shame/src/common/proc_macro_reexports.rs b/shame/src/common/proc_macro_reexports.rs index 86d2f64..b78acee 100644 --- a/shame/src/common/proc_macro_reexports.rs +++ b/shame/src/common/proc_macro_reexports.rs @@ -27,16 +27,8 @@ pub use crate::frontend::rust_types::type_layout::FieldLayout; pub use crate::frontend::rust_types::type_layout::FieldLayoutWithOffset; pub use crate::frontend::rust_types::type_layout::FieldOptions; pub use crate::frontend::rust_types::type_layout::StructLayout; -pub use crate::frontend::rust_types::type_layout::cpu_shareable::SizedStruct; -pub use crate::frontend::rust_types::type_layout::cpu_shareable::BinaryRepr; -pub use crate::frontend::rust_types::type_layout::cpu_shareable::BinaryReprSized; -pub use crate::frontend::rust_types::type_layout::cpu_shareable::LayoutType; -pub use crate::frontend::rust_types::type_layout::cpu_shareable::SizedType; -pub use crate::frontend::rust_types::type_layout::cpu_shareable::SizedOrArray; -pub use crate::frontend::rust_types::type_layout::cpu_shareable::Repr; -pub use crate::frontend::rust_types::type_layout::cpu_shareable::StructFromPartsError; +pub use crate::frontend::rust_types::type_layout::Repr; pub use crate::frontend::rust_types::type_layout::TypeLayout; -pub use crate::frontend::rust_types::type_layout::TypeLayoutRules; pub use crate::frontend::rust_types::type_layout::TypeLayoutSemantics; pub use crate::frontend::rust_types::type_traits::BindingArgs; pub use crate::frontend::rust_types::type_traits::GpuAligned; @@ -48,6 +40,13 @@ pub use crate::frontend::rust_types::type_traits::NoBools; pub use crate::frontend::rust_types::type_traits::NoHandles; pub use crate::frontend::rust_types::type_traits::VertexAttribute; pub use crate::frontend::rust_types::type_traits::GpuLayoutField; +pub use crate::frontend::rust_types::type_layout::layoutable::SizedStruct; +pub use crate::frontend::rust_types::type_layout::layoutable::Layoutable; +pub use crate::frontend::rust_types::type_layout::layoutable::LayoutableSized; +pub use crate::frontend::rust_types::type_layout::layoutable::LayoutableType; +pub use crate::frontend::rust_types::type_layout::layoutable::SizedType; +pub use crate::frontend::rust_types::type_layout::layoutable::SizedOrArray; +pub use crate::frontend::rust_types::type_layout::layoutable::builder::StructFromPartsError; pub use crate::frontend::rust_types::AsAny; pub use crate::frontend::rust_types::GpuType; #[allow(missing_docs)] diff --git a/shame/src/common/proc_macro_utils.rs b/shame/src/common/proc_macro_utils.rs index 7e22a09..85f0d19 100644 --- a/shame/src/common/proc_macro_utils.rs +++ b/shame/src/common/proc_macro_utils.rs @@ -5,9 +5,7 @@ use crate::{ any::{Any, InvalidReason}, rust_types::{ error::FrontendError, - type_layout::{ - FieldLayout, FieldLayoutWithOffset, StructLayout, TypeLayout, TypeLayoutRules, TypeLayoutSemantics, - }, + type_layout::{FieldLayout, FieldLayoutWithOffset, StructLayout, TypeLayout, TypeLayoutSemantics}, }, }, ir::{ diff --git a/shame/src/frontend/any/render_io.rs b/shame/src/frontend/any/render_io.rs index a963506..4b267f1 100644 --- a/shame/src/frontend/any/render_io.rs +++ b/shame/src/frontend/any/render_io.rs @@ -2,10 +2,9 @@ use std::{fmt::Display, rc::Rc}; use thiserror::Error; -use crate::cpu_shareable; +use crate::any::layout; use crate::frontend::any::Any; use crate::frontend::rust_types::type_layout::TypeLayout; -use crate::type_layout::cpu_shareable::array_stride; use crate::{ call_info, common::iterator_ext::try_collect, @@ -110,7 +109,7 @@ impl Any { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum VertexAttribFormat { /// regular [`crate::vec`] types - Fine(Len, cpu_shareable::ScalarType), + Fine(Len, layout::ScalarType), /// packed [`crate::packed::PackedVec`] types Coarse(PackedVector), } @@ -253,7 +252,7 @@ impl Attrib { ) -> Option<(Box<[Attrib]>, u64)> { let stride = { let size = layout.byte_size()?; - array_stride(layout.byte_align(), size) + layout::array_stride(layout.align(), size) }; use TypeLayoutSemantics as TLS; diff --git a/shame/src/frontend/encoding/buffer.rs b/shame/src/frontend/encoding/buffer.rs index ee857c4..c6f3c1c 100644 --- a/shame/src/frontend/encoding/buffer.rs +++ b/shame/src/frontend/encoding/buffer.rs @@ -1,3 +1,4 @@ +use crate::any::layout::LayoutableSized; use crate::common::proc_macro_reexports::GpuStoreImplCategory; use crate::frontend::any::shared_io::{BindPath, BindingType, BufferBindingType}; use crate::frontend::any::{Any, InvalidReason}; @@ -17,7 +18,6 @@ use crate::frontend::rust_types::{reference::AccessModeReadable, scalar_type::Sc use crate::ir::pipeline::StageMask; use crate::ir::recording::{Context, MemoryRegion}; use crate::ir::Type; -use crate::type_layout::cpu_shareable::BinaryReprSized; use crate::{self as shame, call_info, ir, GpuLayout}; use std::borrow::Borrow; @@ -319,7 +319,7 @@ fn store_type_from_impl_category(category: GpuStoreImplCategory) -> ir::StoreTyp #[rustfmt::skip] impl Binding for Buffer, AS, DYN_OFFSET> where - T: GpuType + GpuSized + GpuLayout + BinaryReprSized + T: GpuType + GpuSized + GpuLayout + LayoutableSized { fn binding_type() -> BindingType { BufferInner::::binding_type(DYN_OFFSET) } #[track_caller] diff --git a/shame/src/frontend/rust_types/array.rs b/shame/src/frontend/rust_types/array.rs index 644d10a..cf49295 100644 --- a/shame/src/frontend/rust_types/array.rs +++ b/shame/src/frontend/rust_types/array.rs @@ -5,15 +5,15 @@ use super::len::x1; use super::mem::AddressSpace; use super::reference::{AccessMode, AccessModeReadable, AccessModeWritable, Read}; use super::scalar_type::ScalarTypeInteger; -use super::type_layout::{ElementLayout, TypeLayout, TypeLayoutRules, TypeLayoutSemantics}; +use super::type_layout::{self, layoutable, ElementLayout, TypeLayout, TypeLayoutSemantics}; use super::type_traits::{ BindingArgs, EmptyRefFields, GpuAligned, GpuSized, GpuStore, GpuStoreImplCategory, NoAtomics, NoBools, NoHandles, }; use super::vec::{ToInteger, ToVec}; use super::{AsAny, GpuType}; use super::{To, ToGpuType}; +use crate::any::layout::{Layoutable, LayoutableSized}; use crate::common::small_vec::SmallVec; -use crate::cpu_shareable::{BinaryRepr, BinaryReprSized}; use crate::frontend::any::shared_io::{BindPath, BindingType}; use crate::frontend::any::Any; use crate::frontend::any::InvalidReason; @@ -25,7 +25,7 @@ use crate::frontend::rust_types::vec::vec; use crate::ir::ir_type::stride_of_array_from_element_align_size; use crate::ir::pipeline::StageMask; use crate::ir::recording::Context; -use crate::{call_info, cpu_shareable, for_count, ir}; +use crate::{call_info, for_count, ir}; use std::borrow::Cow; use std::marker::PhantomData; use std::num::NonZeroU32; @@ -156,16 +156,16 @@ impl GpuSized for Array> { #[rustfmt::skip] impl NoHandles for Array {} #[rustfmt::skip] impl NoAtomics for Array {} #[rustfmt::skip] impl NoBools for Array {} -impl BinaryReprSized for Array> { - fn layout_type_sized() -> cpu_shareable::SizedType { - cpu_shareable::SizedArray::new(T::layout_type_sized(), Size::::nonzero()).into() +impl LayoutableSized for Array> { + fn layoutable_type_sized() -> layoutable::SizedType { + layoutable::SizedArray::new(T::layoutable_type_sized(), Size::::nonzero()).into() } } -impl BinaryRepr for Array { - fn layout_type() -> cpu_shareable::LayoutType { +impl Layoutable for Array { + fn layoutable_type() -> layoutable::LayoutableType { match N::LEN { - Some(n) => cpu_shareable::SizedArray::new(T::layout_type_sized(), n).into(), - None => cpu_shareable::RuntimeSizedArray::new(T::layout_type_sized()).into(), + Some(n) => layoutable::SizedArray::new(T::layoutable_type_sized(), n).into(), + None => layoutable::RuntimeSizedArray::new(T::layoutable_type_sized()).into(), } } } @@ -178,8 +178,8 @@ impl ToGpuType for Array { fn as_gpu_type_ref(&self) -> Option<&Self::Gpu> { Some(self) } } -impl GpuLayout for Array { - fn gpu_repr() -> cpu_shareable::Repr { cpu_shareable::Repr::Storage } +impl GpuLayout for Array { + fn gpu_repr() -> type_layout::Repr { type_layout::Repr::Storage } fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { let (t_cpu_name, t_cpu_layout) = match T::cpu_type_name_and_layout()? { @@ -199,10 +199,10 @@ impl GpuLayout name.into(), TypeLayout::new( N::LEN.map(|n| n.get() as u64 * t_cpu_size), - t_cpu_layout.byte_align(), + t_cpu_layout.align(), TypeLayoutSemantics::Array( Rc::new(ElementLayout { - byte_stride: cpu_shareable::array_stride(t_cpu_layout.byte_align(), t_cpu_size), + byte_stride: layoutable::array_stride(t_cpu_layout.align(), t_cpu_size), ty: t_cpu_layout, }), N::LEN.map(NonZeroU32::get), diff --git a/shame/src/frontend/rust_types/atomic.rs b/shame/src/frontend/rust_types/atomic.rs index cb239b8..0a6ff26 100644 --- a/shame/src/frontend/rust_types/atomic.rs +++ b/shame/src/frontend/rust_types/atomic.rs @@ -6,7 +6,11 @@ use super::{ mem::{AddressSpace, AddressSpaceAtomic}, reference::{AccessMode, AccessModeReadable, ReadWrite}, scalar_type::{ScalarType, ScalarTypeInteger}, - type_layout::{cpu_shareable::BinaryReprSized, TypeLayout, TypeLayoutRules}, + type_layout::{ + self, + layoutable::{self, LayoutableSized}, + TypeLayout, + }, type_traits::{ BindingArgs, EmptyRefFields, GpuAligned, GpuSized, GpuStore, GpuStoreImplCategory, NoAtomics, NoBools, NoHandles, @@ -14,10 +18,7 @@ use super::{ vec::vec, AsAny, GpuType, To, ToGpuType, }; -use crate::{ - cpu_shareable::{self, BinaryRepr}, - frontend::rust_types::reference::Ref, -}; +use crate::{any::layout::Layoutable, frontend::rust_types::reference::Ref}; use crate::{ boolx1, frontend::{ @@ -132,20 +133,20 @@ impl GetAllFields for Atomic { fn fields_as_anys_unchecked(self_as_any: Any) -> impl std::borrow::Borrow<[Any]> { [] } } -impl BinaryReprSized for Atomic { - fn layout_type_sized() -> cpu_shareable::SizedType { - cpu_shareable::Atomic { +impl LayoutableSized for Atomic { + fn layoutable_type_sized() -> layoutable::SizedType { + layoutable::Atomic { scalar: T::SCALAR_TYPE_INTEGER, } .into() } } -impl BinaryRepr for Atomic { - fn layout_type() -> cpu_shareable::LayoutType { Self::layout_type_sized().into() } +impl Layoutable for Atomic { + fn layoutable_type() -> layoutable::LayoutableType { Self::layoutable_type_sized().into() } } impl GpuLayout for Atomic { - fn gpu_repr() -> cpu_shareable::Repr { cpu_shareable::Repr::Storage } + fn gpu_repr() -> type_layout::Repr { type_layout::Repr::Storage } fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { diff --git a/shame/src/frontend/rust_types/layout_traits.rs b/shame/src/frontend/rust_types/layout_traits.rs index 0b10946..3a4741c 100644 --- a/shame/src/frontend/rust_types/layout_traits.rs +++ b/shame/src/frontend/rust_types/layout_traits.rs @@ -1,7 +1,7 @@ +use crate::any::layout::{Layoutable, LayoutableSized, Repr}; use crate::call_info; use crate::common::po2::U32PowerOf2; use crate::common::proc_macro_utils::{self, repr_c_struct_layout, ReprCError, ReprCField}; -use crate::cpu_shareable::{BinaryRepr, BinaryReprSized, Repr}; use crate::frontend::any::render_io::{ Attrib, VertexBufferLookupIndex, Location, VertexAttribFormat, VertexBufferLayout, VertexLayoutError, }; @@ -22,11 +22,10 @@ use super::error::FrontendError; use super::mem::AddressSpace; use super::reference::{AccessMode, AccessModeReadable}; use super::struct_::{BufferFields, SizedFields, Struct}; -use super::type_layout::constraint::TypeConstraint; -use super::type_layout::cpu_shareable::{self, array_stride, Vector}; +use super::type_layout::repr::TypeRepr; +use super::type_layout::layoutable::{self, array_stride, Vector}; use super::type_layout::{ - self, constraint, ElementLayout, FieldLayout, FieldLayoutWithOffset, StructLayout, TypeLayout, TypeLayoutError, - TypeLayoutRules, TypeLayoutSemantics, + self, repr, ElementLayout, FieldLayout, FieldLayoutWithOffset, StructLayout, TypeLayout, TypeLayoutSemantics, }; use super::type_traits::{ BindingArgs, GpuAligned, GpuSized, GpuStore, GpuStoreImplCategory, NoAtomics, NoBools, NoHandles, VertexAttribute, @@ -138,7 +137,7 @@ use std::rc::Rc; /// [`Texture`]: crate::Texture /// [`StorageTexture`]: crate::StorageTexture /// -pub trait GpuLayout: BinaryRepr { +pub trait GpuLayout: Layoutable { /// returns a [`TypeLayout`] object that can be used to inspect the layout /// of a type on the gpu. /// @@ -159,9 +158,9 @@ pub trait GpuLayout: BinaryRepr { /// println!("OnGpu:\n{}\n", OnGpu::gpu_layout()); /// println!("OnCpu:\n{}\n", OnCpu::cpu_layout()); /// ``` - fn gpu_layout() -> TypeLayout { TypeLayout::new_layout_for(Self::layout_type(), Self::gpu_repr()) } + fn gpu_layout() -> TypeLayout { TypeLayout::new_layout_for(Self::layoutable_type(), Self::gpu_repr()) } - /// TODO(chronicl) docs + /// Returns the `Repr` of the `TypeLayout` from `GpuLayout::gpu_layout`. fn gpu_repr() -> Repr; /// the `#[cpu(...)]` in `#[derive(GpuLayout)]` allows the definition of a @@ -207,7 +206,7 @@ pub(crate) fn get_layout_compare_with_cpu_push_error( gpu_layout } -pub(crate) fn check_layout_push_error( +pub(crate) fn check_layout_push_error( ctx: &Context, cpu_name: &str, cpu_layout: &TypeLayout, @@ -215,7 +214,7 @@ pub(crate) fn check_layout_push_error( skip_stride_check: bool, comment_on_mismatch_error: &str, ) -> Result<(), InvalidReason> { - type_layout::check_eq(("cpu", cpu_layout), ("gpu", gpu_layout)) + type_layout::eq::check_eq(("cpu", cpu_layout), ("gpu", gpu_layout)) .map_err(|e| LayoutError::LayoutMismatch(e, Some(comment_on_mismatch_error.to_string()))) .and_then(|_| { if skip_stride_check { @@ -228,8 +227,8 @@ pub(crate) fn check_layout_push_error( name: gpu_layout.short_name(), }), (Some(cpu_size), Some(gpu_size)) => { - let cpu_stride = array_stride(cpu_layout.byte_align(), cpu_size); - let gpu_stride = array_stride(gpu_layout.byte_align(), gpu_size); + let cpu_stride = array_stride(cpu_layout.align(), cpu_size); + let gpu_stride = array_stride(gpu_layout.align(), gpu_size); if cpu_stride != gpu_stride { Err(LayoutError::StrideMismatch { @@ -354,7 +353,6 @@ pub(crate) fn from_single_any(mut anys: impl Iterator) -> Any { pub trait VertexLayout: GpuLayout + FromAnys {} impl VertexLayout for T {} -// TODO(chronicl) uncomment // #[derive(GpuLayout)] // TODO(release) remove this type. This is an example impl for figuring out how the derive macro should work #[derive(Clone)] @@ -540,11 +538,11 @@ where } } -impl BinaryReprSized for GpuT { - fn layout_type_sized() -> cpu_shareable::SizedType { todo!() } +impl LayoutableSized for GpuT { + fn layoutable_type_sized() -> layoutable::SizedType { todo!() } } -impl BinaryRepr for GpuT { - fn layout_type() -> cpu_shareable::LayoutType { todo!() } +impl Layoutable for GpuT { + fn layoutable_type() -> layoutable::LayoutableType { todo!() } } impl GpuLayout for GpuT { @@ -696,8 +694,8 @@ where impl CpuLayout for f32 { fn cpu_layout() -> TypeLayout { TypeLayout::from_rust_sized::(TypeLayoutSemantics::Vector(Vector::new( - cpu_shareable::ScalarType::F32, - cpu_shareable::Len::X1, + layoutable::ScalarType::F32, + layoutable::Len::X1, ))) } // fn gpu_type_layout() -> Option> { @@ -708,8 +706,8 @@ impl CpuLayout for f32 { impl CpuLayout for f64 { fn cpu_layout() -> TypeLayout { TypeLayout::from_rust_sized::(TypeLayoutSemantics::Vector(Vector::new( - cpu_shareable::ScalarType::F64, - cpu_shareable::Len::X1, + layoutable::ScalarType::F64, + layoutable::Len::X1, ))) } } @@ -717,8 +715,8 @@ impl CpuLayout for f64 { impl CpuLayout for u32 { fn cpu_layout() -> TypeLayout { TypeLayout::from_rust_sized::(TypeLayoutSemantics::Vector(Vector::new( - cpu_shareable::ScalarType::U32, - cpu_shareable::Len::X1, + layoutable::ScalarType::U32, + layoutable::Len::X1, ))) } } @@ -726,8 +724,8 @@ impl CpuLayout for u32 { impl CpuLayout for i32 { fn cpu_layout() -> TypeLayout { TypeLayout::from_rust_sized::(TypeLayoutSemantics::Vector(Vector::new( - cpu_shareable::ScalarType::I32, - cpu_shareable::Len::X1, + layoutable::ScalarType::I32, + layoutable::Len::X1, ))) } } diff --git a/shame/src/frontend/rust_types/mat.rs b/shame/src/frontend/rust_types/mat.rs index ded204c..4795935 100644 --- a/shame/src/frontend/rust_types/mat.rs +++ b/shame/src/frontend/rust_types/mat.rs @@ -8,8 +8,9 @@ use super::{ reference::{AccessMode, AccessModeReadable}, scalar_type::{ScalarType, ScalarTypeFp}, type_layout::{ - cpu_shareable::{self, BinaryReprSized}, - TypeLayout, TypeLayoutRules, + self, + layoutable::{self, LayoutableSized}, + TypeLayout, }, type_traits::{ BindingArgs, EmptyRefFields, GpuAligned, GpuSized, GpuStore, GpuStoreImplCategory, NoAtomics, NoBools, @@ -18,7 +19,7 @@ use super::{ vec::{scalar, vec, ToInteger}, AsAny, GpuType, To, ToGpuType, }; -use crate::{cpu_shareable::BinaryRepr, frontend::rust_types::reference::Ref, ir::recording::CallInfoScope}; +use crate::{any::layout::Layoutable, frontend::rust_types::reference::Ref, ir::recording::CallInfoScope}; use crate::{ call_info, frontend::{ @@ -53,9 +54,9 @@ impl Default for mat { } } -impl BinaryReprSized for mat { - fn layout_type_sized() -> cpu_shareable::SizedType { - cpu_shareable::Matrix { +impl LayoutableSized for mat { + fn layoutable_type_sized() -> layoutable::SizedType { + layoutable::Matrix { columns: C::LEN2, rows: R::LEN2, scalar: T::SCALAR_TYPE_FP, @@ -63,12 +64,12 @@ impl BinaryReprSized for mat { .into() } } -impl BinaryRepr for mat { - fn layout_type() -> cpu_shareable::LayoutType { Self::layout_type_sized().into() } +impl Layoutable for mat { + fn layoutable_type() -> layoutable::LayoutableType { Self::layoutable_type_sized().into() } } impl GpuLayout for mat { - fn gpu_repr() -> cpu_shareable::Repr { cpu_shareable::Repr::Storage } + fn gpu_repr() -> type_layout::Repr { type_layout::Repr::Storage } fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { None } } diff --git a/shame/src/frontend/rust_types/packed_vec.rs b/shame/src/frontend/rust_types/packed_vec.rs index 148c98e..8682fe9 100644 --- a/shame/src/frontend/rust_types/packed_vec.rs +++ b/shame/src/frontend/rust_types/packed_vec.rs @@ -5,9 +5,11 @@ use std::{ }; use crate::{ - any::{AsAny, DataPackingFn}, + any::{ + layout::{Layoutable, LayoutableSized}, + AsAny, DataPackingFn, + }, common::floating_point::f16, - cpu_shareable::{BinaryRepr, BinaryReprSized}, f32x2, f32x4, i32x4, u32x1, u32x4, }; use crate::frontend::rust_types::len::{x1, x2, x3, x4}; @@ -23,7 +25,7 @@ use super::{ layout_traits::{from_single_any, ArrayElementsUnsizedError, FromAnys, GpuLayout}, len::LenEven, scalar_type::ScalarType, - type_layout::{cpu_shareable, type_layout_internal, TypeLayout, TypeLayoutRules, TypeLayoutSemantics}, + type_layout::{self, layoutable, type_layout_internal, TypeLayout, TypeLayoutSemantics}, type_traits::{GpuAligned, GpuSized, NoAtomics, NoBools, NoHandles, VertexAttribute}, vec::IsVec, GpuType, @@ -131,9 +133,9 @@ impl GpuAligned for PackedVec { impl NoBools for PackedVec {} impl NoHandles for PackedVec {} impl NoAtomics for PackedVec {} -impl BinaryReprSized for PackedVec { - fn layout_type_sized() -> cpu_shareable::SizedType { - cpu_shareable::PackedVector { +impl LayoutableSized for PackedVec { + fn layoutable_type_sized() -> layoutable::SizedType { + layoutable::PackedVector { scalar_type: T::SCALAR_TYPE, bits_per_component: T::BITS_PER_COMPONENT, len: L::LEN_EVEN, @@ -141,17 +143,17 @@ impl BinaryReprSized for PackedVec { .into() } } -impl BinaryRepr for PackedVec { - fn layout_type() -> cpu_shareable::LayoutType { Self::layout_type_sized().into() } +impl Layoutable for PackedVec { + fn layoutable_type() -> layoutable::LayoutableType { Self::layoutable_type_sized().into() } } impl GpuLayout for PackedVec { - fn gpu_repr() -> cpu_shareable::Repr { cpu_shareable::Repr::Storage } + fn gpu_repr() -> type_layout::Repr { type_layout::Repr::Storage } fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { let sized_ty = Self::sized_ty_equivalent(); let name = sized_ty.to_string().into(); - let sized_ty: cpu_shareable::SizedType = sized_ty.try_into().expect("PackedVec is NoBools and NoHandles"); + let sized_ty: layoutable::SizedType = sized_ty.try_into().expect("PackedVec is NoBools and NoHandles"); let layout = TypeLayout::new_storage_layout_for(sized_ty); Some(Ok((name, layout.into()))) } @@ -170,7 +172,7 @@ impl From for PackedVec { let inner = Context::try_with(call_info!(), |ctx| { let err = |ty| { ctx.push_error_get_invalid_any( - FrontendError::InvalidDowncastToNonShaderType(ty, Self::gpu_layout().into()).into(), + FrontendError::InvalidDowncastToNonShaderType(ty, Self::gpu_layout()).into(), ) }; match any.ty() { diff --git a/shame/src/frontend/rust_types/struct_.rs b/shame/src/frontend/rust_types/struct_.rs index 4fec66f..14e4c6e 100644 --- a/shame/src/frontend/rust_types/struct_.rs +++ b/shame/src/frontend/rust_types/struct_.rs @@ -1,5 +1,5 @@ +use crate::any::layout::{Layoutable, LayoutableSized}; use crate::common::small_vec::SmallVec; -use crate::cpu_shareable::{self, BinaryRepr, BinaryReprSized}; use crate::frontend::any::shared_io::{BindPath, BindingType}; use crate::frontend::any::{Any, InvalidReason}; use crate::frontend::encoding::buffer::BufferRefInner; @@ -23,7 +23,7 @@ use std::{ }; use super::layout_traits::{GetAllFields, GpuLayout}; -use super::type_layout::{TypeLayout, TypeLayoutRules}; +use super::type_layout::{self, layoutable, TypeLayout}; use super::type_traits::{GpuAligned, GpuSized, GpuStore, GpuStoreImplCategory, NoBools}; use super::{ error::FrontendError, @@ -135,19 +135,19 @@ impl Deref for Struct { fn deref(&self) -> &Self::Target { &self.fields } } -impl BinaryReprSized for Struct { - fn layout_type_sized() -> cpu_shareable::SizedType { - cpu_shareable::SizedStruct::try_from(T::get_sizedstruct_type()) +impl LayoutableSized for Struct { + fn layoutable_type_sized() -> layoutable::SizedType { + layoutable::SizedStruct::try_from(T::get_sizedstruct_type()) .expect("no bools") .into() } } -impl BinaryRepr for Struct { - fn layout_type() -> cpu_shareable::LayoutType { Self::layout_type_sized().into() } +impl Layoutable for Struct { + fn layoutable_type() -> layoutable::LayoutableType { Self::layoutable_type_sized().into() } } impl GpuLayout for Struct { - fn gpu_repr() -> cpu_shareable::Repr { cpu_shareable::Repr::Storage } + fn gpu_repr() -> type_layout::Repr { type_layout::Repr::Storage } fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { T::cpu_type_name_and_layout().map(|x| x.map(|(name, l)| (format!("Struct<{name}>").into(), l))) diff --git a/shame/src/frontend/rust_types/type_layout/builder.rs b/shame/src/frontend/rust_types/type_layout/builder.rs index 0af94d4..a3bfd53 100644 --- a/shame/src/frontend/rust_types/type_layout/builder.rs +++ b/shame/src/frontend/rust_types/type_layout/builder.rs @@ -1,71 +1,81 @@ -use crate::{ - __private::SmallVec, - cpu_shareable::{self as cs, Major, Repr, SizedField, UnsizedStruct}, - ir::{ir_type::max_u64_po2_dividing, StoreType}, +use crate::ir::{ir_type::max_u64_po2_dividing, StoreType}; +use super::{ + layoutable::{ + MatrixMajor, RuntimeSizedArray, RuntimeSizedArrayField, SizedField, SizedStruct, SizedType, UnsizedStruct, + }, + *, }; -use super::*; use TypeLayoutSemantics as TLS; -impl TypeLayout { - pub fn new_storage_layout_for(ty: impl Into) -> Self { +impl TypeLayout { + /// Returns the type layout of the given `LayoutableType` + /// layed out according to wgsl storage layout rules (std430). + pub fn new_storage_layout_for(ty: impl Into) -> Self { type_layout_internal::cast_unchecked(TypeLayout::new_layout_for(ty, Repr::Storage)) } - /// Get the `CpuShareableType` this layout is based on. - pub fn cpu_shareable(&self) -> &LayoutType { - self.cpu_shareable + /// Get the `LayoutableType` this layout is based on. + pub fn layoutable_type(&self) -> &LayoutableType { + self.layoutable_type .as_ref() - .expect("constraint::Storage always contains a cpu-shareable") + .expect("Storage is always based on a layoutable type") } } -impl TypeLayout { - /// Takes a `SizedType`, because wgsl only supports sized uniform buffers. - /// Use `new_layout_for_unchecked`to obtain the the uniform layout of an unsized host-shareable. /// Using the unsized layout with wgsl as your target language will cause an error. - pub fn new_uniform_layout_for(ty: impl Into) -> Self { +impl TypeLayout { + /// Returns the type layout of the given `LayoutableType` + /// layed out according to wgsl uniform layout rules (std140). + pub fn new_uniform_layout_for(ty: impl Into) -> Self { type_layout_internal::cast_unchecked(TypeLayout::new_layout_for(ty, Repr::Uniform)) } - /// Get the `CpuShareableType` this layout is based on. - pub fn cpu_shareable(&self) -> &LayoutType { - self.cpu_shareable + /// Get the `LayoutableType` this layout is based on. + pub fn layoutable_type(&self) -> &LayoutableType { + self.layoutable_type .as_ref() - .expect("constraint::Uniform always contains a cpu-shareable") + .expect("Uniform is always based on a layoutable type") } } -impl TypeLayout { - pub fn new_packed_layout_for(ty: impl Into) -> Self { +impl TypeLayout { + /// Returns the type layout of the given `LayoutableType` in packed format. + pub fn new_packed_layout_for(ty: impl Into) -> Self { type_layout_internal::cast_unchecked(TypeLayout::new_layout_for(ty, Repr::Packed)) } - /// Get the `CpuShareableType` this layout is based on. - pub fn cpu_shareable(&self) -> &LayoutType { - self.cpu_shareable + /// Get the `LayoutableType` this layout is based on. + pub fn layoutable_type(&self) -> &LayoutableType { + self.layoutable_type .as_ref() - .expect("constraint::Storage always contains a cpu-shareable") + .expect("Packed is always based on a layoutable type") } } impl TypeLayout { - pub fn new_layout_for(ty: impl Into, repr: Repr) -> Self { + /// Returns the type layout of the given `LayoutableType` + /// layed out according to the given `repr`. + pub fn new_layout_for(ty: impl Into, repr: Repr) -> Self { match ty.into() { - LayoutType::Sized(ty) => Self::from_sized_type(ty, repr), - LayoutType::UnsizedStruct(ty) => Self::from_unsized_struct(ty, repr), - LayoutType::RuntimeSizedArray(ty) => Self::from_runtime_sized_array(ty, repr), + LayoutableType::Sized(ty) => Self::from_sized_type(ty, repr), + LayoutableType::UnsizedStruct(ty) => Self::from_unsized_struct(ty, repr), + LayoutableType::RuntimeSizedArray(ty) => Self::from_runtime_sized_array(ty, repr), } } - fn from_sized_type(ty: cs::SizedType, repr: Repr) -> Self { + fn from_sized_type(ty: SizedType, repr: Repr) -> Self { let (size, align, tls) = match &ty { - cs::SizedType::Vector(v) => (v.byte_size(), v.align(), TLS::Vector(*v)), - cs::SizedType::Atomic(a) => ( + SizedType::Vector(v) => (v.byte_size(), v.align(), TLS::Vector(*v)), + SizedType::Atomic(a) => ( a.byte_size(), a.align(), TLS::Vector(Vector::new(a.scalar.into(), ir::Len::X1)), ), - cs::SizedType::Matrix(m) => (m.byte_size(Major::Row), m.align(repr, Major::Row), TLS::Matrix(*m)), - cs::SizedType::Array(a) => ( + SizedType::Matrix(m) => ( + m.byte_size(MatrixMajor::Row), + m.align(repr, MatrixMajor::Row), + TLS::Matrix(*m), + ), + SizedType::Array(a) => ( a.byte_size(repr), a.align(repr), TLS::Array( @@ -76,8 +86,8 @@ impl TypeLayout { Some(a.len.get()), ), ), - cs::SizedType::PackedVec(v) => (v.byte_size().as_u64(), v.align(), TLS::PackedVector(*v)), - cs::SizedType::Struct(s) => { + SizedType::PackedVec(v) => (v.byte_size().as_u64(), v.align(), TLS::PackedVector(*v)), + SizedType::Struct(s) => { let mut field_offsets = s.field_offsets(repr); let fields = (&mut field_offsets) .zip(s.fields()) @@ -128,7 +138,7 @@ impl TypeLayout { } - fn from_runtime_sized_array(ty: cs::RuntimeSizedArray, repr: Repr) -> Self { + fn from_runtime_sized_array(ty: RuntimeSizedArray, repr: Repr) -> Self { Self::new( None, ty.byte_align(repr), @@ -156,44 +166,43 @@ fn sized_field_to_field_layout(field: &SizedField, offset: u64, repr: Repr) -> F } } -impl<'a> TryFrom<&'a TypeLayout> for TypeLayout { +impl<'a> TryFrom<&'a TypeLayout> for TypeLayout { type Error = UniformLayoutError; - fn try_from(s_layout: &'a TypeLayout) -> Result { - let cpu_shareable = s_layout.cpu_shareable().clone(); + fn try_from(s_layout: &'a TypeLayout) -> Result { + let layoutable_type = s_layout.layoutable_type().clone(); // Checking whether it's sized - let sized = match cpu_shareable { - LayoutType::Sized(sized) => sized, + let sized = match layoutable_type { + LayoutableType::Sized(sized) => sized, _ => { - return Err(UniformLayoutError::MustBeSized("wgsl", cpu_shareable.into())); + return Err(UniformLayoutError::MustBeSized("wgsl", layoutable_type)); } }; let u_layout = TypeLayout::new_uniform_layout_for(sized); // Checking fields - fn check_layout( + fn check_layout( s_layout: &TypeLayout, u_layout: &TypeLayout, is_top_level: bool, ) -> Result<(), Mismatch> { - // kinds are the same, because the type layouts are based on the same cpu shareable + // kinds are the same, because the type layouts are based on the same LayoutableType match (&s_layout.kind, &u_layout.kind) { (TLS::Structure(s), TLS::Structure(u)) => { for (i, (s_field, u_field)) in s.fields.iter().zip(u.fields.iter()).enumerate() { // Checking field offset if s_field.rel_byte_offset != u_field.rel_byte_offset { - let (sized_fields, last_unsized) = match s_layout.get_cpu_shareable().unwrap() { - LayoutType::Sized(cs::SizedType::Struct(s)) => (s.fields().to_vec(), None), - LayoutType::UnsizedStruct(s) => (s.sized_fields.clone(), Some(s.last_unsized.clone())), + let struct_type = match s_layout.get_layoutable_type().unwrap() { + LayoutableType::Sized(SizedType::Struct(s)) => StructKind::Sized(s.clone()), + LayoutableType::UnsizedStruct(s) => StructKind::Unsized(s.clone()), _ => unreachable!("is struct, because tls is struct"), }; - return Err(Mismatch::StructureFieldOffset(StructureFieldOffsetError { + return Err(Mismatch::StructureFieldOffset(StructFieldOffsetError { struct_layout: (**s).clone(), - sized_fields, - last_unsized, + struct_type, field_name: s_field.field.name.clone(), field_index: i, actual_offset: s_field.rel_byte_offset, @@ -210,8 +219,8 @@ impl<'a> TryFrom<&'a TypeLayout> for TypeLayout s.clone(), + let element_ty = match u_ele.ty.get_layoutable_type().unwrap() { + LayoutableType::Sized(s) => s.clone(), _ => { unreachable!("elements of an array are always sized for TypeLayout") } @@ -254,34 +263,31 @@ impl<'a> TryFrom<&'a TypeLayout> for TypeLayout; - +/// Enum of possible errors during `TypeLayout -> TypeLayout` conversion. #[derive(thiserror::Error, Debug, Clone)] pub enum UniformLayoutError { #[error("{0}")] ArrayStride(WithContext), #[error("{0}")] - StructureFieldOffset(WithContext), - // TODO(chronicl) consider using CpuShareableType and implementing Display for it instead - // of converting to StoreType + StructureFieldOffset(WithContext), #[error( "The size of `{1}` on the gpu is not known at compile time. `{0}` \ requires that the size of uniform buffers on the gpu is known at compile time." )] - MustBeSized(&'static str, StoreType), + MustBeSized(&'static str, LayoutableType), } #[derive(Debug, Clone)] -pub enum Mismatch { +enum Mismatch { ArrayStride(ArrayStrideError), - StructureFieldOffset(StructureFieldOffsetError), + StructureFieldOffset(StructFieldOffsetError), } #[derive(Debug, Clone)] pub struct LayoutErrorContext { - s_layout: TypeLayout, - u_layout: TypeLayout, + s_layout: TypeLayout, + u_layout: TypeLayout, use_color: bool, } @@ -306,13 +312,13 @@ impl WithContext { pub struct ArrayStrideError { expected: u64, actual: u64, - element_ty: cs::SizedType, + element_ty: SizedType, } impl std::error::Error for WithContext {} impl Display for WithContext { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let top_level: StoreType = self.ctx.s_layout.cpu_shareable().clone().into(); + let top_level: StoreType = self.ctx.s_layout.layoutable_type().clone().into(); writeln!( f, "array elements within type `{}` do not satisfy uniform layout requirements.", @@ -336,12 +342,11 @@ impl Display for WithContext { } } - +#[allow(missing_docs)] #[derive(Debug, Clone)] -pub struct StructureFieldOffsetError { +pub struct StructFieldOffsetError { pub struct_layout: StructLayout, - pub sized_fields: Vec, - pub last_unsized: Option, + pub struct_type: StructKind, pub field_name: CanonName, pub field_index: usize, pub actual_offset: u64, @@ -349,11 +354,16 @@ pub struct StructureFieldOffsetError { pub is_top_level: bool, } -impl std::error::Error for WithContext {} -impl Display for WithContext { +#[derive(Debug, Clone)] +pub enum StructKind { + Sized(SizedStruct), + Unsized(UnsizedStruct), +} + +impl std::error::Error for WithContext {} +impl Display for WithContext { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - // TODO(chronicl) remove conversion here if CpuShareableType implements Display - let top_level_type: StoreType = self.ctx.s_layout.cpu_shareable().clone().into(); + let top_level_type = self.ctx.s_layout.layoutable_type(); writeln!( f, "The type `{top_level_type}` cannot be used as a uniform buffer binding." @@ -372,8 +382,7 @@ impl Display for WithContext { write_struct_layout( &self.struct_layout, - &self.sized_fields, - self.last_unsized.as_ref(), + &self.struct_type, self.ctx.use_color, Some(self.field_index), f, @@ -391,18 +400,18 @@ impl Display for WithContext { set_color(f, None, false)?; writeln!(f)?; - writeln!(f, "Potential solutions include:"); + writeln!(f, "Potential solutions include:")?; writeln!( f, "- add an #[align({})] attribute to the definition of `{}` (not supported by OpenGL/GLSL pipelines)", expected_alignment, self.field_name - ); - writeln!(f, "- use a storage binding instead of a uniform binding"); + )?; + writeln!(f, "- use a storage binding instead of a uniform binding")?; writeln!( f, "- increase the offset of `{}` until it is divisible by {} by making previous fields larger or adding fields before it", self.field_name, expected_alignment - ); + )?; writeln!(f)?; writeln!( @@ -416,8 +425,7 @@ impl Display for WithContext { /// Panics if s is not the layout of a struct and doesn't contain a cpu-shareable. fn write_struct_layout( struct_layout: &StructLayout, - sized_fields: &[cs::SizedField], - last_unsized: Option<&cs::RuntimeSizedArrayField>, + struct_type: &StructKind, colored: bool, highlight_field: Option, f: &mut F, @@ -435,10 +443,15 @@ where false => Ok(()), }; + let (sized_fields, last_unsized) = match struct_type { + StructKind::Sized(s) => (s.fields(), None), + StructKind::Unsized(s) => (s.sized_fields.as_slice(), Some(&s.last_unsized)), + }; + let struct_name = &*struct_layout.name; let indent = " "; - let field_decl_line = |field: &cs::SizedField| { + let field_decl_line = |field: &SizedField| { let sized: ir::SizedType = field.ty.clone().into(); format!("{indent}{}: {},", field.name, sized) }; @@ -459,7 +472,7 @@ where if Some(i) == highlight_field { color(f, "#508EE3")?; } - let (align, size) = (layout.ty.byte_align(), layout.ty.byte_size().expect("is sized field")); + let (align, size) = (layout.ty.align(), layout.ty.byte_size().expect("is sized field")); let decl_line = field_decl_line(field); f.write_str(&decl_line)?; // write spaces to table on the right @@ -481,7 +494,7 @@ where for _ in decl_line.len()..table_start_column { f.write_char(' ')? } - write!(f, "{:6} {:5}", layout.rel_byte_offset, layout.ty.byte_align().as_u32())?; + write!(f, "{:6} {:5}", layout.rel_byte_offset, layout.ty.align().as_u32())?; } writeln!(f, "}}")?; Ok(()) diff --git a/shame/src/frontend/rust_types/type_layout/eq.rs b/shame/src/frontend/rust_types/type_layout/eq.rs index bc43f74..c194666 100644 --- a/shame/src/frontend/rust_types/type_layout/eq.rs +++ b/shame/src/frontend/rust_types/type_layout/eq.rs @@ -4,7 +4,7 @@ use super::*; #[derive(Clone)] pub struct LayoutMismatch { /// 2 (name, layout) pairs - layouts: [(String, TypeLayout); 2], + layouts: [(String, TypeLayout); 2], colored_error: bool, } @@ -65,7 +65,7 @@ impl LayoutMismatch { #[allow(clippy::needless_return)] pub(crate) fn write( indent: &str, - layouts: [(&str, &TypeLayout); 2], + layouts: [(&str, &TypeLayout); 2], colored: bool, f: &mut W, ) -> Result { @@ -270,19 +270,19 @@ impl LayoutMismatch { } write!(f, "{:width$}{indent}}}", ' ', width = pad_width); - let align_matches = a.byte_align() == b.byte_align(); + let align_matches = a.align() == b.align(); let size_matches = a.byte_size() == b.byte_size(); if !align_matches && size_matches { writeln!(f); color_a(f); - writeln!(f, "{a_name}{SEP}{indent}align={}", a.byte_align().as_u32()); + writeln!(f, "{a_name}{SEP}{indent}align={}", a.align().as_u32()); color_b(f); - writeln!(f, "{b_name}{SEP}{indent}align={}", b.byte_align().as_u32()); + writeln!(f, "{b_name}{SEP}{indent}align={}", b.align().as_u32()); color_reset(f); return Err(MismatchWasFound); } else { match align_matches { - true => write!(f, " align={}", a.byte_align().as_u32()), + true => write!(f, " align={}", a.align().as_u32()), false => write!(f, " align=?"), }; } @@ -431,7 +431,7 @@ impl LayoutMismatch { /// /// if the two layouts are not equal it uses the debug names in the returned /// error to tell the two layouts apart. -pub(crate) fn check_eq( +pub(crate) fn check_eq( a: (&str, &TypeLayout), b: (&str, &TypeLayout), ) -> Result<(), LayoutMismatch> diff --git a/shame/src/frontend/rust_types/type_layout/cpu_shareable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs similarity index 81% rename from shame/src/frontend/rust_types/type_layout/cpu_shareable/align_size.rs rename to shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index 9f075d4..76b62dd 100644 --- a/shame/src/frontend/rust_types/type_layout/cpu_shareable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -1,28 +1,16 @@ -use super::super::LayoutCalculator; +use super::super::{LayoutCalculator, Repr}; use super::*; -// Size and align of host-shareable types // +// Size and align of layoutable types // // https://www.w3.org/TR/WGSL/#address-space-layout-constraints // -#[derive(Debug, Clone, Copy)] -pub enum Repr { - /// Wgsl storage address space layout / OpenGL std430 - /// https://www.w3.org/TR/WGSL/#address-space-layout-constraints - Storage, - /// Wgsl uniform address space layout / OpenGL std140 - /// https://www.w3.org/TR/WGSL/#address-space-layout-constraints - Uniform, - /// Packed layout. Vertex buffer only. - Packed, -} - impl SizedType { /// This is expensive for structs. Prefer `byte_size_and_align` if you also need the align. pub fn byte_size(&self, repr: Repr) -> u64 { match self { SizedType::Array(a) => a.byte_size(repr), SizedType::Vector(v) => v.byte_size(), - SizedType::Matrix(m) => m.byte_size(Major::Row), + SizedType::Matrix(m) => m.byte_size(MatrixMajor::Row), SizedType::Atomic(a) => a.byte_size(), SizedType::PackedVec(v) => u8::from(v.byte_size()) as u64, SizedType::Struct(s) => s.byte_size_and_align(repr).0, @@ -34,7 +22,7 @@ impl SizedType { match self { SizedType::Array(a) => a.align(repr), SizedType::Vector(v) => v.align(), - SizedType::Matrix(m) => m.align(repr, Major::Row), + SizedType::Matrix(m) => m.align(repr, MatrixMajor::Row), SizedType::Atomic(a) => a.align(), SizedType::PackedVec(v) => v.align(), SizedType::Struct(s) => s.byte_size_and_align(repr).1, @@ -52,6 +40,10 @@ impl SizedType { impl SizedStruct { + /// Returns [`FieldOffsets`], which serves as an iterator over the offsets of the + /// fields of this struct, as well as a `byte_size` and `align` calculator. + /// See the documentation of `FieldOffsets` on how to obtain `byte_size` and `align` + /// of this struct from it. pub fn field_offsets(&self, repr: Repr) -> FieldOffsets { FieldOffsets { fields: &self.fields, @@ -74,10 +66,13 @@ impl SizedStruct { } } -/// An iterator over the field offsets of a `SizedStruct`. +/// An iterator over the field offsets of a `SizedStruct` or the sized fields of an `UnsizedStruct`. /// /// `FieldOffsets::byte_size` and `FieldOffsets::byte_align` can be used to query the struct's /// `byte_size` and `byte_align`, but only takes into account the fields that have been iterated over. +/// +/// Use [`UnsizedStruct::last_field_offset_and_struct_align`] to obtain the last field's offset +/// and the struct's align for `UnsizedStruct`s. pub struct FieldOffsets<'a> { fields: &'a [SizedField], field_index: usize, @@ -125,6 +120,10 @@ impl FieldOffsets<'_> { impl UnsizedStruct { + /// Returns [`FieldOffsets`], which serves as an iterator over the offsets of the + /// sized fields of this struct. `FieldOffsets` may be also passed to + /// [`UnsizedStruct::last_field_offset_and_struct_align`] to obtain the last field's offset + /// and the struct's align. pub fn sized_field_offsets(&self, repr: Repr) -> FieldOffsets { FieldOffsets { fields: &self.sized_fields, @@ -164,6 +163,7 @@ const fn struct_align(align: U32PowerOf2, repr: Repr) -> U32PowerOf2 { } } +#[allow(missing_docs)] impl Vector { pub const fn new(scalar: ScalarType, len: Len) -> Self { Self { scalar, len } } @@ -180,6 +180,7 @@ impl Vector { } } +#[allow(missing_docs)] impl ScalarType { pub const fn byte_size(&self) -> u64 { match self { @@ -198,28 +199,30 @@ impl ScalarType { } } +#[allow(missing_docs)] #[derive(Debug, Clone, Copy)] -pub enum Major { +pub enum MatrixMajor { Row, Column, } +#[allow(missing_docs)] impl Matrix { - pub const fn byte_size(&self, major: Major) -> u64 { + pub const fn byte_size(&self, major: MatrixMajor) -> u64 { let (vec, array_len) = self.as_vector_array(major); let array_stride = array_stride(vec.align(), vec.byte_size()); array_size(array_stride, array_len) } - pub const fn align(&self, repr: Repr, major: Major) -> U32PowerOf2 { + pub const fn align(&self, repr: Repr, major: MatrixMajor) -> U32PowerOf2 { let (vec, _) = self.as_vector_array(major); array_align(vec.align(), repr) } - const fn as_vector_array(&self, major: Major) -> (Vector, NonZeroU32) { + const fn as_vector_array(&self, major: MatrixMajor) -> (Vector, NonZeroU32) { let (vec_len, array_len): (Len, NonZeroU32) = match major { - Major::Row => (self.rows.as_len(), self.columns.as_non_zero_u32()), - Major::Column => (self.columns.as_len(), self.rows.as_non_zero_u32()), + MatrixMajor::Row => (self.rows.as_len(), self.columns.as_non_zero_u32()), + MatrixMajor::Column => (self.columns.as_len(), self.rows.as_non_zero_u32()), }; ( Vector { @@ -231,11 +234,13 @@ impl Matrix { } } +#[allow(missing_docs)] impl Atomic { pub const fn byte_size(&self) -> u64 { self.scalar.as_scalar_type().byte_size() } pub const fn align(&self) -> U32PowerOf2 { self.scalar.as_scalar_type().align() } } +#[allow(missing_docs)] impl SizedArray { pub fn byte_size(&self, repr: Repr) -> u64 { array_size(self.byte_stride(repr), self.len) } @@ -247,33 +252,41 @@ impl SizedArray { } } +/// Returns an array's size given it's stride and length. +/// +/// Note, this is independent of layout rules (`Repr`). pub const fn array_size(array_stride: u64, len: NonZeroU32) -> u64 { array_stride * len.get() as u64 } -pub const fn array_align(element_align: U32PowerOf2, layout: Repr) -> U32PowerOf2 { - match layout { +/// Returns an array's size given the alignment of it's elements. +pub const fn array_align(element_align: U32PowerOf2, repr: Repr) -> U32PowerOf2 { + match repr { // Packedness is ensured by the `LayoutCalculator`. Repr::Storage | Repr::Packed => element_align, Repr::Uniform => U32PowerOf2::try_from_u32(round_up(16, element_align.as_u64()) as u32).unwrap(), } } +/// Returns an array's size given the alignment and size of it's elements. pub const fn array_stride(element_align: U32PowerOf2, element_size: u64) -> u64 { // Arrays of element type T must have an element stride that is a multiple of the // RequiredAlignOf(T, C) for the address space C: round_up(element_align.as_u64(), element_size) } +#[allow(missing_docs)] impl RuntimeSizedArray { pub fn byte_align(&self, repr: Repr) -> U32PowerOf2 { array_align(self.element.byte_align(repr), repr) } pub fn byte_stride(&self, repr: Repr) -> u64 { array_stride(self.byte_align(repr), self.element.byte_size(repr)) } } +#[allow(missing_docs)] impl SizedField { pub fn byte_size(&self, repr: Repr) -> u64 { self.ty.byte_size(repr) } pub fn byte_align(&self, repr: Repr) -> U32PowerOf2 { self.ty.byte_align(repr) } } +#[allow(missing_docs)] impl RuntimeSizedArrayField { pub fn byte_align(&self, repr: Repr) -> U32PowerOf2 { self.array.byte_align(repr) } } diff --git a/shame/src/frontend/rust_types/type_layout/cpu_shareable/builder.rs b/shame/src/frontend/rust_types/type_layout/layoutable/builder.rs similarity index 70% rename from shame/src/frontend/rust_types/type_layout/cpu_shareable/builder.rs rename to shame/src/frontend/rust_types/type_layout/layoutable/builder.rs index c60e3fc..17b67ec 100644 --- a/shame/src/frontend/rust_types/type_layout/cpu_shareable/builder.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/builder.rs @@ -1,9 +1,15 @@ use super::*; -impl LayoutType { +impl LayoutableType { + /// Fallibly creates a new `LayoutableType` of a struct. + /// + /// An error is returned if the following rules aren't followed: + /// - There must be at least one field. + /// - None of the fields must be an `UnsizedStruct`. + /// - Only the last field may be unsized (a runtime sized array). pub fn struct_from_parts( struct_name: impl Into, - fields: impl IntoIterator, + fields: impl IntoIterator, ) -> Result { use StructFromPartsError::*; @@ -16,13 +22,13 @@ impl LayoutType { .into_iter() .map(|(options, ty)| { Ok(match ty { - LayoutType::Sized(s) => Field::Sized(SizedField::new(options, s)), - LayoutType::RuntimeSizedArray(a) => Field::Unsized(RuntimeSizedArrayField::new( + LayoutableType::Sized(s) => Field::Sized(SizedField::new(options, s)), + LayoutableType::RuntimeSizedArray(a) => Field::Unsized(RuntimeSizedArrayField::new( options.name, options.custom_min_align, a.element, )), - LayoutType::UnsizedStruct(_) => return Err(MustNotHaveUnsizedStructField), + LayoutableType::UnsizedStruct(_) => return Err(MustNotHaveUnsizedStructField), }) }) .peekable(); @@ -60,6 +66,7 @@ impl LayoutType { } } +#[allow(missing_docs)] #[derive(thiserror::Error, Debug)] pub enum StructFromPartsError { #[error("Struct must have at least one field.")] @@ -81,13 +88,6 @@ impl SizedStruct { } } - pub fn from_parts(name: impl Into, fields: Vec) -> Self { - Self { - name: name.into(), - fields, - } - } - /// Adds a sized field to the struct. pub fn extend(mut self, field_options: impl Into, ty: impl Into) -> Self { self.fields.push(SizedField::new(field_options, ty)); @@ -111,9 +111,9 @@ impl SizedStruct { /// Adds either a `SizedType` or a `RuntimeSizedArray` field to the struct. /// - /// Returns a `HostshareableType`, because the `Self` may either stay + /// Returns a `LayoutableType`, because the `Self` may either stay /// a `SizedStruct` or become an `UnsizedStruct` depending on the field's type. - pub fn extend_sized_or_array(self, field_options: impl Into, field: SizedOrArray) -> LayoutType { + pub fn extend_sized_or_array(self, field_options: impl Into, field: SizedOrArray) -> LayoutableType { let options = field_options.into(); match field { SizedOrArray::Sized(ty) => self.extend(options, ty).into(), @@ -123,24 +123,37 @@ impl SizedStruct { } } - pub fn new_sized_or_array( - struct_name: impl Into, - field_options: impl Into, - field: SizedOrArray, - ) -> LayoutType { - let options = field_options.into(); - match field { - SizedOrArray::Sized(ty) => Self::new(struct_name, options, ty).into(), - SizedOrArray::RuntimeSizedArray(a) => UnsizedStruct { - name: struct_name.into(), - sized_fields: Vec::new(), - last_unsized: RuntimeSizedArrayField::new(options.name, options.custom_min_align, a.element), - } - .into(), + /// The fields of this struct. + pub fn fields(&self) -> &[SizedField] { &self.fields } + + pub(crate) fn from_parts(name: impl Into, fields: Vec) -> Self { + Self { + name: name.into(), + fields, } } +} - pub fn fields(&self) -> &[SizedField] { &self.fields } +#[allow(missing_docs)] +pub enum SizedOrArray { + Sized(SizedType), + RuntimeSizedArray(RuntimeSizedArray), +} + +#[allow(missing_docs)] +#[derive(thiserror::Error, Debug)] +#[error("`LayoutType` is `UnsizedStruct`, which is not a variant of `SizedOrArray`")] +pub struct IsUnsizedStructError; +impl TryFrom for SizedOrArray { + type Error = IsUnsizedStructError; + + fn try_from(value: LayoutableType) -> Result { + match value { + LayoutableType::Sized(sized) => Ok(SizedOrArray::Sized(sized)), + LayoutableType::RuntimeSizedArray(array) => Ok(SizedOrArray::RuntimeSizedArray(array)), + LayoutableType::UnsizedStruct(_) => Err(IsUnsizedStructError), + } + } } impl> From<(T, SizedType)> for SizedField { diff --git a/shame/src/frontend/rust_types/type_layout/cpu_shareable/mod.rs b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs similarity index 64% rename from shame/src/frontend/rust_types/type_layout/cpu_shareable/mod.rs rename to shame/src/frontend/rust_types/type_layout/layoutable/mod.rs index 42f6a5e..4a28875 100644 --- a/shame/src/frontend/rust_types/type_layout/cpu_shareable/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs @@ -1,37 +1,38 @@ -#![allow(missing_docs)] +//! This module defines types that can be laid out in memory. + use std::{num::NonZeroU32, rc::Rc}; use crate::{ any::U32PowerOf2, ir::{self, ir_type::BufferBlockDefinitionError, StructureFieldNamesMustBeUnique}, - GpuAligned, GpuSized, GpuStore, GpuType, NoBools, NoHandles, }; -// TODO(chronicl) -// - Consider moving this module into ir_type? -// - We borrow these types from `StoreType` currently. Maybe it would be better the other -// way around - `StoreType` should borrow from `HostShareableType`. -pub use crate::ir::{Len, Len2, LenEven, PackedVector, ScalarTypeFp, ScalarTypeInteger, ir_type::CanonName}; -use super::{constraint, FieldOptions}; +pub use crate::ir::{Len, Len2, PackedVector, ScalarTypeFp, ScalarTypeInteger, ir_type::CanonName}; +use super::FieldOptions; -mod align_size; -mod builder; +pub(crate) mod align_size; +pub(crate) mod builder; -pub use align_size::*; -pub use builder::*; +pub use align_size::{FieldOffsets, MatrixMajor, array_size, array_stride, array_align}; +pub use builder::SizedOrArray; -// TODO(chronicl) rewrite -/// This reprsents a wgsl spec compliant host-shareable type with the addition -/// that f64 is a supported scalar type. +/// Types that have a defined memory layout. /// -/// https://www.w3.org/TR/WGSL/#host-shareable-types +/// `LayoutableType` does not contain any layout information itself, but a layout +/// can be assigned to it using [`TypeLayout`] according to one of the available layout rules: +/// storage, uniform or packed. #[derive(Debug, Clone)] -pub enum LayoutType { +pub enum LayoutableType { + /// A type with a statically known size. Sized(SizedType), + /// A struct with a runtime sized array as it's last field. UnsizedStruct(UnsizedStruct), + /// An array whose size is determined at runtime. RuntimeSizedArray(RuntimeSizedArray), } +/// Types with a statically known size. +#[allow(missing_docs)] #[derive(Debug, Clone)] pub enum SizedType { Vector(Vector), @@ -42,12 +43,14 @@ pub enum SizedType { Struct(SizedStruct), } +#[allow(missing_docs)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Vector { pub scalar: ScalarType, pub len: Len, } +#[allow(missing_docs)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Matrix { pub scalar: ScalarTypeFp, @@ -55,23 +58,30 @@ pub struct Matrix { pub rows: Len2, } +#[allow(missing_docs)] #[derive(Debug, Clone)] pub struct SizedArray { pub element: Rc, pub len: NonZeroU32, } +#[allow(missing_docs)] #[derive(Debug, Clone, Copy)] pub struct Atomic { pub scalar: ScalarTypeInteger, } +#[allow(missing_docs)] #[derive(Debug, Clone)] pub struct RuntimeSizedArray { pub element: SizedType, } -/// Same as `ir::ScalarType`, but without `ScalarType::Bool`. +/// Scalar types with known memory layout. +/// +/// Same as `ir::ScalarType`, but without `ScalarType::Bool` since booleans +/// don't have a standardized memory representation. +#[allow(missing_docs)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum ScalarType { F16, @@ -81,20 +91,29 @@ pub enum ScalarType { F64, } +/// A struct with a known fixed size. #[derive(Debug, Clone)] pub struct SizedStruct { + /// The canonical name of the struct. pub name: CanonName, // This is private to ensure a `SizedStruct` always has at least one field. fields: Vec, } +/// A struct whose size is not known at compile time. +/// +/// This struct has a runtime sized array as it's last field. #[derive(Debug, Clone)] pub struct UnsizedStruct { + /// The canonical name of the struct. pub name: CanonName, + /// Fixed-size fields that come before the unsized field pub sized_fields: Vec, + /// Last runtime sized array field of the struct. pub last_unsized: RuntimeSizedArrayField, } +#[allow(missing_docs)] #[derive(Debug, Clone)] pub struct SizedField { pub name: CanonName, @@ -103,6 +122,7 @@ pub struct SizedField { pub ty: SizedType, } +#[allow(missing_docs)] #[derive(Debug, Clone)] pub struct RuntimeSizedArrayField { pub name: CanonName, @@ -111,6 +131,7 @@ pub struct RuntimeSizedArrayField { } impl SizedField { + /// Creates a new `SizedField`. pub fn new(options: impl Into, ty: impl Into) -> Self { let options = options.into(); Self { @@ -123,6 +144,8 @@ impl SizedField { } impl RuntimeSizedArrayField { + /// Creates a new `RuntimeSizedArrayField` given it's field name, + /// an optional custom minimum align and it's element type. pub fn new( name: impl Into, custom_min_align: Option, @@ -139,6 +162,7 @@ impl RuntimeSizedArrayField { } impl SizedArray { + /// Creates a new `RuntimeSizedArray` from it's element type and length. pub fn new(element_ty: impl Into, len: NonZeroU32) -> Self { Self { element: Rc::new(element_ty.into()), @@ -148,6 +172,7 @@ impl SizedArray { } impl RuntimeSizedArray { + /// Creates a new `RuntimeSizedArray` from it's element type. pub fn new(element_ty: impl Into) -> Self { RuntimeSizedArray { element: element_ty.into(), @@ -155,32 +180,15 @@ impl RuntimeSizedArray { } } -pub enum SizedOrArray { - Sized(SizedType), - RuntimeSizedArray(RuntimeSizedArray), -} - -#[derive(thiserror::Error, Debug)] -#[error("`LayoutType` is `UnsizedStruct`, which is not a variant of `SizedOrArray`")] -pub struct IsUnsizedStruct; -impl TryFrom for SizedOrArray { - type Error = IsUnsizedStruct; - - fn try_from(value: LayoutType) -> Result { - match value { - LayoutType::Sized(sized) => Ok(SizedOrArray::Sized(sized)), - LayoutType::RuntimeSizedArray(array) => Ok(SizedOrArray::RuntimeSizedArray(array)), - LayoutType::UnsizedStruct(_) => Err(IsUnsizedStruct), - } - } -} - -// TODO(chronicl) documentation -pub trait BinaryRepr { - fn layout_type() -> LayoutType; +/// Trait for types that have a well-defined memory layout. +pub trait Layoutable { + /// Returns the `LayoutableType` representation for this type. + fn layoutable_type() -> LayoutableType; } -pub trait BinaryReprSized: BinaryRepr { - fn layout_type_sized() -> SizedType; +/// Trait for types that have a well-defined memory layout and statically known size. +pub trait LayoutableSized: Layoutable { + /// Returns the `SizedType` representation for this type. + fn layoutable_type_sized() -> SizedType; } @@ -196,7 +204,7 @@ impl std::fmt::Display for ScalarType { } } -// Conversions to ScalarType, SizedType and HostshareableType // +// Conversions to ScalarType, SizedType and LayoutableType // macro_rules! impl_into_sized_type { ($($ty:ident -> $variant:path),*) => { @@ -204,9 +212,9 @@ macro_rules! impl_into_sized_type { impl $ty { /// Const conversion to [`SizedType`] pub const fn into_sized_type(self) -> SizedType { $variant(self) } - /// Const conversion to [`HostshareableType`] - pub const fn into_cpu_shareable(self) -> LayoutType { - LayoutType::Sized(self.into_sized_type()) + /// Const conversion to [`LayoutableType`] + pub const fn into_layoutable_type(self) -> LayoutableType { + LayoutableType::Sized(self.into_sized_type()) } } @@ -225,18 +233,18 @@ impl_into_sized_type!( PackedVector -> SizedType::PackedVec ); -impl From for LayoutType +impl From for LayoutableType where SizedType: From, { - fn from(value: T) -> Self { LayoutType::Sized(SizedType::from(value)) } + fn from(value: T) -> Self { LayoutableType::Sized(SizedType::from(value)) } } -impl From for LayoutType { - fn from(s: UnsizedStruct) -> Self { LayoutType::UnsizedStruct(s) } +impl From for LayoutableType { + fn from(s: UnsizedStruct) -> Self { LayoutableType::UnsizedStruct(s) } } -impl From for LayoutType { - fn from(a: RuntimeSizedArray) -> Self { LayoutType::RuntimeSizedArray(a) } +impl From for LayoutableType { + fn from(a: RuntimeSizedArray) -> Self { LayoutableType::RuntimeSizedArray(a) } } impl ScalarTypeInteger { @@ -266,12 +274,12 @@ impl From for ScalarType { // Conversions to ir types // -impl From for ir::StoreType { - fn from(host: LayoutType) -> Self { +impl From for ir::StoreType { + fn from(host: LayoutableType) -> Self { match host { - LayoutType::Sized(s) => ir::StoreType::Sized(s.into()), - LayoutType::RuntimeSizedArray(s) => ir::StoreType::RuntimeSizedArray(s.element.into()), - LayoutType::UnsizedStruct(s) => ir::StoreType::BufferBlock(s.into()), + LayoutableType::Sized(s) => ir::StoreType::Sized(s.into()), + LayoutableType::RuntimeSizedArray(s) => ir::StoreType::RuntimeSizedArray(s.element.into()), + LayoutableType::UnsizedStruct(s) => ir::StoreType::BufferBlock(s.into()), } } } @@ -359,12 +367,13 @@ impl From for ir::ir_type::RuntimeSizedArrayField { // Conversions from ir types // +/// Type contains bools, which doesn't have a known layout. #[derive(thiserror::Error, Debug)] -#[error("Type contains bools, which isn't cpu shareable.")] -pub struct ContainsBools; +#[error("Type contains bools, which doesn't have a known layout.")] +pub struct ContainsBoolsError; impl TryFrom for ScalarType { - type Error = ContainsBools; + type Error = ContainsBoolsError; fn try_from(value: ir::ScalarType) -> Result { Ok(match value { @@ -373,18 +382,13 @@ impl TryFrom for ScalarType { ir::ScalarType::F64 => ScalarType::F64, ir::ScalarType::U32 => ScalarType::U32, ir::ScalarType::I32 => ScalarType::I32, - ir::ScalarType::Bool => return Err(ContainsBools), + ir::ScalarType::Bool => return Err(ContainsBoolsError), }) } } -impl ir::ScalarType { - // TODO(chronicl) remove - pub fn as_host_shareable_unchecked(self) -> ScalarType { self.try_into().unwrap() } -} - impl TryFrom for SizedType { - type Error = ContainsBools; + type Error = ContainsBoolsError; fn try_from(value: ir::SizedType) -> Result { Ok(match value { @@ -404,7 +408,7 @@ impl TryFrom for SizedType { } impl TryFrom for SizedStruct { - type Error = ContainsBools; + type Error = ContainsBoolsError; fn try_from(structure: ir::ir_type::SizedStruct) -> Result { let mut fields = Vec::new(); @@ -425,35 +429,38 @@ impl TryFrom for SizedStruct { } } +/// Errors that can occur when converting IR types to layoutable types. #[derive(thiserror::Error, Debug)] -pub enum CpuShareableConversionError { - #[error("Type contains bools, which isn't cpu shareable.")] +pub enum LayoutableConversionError { + /// Type contains bools, which don't have a standardized memory layout. + #[error("Type contains bools, which don't have a standardized memory layout.")] ContainsBool, - #[error("Type is a handle, which isn't cpu shareable.")] + /// Type is a handle, which don't have a standardized memory layout. + #[error("Type is a handle, which don't have a standardized memory layout.")] IsHandle, } -impl From for CpuShareableConversionError { - fn from(_: ContainsBools) -> Self { Self::ContainsBool } +impl From for LayoutableConversionError { + fn from(_: ContainsBoolsError) -> Self { Self::ContainsBool } } -impl TryFrom for LayoutType { - type Error = CpuShareableConversionError; +impl TryFrom for LayoutableType { + type Error = LayoutableConversionError; fn try_from(value: ir::StoreType) -> Result { Ok(match value { - ir::StoreType::Sized(sized_type) => LayoutType::Sized(sized_type.try_into()?), - ir::StoreType::RuntimeSizedArray(element) => LayoutType::RuntimeSizedArray(RuntimeSizedArray { + ir::StoreType::Sized(sized_type) => LayoutableType::Sized(sized_type.try_into()?), + ir::StoreType::RuntimeSizedArray(element) => LayoutableType::RuntimeSizedArray(RuntimeSizedArray { element: element.try_into()?, }), ir::StoreType::BufferBlock(buffer_block) => buffer_block.try_into()?, - ir::StoreType::Handle(_) => return Err(CpuShareableConversionError::IsHandle), + ir::StoreType::Handle(_) => return Err(LayoutableConversionError::IsHandle), }) } } -impl TryFrom for LayoutType { - type Error = ContainsBools; +impl TryFrom for LayoutableType { + type Error = ContainsBoolsError; fn try_from(buffer_block: ir::ir_type::BufferBlock) -> Result { let mut sized_fields = Vec::new(); @@ -491,3 +498,37 @@ impl TryFrom for LayoutType { .into()) } } + +// Display impls + +impl std::fmt::Display for LayoutableType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + LayoutableType::Sized(s) => write!(f, "{s}"), + LayoutableType::RuntimeSizedArray(a) => write!(f, "Array<{}>", a.element), + LayoutableType::UnsizedStruct(s) => write!(f, "{}", s.name), + } + } +} + +impl std::fmt::Display for SizedType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + SizedType::Vector(v) => write!(f, "{}{}", ir::ScalarType::from(v.scalar), v.len), + SizedType::Matrix(m) => { + write!( + f, + "mat<{}, {}, {}>", + ir::ScalarType::from(m.scalar), + Len::from(m.columns), + Len::from(m.rows) + ) + } + SizedType::Array(a) => write!(f, "Array<{}, {}>", &*a.element, a.len), + SizedType::Atomic(a) => write!(f, "Atomic<{}>", ir::ScalarType::from(a.scalar)), + // TODO(chronicl) figure out scalar type display + SizedType::PackedVec(p) => write!(f, "PackedVec<{:?}, {}>", p.scalar_type, Len::from(p.len)), + SizedType::Struct(s) => write!(f, "{}", s.name), + } + } +} diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index 8c49ad0..9acbe90 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -1,8 +1,5 @@ -#![allow(missing_docs)] // TODO(chronicl) remove //! Everything related to type layouts. - - use std::{ fmt::{Debug, Display, Write}, hash::Hash, @@ -16,25 +13,20 @@ use crate::{ common::{ignore_eq::IgnoreInEqOrdHash, prettify::set_color}, ir::{ self, - ir_type::{round_up, CanonName, ScalarTypeFp}, + ir_type::{round_up, CanonName}, recording::Context, - Len, SizedType, Type, + Len, }, }; -use cpu_shareable::{LayoutType, Matrix, Vector}; -use thiserror::Error; +use layoutable::{LayoutableType, Matrix, Vector}; -mod builder; -pub mod cpu_shareable; -mod eq; - -pub use builder::*; -pub(crate) use eq::*; +pub(crate) mod builder; +pub(crate) mod eq; +pub(crate) mod layoutable; /// The type contained in the bytes of a `TypeLayout`. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum TypeLayoutSemantics { - // TODO(chronicl) consider replacing this scalar type with the host shareable version. /// `vec` Vector(Vector), /// special compressed vectors for vertex attribute types @@ -54,45 +46,37 @@ pub enum TypeLayoutSemantics { /// This models only the layout, not other characteristics of the types. /// For example an `Atomic>` is treated like a regular `vec` layout wise. /// -/// ### Constraint generic +/// ### Generic +/// +/// `TypeLayout` has a generic `T: TypeRepr`, which is used to statically guarantee that +/// it follows specific layout rules. +/// +/// The following types implementing `TypeRepr` exist and can be found in [`shame::any::repr`]: /// -/// `TypeLayout` has a generic `T: TypeConstraint`, which guarantees that the layout -/// follows certain layout rules. The following layout rules exist and are captured by -/// the [`Repr`] enum: /// ``` -/// pub enum Repr { -/// Storage, /// wgsl storage address space layout / OpenGL std430 -/// Uniform, /// wgsl uniform address space layout / OpenGL std140 -/// Packed, /// packed layout, vertex buffer only -/// } +/// struct Plain; /// May not follow any layout rules. This is the default. +/// struct Storage; /// wgsl storage address space layout / OpenGL std430 +/// struct Uniform; /// wgsl uniform address space layout / OpenGL std140 +/// struct Packed; /// Packed layout /// ``` /// /// More information on the exact details of these layout rules is available here /// /// https://www.w3.org/TR/WGSL/#address-space-layout-constraints /// -/// The following types implementing `TypeConstraint` exist and can be found in [`constraint`]: -/// +/// The following methods exist for creating new type layouts based on a [`LayoutableType`] /// ``` -/// struct Plain; /// May not follow any layout rules. -/// struct Storage; /// Follows `Repr::Storage` layout rules. -/// struct Uniform; /// Follows `Repr::Uniform` layout rules. -/// struct Vertex; /// Follows either `Repr::Packed` or both -/// /// `Repr::Storage` and `Repr::Uniform`. +/// let layout_type: LayoutableType = f32x1::layoutable_type(); +/// let _ = TypeLayout::new_layout_for(layout_type); +/// let _ = TypeLayout::new_storage_layout_for(layout_type); +/// let _ = TypeLayout::new_uniform_layout_for(layout_type); +/// let _ = TypeLayout::new_packed_layout_for(layout_type); /// ``` -/// `Vertex` also guarantees that it is either the layout of a [`VertexAttribute`] or the layout -/// of a struct with fields that are all `VertexAttribute`s, which is why `Repr::Storage` -/// and `Repr::Uniform` are equivalent for it. -/// -/// `Storage` and `Uniform` are always based on a [`CpuShareableType`], which can be accessed -/// using [`TypeLayout::cpu_shareable`]. They also guarantee, that all nested `TypeLayout`s -/// (for example struct fields) are also based on a `CpuShareableType`, which can only be accessed -/// through `TypeLayout::get_cpu_shareable`. /// -/// TODO(chronicl) make sure these are caught: -/// There is some target language specific constraints that `TypeLayout` does not capture: -/// - `TypeLayout` may be unsized, which is not allowed for wgsl. -/// - `custom_min_align` and `custom_min_size` are not supported for glsl. +/// Non-`Plain` layouts are always based on a [`LayoutableType`], which can be accessed +/// using [`TypeLayout::layoutable_type`]. They also guarantee, that all nested `TypeLayout`s +/// (for example struct fields) are also based on a `LayoutableType`, which can only be accessed +/// through `TypeLayout::get_layoutable_type`. /// /// ### Layout comparison /// @@ -100,18 +84,17 @@ pub enum TypeLayoutSemantics { /// "do these two types have the same layout" so that uploading a type to the gpu /// will result in no memory errors. /// -/// A layout comparison looks like this: +/// a layout comparison looks like this: /// ``` -/// assert!(f32::cpu_layout() == vec::gpu_layout().into_unconstraint()); +/// assert!(f32::cpu_layout() == vec::gpu_layout()); /// // or, more explicitly /// assert_eq!( /// ::cpu_layout(), /// as GpuLayout>::gpu_layout(), /// ); /// ``` -/// #[derive(Clone)] -pub struct TypeLayout { +pub struct TypeLayout { /// size in bytes (Some), or unsized (None) pub byte_size: Option, /// the byte alignment @@ -122,46 +105,57 @@ pub struct TypeLayout { pub kind: TypeLayoutSemantics, /// Is some for `constraint::Storage` and `constraint::Uniform`. Can be converted to a `StoreType`. - pub(crate) cpu_shareable: Option, + pub(crate) layoutable_type: Option, _phantom: PhantomData, } // PartialEq, Eq, Hash for TypeLayout -impl PartialEq> for TypeLayout { +impl PartialEq> for TypeLayout { fn eq(&self, other: &TypeLayout) -> bool { self.byte_size() == other.byte_size() && self.kind == other.kind } } -impl Eq for TypeLayout {} -impl Hash for TypeLayout { +impl Eq for TypeLayout {} +impl Hash for TypeLayout { fn hash(&self, state: &mut H) { self.byte_size.hash(state); self.kind.hash(state); } } -use constraint::TypeConstraint; +pub use repr::{TypeRepr, Repr}; /// Module for all restrictions on `TypeLayout`. -pub mod constraint { +pub mod repr { use super::*; - /// Type restriction of a `TypeLayout`. This allows type layouts - /// to have guarantees based on their type restriction. For example a - /// `TypeLayout can be used in vertex buffers. For more - /// details see the documentation of [`TypeLayout`]. - pub trait TypeConstraint: Clone + PartialEq + Eq {} + /// Type representation used by `TypeLayout`. This provides guarantees + /// about the alignment rules that the type layout adheres to. + /// See [`TypeLayout`] documentation for more details. + pub trait TypeRepr: Clone + PartialEq + Eq {} + + /// Enum of layout rules. + #[derive(Debug, Clone, Copy)] + pub enum Repr { + /// Wgsl storage address space layout / OpenGL std430 + /// https://www.w3.org/TR/WGSL/#address-space-layout-constraints + Storage, + /// Wgsl uniform address space layout / OpenGL std140 + /// https://www.w3.org/TR/WGSL/#address-space-layout-constraints + Uniform, + /// Packed layout. Vertex buffer only. + Packed, + } - macro_rules! type_restriction { + macro_rules! type_repr { ($($constraint:ident),*) => { $( - /// A type restriction for `TypeLayout`. - /// See [`TypeRestriction`] documentation for TODO(chronicl) + /// A type representation used by `TypeLayout`. + /// See [`TypeLayout`] documentation for more details. #[derive(Clone, PartialEq, Eq, Hash)] pub struct $constraint; - impl TypeConstraint for $constraint {} + impl TypeRepr for $constraint {} )* }; } - - type_restriction!(Plain, Storage, Uniform, Packed); + type_repr!(Plain, Storage, Uniform, Packed); macro_rules! impl_from_into { ($from:ident -> $($into:ident),*) => { @@ -183,13 +177,13 @@ impl TypeLayout { byte_size: Option, byte_align: U32PowerOf2, kind: TypeLayoutSemantics, - hostshareable: Option, + layoutable_type: Option, ) -> Self { TypeLayout { byte_size, align: byte_align, kind, - cpu_shareable: hostshareable, + layoutable_type, _phantom: PhantomData, } } @@ -201,22 +195,22 @@ impl TypeLayout { pub(in super::super::rust_types) mod type_layout_internal { use super::*; - pub fn cast_unchecked(layout: TypeLayout) -> TypeLayout { + pub fn cast_unchecked(layout: TypeLayout) -> TypeLayout { TypeLayout { byte_size: layout.byte_size, align: layout.align, kind: layout.kind, - cpu_shareable: layout.cpu_shareable, + layoutable_type: layout.layoutable_type, _phantom: PhantomData, } } } -/// `LayoutCalculator` helps calculate the size, align and the field offsets of a gpu struct. +/// `LayoutCalculator` helps calculate the size, align and the field offsets of a struct. /// /// If `LayoutCalculator` is created with `packed = true`, provided `field_align`s -/// are ignored and the field is directly after the previous field. However, -/// a `custom_min_align` that is `Some` overwrites the `packedness` of the field. +/// are ignored and the field is inserted directly after the previous field. However, +/// a `custom_min_align` that is `Some` overwrites the "packedness" of the field. #[derive(Debug, Clone)] pub struct LayoutCalculator { next_offset_min: u64, @@ -321,10 +315,11 @@ impl LayoutCalculator { } /// a sized or unsized struct type with 0 or more fields -#[allow(missing_docs)] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct StructLayout { + /// The canonical name of the structure type, ignored in equality/hash comparisons pub name: IgnoreInEqOrdHash, + /// The fields of the structure with their memory offsets pub fields: Vec, } @@ -338,8 +333,10 @@ impl StructLayout { #[allow(missing_docs)] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct FieldLayoutWithOffset { + /// The layout information for the field pub field: FieldLayout, - pub rel_byte_offset: u64, // this being relative is used in TypeLayout::byte_size + /// The relative byte offset of this field from the start of its containing structure + pub rel_byte_offset: u64, } impl std::ops::Deref for FieldLayoutWithOffset { @@ -352,56 +349,32 @@ impl std::ops::Deref for FieldLayoutWithOffset { pub struct ElementLayout { /// Stride of the elements pub byte_stride: u64, - /// The elment layout must be constraint::Plain, because it's shared by all constraints. - // `ElementLayout` could possibly be made generic too, but it would complicate a lot. - pub ty: TypeLayout, -} - -/// the layout rules used when calculating the byte offsets and alignment of a type -#[derive(Debug, Clone, Copy)] -pub enum TypeLayoutRules { - /// wgsl type layout rules, see https://www.w3.org/TR/WGSL/#memory-layouts - Wgsl, - // reprC, - // Std140, - // Std430, - // Scalar, -} - -#[allow(missing_docs)] -#[derive(Error, Debug, Clone)] -pub enum TypeLayoutError { - #[error("An array cannot contain elements of an unsized type {elements}")] - ArrayOfUnsizedElements { elements: TypeLayout }, - #[error("the type `{0}` has no defined {1:?} layout in shaders")] - LayoutUndefined(Type, TypeLayoutRules), - #[error("in `{parent_name}` at field `{field_name}`: {error}")] - AtField { - parent_name: CanonName, - field_name: CanonName, - error: Rc, - }, - #[error("in element of array: {0}")] - InArrayElement(Rc), + /// The type layout of each element in the array. + /// + /// The element layout must be constraint::Plain because it's shared by all constraints. + /// `ElementLayout` could possibly be made generic too, but it would complicate a lot. + pub ty: TypeLayout, } -impl TypeLayout { - /// Byte size of the represented type. +impl TypeLayout { + /// Returns the byte size of the represented type. + /// + /// For sized types, this returns Some(size), while for unsized types + /// (like runtime-sized arrays), this returns None. pub fn byte_size(&self) -> Option { self.byte_size } - /// Align of the represented type. - pub fn byte_align(&self) -> U32PowerOf2 { self.align } + /// Returns the alignment requirement of the represented type. + pub fn align(&self) -> U32PowerOf2 { self.align } /// Although all TypeLayout always implement Into, this method /// is offered to avoid having to declare that as a bound when handling generic TypeLayout. - pub fn into_plain(self) -> TypeLayout { type_layout_internal::cast_unchecked(self) } + pub fn into_plain(self) -> TypeLayout { type_layout_internal::cast_unchecked(self) } - /// Get the `CpuShareableType` this layout is based on. + /// Get the `LayoutableType` this layout is based on. /// - /// This may be `None` for type layouts - /// that aren't `TypeLayout` or `TypeLayout`. Those two also offer direct - /// access via [`TypeLayout::cpu_shareable`]. - pub fn get_cpu_shareable(&self) -> Option<&LayoutType> { self.cpu_shareable.as_ref() } + /// This may be `None` for `TypeLayout`. + /// `TypeLayout` may use [`TypeLayout::layoutable_type`]. + pub fn get_layoutable_type(&self) -> Option<&LayoutableType> { self.layoutable_type.as_ref() } /// a short name for this `TypeLayout`, useful for printing inline pub fn short_name(&self) -> String { @@ -502,8 +475,8 @@ impl TypeLayout { } // TODO(chronicl) this should be removed with improved any api for storage/uniform bindings - pub(crate) fn from_store_ty(store_type: ir::StoreType) -> Result { - let t: cpu_shareable::LayoutType = store_type.try_into()?; + pub(crate) fn from_store_ty(store_type: ir::StoreType) -> Result { + let t: layoutable::LayoutableType = store_type.try_into()?; Ok(TypeLayout::new_storage_layout_for(t).into()) } } @@ -517,24 +490,10 @@ pub struct FieldLayout { pub custom_min_align: IgnoreInEqOrdHash>, /// The fields layout must be constraint::Plain, /// because that's the most it can be while supporting all possible constraints. - pub ty: TypeLayout, + pub ty: TypeLayout, } impl FieldLayout { - pub fn new( - name: CanonName, - custom_min_size: Option, - custom_min_align: Option, - ty: TypeLayout, - ) -> Self { - Self { - name, - custom_min_size: custom_min_size.into(), - custom_min_align: custom_min_align.into(), - ty, - } - } - fn byte_size(&self) -> Option { self.ty .byte_size() @@ -542,7 +501,7 @@ impl FieldLayout { } /// The alignment of the field with `custom_min_align` taken into account. - fn byte_align(&self) -> U32PowerOf2 { Self::calculate_align(self.ty.byte_align(), self.custom_min_align.0) } + fn byte_align(&self) -> U32PowerOf2 { Self::calculate_align(self.ty.align(), self.custom_min_align.0) } const fn calculate_byte_size(byte_size: u64, custom_min_size: Option) -> u64 { // const byte_size.max(custom_min_size.unwrap_or(0)) @@ -555,7 +514,7 @@ impl FieldLayout { } const fn calculate_align(align: U32PowerOf2, custom_min_align: Option) -> U32PowerOf2 { - // const align.max(custom_min_align.unwrap_or(U32PowerOf2::_1) as u32 as u64) + // const align.max(custom_min_align.unwrap_or(U32PowerOf2::_1)) if let Some(min_align) = custom_min_align { align.max(min_align) } else { @@ -602,13 +561,13 @@ impl> From for FieldOptions { fn from(name: T) -> Self { Self::new(name, None, None) } } -impl Display for TypeLayout { +impl Display for TypeLayout { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let colored = Context::try_with(call_info!(), |ctx| ctx.settings().colored_error_messages).unwrap_or(false); self.write("", colored, f) } } -impl Debug for TypeLayout { +impl Debug for TypeLayout { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.write("", false, f) } } diff --git a/shame/src/frontend/rust_types/type_traits.rs b/shame/src/frontend/rust_types/type_traits.rs index 78ff297..4ba3e2d 100644 --- a/shame/src/frontend/rust_types/type_traits.rs +++ b/shame/src/frontend/rust_types/type_traits.rs @@ -8,7 +8,6 @@ use super::{ }; use crate::{ frontend::any::shared_io::{BindPath, BindingType}, - cpu_shareable::{LayoutType}, TypeLayout, }; use crate::{ diff --git a/shame/src/frontend/rust_types/vec.rs b/shame/src/frontend/rust_types/vec.rs index b25587e..df5cf8f 100644 --- a/shame/src/frontend/rust_types/vec.rs +++ b/shame/src/frontend/rust_types/vec.rs @@ -7,20 +7,16 @@ use super::{ mem::AddressSpace, reference::{AccessMode, AccessModeReadable}, scalar_type::{dtype_as_scalar_from_f64, ScalarType, ScalarTypeInteger, ScalarTypeNumber}, - type_layout::{ - cpu_shareable::{self, BinaryReprSized}, - TypeLayoutRules, - }, type_traits::{BindingArgs, GpuAligned, GpuStoreImplCategory, NoAtomics, NoHandles, VertexAttribute}, AsAny, GpuType, To, ToGpuType, }; use crate::{ + any::layout::{self, Layoutable, LayoutableSized}, call_info, common::{ proc_macro_utils::{collect_into_array_exact, push_wrong_amount_of_args_error}, small_vec::SmallVec, }, - cpu_shareable::BinaryRepr, frontend::encoding::{ buffer::{BufferAddressSpace, BufferInner, BufferRefInner}, rasterizer::Gradient, @@ -577,26 +573,26 @@ impl GpuStore for vec { } -impl BinaryReprSized for vec +impl LayoutableSized for vec where vec: NoBools, { - fn layout_type_sized() -> cpu_shareable::SizedType { - cpu_shareable::Vector::new(T::SCALAR_TYPE.try_into().expect("no bools"), L::LEN).into() + fn layoutable_type_sized() -> layout::SizedType { + layout::Vector::new(T::SCALAR_TYPE.try_into().expect("no bools"), L::LEN).into() } } -impl BinaryRepr for vec +impl Layoutable for vec where vec: NoBools, { - fn layout_type() -> cpu_shareable::LayoutType { Self::layout_type_sized().into() } + fn layoutable_type() -> layout::LayoutableType { Self::layoutable_type_sized().into() } } impl GpuLayout for vec where vec: NoBools, { - fn gpu_repr() -> cpu_shareable::Repr { cpu_shareable::Repr::Storage } + fn gpu_repr() -> layout::Repr { layout::Repr::Storage } fn cpu_type_name_and_layout() -> Option, TypeLayout), super::layout_traits::ArrayElementsUnsizedError>> @@ -1119,7 +1115,7 @@ where Self: NoBools, { fn vertex_attrib_format() -> VertexAttribFormat { - VertexAttribFormat::Fine(L::LEN, T::SCALAR_TYPE.as_host_shareable_unchecked()) + VertexAttribFormat::Fine(L::LEN, T::SCALAR_TYPE.try_into().expect("no bools vec")) } } diff --git a/shame/src/ir/ir_type/layout_constraints.rs b/shame/src/ir/ir_type/layout_constraints.rs index 5af9ed9..f2e2182 100644 --- a/shame/src/ir/ir_type/layout_constraints.rs +++ b/shame/src/ir/ir_type/layout_constraints.rs @@ -6,14 +6,12 @@ use std::{ use thiserror::Error; -use crate::{common::proc_macro_reexports::TypeLayoutRules, frontend::rust_types::type_layout::TypeLayout}; +use crate::frontend::rust_types::type_layout::{eq::LayoutMismatch, TypeLayout}; use crate::{ backend::language::Language, call_info, common::prettify::set_color, - frontend::{ - any::shared_io::BufferBindingType, encoding::EncodingErrorKind, rust_types::type_layout::LayoutMismatch, - }, + frontend::{any::shared_io::BufferBindingType, encoding::EncodingErrorKind}, ir::{ ir_type::{max_u64_po2_dividing, AccessModeReadable}, recording::{Context, MemoryRegion}, diff --git a/shame/src/ir/ir_type/tensor.rs b/shame/src/ir/ir_type/tensor.rs index 64e7282..e5ded39 100644 --- a/shame/src/ir/ir_type/tensor.rs +++ b/shame/src/ir/ir_type/tensor.rs @@ -479,6 +479,7 @@ pub struct PackedVector { } /// exhaustive list of all byte sizes a `packed_vec` can have +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum PackedVectorByteSize { _2, _4, diff --git a/shame/src/ir/ir_type/ty.rs b/shame/src/ir/ir_type/ty.rs index ead22d6..5647974 100644 --- a/shame/src/ir/ir_type/ty.rs +++ b/shame/src/ir/ir_type/ty.rs @@ -1,6 +1,6 @@ use self::struct_::SizedStruct; use crate::{ - frontend::{any::shared_io, rust_types::type_layout::TypeLayoutRules}, + frontend::{any::shared_io}, ir::recording::MemoryRegion, }; diff --git a/shame/src/ir/pipeline/wip_pipeline.rs b/shame/src/ir/pipeline/wip_pipeline.rs index 57e21fd..10bf448 100644 --- a/shame/src/ir/pipeline/wip_pipeline.rs +++ b/shame/src/ir/pipeline/wip_pipeline.rs @@ -16,7 +16,6 @@ use crate::{ po2::U32PowerOf2, pool::{Key, PoolRef}, }, - cpu_shareable, frontend::{ any::{ render_io::{Attrib, ColorTarget, Location, VertexBufferLayout}, @@ -33,7 +32,7 @@ use crate::{ error::InternalError, rust_types::{ len::x3, - type_layout::{StructLayout, TypeLayoutRules}, + type_layout::{self, layoutable, StructLayout}, }, }, ir::{ @@ -44,7 +43,6 @@ use crate::{ StructureFieldNamesMustBeUnique, TextureFormatWrapper, Type, }, results::DepthStencilState, - type_layout::TypeLayoutSemantics, BindingIter, DepthLhs, StencilMasking, Test, TypeLayout, }; @@ -354,12 +352,12 @@ impl WipPushConstantsField { let byte_size = sized_struct.byte_size(); // TODO(release) the `.expect()` calls here can be removed by building a `std::alloc::Layout`-like builder for struct layouts. - let sized_struct: cpu_shareable::SizedStruct = sized_struct + let sized_struct: layoutable::SizedStruct = sized_struct .try_into() .expect("push constants are NoBools and NoHandles"); let layout = TypeLayout::new_storage_layout_for(sized_struct); let layout = match &layout.kind { - TypeLayoutSemantics::Structure(layout) => &**layout, + type_layout::TypeLayoutSemantics::Structure(layout) => &**layout, _ => unreachable!("expected struct layout for type layout of struct"), }; diff --git a/shame/src/lib.rs b/shame/src/lib.rs index b54475b..84bdcbd 100644 --- a/shame/src/lib.rs +++ b/shame/src/lib.rs @@ -316,11 +316,7 @@ pub use shame_derive::CpuLayout; pub use shame_derive::GpuLayout; pub use frontend::rust_types::layout_traits::GpuLayout; pub use frontend::rust_types::layout_traits::CpuLayout; -pub use frontend::rust_types::type_layout; pub use frontend::rust_types::type_layout::TypeLayout; -pub use frontend::rust_types::type_layout::TypeLayoutError; -pub use frontend::rust_types::type_layout::cpu_shareable; -pub use frontend::rust_types::layout_traits::ArrayElementsUnsizedError; // derived traits pub use frontend::rust_types::type_traits::GpuStore; @@ -465,6 +461,61 @@ pub mod any { pub use crate::ir::ir_type::StructureDefinitionError; pub use crate::ir::ir_type::StructureFieldNamesMustBeUnique; + pub mod layout { + // type layout + pub use crate::frontend::rust_types::type_layout::TypeLayout; + pub use crate::frontend::rust_types::type_layout::TypeRepr; + pub use crate::frontend::rust_types::type_layout::Repr; + pub mod repr { + pub use crate::frontend::rust_types::type_layout::repr::Plain; + pub use crate::frontend::rust_types::type_layout::repr::Storage; + pub use crate::frontend::rust_types::type_layout::repr::Uniform; + pub use crate::frontend::rust_types::type_layout::repr::Packed; + } + pub use crate::frontend::rust_types::type_layout::TypeLayoutSemantics; + pub use crate::frontend::rust_types::type_layout::StructLayout; + pub use crate::frontend::rust_types::type_layout::FieldLayoutWithOffset; + pub use crate::frontend::rust_types::type_layout::FieldLayout; + pub use crate::frontend::rust_types::type_layout::FieldOptions; + pub use crate::frontend::rust_types::type_layout::ElementLayout; + // layoutable traits + pub use crate::frontend::rust_types::type_layout::layoutable::Layoutable; + pub use crate::frontend::rust_types::type_layout::layoutable::LayoutableSized; + // layoutable types + pub use crate::frontend::rust_types::type_layout::layoutable::LayoutableType; + pub use crate::frontend::rust_types::type_layout::layoutable::UnsizedStruct; + pub use crate::frontend::rust_types::type_layout::layoutable::RuntimeSizedArray; + pub use crate::frontend::rust_types::type_layout::layoutable::SizedType; + pub use crate::frontend::rust_types::type_layout::layoutable::Vector; + pub use crate::frontend::rust_types::type_layout::layoutable::Matrix; + pub use crate::frontend::rust_types::type_layout::layoutable::MatrixMajor; + pub use crate::frontend::rust_types::type_layout::layoutable::SizedArray; + pub use crate::frontend::rust_types::type_layout::layoutable::Atomic; + pub use crate::frontend::rust_types::type_layout::layoutable::PackedVector; + pub use crate::frontend::rust_types::type_layout::layoutable::SizedStruct; + // layoutable type parts + pub use crate::frontend::rust_types::type_layout::layoutable::ScalarType; + pub use crate::frontend::rust_types::type_layout::layoutable::ScalarTypeFp; + pub use crate::frontend::rust_types::type_layout::layoutable::ScalarTypeInteger; + pub use crate::frontend::rust_types::type_layout::layoutable::Len; + pub use crate::frontend::rust_types::type_layout::layoutable::Len2; + pub use crate::frontend::rust_types::type_layout::layoutable::SizedField; + pub use crate::frontend::rust_types::type_layout::layoutable::RuntimeSizedArrayField; + pub use crate::frontend::rust_types::type_layout::layoutable::CanonName; + pub use crate::frontend::rust_types::type_layout::layoutable::SizedOrArray; + // layout calculation utility + pub use crate::frontend::rust_types::type_layout::LayoutCalculator; + pub use crate::frontend::rust_types::type_layout::layoutable::array_stride; + pub use crate::frontend::rust_types::type_layout::layoutable::array_size; + pub use crate::frontend::rust_types::type_layout::layoutable::array_align; + pub use crate::frontend::rust_types::type_layout::layoutable::FieldOffsets; + // conversion and builder errors + pub use crate::frontend::rust_types::type_layout::layoutable::LayoutableConversionError; + pub use crate::frontend::rust_types::type_layout::layoutable::ContainsBoolsError; + pub use crate::frontend::rust_types::type_layout::layoutable::builder::IsUnsizedStructError; + pub use crate::frontend::rust_types::type_layout::layoutable::builder::StructFromPartsError; + } + // runtime binding api pub use any::shared_io::BindPath; pub use any::shared_io::BindingType; diff --git a/shame/tests/test_layout.rs b/shame/tests/test_layout.rs index 855d8a9..0b5cb9f 100644 --- a/shame/tests/test_layout.rs +++ b/shame/tests/test_layout.rs @@ -248,9 +248,9 @@ fn unsized_struct_vec3_align_layout_eq() { } assert_ne!(OnGpu::gpu_layout(), OnCpu::cpu_layout()); assert!(OnGpu::gpu_layout().byte_size() == Some(16)); - assert!(OnGpu::gpu_layout().byte_align().as_u32() == 16); + assert!(OnGpu::gpu_layout().align().as_u32() == 16); assert!(OnCpu::cpu_layout().byte_size() == Some(12)); - assert!(OnCpu::cpu_layout().byte_align().as_u32() == 4); + assert!(OnCpu::cpu_layout().align().as_u32() == 4); } #[test] diff --git a/shame_derive/src/derive_layout.rs b/shame_derive/src/derive_layout.rs index 45a1446..64a795e 100644 --- a/shame_derive/src/derive_layout.rs +++ b/shame_derive/src/derive_layout.rs @@ -200,8 +200,8 @@ pub fn impl_for_struct( match which_derive { WhichDerive::GpuLayout => { - let layout_type_fn = quote! { - let result = #re::LayoutType::struct_from_parts( + let layoutable_type_fn = quote! { + let result = #re::LayoutableType::struct_from_parts( std::stringify!(#derive_struct_ident), [ #(( @@ -210,13 +210,13 @@ pub fn impl_for_struct( #field_align.map(|align: u32| TryFrom::try_from(align).expect("power of two validated during codegen")).into(), #field_size.into(), ), - <#field_type as #re::BinaryRepr>::layout_type() + <#field_type as #re::Layoutable>::layoutable_type() ),)* ] ); match result { - Ok(layout_type) => layout_type, + Ok(layoutable_type) => layoutable_type, Err(#re::StructFromPartsError::MustHaveAtLeastOneField) => unreachable!("checked above"), Err(#re::StructFromPartsError::OnlyLastFieldMayBeUnsized) => unreachable!("ensured by field trait bounds"), // GpuType is not implemented for derived structs directly, so they can't be used @@ -226,28 +226,28 @@ pub fn impl_for_struct( } }; - let impl_binary_repr = quote! { - impl<#generics_decl> #re::BinaryRepr for #derive_struct_ident<#(#idents_of_generics),*> + let impl_layoutable = quote! { + impl<#generics_decl> #re::Layoutable for #derive_struct_ident<#(#idents_of_generics),*> where - // These NoBools and NoHandle bounds are only for better diagnostics, BinaryRepr already implies them - #(#first_fields_type: #re::NoBools + #re::NoHandles + #re::BinaryReprSized,)* - #last_field_type: #re::NoBools + #re::NoHandles + #re::BinaryRepr, + // These NoBools and NoHandle bounds are only for better diagnostics, Layoutable already implies them + #(#first_fields_type: #re::NoBools + #re::NoHandles + #re::LayoutableSized,)* + #last_field_type: #re::NoBools + #re::NoHandles + #re::Layoutable, #where_clause_predicates { - fn layout_type() -> #re::LayoutType { - #layout_type_fn + fn layoutable_type() -> #re::LayoutableType { + #layoutable_type_fn } } - impl<#generics_decl> #re::BinaryReprSized for #derive_struct_ident<#(#idents_of_generics),*> + impl<#generics_decl> #re::LayoutableSized for #derive_struct_ident<#(#idents_of_generics),*> where - #(#field_type: #re::NoBools + #re::NoHandles + #triv #re::BinaryReprSized,)* + #(#field_type: #re::NoBools + #re::NoHandles + #triv #re::LayoutableSized,)* #where_clause_predicates { - fn layout_type_sized() -> #re::SizedType { - match { #layout_type_fn } { - #re::LayoutType::Sized(s) => s, - _ => unreachable!("ensured by BinaryReprSized field trait bounds above") + fn layoutable_type_sized() -> #re::SizedType { + match { #layoutable_type_fn } { + #re::LayoutableType::Sized(s) => s, + _ => unreachable!("ensured by LayoutableSized field trait bounds above") } } } @@ -256,8 +256,8 @@ pub fn impl_for_struct( let impl_gpu_layout = quote! { impl<#generics_decl> #re::GpuLayout for #derive_struct_ident<#(#idents_of_generics),*> where - #(#first_fields_type: #re::BinaryReprSized,)* - #last_field_type: #re::BinaryRepr, + #(#first_fields_type: #re::LayoutableSized,)* + #last_field_type: #re::Layoutable, #where_clause_predicates { fn gpu_repr() -> #re::Repr { @@ -365,7 +365,7 @@ pub fn impl_for_struct( // this is basically only for vertex buffers, so // we only implement `GpuLayout` and `VertexLayout`, as well as their implied traits Ok(quote! { - #impl_binary_repr + #impl_layoutable #impl_gpu_layout #impl_vertex_buffer_layout #impl_fake_auto_traits @@ -380,7 +380,7 @@ pub fn impl_for_struct( ); Ok(quote! { - #impl_binary_repr + #impl_layoutable #impl_gpu_layout #impl_vertex_buffer_layout #impl_fake_auto_traits From 8420a3c374b6839b975b1ae59fc8209ffc1cec69 Mon Sep 17 00:00:00 2001 From: chronicl Date: Fri, 23 May 2025 03:39:00 +0200 Subject: [PATCH 07/32] Small import change --- shame/src/frontend/any/render_io.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/shame/src/frontend/any/render_io.rs b/shame/src/frontend/any/render_io.rs index 4b267f1..93e817d 100644 --- a/shame/src/frontend/any/render_io.rs +++ b/shame/src/frontend/any/render_io.rs @@ -2,9 +2,8 @@ use std::{fmt::Display, rc::Rc}; use thiserror::Error; -use crate::any::layout; use crate::frontend::any::Any; -use crate::frontend::rust_types::type_layout::TypeLayout; +use crate::frontend::rust_types::type_layout::{layoutable, TypeLayout}; use crate::{ call_info, common::iterator_ext::try_collect, @@ -109,7 +108,7 @@ impl Any { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum VertexAttribFormat { /// regular [`crate::vec`] types - Fine(Len, layout::ScalarType), + Fine(Len, layoutable::ScalarType), /// packed [`crate::packed::PackedVec`] types Coarse(PackedVector), } @@ -252,7 +251,7 @@ impl Attrib { ) -> Option<(Box<[Attrib]>, u64)> { let stride = { let size = layout.byte_size()?; - layout::array_stride(layout.align(), size) + layoutable::array_stride(layout.align(), size) }; use TypeLayoutSemantics as TLS; From 6ce7e49ad51c4a768ab40a0bc1f0adec500bff0f Mon Sep 17 00:00:00 2001 From: chronicl Date: Fri, 23 May 2025 03:43:18 +0200 Subject: [PATCH 08/32] Fix example name --- examples/type_layout/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/type_layout/Cargo.toml b/examples/type_layout/Cargo.toml index 9d53846..dd64124 100644 --- a/examples/type_layout/Cargo.toml +++ b/examples/type_layout/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "type_layout2" +name = "type_layout" version = "0.1.0" edition = "2024" From c5b9088be4e4cc8ffe0bd2f758db696596838150 Mon Sep 17 00:00:00 2001 From: chronicl Date: Fri, 23 May 2025 04:14:27 +0200 Subject: [PATCH 09/32] PackedVector to ir type conversion --- .../src/frontend/rust_types/type_layout/layoutable/mod.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs index 4a28875..230e2b6 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs @@ -291,8 +291,12 @@ impl From for ir::SizedType { SizedType::Matrix(m) => ir::SizedType::Matrix(m.columns, m.rows, m.scalar), SizedType::Array(a) => ir::SizedType::Array(Rc::new(Rc::unwrap_or_clone(a.element).into()), a.len), SizedType::Atomic(i) => ir::SizedType::Atomic(i.scalar), - // TODO(chronicl) check if this should be decompressed - SizedType::PackedVec(v) => v.decompressed_ty(), + SizedType::PackedVec(v) => SizedType::Vector(match v.byte_size() { + ir::ir_type::PackedVectorByteSize::_2 => Vector::new(ScalarType::F16, Len::X1), + ir::ir_type::PackedVectorByteSize::_4 => Vector::new(ScalarType::U32, Len::X1), + ir::ir_type::PackedVectorByteSize::_8 => Vector::new(ScalarType::U32, Len::X2), + }) + .into(), SizedType::Struct(s) => ir::SizedType::Structure(s.into()), } } From bdcf1573e41dd0f54679cd96453155b0db08c67c Mon Sep 17 00:00:00 2001 From: chronicl Date: Fri, 23 May 2025 15:19:43 +0200 Subject: [PATCH 10/32] Remove alignment rounding duplication --- .../rust_types/type_layout/layoutable/align_size.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index 76b62dd..868f7fa 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -92,14 +92,12 @@ impl Iterator for FieldOffsets<'_> { match self.repr { // Packedness is ensured by the `LayoutCalculator`. Repr::Storage | Repr::Packed => (size, align), - // https://www.w3.org/TR/WGSL/#address-space-layout-constraints // The uniform address space requires that: - // - struct S align: roundUp(16, AlignOf(S)) // - If a structure member itself has a structure type S, then the number of // bytes between the start of that member and the start of any following // member must be at least roundUp(16, SizeOf(S)). - // -> We adjust size too. - Repr::Uniform => (round_up(16, size), round_up_align(U32PowerOf2::_16, align)), + // -> We need to adjust size. + Repr::Uniform => (round_up(16, size), align), } } non_struct => non_struct.byte_size_and_align(self.repr), From f3690d03c05320ce4768c8e4b841e158f80d0d53 Mon Sep 17 00:00:00 2001 From: chronicl Date: Fri, 23 May 2025 17:38:29 +0200 Subject: [PATCH 11/32] Add byte_size and align for LayoutableType --- .../rust_types/type_layout/builder.rs | 2 +- .../type_layout/layoutable/align_size.rs | 53 +++++++++++++++---- 2 files changed, 43 insertions(+), 12 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/builder.rs b/shame/src/frontend/rust_types/type_layout/builder.rs index a3bfd53..ab7b1f0 100644 --- a/shame/src/frontend/rust_types/type_layout/builder.rs +++ b/shame/src/frontend/rust_types/type_layout/builder.rs @@ -141,7 +141,7 @@ impl TypeLayout { fn from_runtime_sized_array(ty: RuntimeSizedArray, repr: Repr) -> Self { Self::new( None, - ty.byte_align(repr), + ty.align(repr), TLS::Array( Rc::new(ElementLayout { byte_stride: ty.byte_stride(repr), diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index 868f7fa..97683e9 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -4,6 +4,37 @@ use super::*; // Size and align of layoutable types // // https://www.w3.org/TR/WGSL/#address-space-layout-constraints // +impl LayoutableType { + /// This is expensive for structs. Prefer `byte_size_and_align` if you also need the align. + pub fn byte_size(&self, repr: Repr) -> Option { + match self { + LayoutableType::Sized(s) => Some(s.byte_size(repr)), + LayoutableType::UnsizedStruct(_) | LayoutableType::RuntimeSizedArray(_) => None, + } + } + + /// This is expensive for structs. Prefer `byte_size_and_align` if you also need the align. + pub fn align(&self, repr: Repr) -> U32PowerOf2 { + match self { + LayoutableType::Sized(s) => s.align(repr), + LayoutableType::UnsizedStruct(s) => s.align(repr), + LayoutableType::RuntimeSizedArray(a) => a.align(repr), + } + } + + /// This is expensive for structs as it calculates the byte size and align from scratch. + pub fn byte_size_and_align(&self, repr: Repr) -> (Option, U32PowerOf2) { + match self { + LayoutableType::Sized(s) => { + let (size, align) = s.byte_size_and_align(repr); + (Some(size), align) + } + LayoutableType::UnsizedStruct(s) => (None, s.align(repr)), + LayoutableType::RuntimeSizedArray(a) => (None, a.align(repr)), + } + } +} + impl SizedType { /// This is expensive for structs. Prefer `byte_size_and_align` if you also need the align. pub fn byte_size(&self, repr: Repr) -> u64 { @@ -17,8 +48,8 @@ impl SizedType { } } - /// This is expensive for structs. Prefer `byte_size_and_align` if you also need the size. - pub fn byte_align(&self, repr: Repr) -> U32PowerOf2 { + /// This is expensive for structs. Prefer `byte_size_and_align` if you also need the align. + pub fn align(&self, repr: Repr) -> U32PowerOf2 { match self { SizedType::Array(a) => a.align(repr), SizedType::Vector(v) => v.align(), @@ -29,11 +60,11 @@ impl SizedType { } } - /// This is expensive for structs. + /// This is expensive for structs as it calculates the byte size and align from scratch. pub fn byte_size_and_align(&self, repr: Repr) -> (u64, U32PowerOf2) { match self { SizedType::Struct(s) => s.byte_size_and_align(repr), - non_struct => (non_struct.byte_size(repr), non_struct.byte_align(repr)), + non_struct => (non_struct.byte_size(repr), non_struct.align(repr)), } } } @@ -140,14 +171,14 @@ impl UnsizedStruct { // Iterating over any remaining field offsets to update the layout calculator. (&mut offsets).count(); - let array_align = self.last_unsized.array.byte_align(offsets.repr); + let array_align = self.last_unsized.array.align(offsets.repr); let custom_min_align = self.last_unsized.custom_min_align; let (offset, align) = offsets.calc.extend_unsized(array_align, custom_min_align); (offset, struct_align(align, offsets.repr)) } /// This is expensive as it calculates the byte align from scratch. - pub fn byte_align(&self, repr: Repr) -> U32PowerOf2 { + pub fn align(&self, repr: Repr) -> U32PowerOf2 { let offsets = self.sized_field_offsets(repr); self.last_field_offset_and_struct_align(offsets).1 } @@ -242,7 +273,7 @@ impl Atomic { impl SizedArray { pub fn byte_size(&self, repr: Repr) -> u64 { array_size(self.byte_stride(repr), self.len) } - pub fn align(&self, repr: Repr) -> U32PowerOf2 { array_align(self.element.byte_align(repr), repr) } + pub fn align(&self, repr: Repr) -> U32PowerOf2 { array_align(self.element.align(repr), repr) } pub fn byte_stride(&self, repr: Repr) -> u64 { let (element_size, element_align) = self.element.byte_size_and_align(repr); @@ -273,20 +304,20 @@ pub const fn array_stride(element_align: U32PowerOf2, element_size: u64) -> u64 #[allow(missing_docs)] impl RuntimeSizedArray { - pub fn byte_align(&self, repr: Repr) -> U32PowerOf2 { array_align(self.element.byte_align(repr), repr) } + pub fn align(&self, repr: Repr) -> U32PowerOf2 { array_align(self.element.align(repr), repr) } - pub fn byte_stride(&self, repr: Repr) -> u64 { array_stride(self.byte_align(repr), self.element.byte_size(repr)) } + pub fn byte_stride(&self, repr: Repr) -> u64 { array_stride(self.align(repr), self.element.byte_size(repr)) } } #[allow(missing_docs)] impl SizedField { pub fn byte_size(&self, repr: Repr) -> u64 { self.ty.byte_size(repr) } - pub fn byte_align(&self, repr: Repr) -> U32PowerOf2 { self.ty.byte_align(repr) } + pub fn byte_align(&self, repr: Repr) -> U32PowerOf2 { self.ty.align(repr) } } #[allow(missing_docs)] impl RuntimeSizedArrayField { - pub fn byte_align(&self, repr: Repr) -> U32PowerOf2 { self.array.byte_align(repr) } + pub fn byte_align(&self, repr: Repr) -> U32PowerOf2 { self.array.align(repr) } } pub const fn round_up(multiple_of: u64, n: u64) -> u64 { From e255e1b364521d54a75e3490cd910672fb162080 Mon Sep 17 00:00:00 2001 From: chronicl Date: Fri, 23 May 2025 22:12:20 +0200 Subject: [PATCH 12/32] Make default use_color false --- shame/src/frontend/rust_types/type_layout/builder.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shame/src/frontend/rust_types/type_layout/builder.rs b/shame/src/frontend/rust_types/type_layout/builder.rs index ab7b1f0..4ee2898 100644 --- a/shame/src/frontend/rust_types/type_layout/builder.rs +++ b/shame/src/frontend/rust_types/type_layout/builder.rs @@ -249,7 +249,7 @@ impl<'a> TryFrom<&'a TypeLayout> for TypeLayout { u_layout, // TODO(chronicl) default shouldn't be true? use_color: Context::try_with(call_info!(), |ctx| ctx.settings().colored_error_messages) - .unwrap_or(true), + .unwrap_or(false), }; use UniformLayoutError as U; From 3f1dcf7c15cab6ee9e32ebf7b54843a69091a13b Mon Sep 17 00:00:00 2001 From: chronicl Date: Fri, 23 May 2025 22:24:52 +0200 Subject: [PATCH 13/32] Decouple gpu_layout from GpuLayout --- examples/api_showcase/src/main.rs | 37 +++++----- .../hello_triangles/src/util/shame_glam.rs | 8 +- examples/type_layout/src/main.rs | 9 ++- .../src/frontend/rust_types/layout_traits.rs | 54 ++++++++------ shame/src/frontend/rust_types/packed_vec.rs | 4 +- shame/src/lib.rs | 2 + shame/tests/test_layout.rs | 73 ++++++++++--------- 7 files changed, 100 insertions(+), 87 deletions(-) diff --git a/examples/api_showcase/src/main.rs b/examples/api_showcase/src/main.rs index f8e2d88..0d25789 100644 --- a/examples/api_showcase/src/main.rs +++ b/examples/api_showcase/src/main.rs @@ -1,15 +1,16 @@ #![allow(unused, clippy::no_effect)] +use shame::gpu_layout; use shame as sm; use shame::prelude::*; use shame::aliases::*; #[rustfmt::skip] fn make_pipeline(some_param: u32) -> Result { - + // start a pipeline encoding with the default settings. // (in the `shame_wgpu` examples, this is wrapped by the `Gpu` object) - // - // compared to earlier versions of `shame`, pipeline + // + // compared to earlier versions of `shame`, pipeline // encoding is no longer based on a closure, but based on a // RAII guard `sm::EncodingGuard<...>` instead. // That way you can use the `?` operator for your own @@ -60,7 +61,7 @@ fn make_pipeline(some_param: u32) -> Result Result Result = group0.next(); let xforms_uni: sm::Buffer = group0.next(); - + // conditional code generation based on pipeline parameter if some_param > 0 { // if not further specified, defaults to `sm::mem::Storage` @@ -99,7 +100,7 @@ fn make_pipeline(some_param: u32) -> Result Result Result Result Result Result Result().set_with_alpha_to_coverage(rg); + // targets.next::().set_with_alpha_to_coverage(rg); // finish the encoding and obtain the pipeline setup info + shader code. encoder.finish() @@ -486,13 +487,13 @@ struct Mat2([[f32; 2]; 2]); // tell `shame` about the layout semantics of your cpu types // Mat2::layout() == sm::f32x2x2::layout() impl sm::CpuLayout for Mat2 { - fn cpu_layout() -> sm::TypeLayout { sm::f32x2x2::gpu_layout() } + fn cpu_layout() -> sm::TypeLayout { gpu_layout::() } } #[repr(C, align(16))] struct Mat4([[f32; 4]; 4]); impl sm::CpuLayout for Mat4 { - fn cpu_layout() -> sm::TypeLayout { sm::f32x4x4::gpu_layout() } + fn cpu_layout() -> sm::TypeLayout { gpu_layout::() } } // using "duck-traiting" allows you to define layouts for foreign cpu-types, @@ -509,4 +510,4 @@ impl sm::CpuLayout for Mat4 { // // tell `shame` about the layout semantics of `glam` types // impl MyCpuLayoutTrait for glam::Mat4 { // fn layout() -> shame::TypeLayout { sm::f32x4x4::layout() } -// } \ No newline at end of file +// } diff --git a/examples/hello_triangles/src/util/shame_glam.rs b/examples/hello_triangles/src/util/shame_glam.rs index 7f85092..faebf6c 100644 --- a/examples/hello_triangles/src/util/shame_glam.rs +++ b/examples/hello_triangles/src/util/shame_glam.rs @@ -2,7 +2,7 @@ //! //! implement `shame::CpuLayout` for some glam types. -use shame as sm; +use shame::{self as sm, gpu_layout}; use sm::GpuLayout; /// circumventing the orphan rule by defining our own trait. @@ -14,14 +14,14 @@ pub trait CpuLayoutExt { // glam::Vec4 matches sm::f32x4 in size and alignment impl CpuLayoutExt for glam::Vec4 { - fn cpu_layout() -> sm::TypeLayout { sm::f32x4::gpu_layout() } + fn cpu_layout() -> sm::TypeLayout { gpu_layout::() } } // glam::Vec2 only matches sm::f32x2 if it has 8 byte alignment impl CpuLayoutExt for glam::Vec2 { fn cpu_layout() -> sm::TypeLayout { if align_of::() == 8 { - sm::f32x2::gpu_layout() + gpu_layout::() } else { panic!("glam needs to use the `cuda` crate feature for Vec2 to be 8 byte aligned"); } @@ -30,5 +30,5 @@ impl CpuLayoutExt for glam::Vec2 { // glam::Mat4 matches sm::f32x4x4 in size and alignment impl CpuLayoutExt for glam::Mat4 { - fn cpu_layout() -> sm::TypeLayout { sm::f32x4x4::gpu_layout() } + fn cpu_layout() -> sm::TypeLayout { gpu_layout::() } } diff --git a/examples/type_layout/src/main.rs b/examples/type_layout/src/main.rs index 56bfbc0..11b99aa 100644 --- a/examples/type_layout/src/main.rs +++ b/examples/type_layout/src/main.rs @@ -6,12 +6,13 @@ use shame::{ any::{ self, layout::{ - self, LayoutableSized, UnsizedStruct, SizedField, SizedType, ScalarType, Len, RuntimeSizedArrayField, - FieldOptions, Vector, + self, FieldOptions, LayoutableSized, Len, RuntimeSizedArrayField, ScalarType, SizedField, SizedType, + UnsizedStruct, Vector, }, U32PowerOf2, }, - boolx1, f32x1, f32x2, f32x3, f32x4, Array, GpuLayout, GpuSized, TypeLayout, VertexAttribute, VertexLayout, + boolx1, f32x1, f32x2, f32x3, f32x4, gpu_layout, Array, GpuLayout, GpuSized, TypeLayout, VertexAttribute, + VertexLayout, }; fn main() { @@ -118,7 +119,7 @@ fn main() { } // this would be 8 for storage layout rules - assert!(D::gpu_layout().align.as_u32() == 16); + assert!(gpu_layout::().align.as_u32() == 16); // Let's end on a pretty error message let mut sized_struct = SizedStruct::new("D", "a", f32x2::layoutable_type_sized()) diff --git a/shame/src/frontend/rust_types/layout_traits.rs b/shame/src/frontend/rust_types/layout_traits.rs index 3a4741c..bb2bdff 100644 --- a/shame/src/frontend/rust_types/layout_traits.rs +++ b/shame/src/frontend/rust_types/layout_traits.rs @@ -138,28 +138,6 @@ use std::rc::Rc; /// [`StorageTexture`]: crate::StorageTexture /// pub trait GpuLayout: Layoutable { - /// returns a [`TypeLayout`] object that can be used to inspect the layout - /// of a type on the gpu. - /// - /// # Layout comparison of different types - /// - /// The layouts of different [`GpuLayout`]/[`CpuLayout`] types can be compared - /// by comparing [`TypeLayout`] objects returned by `.gpu_layout()`/`.cpu_layout()` - /// ``` - /// use shame as sm; - /// use sm::{ GpuLayout, CpuLayout }; - /// - /// type OnGpu = sm::Array>; - /// type OnCpu = [f32; 16]; - /// - /// if OnGpu::gpu_layout() == OnCpu::cpu_layout() { - /// println!("same layout") - /// } - /// println!("OnGpu:\n{}\n", OnGpu::gpu_layout()); - /// println!("OnCpu:\n{}\n", OnCpu::cpu_layout()); - /// ``` - fn gpu_layout() -> TypeLayout { TypeLayout::new_layout_for(Self::layoutable_type(), Self::gpu_repr()) } - /// Returns the `Repr` of the `TypeLayout` from `GpuLayout::gpu_layout`. fn gpu_repr() -> Repr; @@ -176,6 +154,36 @@ pub trait GpuLayout: Layoutable { fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>>; } +/// returns a [`TypeLayout`] object that can be used to inspect the layout +/// of a type on the gpu. +/// +/// # Layout comparison of different types +/// +/// The layouts of different [`GpuLayout`]/[`CpuLayout`] types can be compared +/// by comparing [`TypeLayout`] objects returned by `.gpu_layout()`/`.cpu_layout()` +/// ``` +/// use shame as sm; +/// use sm::{ GpuLayout, CpuLayout }; +/// +/// type OnGpu = sm::Array>; +/// type OnCpu = [f32; 16]; +/// +/// if OnGpu::gpu_layout() == OnCpu::cpu_layout() { +/// println!("same layout") +/// } +/// println!("OnGpu:\n{}\n", OnGpu::gpu_layout()); +/// println!("OnCpu:\n{}\n", OnCpu::cpu_layout()); +/// ``` +pub fn gpu_layout() -> TypeLayout { + TypeLayout::new_layout_for(T::layoutable_type(), T::gpu_repr()) +} + +/// (no documentation yet) +// `CpuLayout::cpu_layout` exists, but this function exists for consistency with +// the `gpu_layout` function. `GpuLayout::gpu_layout` does not exist, so that implementors +// of `GpuLayout` can't overwrite it. +pub fn cpu_layout() -> TypeLayout { T::cpu_layout() } + pub(crate) fn cpu_type_name_and_layout(ctx: &Context) -> Option<(Cow<'static, str>, TypeLayout)> { match T::cpu_type_name_and_layout().transpose() { Ok(t) => t, @@ -199,7 +207,7 @@ pub(crate) fn get_layout_compare_with_cpu_push_error( ) -> TypeLayout { const ERR_COMMENT: &str = "`GpuLayout` uses WGSL layout rules unless #[gpu_repr(packed)] is used.\nsee https://www.w3.org/TR/WGSL/#structure-member-layout\n`CpuLayout` uses #[repr(C)].\nsee https://doc.rust-lang.org/reference/type-layout.html#r-layout.repr.c.struct"; - let gpu_layout = T::gpu_layout(); + let gpu_layout = gpu_layout::(); if let Some((cpu_name, cpu_layout)) = cpu_type_name_and_layout::(ctx) { check_layout_push_error(ctx, &cpu_name, &cpu_layout, &gpu_layout, skip_stride_check, ERR_COMMENT).ok(); } diff --git a/shame/src/frontend/rust_types/packed_vec.rs b/shame/src/frontend/rust_types/packed_vec.rs index 8682fe9..596a0bb 100644 --- a/shame/src/frontend/rust_types/packed_vec.rs +++ b/shame/src/frontend/rust_types/packed_vec.rs @@ -10,7 +10,7 @@ use crate::{ AsAny, DataPackingFn, }, common::floating_point::f16, - f32x2, f32x4, i32x4, u32x1, u32x4, + f32x2, f32x4, gpu_layout, i32x4, u32x1, u32x4, }; use crate::frontend::rust_types::len::{x1, x2, x3, x4}; use crate::frontend::rust_types::vec::vec; @@ -172,7 +172,7 @@ impl From for PackedVec { let inner = Context::try_with(call_info!(), |ctx| { let err = |ty| { ctx.push_error_get_invalid_any( - FrontendError::InvalidDowncastToNonShaderType(ty, Self::gpu_layout()).into(), + FrontendError::InvalidDowncastToNonShaderType(ty, gpu_layout::()).into(), ) }; match any.ty() { diff --git a/shame/src/lib.rs b/shame/src/lib.rs index 84bdcbd..3501272 100644 --- a/shame/src/lib.rs +++ b/shame/src/lib.rs @@ -315,7 +315,9 @@ pub use frontend::texture::texture_formats as tf; pub use shame_derive::CpuLayout; pub use shame_derive::GpuLayout; pub use frontend::rust_types::layout_traits::GpuLayout; +pub use frontend::rust_types::layout_traits::gpu_layout; pub use frontend::rust_types::layout_traits::CpuLayout; +pub use frontend::rust_types::layout_traits::cpu_layout; pub use frontend::rust_types::type_layout::TypeLayout; // derived traits diff --git a/shame/tests/test_layout.rs b/shame/tests/test_layout.rs index 0b5cb9f..041d20d 100644 --- a/shame/tests/test_layout.rs +++ b/shame/tests/test_layout.rs @@ -1,7 +1,7 @@ #![allow(non_camel_case_types, unused)] use pretty_assertions::{assert_eq, assert_ne}; -use shame as sm; +use shame::{self as sm, cpu_layout, gpu_layout}; use sm::{aliases::*, CpuLayout, GpuLayout}; #[test] @@ -21,7 +21,7 @@ fn basic_layout_eq() { c: i32, } - assert_eq!(OnGpu::gpu_layout(), OnCpu::cpu_layout()); + assert_eq!(gpu_layout::(), cpu_layout::()); } #[test] @@ -50,9 +50,9 @@ fn attributes_dont_contribute_to_eq() { c: i32, } - assert_eq!(OnGpuA::gpu_layout(), OnCpu::cpu_layout()); - assert_eq!(OnGpuB::gpu_layout(), OnCpu::cpu_layout()); - assert_eq!(OnGpuA::gpu_layout(), OnGpuB::gpu_layout()); + assert_eq!(gpu_layout::(), cpu_layout::()); + assert_eq!(gpu_layout::(), cpu_layout::()); + assert_eq!(gpu_layout::(), gpu_layout::()); } #[test] @@ -74,7 +74,7 @@ fn fixed_by_align_size_attribute() { c: i32, } - assert_eq!(OnGpu::gpu_layout(), OnCpu::cpu_layout()); + assert_eq!(gpu_layout::(), cpu_layout::()); } { @@ -94,7 +94,7 @@ fn fixed_by_align_size_attribute() { c: f32x3_size32, } - assert_eq!(OnGpu::gpu_layout(), OnCpu::cpu_layout()); + assert_eq!(gpu_layout::(), cpu_layout::()); } } @@ -115,7 +115,7 @@ fn different_align_struct_eq() { c: i32, } - assert_eq!(OnGpu::gpu_layout(), OnCpu::cpu_layout()); + assert_eq!(gpu_layout::(), cpu_layout::()); } #[test] @@ -135,7 +135,7 @@ fn unsized_struct_layout_eq() { c: [i32], } - assert_eq!(OnGpu::gpu_layout(), OnCpu::cpu_layout()); + assert_eq!(gpu_layout::(), cpu_layout::()); } #[derive(Clone, Copy)] @@ -147,21 +147,21 @@ struct f32x4_cpu(pub [f32; 4]); struct f32x3_cpu(pub [f32; 3]); impl CpuLayout for f32x3_cpu { - fn cpu_layout() -> shame::TypeLayout { f32x3::gpu_layout() } + fn cpu_layout() -> shame::TypeLayout { gpu_layout::() } } #[derive(Clone, Copy)] #[repr(C, align(8))] struct f32x2_cpu(pub [f32; 2]); impl CpuLayout for f32x2_cpu { - fn cpu_layout() -> shame::TypeLayout { f32x2::gpu_layout() } + fn cpu_layout() -> shame::TypeLayout { gpu_layout::() } } #[derive(Clone, Copy)] #[repr(C)] struct f32x2_align4(pub [f32; 2]); impl CpuLayout for f32x2_align4 { - fn cpu_layout() -> shame::TypeLayout { f32x2::gpu_layout() } + fn cpu_layout() -> shame::TypeLayout { gpu_layout::() } } #[derive(Clone, Copy)] @@ -169,7 +169,7 @@ impl CpuLayout for f32x2_align4 { struct f32x4_align4(pub [f32; 4]); impl CpuLayout for f32x4_align4 { - fn cpu_layout() -> shame::TypeLayout { f32x4::gpu_layout() } + fn cpu_layout() -> shame::TypeLayout { gpu_layout::() } } #[derive(Clone, Copy)] @@ -182,7 +182,7 @@ static_assertions::assert_eq_align!(glam::Vec3, f32x3_align4); static_assertions::assert_eq_align!(glam::Vec4, f32x4_cpu); impl CpuLayout for f32x3_align4 { - fn cpu_layout() -> shame::TypeLayout { f32x3::gpu_layout() } + fn cpu_layout() -> shame::TypeLayout { gpu_layout::() } } #[derive(Clone, Copy)] @@ -190,7 +190,7 @@ impl CpuLayout for f32x3_align4 { struct f32x3_size32(pub [f32; 3], [u8; 20]); impl CpuLayout for f32x3_size32 { - fn cpu_layout() -> shame::TypeLayout { f32x3::gpu_layout() } + fn cpu_layout() -> shame::TypeLayout { gpu_layout::() } } @@ -212,7 +212,7 @@ fn unsized_struct_vec3_align_layout_eq() { c: [f32x3_cpu], } - assert_eq!(OnGpu::gpu_layout(), OnCpu::cpu_layout()); + assert_eq!(gpu_layout::(), cpu_layout::()); } #[test] @@ -231,7 +231,7 @@ fn unsized_struct_vec3_align_layout_eq() { // the alignment on the top level of the layout doesn't matter. // two layouts are only considered different if an alignment mismatch // leads to different offsets of fields or array elements - assert_eq!(OnGpu::gpu_layout(), OnCpu::cpu_layout()); + assert_eq!(gpu_layout::(), cpu_layout::()); } #[test] @@ -246,11 +246,11 @@ fn unsized_struct_vec3_align_layout_eq() { struct OnCpu { // size=12, align=4 a: f32x3_align4, } - assert_ne!(OnGpu::gpu_layout(), OnCpu::cpu_layout()); - assert!(OnGpu::gpu_layout().byte_size() == Some(16)); - assert!(OnGpu::gpu_layout().align().as_u32() == 16); - assert!(OnCpu::cpu_layout().byte_size() == Some(12)); - assert!(OnCpu::cpu_layout().align().as_u32() == 4); + assert_ne!(gpu_layout::(), cpu_layout::()); + assert!(gpu_layout::().byte_size() == Some(16)); + assert!(gpu_layout::().align().as_u32() == 16); + assert!(cpu_layout::().byte_size() == Some(12)); + assert!(cpu_layout::().align().as_u32() == 4); } #[test] @@ -283,15 +283,15 @@ fn unsized_struct_nested_vec3_align_layout_eq() { c: [InnerCpu], } - assert_eq!(OnGpu::gpu_layout(), OnCpu::cpu_layout()); + assert_eq!(gpu_layout::(), cpu_layout::()); } #[test] fn unsized_array_layout_eq() { - assert_eq!(>::gpu_layout(), <[f32]>::cpu_layout()); - assert_eq!(>::gpu_layout(), <[f32x3_cpu]>::cpu_layout()); - assert_ne!(>::gpu_layout(), <[f32x3_align4]>::cpu_layout()); - assert_ne!(>::gpu_layout(), <[f32x3_size32]>::cpu_layout()); + assert_eq!(gpu_layout::>(), cpu_layout::<[f32]>()); + assert_eq!(gpu_layout::>(), cpu_layout::<[f32x3_cpu]>()); + assert_ne!(gpu_layout::>(), cpu_layout::<[f32x3_align4]>()); + assert_ne!(gpu_layout::>(), cpu_layout::<[f32x3_size32]>()); } #[test] @@ -318,14 +318,15 @@ fn layouts_mismatch() { c: i32, } - assert_ne!(OnGpuLess::gpu_layout(), OnCpu::cpu_layout()); - assert_ne!(OnGpuMore::gpu_layout(), OnCpu::cpu_layout()); + assert_ne!(gpu_layout::(), cpu_layout::()); + assert_ne!(gpu_layout::(), cpu_layout::()); } #[test] fn external_vec_type() { // using duck-traiting just so that the proc-macro uses `CpuLayoutExt::layout()` pub mod my_mod { + use shame::gpu_layout; use shame as sm; use sm::aliases::*; use sm::GpuLayout as _; @@ -335,11 +336,11 @@ fn external_vec_type() { } impl CpuLayoutExt for glam::Vec4 { - fn cpu_layout() -> shame::TypeLayout { f32x4::gpu_layout() } + fn cpu_layout() -> shame::TypeLayout { gpu_layout::() } } impl CpuLayoutExt for glam::Vec3 { - fn cpu_layout() -> shame::TypeLayout { f32x3::gpu_layout() } + fn cpu_layout() -> shame::TypeLayout { gpu_layout::() } } } @@ -357,7 +358,7 @@ fn external_vec_type() { b: glam::Vec4, } - assert_eq!(OnGpu::gpu_layout(), OnCpu::cpu_layout()); + assert_eq!(gpu_layout::(), cpu_layout::()); #[derive(sm::GpuLayout)] struct OnGpu2 { @@ -384,8 +385,8 @@ fn external_vec_type() { c: glam::Vec4, } - assert_ne!(OnGpu2::gpu_layout(), OnCpu2::cpu_layout()); - assert_eq!(OnGpu2Packed::gpu_layout(), OnCpu2::cpu_layout()); + assert_ne!(gpu_layout::(), cpu_layout::()); + assert_eq!(gpu_layout::(), cpu_layout::()); } #[test] @@ -407,7 +408,7 @@ fn external_vec_type() { uv : f32x2_align4, } - assert_eq!(OnGpu::gpu_layout(), OnCpu::cpu_layout()); + assert_eq!(gpu_layout::(), cpu_layout::()); } { #[derive(sm::GpuLayout)] @@ -426,7 +427,7 @@ fn external_vec_type() { uv : f32x2_cpu, } - assert_eq!(OnGpu::gpu_layout(), OnCpu::cpu_layout()); + assert_eq!(gpu_layout::(), cpu_layout::()); enum __ where OnGpu: sm::VertexLayout {} } } From e19e85b8be3ab7500a500086045a1d3d0b22b927 Mon Sep 17 00:00:00 2001 From: chronicl Date: Fri, 23 May 2025 22:29:41 +0200 Subject: [PATCH 14/32] Move Display impl of ScalarType --- .../rust_types/type_layout/layoutable/mod.rs | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs index 230e2b6..61b188a 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs @@ -191,19 +191,6 @@ pub trait LayoutableSized: Layoutable { fn layoutable_type_sized() -> SizedType; } - -impl std::fmt::Display for ScalarType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(match self { - ScalarType::F16 => "f16", - ScalarType::F32 => "f32", - ScalarType::F64 => "f64", - ScalarType::U32 => "u32", - ScalarType::I32 => "i32", - }) - } -} - // Conversions to ScalarType, SizedType and LayoutableType // macro_rules! impl_into_sized_type { @@ -536,3 +523,15 @@ impl std::fmt::Display for SizedType { } } } + +impl std::fmt::Display for ScalarType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + ScalarType::F16 => "f16", + ScalarType::F32 => "f32", + ScalarType::F64 => "f64", + ScalarType::U32 => "u32", + ScalarType::I32 => "i32", + }) + } +} From e2f41cda846e10ea30568f13a3df75d403309500 Mon Sep 17 00:00:00 2001 From: chronicl Date: Sat, 24 May 2025 02:35:17 +0200 Subject: [PATCH 15/32] Make TryFrom for StoreType fallible started --- .../rust_types/type_layout/builder.rs | 14 +- .../type_layout/layoutable/ir_compat.rs | 498 ++++++++++++++++++ .../rust_types/type_layout/layoutable/mod.rs | 290 ++-------- .../frontend/rust_types/type_layout/mod.rs | 4 +- shame/src/lib.rs | 90 ++-- 5 files changed, 595 insertions(+), 301 deletions(-) create mode 100644 shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs diff --git a/shame/src/frontend/rust_types/type_layout/builder.rs b/shame/src/frontend/rust_types/type_layout/builder.rs index 4ee2898..8e758c8 100644 --- a/shame/src/frontend/rust_types/type_layout/builder.rs +++ b/shame/src/frontend/rust_types/type_layout/builder.rs @@ -318,7 +318,7 @@ pub struct ArrayStrideError { impl std::error::Error for WithContext {} impl Display for WithContext { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let top_level: StoreType = self.ctx.s_layout.layoutable_type().clone().into(); + let top_level = self.ctx.s_layout.layoutable_type(); writeln!( f, "array elements within type `{}` do not satisfy uniform layout requirements.", @@ -327,9 +327,7 @@ impl Display for WithContext { writeln!( f, "The array with `{}` elements requires stride {}, but has stride {}.", - Into::::into(self.element_ty.clone()), - self.expected, - self.actual + self.element_ty, self.expected, self.actual )?; writeln!(f, "The full layout of `{}` is:", top_level)?; self.ctx.s_layout.write("", self.ctx.use_color, f)?; @@ -451,10 +449,7 @@ where let struct_name = &*struct_layout.name; let indent = " "; - let field_decl_line = |field: &SizedField| { - let sized: ir::SizedType = field.ty.clone().into(); - format!("{indent}{}: {},", field.name, sized) - }; + let field_decl_line = |field: &SizedField| format!("{indent}{}: {},", field.name, field.ty); let header = format!("struct {} {{", struct_name); let table_start_column = 1 + sized_fields .iter() @@ -487,8 +482,7 @@ where if let Some(last_field) = last_unsized { let layout = struct_layout.fields.last().expect("structs have at least one field"); - let store_type: ir::StoreType = last_field.array.clone().into(); - let decl_line = format!("{indent}{}: {},", last_field.name, store_type); + let decl_line = format!("{indent}{}: {},", last_field.name, last_field.array); f.write_str(&decl_line)?; // write spaces to table on the right for _ in decl_line.len()..table_start_column { diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs b/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs new file mode 100644 index 0000000..98315b3 --- /dev/null +++ b/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs @@ -0,0 +1,498 @@ +use super::*; + +// Conversions to ir types // + +/// Errors that can occur when converting IR types to layoutable types. +#[derive(thiserror::Error, Debug)] +pub enum IRConversionError { + /// Packed vectors do not exist in the shader type system. + #[error("{0}")] + ContainsPackedVector(#[from] ContainsPackedVectorError), + /// Struct field names must be unique in the shader type system. + #[error("{0}")] + DuplicateFieldName(#[from] DuplicateFieldNameError), +} + +#[derive(Debug)] +pub enum ContainsPackedVectorError { + /// Top level type is a packed vector. + SelfIsPackedVector(LayoutableType), + /// Top level nested Array packed vector, for example Array>>. + InArray(LayoutableType), + /// A struct field is a packed vector or a struct field is a nested Array packed vector. + InStruct { + struct_type: StructKind, + packed_vector_field: usize, + top_level_type: Option, + use_color: bool, + }, +} + +impl std::error::Error for ContainsPackedVectorError {} + +impl std::fmt::Display for ContainsPackedVectorError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let ending = + ", which does not exist in the shader type system.\nPacked vectors may only be used in vertex buffers."; + let (struct_name, sized_fields, last_unsized, packed_vector_field, use_color) = match self { + ContainsPackedVectorError::SelfIsPackedVector(t) => return writeln!(f, "{t} is a packed vector{ending}"), + ContainsPackedVectorError::InArray(a) => return writeln!(f, "{a} contains a packed vector{ending}"), + ContainsPackedVectorError::InStruct { + struct_type, + packed_vector_field, + top_level_type, + use_color, + } => { + let (struct_name, sized_fields, last_unsized) = match struct_type { + StructKind::Sized(s) => (&s.name, s.fields(), None), + StructKind::Unsized(s) => (&s.name, s.sized_fields.as_slice(), Some(&s.last_unsized)), + }; + + if let Some(top_level_type) = top_level_type { + writeln!( + f, + "The struct {struct_name} in {top_level_type} contains a packed vector{ending}" + )?; + } else { + writeln!(f, "{struct_name} contains a packed vector{ending}")?; + } + + ( + struct_name, + sized_fields, + last_unsized, + *packed_vector_field, + *use_color, + ) + } + }; + + + + let indent = " "; + let is_packed_vec = |i| packed_vector_field == i; + let arrow = |i| match is_packed_vec(i) { + true => " <--", + false => "", + }; + let color = |f: &mut Formatter<'_>, i| { + if (use_color && is_packed_vec(i)) { + set_color(f, Some("#508EE3"), false) + } else { + Ok(()) + } + }; + let color_reset = |f: &mut Formatter<'_>, i| { + if use_color && is_packed_vec(i) { + set_color(f, None, false) + } else { + Ok(()) + } + }; + + writeln!(f, "The following struct contains a packed vector:")?; + let header = writeln!(f, "struct {} {{", struct_name); + for (i, field) in sized_fields.iter().enumerate() { + color(f, i)?; + writeln!(f, "{indent}{}: {},{}", field.name, field.ty, arrow(i))?; + color_reset(f, i)?; + } + if let Some(field) = last_unsized { + let i = sized_fields.len(); + color(f, i)?; + writeln!(f, "{indent}{}: {},{}", field.name, field.array, arrow(i))?; + color_reset(f, i)?; + } + writeln!(f, "}}")?; + + Ok(()) + } +} + +#[derive(Debug)] +pub struct DuplicateFieldNameError { + pub struct_type: StructKind, + pub first_field: usize, + pub second_field: usize, + pub is_top_level: bool, + pub use_color: bool, +} + +impl std::error::Error for DuplicateFieldNameError {} + +impl std::fmt::Display for DuplicateFieldNameError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let (struct_name, sized_fields, last_unsized) = match &self.struct_type { + StructKind::Sized(s) => (&s.name, s.fields(), None), + StructKind::Unsized(s) => (&s.name, s.sized_fields.as_slice(), Some(&s.last_unsized)), + }; + + let indent = " "; + let is_duplicate = |i| self.first_field == i || self.second_field == i; + let arrow = |i| match is_duplicate(i) { + true => " <--", + false => "", + }; + let color = |f: &mut Formatter<'_>, i| { + if (self.use_color && is_duplicate(i)) { + set_color(f, Some("#508EE3"), false) + } else { + Ok(()) + } + }; + let color_reset = |f: &mut Formatter<'_>, i| { + if self.use_color && is_duplicate(i) { + set_color(f, None, false) + } else { + Ok(()) + } + }; + + writeln!( + f, + "Type contains or is a struct with duplicate field names.\ + Field names must be unique in the shader type system.\n\ + The following struct contains duplicate field names:" + )?; + let header = writeln!(f, "struct {} {{", struct_name); + for (i, field) in sized_fields.iter().enumerate() { + color(f, i)?; + writeln!(f, "{indent}{}: {},{}", field.name, field.ty, arrow(i))?; + color_reset(f, i)?; + } + if let Some(field) = last_unsized { + let i = sized_fields.len(); + color(f, i)?; + writeln!(f, "{indent}{}: {},{}", field.name, field.array, arrow(i))?; + color_reset(f, i)?; + } + writeln!(f, "}}")?; + + Ok(()) + } +} + +fn check_for_duplicate_field_names( + struct_type: StructKind, + is_top_level: bool, +) -> Result { + let (sized_fields, last_unsized) = match &struct_type { + StructKind::Sized(s) => (s.fields(), None), + StructKind::Unsized(s) => (s.sized_fields.as_slice(), Some(&s.last_unsized)), + }; + + // Brute force search > HashMap for the amount of fields + // we'd usually deal with. + let mut duplicate_fields = None; + for (i, field1) in sized_fields.iter().enumerate() { + for (j, field2) in sized_fields[i..].iter().enumerate() { + if field1.name == field2.name { + duplicate_fields = Some((i, j)); + break; + } + } + } + + match duplicate_fields { + Some((first_field, second_field)) => Err(DuplicateFieldNameError { + struct_type, + first_field, + second_field, + is_top_level, + use_color: use_color(), + }), + None => Ok(struct_type), + } +} + +#[track_caller] +fn use_color() -> bool { Context::try_with(call_info!(), |ctx| ctx.settings().colored_error_messages).unwrap_or(false) } + +impl TryFrom for ir::StoreType { + type Error = IRConversionError; + + fn try_from(ty: LayoutableType) -> Result { + // Checking for top level packed vec. + if matches!(ty, LayoutableType::Sized(SizedType::PackedVec(_))) { + return Err(IRConversionError::ContainsPackedVector( + ContainsPackedVectorError::SelfIsPackedVector(ty), + )); + } + // Checking for nested array packed vec. + if is_packed_vec(&ty) { + return Err(IRConversionError::ContainsPackedVector( + ContainsPackedVectorError::InArray(ty), + )); + } + + fn convert + + todo!() + } +} + +/// Returns true if `ty` is a packed vector or a nesting of Arrays +/// with the last Array directly containing a packed vector. +fn is_packed_vec(ty: &LayoutableType) -> bool { + let mut ty = match ty { + LayoutableType::Sized(s) => s, + LayoutableType::RuntimeSizedArray(a) => &a.element, + LayoutableType::UnsizedStruct(_) => return false, + }; + + loop { + match ty { + SizedType::PackedVec(_) => return true, + SizedType::Array(a) => ty = &a.element, + _ => return false, + } + } +} + +// impl TryFrom for ir::SizedType { +// type Error = IRConversionError; + +// fn try_from(host: SizedType) -> Result { +// // Ok(match host { +// // SizedType::Vector(v) => ir::SizedType::Vector(v.len, v.scalar.into()), +// // SizedType::Matrix(m) => ir::SizedType::Matrix(m.columns, m.rows, m.scalar), +// // SizedType::Array(a) => { +// // let element = Rc::unwrap_or_clone(a.element); +// // let converted_element = element.try_into()?; +// // ir::SizedType::Array(Rc::new(converted_element), a.len) +// // } +// // SizedType::Atomic(i) => ir::SizedType::Atomic(i.scalar), +// // SizedType::PackedVec(_) => return Err(IRConversionError::ContainsPackedVector), +// // SizedType::Struct(s) => ir::SizedType::Structure(s.try_into()?), +// // }) +// todo!() +// } +// } + +impl From for ir::ScalarType { + fn from(scalar_type: ScalarType) -> Self { + match scalar_type { + ScalarType::F16 => ir::ScalarType::F16, + ScalarType::F32 => ir::ScalarType::F32, + ScalarType::F64 => ir::ScalarType::F64, + ScalarType::U32 => ir::ScalarType::U32, + ScalarType::I32 => ir::ScalarType::I32, + } + } +} + +// impl TryFrom for ir::ir_type::SizedStruct { +// type Error = IRConversionError; + +// fn try_from(host: SizedStruct) -> Result { +// let mut fields: Vec = Vec::new(); + +// for field in host.fields { +// fields.push(field.try_into()?); +// } + +// // has at least one field +// if fields.is_empty() { +// // This should not happen based on SizedStruct's implementation +// return Err(IRConversionError::DuplicateFieldName); +// } + +// let last_field = fields.pop().unwrap(); + +// match ir::ir_type::SizedStruct::new_nonempty(host.name, fields, last_field) { +// Ok(s) => Ok(s), +// Err(StructureFieldNamesMustBeUnique) => Err(IRConversionError::DuplicateFieldName), +// } +// } +// } + +// impl TryFrom for ir::ir_type::BufferBlock { +// type Error = IRConversionError; + +// fn try_from(host: UnsizedStruct) -> Result { +// let mut sized_fields: Vec = Vec::new(); + +// for field in host.sized_fields { +// sized_fields.push(field.try_into()?); +// } + +// let last_unsized = host.last_unsized.try_into()?; + +// match ir::ir_type::BufferBlock::new(host.name, sized_fields, Some(last_unsized)) { +// Ok(b) => Ok(b), +// Err(BufferBlockDefinitionError::FieldNamesMustBeUnique) => Err(IRConversionError::DuplicateFieldName), +// Err(BufferBlockDefinitionError::MustHaveAtLeastOneField) => { +// // This should not happen based on UnsizedStruct's implementation +// Err(IRConversionError::DuplicateFieldName) +// } +// } +// } +// } + +// impl TryFrom for ir::StoreType { +// type Error = IRConversionError; + +// fn try_from(array: RuntimeSizedArray) -> Result { +// Ok(ir::StoreType::RuntimeSizedArray(array.element.try_into()?)) +// } +// } + +// impl TryFrom for ir::ir_type::SizedField { +// type Error = IRConversionError; + +// fn try_from(f: SizedField) -> Result { +// Ok(ir::SizedField::new( +// f.name, +// f.custom_min_size, +// f.custom_min_align, +// f.ty.try_into()?, +// )) +// } +// } + +// impl TryFrom for ir::ir_type::RuntimeSizedArrayField { +// type Error = IRConversionError; + +// fn try_from(f: RuntimeSizedArrayField) -> Result { +// Ok(ir::RuntimeSizedArrayField::new( +// f.name, +// f.custom_min_align, +// f.array.element.try_into()?, +// )) +// } +// } + + +// Conversions from ir types // + +/// Type contains bools, which doesn't have a known layout. +#[derive(thiserror::Error, Debug)] +#[error("Type contains bools, which doesn't have a known layout.")] +pub struct ContainsBoolsError; + +/// Errors that can occur when converting IR types to layoutable types. +#[derive(thiserror::Error, Debug)] +pub enum LayoutableConversionError { + /// Type contains bools, which don't have a standardized memory layout. + #[error("Type contains bools, which don't have a standardized memory layout.")] + ContainsBool, + /// Type is a handle, which don't have a standardized memory layout. + #[error("Type is a handle, which don't have a standardized memory layout.")] + IsHandle, +} + +impl TryFrom for ScalarType { + type Error = ContainsBoolsError; + + fn try_from(value: ir::ScalarType) -> Result { + Ok(match value { + ir::ScalarType::F16 => ScalarType::F16, + ir::ScalarType::F32 => ScalarType::F32, + ir::ScalarType::F64 => ScalarType::F64, + ir::ScalarType::U32 => ScalarType::U32, + ir::ScalarType::I32 => ScalarType::I32, + ir::ScalarType::Bool => return Err(ContainsBoolsError), + }) + } +} + +impl TryFrom for SizedType { + type Error = ContainsBoolsError; + + fn try_from(value: ir::SizedType) -> Result { + Ok(match value { + ir::SizedType::Vector(len, scalar) => SizedType::Vector(Vector { + scalar: scalar.try_into()?, + len, + }), + ir::SizedType::Matrix(columns, rows, scalar) => SizedType::Matrix(Matrix { scalar, columns, rows }), + ir::SizedType::Array(element, len) => SizedType::Array(SizedArray { + element: Rc::new((*element).clone().try_into()?), + len, + }), + ir::SizedType::Atomic(scalar_type) => SizedType::Atomic(Atomic { scalar: scalar_type }), + ir::SizedType::Structure(structure) => SizedType::Struct(structure.try_into()?), + }) + } +} + +impl TryFrom for SizedStruct { + type Error = ContainsBoolsError; + + fn try_from(structure: ir::ir_type::SizedStruct) -> Result { + let mut fields = Vec::new(); + + for field in structure.sized_fields() { + fields.push(SizedField { + name: field.name.clone(), + custom_min_size: field.custom_min_size, + custom_min_align: field.custom_min_align, + ty: field.ty.clone().try_into()?, + }); + } + + Ok(SizedStruct { + name: structure.name().clone(), + fields, + }) + } +} + +impl From for LayoutableConversionError { + fn from(_: ContainsBoolsError) -> Self { Self::ContainsBool } +} + +impl TryFrom for LayoutableType { + type Error = LayoutableConversionError; + + fn try_from(value: ir::StoreType) -> Result { + Ok(match value { + ir::StoreType::Sized(sized_type) => LayoutableType::Sized(sized_type.try_into()?), + ir::StoreType::RuntimeSizedArray(element) => LayoutableType::RuntimeSizedArray(RuntimeSizedArray { + element: element.try_into()?, + }), + ir::StoreType::BufferBlock(buffer_block) => buffer_block.try_into()?, + ir::StoreType::Handle(_) => return Err(LayoutableConversionError::IsHandle), + }) + } +} + +impl TryFrom for LayoutableType { + type Error = ContainsBoolsError; + + fn try_from(buffer_block: ir::ir_type::BufferBlock) -> Result { + let mut sized_fields = Vec::new(); + + for field in buffer_block.sized_fields() { + sized_fields.push(SizedField { + name: field.name.clone(), + custom_min_size: field.custom_min_size, + custom_min_align: field.custom_min_align, + ty: field.ty.clone().try_into()?, + }); + } + + let last_unsized = if let Some(last_field) = buffer_block.last_unsized_field() { + RuntimeSizedArrayField { + name: last_field.name.clone(), + custom_min_align: last_field.custom_min_align, + array: RuntimeSizedArray { + element: last_field.element_ty.clone().try_into()?, + }, + } + } else { + return Ok(SizedStruct { + name: buffer_block.name().clone(), + fields: sized_fields, + } + .into()); + }; + + Ok(UnsizedStruct { + name: buffer_block.name().clone(), + sized_fields, + last_unsized, + } + .into()) + } +} diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs index 61b188a..8016763 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs @@ -1,17 +1,20 @@ //! This module defines types that can be laid out in memory. -use std::{num::NonZeroU32, rc::Rc}; +use std::{fmt::Formatter, num::NonZeroU32, rc::Rc}; use crate::{ any::U32PowerOf2, - ir::{self, ir_type::BufferBlockDefinitionError, StructureFieldNamesMustBeUnique}, + call_info, + common::prettify::set_color, + ir::{self, ir_type::BufferBlockDefinitionError, recording::Context, StructureFieldNamesMustBeUnique}, }; pub use crate::ir::{Len, Len2, PackedVector, ScalarTypeFp, ScalarTypeInteger, ir_type::CanonName}; -use super::FieldOptions; +use super::{builder::StructKind, FieldOptions}; pub(crate) mod align_size; pub(crate) mod builder; +pub(crate) mod ir_compat; pub use align_size::{FieldOffsets, MatrixMajor, array_size, array_stride, array_align}; pub use builder::SizedOrArray; @@ -258,274 +261,69 @@ impl From for ScalarType { fn from(int: ScalarTypeFp) -> Self { int.as_scalar_type() } } +// Display impls -// Conversions to ir types // - -impl From for ir::StoreType { - fn from(host: LayoutableType) -> Self { - match host { - LayoutableType::Sized(s) => ir::StoreType::Sized(s.into()), - LayoutableType::RuntimeSizedArray(s) => ir::StoreType::RuntimeSizedArray(s.element.into()), - LayoutableType::UnsizedStruct(s) => ir::StoreType::BufferBlock(s.into()), +impl std::fmt::Display for LayoutableType { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + LayoutableType::Sized(s) => s.fmt(f), + LayoutableType::RuntimeSizedArray(a) => a.fmt(f), + LayoutableType::UnsizedStruct(s) => s.fmt(f), } } } -impl From for ir::SizedType { - fn from(host: SizedType) -> Self { - match host { - SizedType::Vector(v) => ir::SizedType::Vector(v.len, v.scalar.into()), - SizedType::Matrix(m) => ir::SizedType::Matrix(m.columns, m.rows, m.scalar), - SizedType::Array(a) => ir::SizedType::Array(Rc::new(Rc::unwrap_or_clone(a.element).into()), a.len), - SizedType::Atomic(i) => ir::SizedType::Atomic(i.scalar), - SizedType::PackedVec(v) => SizedType::Vector(match v.byte_size() { - ir::ir_type::PackedVectorByteSize::_2 => Vector::new(ScalarType::F16, Len::X1), - ir::ir_type::PackedVectorByteSize::_4 => Vector::new(ScalarType::U32, Len::X1), - ir::ir_type::PackedVectorByteSize::_8 => Vector::new(ScalarType::U32, Len::X2), - }) - .into(), - SizedType::Struct(s) => ir::SizedType::Structure(s.into()), +impl std::fmt::Display for SizedType { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + SizedType::Vector(v) => v.fmt(f), + SizedType::Matrix(m) => m.fmt(f), + SizedType::Array(a) => a.fmt(f), + SizedType::Atomic(a) => a.fmt(f), + SizedType::PackedVec(p) => p.fmt(f), + SizedType::Struct(s) => s.fmt(f), } } } -impl From for ir::ScalarType { - fn from(scalar_type: ScalarType) -> Self { - match scalar_type { - ScalarType::F16 => ir::ScalarType::F16, - ScalarType::F32 => ir::ScalarType::F32, - ScalarType::F64 => ir::ScalarType::F64, - ScalarType::U32 => ir::ScalarType::U32, - ScalarType::I32 => ir::ScalarType::I32, - } - } +impl std::fmt::Display for Vector { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}x{}", self.scalar, self.len) } } -impl From for ir::ir_type::SizedStruct { - fn from(host: SizedStruct) -> Self { - let mut fields: Vec = host.fields.into_iter().map(Into::into).collect(); - // has at least one field - let last_field = fields.pop().unwrap(); - - // Note: This might throw an error in real usage if the fields aren't valid - // We're assuming they're valid in the conversion - match ir::ir_type::SizedStruct::new_nonempty(host.name, fields, last_field) { - Ok(s) => s, - Err(StructureFieldNamesMustBeUnique) => { - // TODO(chronicl) this isn't true - unreachable!("field names are unique for `LayoutType`") - } - } +impl std::fmt::Display for Matrix { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "mat<{}, {}, {}>", + self.scalar, + Len::from(self.columns), + Len::from(self.rows) + ) } } -impl From for ir::ir_type::BufferBlock { - fn from(host: UnsizedStruct) -> Self { - let sized_fields: Vec = host.sized_fields.into_iter().map(Into::into).collect(); - - let last_unsized = host.last_unsized.into(); - - // TODO(chronicl) - // Note: This might throw an error in real usage if the struct isn't valid - // We're assuming it's valid in the conversion - match ir::ir_type::BufferBlock::new(host.name, sized_fields, Some(last_unsized)) { - Ok(b) => b, - Err(BufferBlockDefinitionError::FieldNamesMustBeUnique) => { - // TODO(chronicl) this isn't true - unreachable!("field names are unique for `UnsizedStruct`") - } - Err(BufferBlockDefinitionError::MustHaveAtLeastOneField) => { - unreachable!("`UnsizedStruct` has at least one field.") - } - } - } +impl std::fmt::Display for SizedArray { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "Array<{}, {}>", &*self.element, self.len) } } -impl From for ir::StoreType { - fn from(array: RuntimeSizedArray) -> Self { ir::StoreType::RuntimeSizedArray(array.element.into()) } +impl std::fmt::Display for Atomic { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "Atomic<{}>", ScalarType::from(self.scalar)) } } -impl From for ir::ir_type::SizedField { - fn from(f: SizedField) -> Self { ir::SizedField::new(f.name, f.custom_min_size, f.custom_min_align, f.ty.into()) } +impl std::fmt::Display for SizedStruct { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.name) } } -impl From for ir::ir_type::RuntimeSizedArrayField { - fn from(f: RuntimeSizedArrayField) -> Self { - ir::RuntimeSizedArrayField::new(f.name, f.custom_min_align, f.array.element.into()) - } +impl std::fmt::Display for UnsizedStruct { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.name) } } - -// Conversions from ir types // - -/// Type contains bools, which doesn't have a known layout. -#[derive(thiserror::Error, Debug)] -#[error("Type contains bools, which doesn't have a known layout.")] -pub struct ContainsBoolsError; - -impl TryFrom for ScalarType { - type Error = ContainsBoolsError; - - fn try_from(value: ir::ScalarType) -> Result { - Ok(match value { - ir::ScalarType::F16 => ScalarType::F16, - ir::ScalarType::F32 => ScalarType::F32, - ir::ScalarType::F64 => ScalarType::F64, - ir::ScalarType::U32 => ScalarType::U32, - ir::ScalarType::I32 => ScalarType::I32, - ir::ScalarType::Bool => return Err(ContainsBoolsError), - }) - } -} - -impl TryFrom for SizedType { - type Error = ContainsBoolsError; - - fn try_from(value: ir::SizedType) -> Result { - Ok(match value { - ir::SizedType::Vector(len, scalar) => SizedType::Vector(Vector { - scalar: scalar.try_into()?, - len, - }), - ir::SizedType::Matrix(columns, rows, scalar) => SizedType::Matrix(Matrix { scalar, columns, rows }), - ir::SizedType::Array(element, len) => SizedType::Array(SizedArray { - element: Rc::new((*element).clone().try_into()?), - len, - }), - ir::SizedType::Atomic(scalar_type) => SizedType::Atomic(Atomic { scalar: scalar_type }), - ir::SizedType::Structure(structure) => SizedType::Struct(structure.try_into()?), - }) - } -} - -impl TryFrom for SizedStruct { - type Error = ContainsBoolsError; - - fn try_from(structure: ir::ir_type::SizedStruct) -> Result { - let mut fields = Vec::new(); - - for field in structure.sized_fields() { - fields.push(SizedField { - name: field.name.clone(), - custom_min_size: field.custom_min_size, - custom_min_align: field.custom_min_align, - ty: field.ty.clone().try_into()?, - }); - } - - Ok(SizedStruct { - name: structure.name().clone(), - fields, - }) - } -} - -/// Errors that can occur when converting IR types to layoutable types. -#[derive(thiserror::Error, Debug)] -pub enum LayoutableConversionError { - /// Type contains bools, which don't have a standardized memory layout. - #[error("Type contains bools, which don't have a standardized memory layout.")] - ContainsBool, - /// Type is a handle, which don't have a standardized memory layout. - #[error("Type is a handle, which don't have a standardized memory layout.")] - IsHandle, -} - -impl From for LayoutableConversionError { - fn from(_: ContainsBoolsError) -> Self { Self::ContainsBool } -} - -impl TryFrom for LayoutableType { - type Error = LayoutableConversionError; - - fn try_from(value: ir::StoreType) -> Result { - Ok(match value { - ir::StoreType::Sized(sized_type) => LayoutableType::Sized(sized_type.try_into()?), - ir::StoreType::RuntimeSizedArray(element) => LayoutableType::RuntimeSizedArray(RuntimeSizedArray { - element: element.try_into()?, - }), - ir::StoreType::BufferBlock(buffer_block) => buffer_block.try_into()?, - ir::StoreType::Handle(_) => return Err(LayoutableConversionError::IsHandle), - }) - } -} - -impl TryFrom for LayoutableType { - type Error = ContainsBoolsError; - - fn try_from(buffer_block: ir::ir_type::BufferBlock) -> Result { - let mut sized_fields = Vec::new(); - - for field in buffer_block.sized_fields() { - sized_fields.push(SizedField { - name: field.name.clone(), - custom_min_size: field.custom_min_size, - custom_min_align: field.custom_min_align, - ty: field.ty.clone().try_into()?, - }); - } - - let last_unsized = if let Some(last_field) = buffer_block.last_unsized_field() { - RuntimeSizedArrayField { - name: last_field.name.clone(), - custom_min_align: last_field.custom_min_align, - array: RuntimeSizedArray { - element: last_field.element_ty.clone().try_into()?, - }, - } - } else { - return Ok(SizedStruct { - name: buffer_block.name().clone(), - fields: sized_fields, - } - .into()); - }; - - Ok(UnsizedStruct { - name: buffer_block.name().clone(), - sized_fields, - last_unsized, - } - .into()) - } -} - -// Display impls - -impl std::fmt::Display for LayoutableType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - LayoutableType::Sized(s) => write!(f, "{s}"), - LayoutableType::RuntimeSizedArray(a) => write!(f, "Array<{}>", a.element), - LayoutableType::UnsizedStruct(s) => write!(f, "{}", s.name), - } - } -} - -impl std::fmt::Display for SizedType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - SizedType::Vector(v) => write!(f, "{}{}", ir::ScalarType::from(v.scalar), v.len), - SizedType::Matrix(m) => { - write!( - f, - "mat<{}, {}, {}>", - ir::ScalarType::from(m.scalar), - Len::from(m.columns), - Len::from(m.rows) - ) - } - SizedType::Array(a) => write!(f, "Array<{}, {}>", &*a.element, a.len), - SizedType::Atomic(a) => write!(f, "Atomic<{}>", ir::ScalarType::from(a.scalar)), - // TODO(chronicl) figure out scalar type display - SizedType::PackedVec(p) => write!(f, "PackedVec<{:?}, {}>", p.scalar_type, Len::from(p.len)), - SizedType::Struct(s) => write!(f, "{}", s.name), - } - } +impl std::fmt::Display for RuntimeSizedArray { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "Array<{}>", self.element) } } impl std::fmt::Display for ScalarType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.write_str(match self { ScalarType::F16 => "f16", ScalarType::F32 => "f32", diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index 9acbe90..d46fce2 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -475,7 +475,9 @@ impl TypeLayout { } // TODO(chronicl) this should be removed with improved any api for storage/uniform bindings - pub(crate) fn from_store_ty(store_type: ir::StoreType) -> Result { + pub(crate) fn from_store_ty( + store_type: ir::StoreType, + ) -> Result { let t: layoutable::LayoutableType = store_type.try_into()?; Ok(TypeLayout::new_storage_layout_for(t).into()) } diff --git a/shame/src/lib.rs b/shame/src/lib.rs index 3501272..ed8770f 100644 --- a/shame/src/lib.rs +++ b/shame/src/lib.rs @@ -464,58 +464,60 @@ pub mod any { pub use crate::ir::ir_type::StructureFieldNamesMustBeUnique; pub mod layout { + use crate::frontend::rust_types::type_layout; // type layout - pub use crate::frontend::rust_types::type_layout::TypeLayout; - pub use crate::frontend::rust_types::type_layout::TypeRepr; - pub use crate::frontend::rust_types::type_layout::Repr; + pub use type_layout::TypeLayout; + pub use type_layout::TypeRepr; + pub use type_layout::Repr; pub mod repr { - pub use crate::frontend::rust_types::type_layout::repr::Plain; - pub use crate::frontend::rust_types::type_layout::repr::Storage; - pub use crate::frontend::rust_types::type_layout::repr::Uniform; - pub use crate::frontend::rust_types::type_layout::repr::Packed; + use crate::frontend::rust_types::type_layout; + pub use type_layout::repr::Plain; + pub use type_layout::repr::Storage; + pub use type_layout::repr::Uniform; + pub use type_layout::repr::Packed; } - pub use crate::frontend::rust_types::type_layout::TypeLayoutSemantics; - pub use crate::frontend::rust_types::type_layout::StructLayout; - pub use crate::frontend::rust_types::type_layout::FieldLayoutWithOffset; - pub use crate::frontend::rust_types::type_layout::FieldLayout; - pub use crate::frontend::rust_types::type_layout::FieldOptions; - pub use crate::frontend::rust_types::type_layout::ElementLayout; + pub use type_layout::TypeLayoutSemantics; + pub use type_layout::StructLayout; + pub use type_layout::FieldLayoutWithOffset; + pub use type_layout::FieldLayout; + pub use type_layout::FieldOptions; + pub use type_layout::ElementLayout; // layoutable traits - pub use crate::frontend::rust_types::type_layout::layoutable::Layoutable; - pub use crate::frontend::rust_types::type_layout::layoutable::LayoutableSized; + pub use type_layout::layoutable::Layoutable; + pub use type_layout::layoutable::LayoutableSized; // layoutable types - pub use crate::frontend::rust_types::type_layout::layoutable::LayoutableType; - pub use crate::frontend::rust_types::type_layout::layoutable::UnsizedStruct; - pub use crate::frontend::rust_types::type_layout::layoutable::RuntimeSizedArray; - pub use crate::frontend::rust_types::type_layout::layoutable::SizedType; - pub use crate::frontend::rust_types::type_layout::layoutable::Vector; - pub use crate::frontend::rust_types::type_layout::layoutable::Matrix; - pub use crate::frontend::rust_types::type_layout::layoutable::MatrixMajor; - pub use crate::frontend::rust_types::type_layout::layoutable::SizedArray; - pub use crate::frontend::rust_types::type_layout::layoutable::Atomic; - pub use crate::frontend::rust_types::type_layout::layoutable::PackedVector; - pub use crate::frontend::rust_types::type_layout::layoutable::SizedStruct; + pub use type_layout::layoutable::LayoutableType; + pub use type_layout::layoutable::UnsizedStruct; + pub use type_layout::layoutable::RuntimeSizedArray; + pub use type_layout::layoutable::SizedType; + pub use type_layout::layoutable::Vector; + pub use type_layout::layoutable::Matrix; + pub use type_layout::layoutable::MatrixMajor; + pub use type_layout::layoutable::SizedArray; + pub use type_layout::layoutable::Atomic; + pub use type_layout::layoutable::PackedVector; + pub use type_layout::layoutable::SizedStruct; // layoutable type parts - pub use crate::frontend::rust_types::type_layout::layoutable::ScalarType; - pub use crate::frontend::rust_types::type_layout::layoutable::ScalarTypeFp; - pub use crate::frontend::rust_types::type_layout::layoutable::ScalarTypeInteger; - pub use crate::frontend::rust_types::type_layout::layoutable::Len; - pub use crate::frontend::rust_types::type_layout::layoutable::Len2; - pub use crate::frontend::rust_types::type_layout::layoutable::SizedField; - pub use crate::frontend::rust_types::type_layout::layoutable::RuntimeSizedArrayField; - pub use crate::frontend::rust_types::type_layout::layoutable::CanonName; - pub use crate::frontend::rust_types::type_layout::layoutable::SizedOrArray; + pub use type_layout::layoutable::ScalarType; + pub use type_layout::layoutable::ScalarTypeFp; + pub use type_layout::layoutable::ScalarTypeInteger; + pub use type_layout::layoutable::Len; + pub use type_layout::layoutable::Len2; + pub use type_layout::layoutable::SizedField; + pub use type_layout::layoutable::RuntimeSizedArrayField; + pub use type_layout::layoutable::CanonName; + pub use type_layout::layoutable::SizedOrArray; // layout calculation utility - pub use crate::frontend::rust_types::type_layout::LayoutCalculator; - pub use crate::frontend::rust_types::type_layout::layoutable::array_stride; - pub use crate::frontend::rust_types::type_layout::layoutable::array_size; - pub use crate::frontend::rust_types::type_layout::layoutable::array_align; - pub use crate::frontend::rust_types::type_layout::layoutable::FieldOffsets; + pub use type_layout::LayoutCalculator; + pub use type_layout::layoutable::array_stride; + pub use type_layout::layoutable::array_size; + pub use type_layout::layoutable::array_align; + pub use type_layout::layoutable::FieldOffsets; // conversion and builder errors - pub use crate::frontend::rust_types::type_layout::layoutable::LayoutableConversionError; - pub use crate::frontend::rust_types::type_layout::layoutable::ContainsBoolsError; - pub use crate::frontend::rust_types::type_layout::layoutable::builder::IsUnsizedStructError; - pub use crate::frontend::rust_types::type_layout::layoutable::builder::StructFromPartsError; + pub use type_layout::layoutable::ir_compat::LayoutableConversionError; + pub use type_layout::layoutable::ir_compat::ContainsBoolsError; + pub use type_layout::layoutable::builder::IsUnsizedStructError; + pub use type_layout::layoutable::builder::StructFromPartsError; } // runtime binding api From 1aecb7e3725535c4b2d9af3bd628756377ffe715 Mon Sep 17 00:00:00 2001 From: chronicl Date: Sat, 24 May 2025 02:57:09 +0200 Subject: [PATCH 16/32] Make ContainsPackedVecError simple --- .../type_layout/layoutable/ir_compat.rs | 370 ++++++------------ 1 file changed, 123 insertions(+), 247 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs b/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs index 98315b3..fd23aa2 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs @@ -6,115 +6,21 @@ use super::*; #[derive(thiserror::Error, Debug)] pub enum IRConversionError { /// Packed vectors do not exist in the shader type system. - #[error("{0}")] - ContainsPackedVector(#[from] ContainsPackedVectorError), + #[error( + "Type is or contains a packed vector, which does not exist in the shader type system.\n\ + Packed vectors may only be used in vertex buffers." + )] + ContainsPackedVector, /// Struct field names must be unique in the shader type system. #[error("{0}")] DuplicateFieldName(#[from] DuplicateFieldNameError), } -#[derive(Debug)] -pub enum ContainsPackedVectorError { - /// Top level type is a packed vector. - SelfIsPackedVector(LayoutableType), - /// Top level nested Array packed vector, for example Array>>. - InArray(LayoutableType), - /// A struct field is a packed vector or a struct field is a nested Array packed vector. - InStruct { - struct_type: StructKind, - packed_vector_field: usize, - top_level_type: Option, - use_color: bool, - }, -} - -impl std::error::Error for ContainsPackedVectorError {} - -impl std::fmt::Display for ContainsPackedVectorError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let ending = - ", which does not exist in the shader type system.\nPacked vectors may only be used in vertex buffers."; - let (struct_name, sized_fields, last_unsized, packed_vector_field, use_color) = match self { - ContainsPackedVectorError::SelfIsPackedVector(t) => return writeln!(f, "{t} is a packed vector{ending}"), - ContainsPackedVectorError::InArray(a) => return writeln!(f, "{a} contains a packed vector{ending}"), - ContainsPackedVectorError::InStruct { - struct_type, - packed_vector_field, - top_level_type, - use_color, - } => { - let (struct_name, sized_fields, last_unsized) = match struct_type { - StructKind::Sized(s) => (&s.name, s.fields(), None), - StructKind::Unsized(s) => (&s.name, s.sized_fields.as_slice(), Some(&s.last_unsized)), - }; - - if let Some(top_level_type) = top_level_type { - writeln!( - f, - "The struct {struct_name} in {top_level_type} contains a packed vector{ending}" - )?; - } else { - writeln!(f, "{struct_name} contains a packed vector{ending}")?; - } - - ( - struct_name, - sized_fields, - last_unsized, - *packed_vector_field, - *use_color, - ) - } - }; - - - - let indent = " "; - let is_packed_vec = |i| packed_vector_field == i; - let arrow = |i| match is_packed_vec(i) { - true => " <--", - false => "", - }; - let color = |f: &mut Formatter<'_>, i| { - if (use_color && is_packed_vec(i)) { - set_color(f, Some("#508EE3"), false) - } else { - Ok(()) - } - }; - let color_reset = |f: &mut Formatter<'_>, i| { - if use_color && is_packed_vec(i) { - set_color(f, None, false) - } else { - Ok(()) - } - }; - - writeln!(f, "The following struct contains a packed vector:")?; - let header = writeln!(f, "struct {} {{", struct_name); - for (i, field) in sized_fields.iter().enumerate() { - color(f, i)?; - writeln!(f, "{indent}{}: {},{}", field.name, field.ty, arrow(i))?; - color_reset(f, i)?; - } - if let Some(field) = last_unsized { - let i = sized_fields.len(); - color(f, i)?; - writeln!(f, "{indent}{}: {},{}", field.name, field.array, arrow(i))?; - color_reset(f, i)?; - } - writeln!(f, "}}")?; - - Ok(()) - } -} - #[derive(Debug)] pub struct DuplicateFieldNameError { pub struct_type: StructKind, pub first_field: usize, pub second_field: usize, - pub is_top_level: bool, pub use_color: bool, } @@ -173,14 +79,9 @@ impl std::fmt::Display for DuplicateFieldNameError { } fn check_for_duplicate_field_names( - struct_type: StructKind, - is_top_level: bool, -) -> Result { - let (sized_fields, last_unsized) = match &struct_type { - StructKind::Sized(s) => (s.fields(), None), - StructKind::Unsized(s) => (s.sized_fields.as_slice(), Some(&s.last_unsized)), - }; - + sized_fields: &[SizedField], + last_unsized: Option<&RuntimeSizedArrayField>, +) -> Option<(usize, usize)> { // Brute force search > HashMap for the amount of fields // we'd usually deal with. let mut duplicate_fields = None; @@ -191,18 +92,14 @@ fn check_for_duplicate_field_names( break; } } + if let Some(last_unsized) = last_unsized { + if field1.name == last_unsized.name { + duplicate_fields = Some((i, sized_fields.len())); + break; + } + } } - - match duplicate_fields { - Some((first_field, second_field)) => Err(DuplicateFieldNameError { - struct_type, - first_field, - second_field, - is_top_level, - use_color: use_color(), - }), - None => Ok(struct_type), - } + duplicate_fields } #[track_caller] @@ -212,63 +109,33 @@ impl TryFrom for ir::StoreType { type Error = IRConversionError; fn try_from(ty: LayoutableType) -> Result { - // Checking for top level packed vec. - if matches!(ty, LayoutableType::Sized(SizedType::PackedVec(_))) { - return Err(IRConversionError::ContainsPackedVector( - ContainsPackedVectorError::SelfIsPackedVector(ty), - )); - } - // Checking for nested array packed vec. - if is_packed_vec(&ty) { - return Err(IRConversionError::ContainsPackedVector( - ContainsPackedVectorError::InArray(ty), - )); + match ty { + LayoutableType::Sized(s) => Ok(ir::StoreType::Sized(s.try_into()?)), + LayoutableType::RuntimeSizedArray(s) => Ok(ir::StoreType::RuntimeSizedArray(s.element.try_into()?)), + LayoutableType::UnsizedStruct(s) => Ok(ir::StoreType::BufferBlock(s.try_into()?)), } - - fn convert - - todo!() } } -/// Returns true if `ty` is a packed vector or a nesting of Arrays -/// with the last Array directly containing a packed vector. -fn is_packed_vec(ty: &LayoutableType) -> bool { - let mut ty = match ty { - LayoutableType::Sized(s) => s, - LayoutableType::RuntimeSizedArray(a) => &a.element, - LayoutableType::UnsizedStruct(_) => return false, - }; +impl TryFrom for ir::SizedType { + type Error = IRConversionError; - loop { - match ty { - SizedType::PackedVec(_) => return true, - SizedType::Array(a) => ty = &a.element, - _ => return false, - } + fn try_from(host: SizedType) -> Result { + Ok(match host { + SizedType::Vector(v) => ir::SizedType::Vector(v.len, v.scalar.into()), + SizedType::Matrix(m) => ir::SizedType::Matrix(m.columns, m.rows, m.scalar), + SizedType::Array(a) => { + let element = Rc::unwrap_or_clone(a.element); + let converted_element = element.try_into()?; + ir::SizedType::Array(Rc::new(converted_element), a.len) + } + SizedType::Atomic(i) => ir::SizedType::Atomic(i.scalar), + SizedType::PackedVec(_) => return Err(IRConversionError::ContainsPackedVector), + SizedType::Struct(s) => ir::SizedType::Structure(s.try_into()?), + }) } } -// impl TryFrom for ir::SizedType { -// type Error = IRConversionError; - -// fn try_from(host: SizedType) -> Result { -// // Ok(match host { -// // SizedType::Vector(v) => ir::SizedType::Vector(v.len, v.scalar.into()), -// // SizedType::Matrix(m) => ir::SizedType::Matrix(m.columns, m.rows, m.scalar), -// // SizedType::Array(a) => { -// // let element = Rc::unwrap_or_clone(a.element); -// // let converted_element = element.try_into()?; -// // ir::SizedType::Array(Rc::new(converted_element), a.len) -// // } -// // SizedType::Atomic(i) => ir::SizedType::Atomic(i.scalar), -// // SizedType::PackedVec(_) => return Err(IRConversionError::ContainsPackedVector), -// // SizedType::Struct(s) => ir::SizedType::Structure(s.try_into()?), -// // }) -// todo!() -// } -// } - impl From for ir::ScalarType { fn from(scalar_type: ScalarType) -> Self { match scalar_type { @@ -281,86 +148,95 @@ impl From for ir::ScalarType { } } -// impl TryFrom for ir::ir_type::SizedStruct { -// type Error = IRConversionError; - -// fn try_from(host: SizedStruct) -> Result { -// let mut fields: Vec = Vec::new(); - -// for field in host.fields { -// fields.push(field.try_into()?); -// } - -// // has at least one field -// if fields.is_empty() { -// // This should not happen based on SizedStruct's implementation -// return Err(IRConversionError::DuplicateFieldName); -// } - -// let last_field = fields.pop().unwrap(); - -// match ir::ir_type::SizedStruct::new_nonempty(host.name, fields, last_field) { -// Ok(s) => Ok(s), -// Err(StructureFieldNamesMustBeUnique) => Err(IRConversionError::DuplicateFieldName), -// } -// } -// } - -// impl TryFrom for ir::ir_type::BufferBlock { -// type Error = IRConversionError; - -// fn try_from(host: UnsizedStruct) -> Result { -// let mut sized_fields: Vec = Vec::new(); - -// for field in host.sized_fields { -// sized_fields.push(field.try_into()?); -// } - -// let last_unsized = host.last_unsized.try_into()?; - -// match ir::ir_type::BufferBlock::new(host.name, sized_fields, Some(last_unsized)) { -// Ok(b) => Ok(b), -// Err(BufferBlockDefinitionError::FieldNamesMustBeUnique) => Err(IRConversionError::DuplicateFieldName), -// Err(BufferBlockDefinitionError::MustHaveAtLeastOneField) => { -// // This should not happen based on UnsizedStruct's implementation -// Err(IRConversionError::DuplicateFieldName) -// } -// } -// } -// } - -// impl TryFrom for ir::StoreType { -// type Error = IRConversionError; - -// fn try_from(array: RuntimeSizedArray) -> Result { -// Ok(ir::StoreType::RuntimeSizedArray(array.element.try_into()?)) -// } -// } - -// impl TryFrom for ir::ir_type::SizedField { -// type Error = IRConversionError; - -// fn try_from(f: SizedField) -> Result { -// Ok(ir::SizedField::new( -// f.name, -// f.custom_min_size, -// f.custom_min_align, -// f.ty.try_into()?, -// )) -// } -// } - -// impl TryFrom for ir::ir_type::RuntimeSizedArrayField { -// type Error = IRConversionError; - -// fn try_from(f: RuntimeSizedArrayField) -> Result { -// Ok(ir::RuntimeSizedArrayField::new( -// f.name, -// f.custom_min_align, -// f.array.element.try_into()?, -// )) -// } -// } +impl TryFrom for ir::ir_type::SizedStruct { + type Error = IRConversionError; + + fn try_from(ty: SizedStruct) -> Result { + if let Some((first, second)) = check_for_duplicate_field_names(ty.fields(), None) { + return Err(IRConversionError::DuplicateFieldName(DuplicateFieldNameError { + struct_type: StructKind::Sized(ty), + first_field: first, + second_field: second, + use_color: use_color(), + })); + } + + let mut fields: Vec = Vec::new(); + for field in ty.fields { + fields.push(field.try_into()?); + } + let last_field = fields.pop().unwrap(); + + match ir::ir_type::SizedStruct::new_nonempty(ty.name, fields, last_field) { + Ok(s) => Ok(s), + Err(StructureFieldNamesMustBeUnique) => unreachable!("checked above"), + } + } +} + +impl TryFrom for ir::ir_type::BufferBlock { + type Error = IRConversionError; + + fn try_from(ty: UnsizedStruct) -> Result { + if let Some((first, second)) = check_for_duplicate_field_names(&ty.sized_fields, Some(&ty.last_unsized)) { + return Err(IRConversionError::DuplicateFieldName(DuplicateFieldNameError { + struct_type: StructKind::Unsized(ty), + first_field: first, + second_field: second, + use_color: use_color(), + })); + } + + let mut sized_fields: Vec = Vec::new(); + + for field in ty.sized_fields { + sized_fields.push(field.try_into()?); + } + + let last_unsized = ty.last_unsized.try_into()?; + + match ir::ir_type::BufferBlock::new(ty.name, sized_fields, Some(last_unsized)) { + Ok(b) => Ok(b), + Err(BufferBlockDefinitionError::FieldNamesMustBeUnique) => unreachable!("checked above"), + Err(BufferBlockDefinitionError::MustHaveAtLeastOneField) => { + unreachable!("last_unsized is at least one field") + } + } + } +} + +impl TryFrom for ir::StoreType { + type Error = IRConversionError; + + fn try_from(array: RuntimeSizedArray) -> Result { + Ok(ir::StoreType::RuntimeSizedArray(array.element.try_into()?)) + } +} + +impl TryFrom for ir::ir_type::SizedField { + type Error = IRConversionError; + + fn try_from(f: SizedField) -> Result { + Ok(ir::SizedField::new( + f.name, + f.custom_min_size, + f.custom_min_align, + f.ty.try_into()?, + )) + } +} + +impl TryFrom for ir::ir_type::RuntimeSizedArrayField { + type Error = IRConversionError; + + fn try_from(f: RuntimeSizedArrayField) -> Result { + Ok(ir::RuntimeSizedArrayField::new( + f.name, + f.custom_min_align, + f.array.element.try_into()?, + )) + } +} // Conversions from ir types // From 38b535e72992ab946407f7a2d87d9a6d09e26b8e Mon Sep 17 00:00:00 2001 From: chronicl Date: Sat, 24 May 2025 03:09:25 +0200 Subject: [PATCH 17/32] Test LayoutableType -> StoreType conversion --- .../type_layout/layoutable/ir_compat.rs | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs b/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs index fd23aa2..81fd987 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs @@ -86,9 +86,9 @@ fn check_for_duplicate_field_names( // we'd usually deal with. let mut duplicate_fields = None; for (i, field1) in sized_fields.iter().enumerate() { - for (j, field2) in sized_fields[i..].iter().enumerate() { + for (j, field2) in sized_fields.iter().skip(i + 1).enumerate() { if field1.name == field2.name { - duplicate_fields = Some((i, j)); + duplicate_fields = Some((i, i + 1 + j)); break; } } @@ -372,3 +372,28 @@ impl TryFrom for LayoutableType { .into()) } } + + +#[test] +fn test_ir_conversion_error() { + use crate::{f32x1, packed::unorm8x2}; + + let ty: LayoutableType = SizedStruct::new("A", "a", f32x1::layoutable_type_sized()) + .extend("b", f32x1::layoutable_type_sized()) + .extend("a", f32x1::layoutable_type_sized()) + .into(); + let result: Result = ty.try_into(); + assert!(matches!( + result, + Err(IRConversionError::DuplicateFieldName(DuplicateFieldNameError { + struct_type: StructKind::Sized(_), + first_field: 0, + second_field: 2, + .. + })) + )); + + let ty: LayoutableType = SizedStruct::new("A", "a", unorm8x2::layoutable_type_sized()).into(); + let result: Result = ty.try_into(); + assert!(matches!(result, Err(IRConversionError::ContainsPackedVector))); +} From f49aa5b415f3e1acbdcdffc4acb0b3ee79983eb9 Mon Sep 17 00:00:00 2001 From: chronicl Date: Sat, 24 May 2025 03:53:43 +0200 Subject: [PATCH 18/32] Make align of all types 1 when Repr::Packed --- .../rust_types/type_layout/builder.rs | 11 +- .../src/frontend/rust_types/type_layout/eq.rs | 4 +- .../type_layout/layoutable/align_size.rs | 66 +++++---- .../frontend/rust_types/type_layout/mod.rs | 127 +++++++++--------- shame/src/ir/ir_type/tensor.rs | 7 +- 5 files changed, 111 insertions(+), 104 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/builder.rs b/shame/src/frontend/rust_types/type_layout/builder.rs index 8e758c8..fdaaaa7 100644 --- a/shame/src/frontend/rust_types/type_layout/builder.rs +++ b/shame/src/frontend/rust_types/type_layout/builder.rs @@ -64,14 +64,14 @@ impl TypeLayout { fn from_sized_type(ty: SizedType, repr: Repr) -> Self { let (size, align, tls) = match &ty { - SizedType::Vector(v) => (v.byte_size(), v.align(), TLS::Vector(*v)), + SizedType::Vector(v) => (v.byte_size(), v.align(repr), TLS::Vector(*v)), SizedType::Atomic(a) => ( a.byte_size(), - a.align(), + a.align(repr), TLS::Vector(Vector::new(a.scalar.into(), ir::Len::X1)), ), SizedType::Matrix(m) => ( - m.byte_size(MatrixMajor::Row), + m.byte_size(repr, MatrixMajor::Row), m.align(repr, MatrixMajor::Row), TLS::Matrix(*m), ), @@ -86,7 +86,7 @@ impl TypeLayout { Some(a.len.get()), ), ), - SizedType::PackedVec(v) => (v.byte_size().as_u64(), v.align(), TLS::PackedVector(*v)), + SizedType::PackedVec(v) => (v.byte_size().as_u64(), v.align(repr), TLS::PackedVector(*v)), SizedType::Struct(s) => { let mut field_offsets = s.field_offsets(repr); let fields = (&mut field_offsets) @@ -137,7 +137,6 @@ impl TypeLayout { ) } - fn from_runtime_sized_array(ty: RuntimeSizedArray, repr: Repr) -> Self { Self::new( None, @@ -206,7 +205,7 @@ impl<'a> TryFrom<&'a TypeLayout> for TypeLayout { field_name: s_field.field.name.clone(), field_index: i, actual_offset: s_field.rel_byte_offset, - expected_alignment: u_field.field.byte_align(), + expected_alignment: u_field.field.align(), is_top_level, })); } diff --git a/shame/src/frontend/rust_types/type_layout/eq.rs b/shame/src/frontend/rust_types/type_layout/eq.rs index c194666..5ece418 100644 --- a/shame/src/frontend/rust_types/type_layout/eq.rs +++ b/shame/src/frontend/rust_types/type_layout/eq.rs @@ -129,7 +129,7 @@ impl LayoutMismatch { "{a_name}{SEP}{indent}{:3} {}: {a_ty_string} align={}", a_field.rel_byte_offset, a_field.field.name, - a_field.field.byte_align().as_u32() + a_field.field.align().as_u32() ); color_b(f); writeln!( @@ -137,7 +137,7 @@ impl LayoutMismatch { "{b_name}{SEP}{indent}{:3} {}: {b_ty_string} align={}", b_field.rel_byte_offset, b_field.field.name, - b_field.field.byte_align().as_u32() + b_field.field.align().as_u32() ); color_reset(f); writeln!( diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index 97683e9..e080a97 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -4,6 +4,8 @@ use super::*; // Size and align of layoutable types // // https://www.w3.org/TR/WGSL/#address-space-layout-constraints // +pub(crate) const PACKED_ALIGN: U32PowerOf2 = U32PowerOf2::_1; + impl LayoutableType { /// This is expensive for structs. Prefer `byte_size_and_align` if you also need the align. pub fn byte_size(&self, repr: Repr) -> Option { @@ -41,7 +43,7 @@ impl SizedType { match self { SizedType::Array(a) => a.byte_size(repr), SizedType::Vector(v) => v.byte_size(), - SizedType::Matrix(m) => m.byte_size(MatrixMajor::Row), + SizedType::Matrix(m) => m.byte_size(repr, MatrixMajor::Row), SizedType::Atomic(a) => a.byte_size(), SizedType::PackedVec(v) => u8::from(v.byte_size()) as u64, SizedType::Struct(s) => s.byte_size_and_align(repr).0, @@ -52,10 +54,10 @@ impl SizedType { pub fn align(&self, repr: Repr) -> U32PowerOf2 { match self { SizedType::Array(a) => a.align(repr), - SizedType::Vector(v) => v.align(), + SizedType::Vector(v) => v.align(repr), SizedType::Matrix(m) => m.align(repr, MatrixMajor::Row), - SizedType::Atomic(a) => a.align(), - SizedType::PackedVec(v) => v.align(), + SizedType::Atomic(a) => a.align(repr), + SizedType::PackedVec(v) => v.align(repr), SizedType::Struct(s) => s.byte_size_and_align(repr).1, } } @@ -79,12 +81,12 @@ impl SizedStruct { FieldOffsets { fields: &self.fields, field_index: 0, - calc: LayoutCalculator::new(matches!(repr, Repr::Packed)), + calc: LayoutCalculator::new(repr), repr, } } - /// Returns (byte_size, byte_align) + /// Returns (byte_size, align) /// /// ### Careful! /// This is an expensive operation as it calculates byte size and align from scratch. @@ -117,25 +119,11 @@ impl Iterator for FieldOffsets<'_> { fn next(&mut self) -> Option { self.field_index += 1; self.fields.get(self.field_index - 1).map(|field| { - let (size, align) = match &field.ty { - SizedType::Struct(s) => { - let (size, align) = s.byte_size_and_align(self.repr); - match self.repr { - // Packedness is ensured by the `LayoutCalculator`. - Repr::Storage | Repr::Packed => (size, align), - // The uniform address space requires that: - // - If a structure member itself has a structure type S, then the number of - // bytes between the start of that member and the start of any following - // member must be at least roundUp(16, SizeOf(S)). - // -> We need to adjust size. - Repr::Uniform => (round_up(16, size), align), - } - } - non_struct => non_struct.byte_size_and_align(self.repr), - }; + let (size, align) = field.ty.byte_size_and_align(self.repr); + let is_struct = matches!(field.ty, SizedType::Struct(_)); self.calc - .extend(size, align, field.custom_min_size, field.custom_min_align) + .extend(size, align, field.custom_min_size, field.custom_min_align, is_struct) }) } } @@ -157,7 +145,7 @@ impl UnsizedStruct { FieldOffsets { fields: &self.sized_fields, field_index: 0, - calc: LayoutCalculator::new(matches!(repr, Repr::Packed)), + calc: LayoutCalculator::new(repr), repr, } } @@ -187,8 +175,9 @@ impl UnsizedStruct { const fn struct_align(align: U32PowerOf2, repr: Repr) -> U32PowerOf2 { match repr { // Packedness is ensured by the `LayoutCalculator`. - Repr::Storage | Repr::Packed => align, + Repr::Storage => align, Repr::Uniform => round_up_align(U32PowerOf2::_16, align), + Repr::Packed => PACKED_ALIGN, } } @@ -198,12 +187,15 @@ impl Vector { pub const fn byte_size(&self) -> u64 { self.len.as_u64() * self.scalar.byte_size() } - pub const fn align(&self) -> U32PowerOf2 { + pub const fn align(&self, repr: Repr) -> U32PowerOf2 { + if repr.is_packed() { + return PACKED_ALIGN; + } + let len = match self.len { Len::X1 | Len::X2 | Len::X4 => self.len.as_u32(), Len::X3 => 4, }; - // len * self.scalar.align() = power of 2 * power of 2 = power of 2 U32PowerOf2::try_from_u32(len * self.scalar.align().as_u32()).unwrap() } @@ -237,15 +229,15 @@ pub enum MatrixMajor { #[allow(missing_docs)] impl Matrix { - pub const fn byte_size(&self, major: MatrixMajor) -> u64 { + pub const fn byte_size(&self, repr: Repr, major: MatrixMajor) -> u64 { let (vec, array_len) = self.as_vector_array(major); - let array_stride = array_stride(vec.align(), vec.byte_size()); + let array_stride = array_stride(vec.align(repr), vec.byte_size()); array_size(array_stride, array_len) } pub const fn align(&self, repr: Repr, major: MatrixMajor) -> U32PowerOf2 { let (vec, _) = self.as_vector_array(major); - array_align(vec.align(), repr) + array_align(vec.align(repr), repr) } const fn as_vector_array(&self, major: MatrixMajor) -> (Vector, NonZeroU32) { @@ -266,7 +258,12 @@ impl Matrix { #[allow(missing_docs)] impl Atomic { pub const fn byte_size(&self) -> u64 { self.scalar.as_scalar_type().byte_size() } - pub const fn align(&self) -> U32PowerOf2 { self.scalar.as_scalar_type().align() } + pub const fn align(&self, repr: Repr) -> U32PowerOf2 { + if repr.is_packed() { + return PACKED_ALIGN; + } + self.scalar.as_scalar_type().align() + } } #[allow(missing_docs)] @@ -290,8 +287,9 @@ pub const fn array_size(array_stride: u64, len: NonZeroU32) -> u64 { array_strid pub const fn array_align(element_align: U32PowerOf2, repr: Repr) -> U32PowerOf2 { match repr { // Packedness is ensured by the `LayoutCalculator`. - Repr::Storage | Repr::Packed => element_align, + Repr::Storage => element_align, Repr::Uniform => U32PowerOf2::try_from_u32(round_up(16, element_align.as_u64()) as u32).unwrap(), + Repr::Packed => PACKED_ALIGN, } } @@ -312,12 +310,12 @@ impl RuntimeSizedArray { #[allow(missing_docs)] impl SizedField { pub fn byte_size(&self, repr: Repr) -> u64 { self.ty.byte_size(repr) } - pub fn byte_align(&self, repr: Repr) -> U32PowerOf2 { self.ty.align(repr) } + pub fn align(&self, repr: Repr) -> U32PowerOf2 { self.ty.align(repr) } } #[allow(missing_docs)] impl RuntimeSizedArrayField { - pub fn byte_align(&self, repr: Repr) -> U32PowerOf2 { self.array.align(repr) } + pub fn align(&self, repr: Repr) -> U32PowerOf2 { self.array.align(repr) } } pub const fn round_up(multiple_of: u64, n: u64) -> u64 { diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index d46fce2..4e95718 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -18,7 +18,7 @@ use crate::{ Len, }, }; -use layoutable::{LayoutableType, Matrix, Vector}; +use layoutable::{align_size::PACKED_ALIGN, LayoutableType, Matrix, Vector}; pub(crate) mod builder; pub(crate) mod eq; @@ -144,6 +144,15 @@ pub mod repr { Packed, } + impl Repr { + /// True if `Repr::Storage` + pub const fn is_storage(self) -> bool { matches!(self, Repr::Storage) } + /// True if `Repr::Uniform` + pub const fn is_uniform(self) -> bool { matches!(self, Repr::Uniform) } + /// True if `Repr::Packed` + pub const fn is_packed(self) -> bool { matches!(self, Repr::Packed) } + } + macro_rules! type_repr { ($($constraint:ident),*) => { $( @@ -215,65 +224,56 @@ pub(in super::super::rust_types) mod type_layout_internal { pub struct LayoutCalculator { next_offset_min: u64, align: U32PowerOf2, - packed: bool, + repr: Repr, } impl LayoutCalculator { /// Creates a new `LayoutCalculator`, which calculates the size, align and /// the field offsets of a gpu struct. - pub const fn new(packed: bool) -> Self { + pub const fn new(repr: Repr) -> Self { Self { next_offset_min: 0, align: U32PowerOf2::_1, - packed, + repr, } } - /// Extends the layout by a field given it's size and align. + /// Extends the layout by a field. + /// + /// `is_struct` must be true if the field is a struct. /// /// Returns the field's offset. pub const fn extend( &mut self, field_size: u64, - field_align: U32PowerOf2, + mut field_align: U32PowerOf2, custom_min_size: Option, custom_min_align: Option, + is_struct: bool, ) -> u64 { - let size = FieldLayout::calculate_byte_size(field_size, custom_min_size); - let align = FieldLayout::calculate_align(field_align, custom_min_align); + // Just in case the user didn't already do this. + if self.repr.is_packed() { + field_align = PACKED_ALIGN; + } + + let size = Self::calculate_byte_size(field_size, custom_min_size); + let align = Self::calculate_align(field_align, custom_min_align); let offset = self.next_field_offset(align, custom_min_align); - self.next_offset_min = offset + size; + self.next_offset_min = match (self.repr, is_struct) { + // The uniform address space requires that: + // - If a structure member itself has a structure type S, then the number of + // bytes between the start of that member and the start of any following + // member must be at least roundUp(16, SizeOf(S)). + (Repr::Uniform, true) => round_up(16, offset + size), + _ => offset + size, + }; self.align = self.align.max(align); offset } - /// Extends the layout by a field given it's size and align. If the field - /// is unsized, pass `None` as it's size. - /// - /// Returns (byte size, byte align, last field offset). - /// - /// `self` is consumed, so that no further fields may be extended, because - /// only the last field may be unsized. - pub const fn extend_maybe_unsized( - mut self, - field_size: Option, - field_align: U32PowerOf2, - custom_min_size: Option, - custom_min_align: Option, - ) -> (Option, U32PowerOf2, u64) { - if let Some(size) = field_size { - let offset = self.extend(size, field_align, custom_min_size, custom_min_align); - (Some(self.byte_size()), self.align(), offset) - } else { - let (offset, align) = self.extend_unsized(field_align, custom_min_align); - (None, align, offset) - } - } - - - /// Extends the layout by an unsized field given it's align. + /// Extends the layout by an runtime sized array field given it's align. /// /// Returns (last field offset, align) /// @@ -281,10 +281,15 @@ impl LayoutCalculator { /// only the last field may be unsized. pub const fn extend_unsized( mut self, - field_align: U32PowerOf2, + mut field_align: U32PowerOf2, custom_min_align: Option, ) -> (u64, U32PowerOf2) { - let align = FieldLayout::calculate_align(field_align, custom_min_align); + // Just in case the user didn't already do this. + if self.repr.is_packed() { + field_align = PACKED_ALIGN; + } + + let align = Self::calculate_align(field_align, custom_min_align); let offset = self.next_field_offset(align, custom_min_align); self.align = self.align.max(align); @@ -306,10 +311,29 @@ impl LayoutCalculator { /// field_align should already respect field_custom_min_align. /// field_custom_min_align is used to overwrite packing if self is packed. const fn next_field_offset(&self, field_align: U32PowerOf2, field_custom_min_align: Option) -> u64 { - match (self.packed, field_custom_min_align) { - (true, None) => self.next_offset_min, - (true, Some(custom_align)) => round_up(custom_align.as_u64(), self.next_offset_min), - (false, _) => round_up(field_align.as_u64(), self.next_offset_min), + match (self.repr, field_custom_min_align) { + (Repr::Packed, None) => self.next_offset_min, + (Repr::Packed, Some(custom_align)) => round_up(custom_align.as_u64(), self.next_offset_min), + (_, _) => round_up(field_align.as_u64(), self.next_offset_min), + } + } + + const fn calculate_byte_size(byte_size: u64, custom_min_size: Option) -> u64 { + // const byte_size.max(custom_min_size.unwrap_or(0)) + if let Some(min_size) = custom_min_size { + if min_size > byte_size { + return min_size; + } + } + byte_size + } + + const fn calculate_align(align: U32PowerOf2, custom_min_align: Option) -> U32PowerOf2 { + // const align.max(custom_min_align.unwrap_or(U32PowerOf2::_1)) + if let Some(min_align) = custom_min_align { + align.max(min_align) + } else { + align } } } @@ -499,30 +523,11 @@ impl FieldLayout { fn byte_size(&self) -> Option { self.ty .byte_size() - .map(|byte_size| Self::calculate_byte_size(byte_size, self.custom_min_size.0)) + .map(|byte_size| LayoutCalculator::calculate_byte_size(byte_size, self.custom_min_size.0)) } /// The alignment of the field with `custom_min_align` taken into account. - fn byte_align(&self) -> U32PowerOf2 { Self::calculate_align(self.ty.align(), self.custom_min_align.0) } - - const fn calculate_byte_size(byte_size: u64, custom_min_size: Option) -> u64 { - // const byte_size.max(custom_min_size.unwrap_or(0)) - if let Some(min_size) = custom_min_size { - if min_size > byte_size { - return min_size; - } - } - byte_size - } - - const fn calculate_align(align: U32PowerOf2, custom_min_align: Option) -> U32PowerOf2 { - // const align.max(custom_min_align.unwrap_or(U32PowerOf2::_1)) - if let Some(min_align) = custom_min_align { - align.max(min_align) - } else { - align - } - } + fn align(&self) -> U32PowerOf2 { LayoutCalculator::calculate_align(self.ty.align(), self.custom_min_align.0) } } /// Options for the field of a struct. diff --git a/shame/src/ir/ir_type/tensor.rs b/shame/src/ir/ir_type/tensor.rs index e5ded39..9783eaf 100644 --- a/shame/src/ir/ir_type/tensor.rs +++ b/shame/src/ir/ir_type/tensor.rs @@ -3,6 +3,7 @@ use std::{fmt::Display, num::NonZeroU32}; use crate::{ any::U32PowerOf2, common::floating_point::{f16, f32_eq_where_nans_are_equal, f64_eq_where_nans_are_equal}, + frontend::rust_types::type_layout::{self, layoutable::align_size::PACKED_ALIGN}, ir::Comp4, }; @@ -529,7 +530,11 @@ impl PackedVector { } } - pub fn align(&self) -> U32PowerOf2 { + pub fn align(&self, repr: type_layout::Repr) -> U32PowerOf2 { + if repr.is_packed() { + return PACKED_ALIGN; + } + let align = match self.byte_size() { PackedVectorByteSize::_2 => SizedType::Vector(Len::X1, ScalarType::F16).align(), PackedVectorByteSize::_4 => SizedType::Vector(Len::X1, ScalarType::U32).align(), From 1d3aadc386db15595cf2245249f4d8b4c298d7e3 Mon Sep 17 00:00:00 2001 From: chronicl Date: Sat, 24 May 2025 20:07:41 +0200 Subject: [PATCH 19/32] Fix shame::Struct layout trait impls --- shame/src/frontend/rust_types/struct_.rs | 16 ++++++---------- .../type_layout/layoutable/align_size.rs | 6 +++--- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/shame/src/frontend/rust_types/struct_.rs b/shame/src/frontend/rust_types/struct_.rs index 14e4c6e..aaa82aa 100644 --- a/shame/src/frontend/rust_types/struct_.rs +++ b/shame/src/frontend/rust_types/struct_.rs @@ -135,19 +135,15 @@ impl Deref for Struct { fn deref(&self) -> &Self::Target { &self.fields } } -impl LayoutableSized for Struct { - fn layoutable_type_sized() -> layoutable::SizedType { - layoutable::SizedStruct::try_from(T::get_sizedstruct_type()) - .expect("no bools") - .into() - } +impl LayoutableSized for Struct { + fn layoutable_type_sized() -> layoutable::SizedType { T::layoutable_type_sized() } } -impl Layoutable for Struct { - fn layoutable_type() -> layoutable::LayoutableType { Self::layoutable_type_sized().into() } +impl Layoutable for Struct { + fn layoutable_type() -> layoutable::LayoutableType { T::layoutable_type() } } -impl GpuLayout for Struct { - fn gpu_repr() -> type_layout::Repr { type_layout::Repr::Storage } +impl GpuLayout for Struct { + fn gpu_repr() -> type_layout::Repr { T::gpu_repr() } fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { T::cpu_type_name_and_layout().map(|x| x.map(|(name, l)| (format!("Struct<{name}>").into(), l))) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index e080a97..21204a8 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -92,8 +92,8 @@ impl SizedStruct { /// This is an expensive operation as it calculates byte size and align from scratch. /// If you also need field offsets, use [`SizedStruct::field_offsets`] instead and /// read the documentation of [`FieldOffsets`] on how to obtain the byte size and align from it. - pub fn byte_size_and_align(&self, layout: Repr) -> (u64, U32PowerOf2) { - let mut field_offsets = self.field_offsets(layout); + pub fn byte_size_and_align(&self, repr: Repr) -> (u64, U32PowerOf2) { + let mut field_offsets = self.field_offsets(repr); (&mut field_offsets).count(); // &mut so it doesn't consume (field_offsets.byte_size(), field_offsets.align()) } @@ -288,7 +288,7 @@ pub const fn array_align(element_align: U32PowerOf2, repr: Repr) -> U32PowerOf2 match repr { // Packedness is ensured by the `LayoutCalculator`. Repr::Storage => element_align, - Repr::Uniform => U32PowerOf2::try_from_u32(round_up(16, element_align.as_u64()) as u32).unwrap(), + Repr::Uniform => round_up_align(U32PowerOf2::_16, element_align), Repr::Packed => PACKED_ALIGN, } } From ea6f659ef5ef5ce14366421241bed5a14e8ac0bd Mon Sep 17 00:00:00 2001 From: chronicl Date: Sun, 25 May 2025 01:17:19 +0200 Subject: [PATCH 20/32] Move LayoutCalculator --- .../type_layout/layoutable/align_size.rs | 125 ++++++++++++++++- .../rust_types/type_layout/layoutable/mod.rs | 2 +- .../frontend/rust_types/type_layout/mod.rs | 128 +----------------- shame/src/lib.rs | 2 +- 4 files changed, 130 insertions(+), 127 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index 21204a8..5213c05 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -1,4 +1,4 @@ -use super::super::{LayoutCalculator, Repr}; +use super::super::{Repr}; use super::*; // Size and align of layoutable types // @@ -336,3 +336,126 @@ pub const fn round_up_align(multiple_of: U32PowerOf2, n: U32PowerOf2) -> U32Powe // In both cases rounded_up is a power of 2 U32PowerOf2::try_from_u32(rounded_up as u32).unwrap() } + +/// `LayoutCalculator` helps calculate the size, align and the field offsets of a struct. +/// +/// If `LayoutCalculator` is created with `packed = true`, provided `field_align`s +/// are ignored and the field is inserted directly after the previous field. However, +/// a `custom_min_align` that is `Some` overwrites the "packedness" of the field. +#[derive(Debug, Clone)] +pub struct LayoutCalculator { + next_offset_min: u64, + align: U32PowerOf2, + repr: Repr, +} + +impl LayoutCalculator { + /// Creates a new `LayoutCalculator`, which calculates the size, align and + /// the field offsets of a gpu struct. + pub const fn new(repr: Repr) -> Self { + Self { + next_offset_min: 0, + align: U32PowerOf2::_1, + repr, + } + } + + /// Extends the layout by a field. + /// + /// `is_struct` must be true if the field is a struct. + /// + /// Returns the field's offset. + pub const fn extend( + &mut self, + field_size: u64, + mut field_align: U32PowerOf2, + custom_min_size: Option, + custom_min_align: Option, + is_struct: bool, + ) -> u64 { + // Just in case the user didn't already do this. + if self.repr.is_packed() { + field_align = PACKED_ALIGN; + } + + let size = Self::calculate_byte_size(field_size, custom_min_size); + let align = Self::calculate_align(field_align, custom_min_align); + + let offset = self.next_field_offset(align, custom_min_align); + self.next_offset_min = match (self.repr, is_struct) { + // The uniform address space requires that: + // - If a structure member itself has a structure type S, then the number of + // bytes between the start of that member and the start of any following + // member must be at least roundUp(16, SizeOf(S)). + (Repr::Uniform, true) => round_up(16, offset + size), + _ => offset + size, + }; + self.align = self.align.max(align); + + offset + } + + /// Extends the layout by an runtime sized array field given it's align. + /// + /// Returns (last field offset, align) + /// + /// `self` is consumed, so that no further fields may be extended, because + /// only the last field may be unsized. + pub const fn extend_unsized( + mut self, + mut field_align: U32PowerOf2, + custom_min_align: Option, + ) -> (u64, U32PowerOf2) { + // Just in case the user didn't already do this. + if self.repr.is_packed() { + field_align = PACKED_ALIGN; + } + + let align = Self::calculate_align(field_align, custom_min_align); + + let offset = self.next_field_offset(align, custom_min_align); + self.align = self.align.max(align); + + (offset, self.align) + } + + /// Returns the byte size of the struct. + // wgsl spec: + // roundUp(AlignOf(S), justPastLastMember) + // where justPastLastMember = OffsetOfMember(S,N) + SizeOfMember(S,N) + // + // self.next_offset_min is justPastLastMember already. + pub const fn byte_size(&self) -> u64 { round_up(self.align.as_u64(), self.next_offset_min) } + + /// Returns the align of the struct. + pub const fn align(&self) -> U32PowerOf2 { self.align } + + /// field_align should already respect field_custom_min_align. + /// field_custom_min_align is used to overwrite packing if self is packed. + const fn next_field_offset(&self, field_align: U32PowerOf2, field_custom_min_align: Option) -> u64 { + match (self.repr, field_custom_min_align) { + (Repr::Packed, None) => self.next_offset_min, + (Repr::Packed, Some(custom_align)) => round_up(custom_align.as_u64(), self.next_offset_min), + (_, _) => round_up(field_align.as_u64(), self.next_offset_min), + } + } + + pub(crate) const fn calculate_byte_size(byte_size: u64, custom_min_size: Option) -> u64 { + // const byte_size.max(custom_min_size.unwrap_or(0)) + if let Some(min_size) = custom_min_size { + if min_size > byte_size { + return min_size; + } + } + byte_size + } + + pub(crate) const fn calculate_align(align: U32PowerOf2, custom_min_align: Option) -> U32PowerOf2 { + // const align.max(custom_min_align.unwrap_or(U32PowerOf2::_1)) + if let Some(min_align) = custom_min_align { + align.max(min_align) + } else { + align + } + } +} diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs index 8016763..16a2c93 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs @@ -16,7 +16,7 @@ pub(crate) mod align_size; pub(crate) mod builder; pub(crate) mod ir_compat; -pub use align_size::{FieldOffsets, MatrixMajor, array_size, array_stride, array_align}; +pub use align_size::{FieldOffsets, MatrixMajor, LayoutCalculator, array_size, array_stride, array_align}; pub use builder::SizedOrArray; /// Types that have a defined memory layout. diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index 4e95718..4a1a520 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -18,7 +18,10 @@ use crate::{ Len, }, }; -use layoutable::{align_size::PACKED_ALIGN, LayoutableType, Matrix, Vector}; +use layoutable::{ + align_size::{LayoutCalculator, PACKED_ALIGN}, + LayoutableType, Matrix, Vector, +}; pub(crate) mod builder; pub(crate) mod eq; @@ -215,129 +218,6 @@ pub(in super::super::rust_types) mod type_layout_internal { } } -/// `LayoutCalculator` helps calculate the size, align and the field offsets of a struct. -/// -/// If `LayoutCalculator` is created with `packed = true`, provided `field_align`s -/// are ignored and the field is inserted directly after the previous field. However, -/// a `custom_min_align` that is `Some` overwrites the "packedness" of the field. -#[derive(Debug, Clone)] -pub struct LayoutCalculator { - next_offset_min: u64, - align: U32PowerOf2, - repr: Repr, -} - -impl LayoutCalculator { - /// Creates a new `LayoutCalculator`, which calculates the size, align and - /// the field offsets of a gpu struct. - pub const fn new(repr: Repr) -> Self { - Self { - next_offset_min: 0, - align: U32PowerOf2::_1, - repr, - } - } - - /// Extends the layout by a field. - /// - /// `is_struct` must be true if the field is a struct. - /// - /// Returns the field's offset. - pub const fn extend( - &mut self, - field_size: u64, - mut field_align: U32PowerOf2, - custom_min_size: Option, - custom_min_align: Option, - is_struct: bool, - ) -> u64 { - // Just in case the user didn't already do this. - if self.repr.is_packed() { - field_align = PACKED_ALIGN; - } - - let size = Self::calculate_byte_size(field_size, custom_min_size); - let align = Self::calculate_align(field_align, custom_min_align); - - let offset = self.next_field_offset(align, custom_min_align); - self.next_offset_min = match (self.repr, is_struct) { - // The uniform address space requires that: - // - If a structure member itself has a structure type S, then the number of - // bytes between the start of that member and the start of any following - // member must be at least roundUp(16, SizeOf(S)). - (Repr::Uniform, true) => round_up(16, offset + size), - _ => offset + size, - }; - self.align = self.align.max(align); - - offset - } - - /// Extends the layout by an runtime sized array field given it's align. - /// - /// Returns (last field offset, align) - /// - /// `self` is consumed, so that no further fields may be extended, because - /// only the last field may be unsized. - pub const fn extend_unsized( - mut self, - mut field_align: U32PowerOf2, - custom_min_align: Option, - ) -> (u64, U32PowerOf2) { - // Just in case the user didn't already do this. - if self.repr.is_packed() { - field_align = PACKED_ALIGN; - } - - let align = Self::calculate_align(field_align, custom_min_align); - - let offset = self.next_field_offset(align, custom_min_align); - self.align = self.align.max(align); - - (offset, self.align) - } - - /// Returns the byte size of the struct. - // wgsl spec: - // roundUp(AlignOf(S), justPastLastMember) - // where justPastLastMember = OffsetOfMember(S,N) + SizeOfMember(S,N) - // - // self.next_offset_min is justPastLastMember already. - pub const fn byte_size(&self) -> u64 { round_up(self.align.as_u64(), self.next_offset_min) } - - /// Returns the align of the struct. - pub const fn align(&self) -> U32PowerOf2 { self.align } - - /// field_align should already respect field_custom_min_align. - /// field_custom_min_align is used to overwrite packing if self is packed. - const fn next_field_offset(&self, field_align: U32PowerOf2, field_custom_min_align: Option) -> u64 { - match (self.repr, field_custom_min_align) { - (Repr::Packed, None) => self.next_offset_min, - (Repr::Packed, Some(custom_align)) => round_up(custom_align.as_u64(), self.next_offset_min), - (_, _) => round_up(field_align.as_u64(), self.next_offset_min), - } - } - - const fn calculate_byte_size(byte_size: u64, custom_min_size: Option) -> u64 { - // const byte_size.max(custom_min_size.unwrap_or(0)) - if let Some(min_size) = custom_min_size { - if min_size > byte_size { - return min_size; - } - } - byte_size - } - - const fn calculate_align(align: U32PowerOf2, custom_min_align: Option) -> U32PowerOf2 { - // const align.max(custom_min_align.unwrap_or(U32PowerOf2::_1)) - if let Some(min_align) = custom_min_align { - align.max(min_align) - } else { - align - } - } -} - /// a sized or unsized struct type with 0 or more fields #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct StructLayout { diff --git a/shame/src/lib.rs b/shame/src/lib.rs index ed8770f..b5dac06 100644 --- a/shame/src/lib.rs +++ b/shame/src/lib.rs @@ -508,7 +508,7 @@ pub mod any { pub use type_layout::layoutable::CanonName; pub use type_layout::layoutable::SizedOrArray; // layout calculation utility - pub use type_layout::LayoutCalculator; + pub use type_layout::layoutable::LayoutCalculator; pub use type_layout::layoutable::array_stride; pub use type_layout::layoutable::array_size; pub use type_layout::layoutable::array_align; From c4456f2c30a90bed85d6e011dbd0288fa12cee87 Mon Sep 17 00:00:00 2001 From: chronicl Date: Mon, 26 May 2025 06:56:17 +0200 Subject: [PATCH 21/32] Split TypeLayout into TypeLayout and GpuTypeLayout --- examples/type_layout/src/main.rs | 109 +--- shame/src/common/proc_macro_reexports.rs | 1 + shame/src/common/proc_macro_utils.rs | 46 +- shame/src/common/small_vec_actual.rs | 17 + shame/src/frontend/encoding/io_iter.rs | 2 +- shame/src/frontend/rust_types/array.rs | 5 +- shame/src/frontend/rust_types/atomic.rs | 4 +- .../src/frontend/rust_types/layout_traits.rs | 25 +- shame/src/frontend/rust_types/mat.rs | 4 +- shame/src/frontend/rust_types/packed_vec.rs | 6 +- shame/src/frontend/rust_types/struct_.rs | 4 +- .../rust_types/type_layout/builder.rs | 494 ---------------- .../rust_types/type_layout/construction.rs | 555 ++++++++++++++++++ .../src/frontend/rust_types/type_layout/eq.rs | 16 +- .../type_layout/layoutable/align_size.rs | 27 +- .../rust_types/type_layout/layoutable/mod.rs | 2 +- .../frontend/rust_types/type_layout/mod.rs | 136 ++--- shame/src/frontend/rust_types/type_traits.rs | 2 +- shame/src/frontend/rust_types/vec.rs | 3 +- shame/src/ir/pipeline/wip_pipeline.rs | 3 +- shame/src/lib.rs | 2 +- shame_derive/src/derive_layout.rs | 9 +- shame_derive/src/util.rs | 4 - 23 files changed, 728 insertions(+), 748 deletions(-) delete mode 100644 shame/src/frontend/rust_types/type_layout/builder.rs create mode 100644 shame/src/frontend/rust_types/type_layout/construction.rs diff --git a/examples/type_layout/src/main.rs b/examples/type_layout/src/main.rs index 11b99aa..35830bc 100644 --- a/examples/type_layout/src/main.rs +++ b/examples/type_layout/src/main.rs @@ -7,7 +7,7 @@ use shame::{ self, layout::{ self, FieldOptions, LayoutableSized, Len, RuntimeSizedArrayField, ScalarType, SizedField, SizedType, - UnsizedStruct, Vector, + UnsizedStruct, Vector, GpuTypeLayout, }, U32PowerOf2, }, @@ -16,7 +16,7 @@ use shame::{ }; fn main() { - // We'll start by building a `TypeLayout`, which for this Vertex type. + // We'll start by replicating this struct using `any::layout` types. #[derive(GpuLayout)] struct Vertex { position: f32x3, @@ -29,105 +29,28 @@ fn main() { let sized_struct = SizedStruct::new("Vertex", "position", f32x3::layoutable_type_sized()) .extend("normal", f32x3::layoutable_type_sized()) .extend("uv", f32x1::layoutable_type_sized()); - // A layout that follows the storage layout rules - let layout: TypeLayout = TypeLayout::new_layout_for(sized_struct.clone(), Repr::Storage); - // Or we can get a TypeLayout, which guarantees the storage layout rules. - let layout_storage: TypeLayout = TypeLayout::new_storage_layout_for(sized_struct.clone()); - assert_eq!(layout, layout_storage); - // Or we get it as a packed layout - let layout_packed: TypeLayout = TypeLayout::new_packed_layout_for(sized_struct); - assert_ne!(layout_storage, layout_packed); + let storage = GpuTypeLayout::::new(sized_struct.clone()); + let packed = GpuTypeLayout::::new(sized_struct); + assert_ne!(storage.layout(), packed.layout()); - // Now we'll replicate the layout of this struct - #[derive(GpuLayout)] - struct A { - a: f32x4, - b: f32x3, - c: Array, - } - - // By default structs are #[gpu_repr(Storage)], which means that it follows - // the wgsl storage layout rules (std430). To obtain a corresponding TypeLayout - // we first need to build a `CpuShareableType`, in our case an `UnsizedStruct`. - let unsized_struct = UnsizedStruct { - name: "A".into(), - sized_fields: vec![ - SizedField::new("a", Vector::new(ScalarType::F32, Len::X4)), - SizedField::new("b", f32x3::layoutable_type_sized()), - ], - last_unsized: RuntimeSizedArrayField::new("c", None, f32x1::layoutable_type_sized()), - }; - // And now we can get the `TypeLayout`. - let s_layout = TypeLayout::new_storage_layout_for(unsized_struct.clone()); - // For now `TypeLayout::::new_layout_for` only accepts sized types, - // however `TypeLayout::::new_layout_for_unchecked` allows to obtain the - // the uniform layout of an unsized cpu-shareable. Using that layout with wgsl as your - // target language will cause an error. - let u_layout = TypeLayout::new_uniform_layout_for(unsized_struct); - // This struct's field offsets are different for storage and uniform layout rules. The array - // has an alignment of 4 with storage alignment and an alignment of 16 with uniform alignment. - assert_ne!(s_layout, u_layout); - - #[derive(GpuLayout)] - struct B { - a: f32x4, - b: f32x3, - c: f32x1, - } + // Does not exist: + // let uniform = GpuTypeLayout::::new(sized_struct.clone()); - // Sized structs require a builder to ensure it always contains at least one field. - let mut sized_struct = SizedStruct::new("B", "b", f32x4::layoutable_type_sized()) - .extend("b", f32x3::layoutable_type_sized()) - .extend("c", f32x1::layoutable_type_sized()); - // Since this struct is sized we can use TypeLayout::::new_layout_for. - let u_layout = TypeLayout::new_uniform_layout_for(sized_struct.clone()); - let s_layout = TypeLayout::new_storage_layout_for(sized_struct); - // And this time they are equal, despite different layout rules. - assert_eq!(s_layout, u_layout); - // Using `TryFrom::try_from` we can check whether the storage type layout also follows - // uniform layout rules despite not being `TypeLayout`, - // which in this case will succeed, but if it doesn't we get a very nice error message about - // why the layout is not compatible with the uniform layout rules. - let u_layout = TypeLayout::::try_from(&s_layout).unwrap(); + // However we can try to upgrade a GpuTypeLayout:: + let uniform = GpuTypeLayout::::try_from(storage.clone()).unwrap(); - // Let's replicate a more complex example with explicit field size and align. - #[derive(shame::GpuLayout)] - struct C { - a: f32x2, - #[size(16)] - b: f32x1, - #[align(16)] - c: f32x2, - } - - let mut sized_struct = SizedStruct::new("C", "a", f32x3::layoutable_type_sized()) - .extend(FieldOptions::new("b", None, Some(16)), f32x3::layoutable_type_sized()) - .extend( - FieldOptions::new("c", Some(U32PowerOf2::_16), None), - f32x1::layoutable_type_sized(), - ); - let layout = TypeLayout::new_storage_layout_for(sized_struct); - assert!(layout.align.as_u32() == 16); - - // gpu_repr(uniform) and gpu_repr(storage), which is the default, are now supported - #[derive(shame::GpuLayout)] - #[gpu_repr(uniform)] - struct D { - a: f32x2, - b: Array, - } - - // this would be 8 for storage layout rules - assert!(gpu_layout::().align.as_u32() == 16); + // Which if it succeeds, guarantees: + assert_eq!(storage.layout(), uniform.layout()); - // Let's end on a pretty error message + // // Let's end on a pretty error message let mut sized_struct = SizedStruct::new("D", "a", f32x2::layoutable_type_sized()) // This has align of 4 for storage and align of 16 for uniform. .extend("b", Array::>::layoutable_type_sized()); - let s_layout = TypeLayout::new_storage_layout_for(sized_struct); - let result = TypeLayout::::try_from(&s_layout); - match result { + + let storage = GpuTypeLayout::::new(sized_struct.clone()); + let uniform_result = GpuTypeLayout::::try_from(storage.clone()); + match uniform_result { Err(e) => println!("This error is a showcase:\n{}", e), Ok(u_layout) => println!("It unexpectedly worked, ohh no."), } diff --git a/shame/src/common/proc_macro_reexports.rs b/shame/src/common/proc_macro_reexports.rs index b78acee..e467fd8 100644 --- a/shame/src/common/proc_macro_reexports.rs +++ b/shame/src/common/proc_macro_reexports.rs @@ -28,6 +28,7 @@ pub use crate::frontend::rust_types::type_layout::FieldLayoutWithOffset; pub use crate::frontend::rust_types::type_layout::FieldOptions; pub use crate::frontend::rust_types::type_layout::StructLayout; pub use crate::frontend::rust_types::type_layout::Repr; +pub use crate::frontend::rust_types::type_layout::repr; pub use crate::frontend::rust_types::type_layout::TypeLayout; pub use crate::frontend::rust_types::type_layout::TypeLayoutSemantics; pub use crate::frontend::rust_types::type_traits::BindingArgs; diff --git a/shame/src/common/proc_macro_utils.rs b/shame/src/common/proc_macro_utils.rs index 85f0d19..164aecb 100644 --- a/shame/src/common/proc_macro_utils.rs +++ b/shame/src/common/proc_macro_utils.rs @@ -54,6 +54,7 @@ pub fn collect_into_array_exact(mut it: impl Iterator, struct_name: &'static str, first_fields_with_offsets_and_sizes: &[(ReprCField, usize, usize)], - last_field: ReprCField, + mut last_field: ReprCField, last_field_size: Option, ) -> Result { let last_field_offset = match first_fields_with_offsets_and_sizes.last() { @@ -99,29 +100,31 @@ pub fn repr_c_struct_layout( let total_struct_size = last_field_size.map(|last_size| round_up(struct_alignment.as_u64(), last_field_offset + last_size)); + let new_size = |layout_size: Option, actual_size: Option| { + (layout_size != actual_size).then_some(actual_size).flatten() + }; let mut fields = first_fields_with_offsets_and_sizes .iter() - .map(|(field, offset, size)| (field, *offset as u64, *size as u64)) - .map(|(field, offset, size)| FieldLayoutWithOffset { - field: FieldLayout { - custom_min_align: None.into(), - custom_min_size: (field.layout.byte_size() != Some(size)).then_some(size).into(), - name: field.name.into(), - ty: field.layout.clone(), - }, - rel_byte_offset: offset, + .map(|(field, offset, size)| (field.clone(), *offset as u64, *size as u64)) + .map(|(mut field, offset, size)| { + field.layout.byte_size = new_size(field.layout.byte_size(), Some(size)); + FieldLayoutWithOffset { + field: FieldLayout { + name: field.name.into(), + ty: field.layout.clone(), + }, + rel_byte_offset: offset, + } }) - .chain(std::iter::once(FieldLayoutWithOffset { - field: FieldLayout { - custom_min_align: None.into(), - custom_min_size: (last_field.layout.byte_size() != last_field_size) - .then_some(last_field_size) - .flatten() - .into(), - name: last_field.name.into(), - ty: last_field.layout, - }, - rel_byte_offset: last_field_offset, + .chain(std::iter::once({ + last_field.layout.byte_size = new_size(last_field.layout.byte_size(), last_field_size); + FieldLayoutWithOffset { + field: FieldLayout { + name: last_field.name.into(), + ty: last_field.layout, + }, + rel_byte_offset: last_field_offset, + } })) .collect::>(); @@ -132,7 +135,6 @@ pub fn repr_c_struct_layout( name: struct_name.into(), fields, })), - None, )) } diff --git a/shame/src/common/small_vec_actual.rs b/shame/src/common/small_vec_actual.rs index 939cbb9..ccdca81 100644 --- a/shame/src/common/small_vec_actual.rs +++ b/shame/src/common/small_vec_actual.rs @@ -222,6 +222,23 @@ impl std::borrow::Borrow<[T]> for SmallVec { fn borrow(&self) -> &[T] { self } } +impl std::fmt::Display for SmallVec +where + T: std::fmt::Display, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "[")?; + let mut iter = self.iter(); + if let Some(first) = iter.next() { + write!(f, "{}", first)?; + for item in iter { + write!(f, ", {}", item)?; + } + } + write!(f, "]") + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/shame/src/frontend/encoding/io_iter.rs b/shame/src/frontend/encoding/io_iter.rs index e3e20af..b31c427 100644 --- a/shame/src/frontend/encoding/io_iter.rs +++ b/shame/src/frontend/encoding/io_iter.rs @@ -107,7 +107,7 @@ impl VertexBuffer<'_, T> { let call_info = call_info!(); let attribs_and_stride = Context::try_with(call_info, |ctx| { let skip_stride_check = false; // it is implied that T is in an array, the strides must match - let gpu_layout = get_layout_compare_with_cpu_push_error::(ctx, skip_stride_check).into_plain(); + let gpu_layout = get_layout_compare_with_cpu_push_error::(ctx, skip_stride_check); let attribs_and_stride = Attrib::get_attribs_and_stride(&gpu_layout, &location_counter).ok_or_else(|| { ctx.push_error(FrontendError::MalformedVertexBufferLayout(gpu_layout).into()); diff --git a/shame/src/frontend/rust_types/array.rs b/shame/src/frontend/rust_types/array.rs index cf49295..8899dc4 100644 --- a/shame/src/frontend/rust_types/array.rs +++ b/shame/src/frontend/rust_types/array.rs @@ -5,7 +5,7 @@ use super::len::x1; use super::mem::AddressSpace; use super::reference::{AccessMode, AccessModeReadable, AccessModeWritable, Read}; use super::scalar_type::ScalarTypeInteger; -use super::type_layout::{self, layoutable, ElementLayout, TypeLayout, TypeLayoutSemantics}; +use super::type_layout::{self, layoutable, repr, ElementLayout, TypeLayout, TypeLayoutSemantics}; use super::type_traits::{ BindingArgs, EmptyRefFields, GpuAligned, GpuSized, GpuStore, GpuStoreImplCategory, NoAtomics, NoBools, NoHandles, }; @@ -179,7 +179,7 @@ impl ToGpuType for Array { } impl GpuLayout for Array { - fn gpu_repr() -> type_layout::Repr { type_layout::Repr::Storage } + type GpuRepr = repr::Storage; fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { let (t_cpu_name, t_cpu_layout) = match T::cpu_type_name_and_layout()? { @@ -207,7 +207,6 @@ impl GpuLayout }), N::LEN.map(NonZeroU32::get), ), - None, ), ); diff --git a/shame/src/frontend/rust_types/atomic.rs b/shame/src/frontend/rust_types/atomic.rs index 0a6ff26..0e461c7 100644 --- a/shame/src/frontend/rust_types/atomic.rs +++ b/shame/src/frontend/rust_types/atomic.rs @@ -9,7 +9,7 @@ use super::{ type_layout::{ self, layoutable::{self, LayoutableSized}, - TypeLayout, + repr, TypeLayout, }, type_traits::{ BindingArgs, EmptyRefFields, GpuAligned, GpuSized, GpuStore, GpuStoreImplCategory, NoAtomics, NoBools, @@ -146,7 +146,7 @@ impl Layoutable for Atomic { } impl GpuLayout for Atomic { - fn gpu_repr() -> type_layout::Repr { type_layout::Repr::Storage } + type GpuRepr = repr::Storage; fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { diff --git a/shame/src/frontend/rust_types/layout_traits.rs b/shame/src/frontend/rust_types/layout_traits.rs index bb2bdff..6bcc937 100644 --- a/shame/src/frontend/rust_types/layout_traits.rs +++ b/shame/src/frontend/rust_types/layout_traits.rs @@ -22,10 +22,11 @@ use super::error::FrontendError; use super::mem::AddressSpace; use super::reference::{AccessMode, AccessModeReadable}; use super::struct_::{BufferFields, SizedFields, Struct}; -use super::type_layout::repr::TypeRepr; +use super::type_layout::repr::{TypeRepr, TypeReprStorageOrPacked}; use super::type_layout::layoutable::{self, array_stride, Vector}; use super::type_layout::{ - self, repr, ElementLayout, FieldLayout, FieldLayoutWithOffset, StructLayout, TypeLayout, TypeLayoutSemantics, + self, repr, ElementLayout, FieldLayout, FieldLayoutWithOffset, GpuTypeLayout, StructLayout, TypeLayout, + TypeLayoutSemantics, }; use super::type_traits::{ BindingArgs, GpuAligned, GpuSized, GpuStore, GpuStoreImplCategory, NoAtomics, NoBools, NoHandles, VertexAttribute, @@ -139,7 +140,8 @@ use std::rc::Rc; /// pub trait GpuLayout: Layoutable { /// Returns the `Repr` of the `TypeLayout` from `GpuLayout::gpu_layout`. - fn gpu_repr() -> Repr; + // fn gpu_repr() -> Repr; + type GpuRepr: TypeReprStorageOrPacked; /// the `#[cpu(...)]` in `#[derive(GpuLayout)]` allows the definition of a /// corresponding Cpu type to the Gpu type that the derive macro is used on. @@ -175,7 +177,11 @@ pub trait GpuLayout: Layoutable { /// println!("OnCpu:\n{}\n", OnCpu::cpu_layout()); /// ``` pub fn gpu_layout() -> TypeLayout { - TypeLayout::new_layout_for(T::layoutable_type(), T::gpu_repr()) + TypeLayout::new_layout_for(&T::layoutable_type(), ::REPR) +} + +pub fn gpu_type_layout() -> GpuTypeLayout { + GpuTypeLayout::new(T::layoutable_type()) } /// (no documentation yet) @@ -214,11 +220,11 @@ pub(crate) fn get_layout_compare_with_cpu_push_error( gpu_layout } -pub(crate) fn check_layout_push_error( +pub(crate) fn check_layout_push_error( ctx: &Context, cpu_name: &str, - cpu_layout: &TypeLayout, - gpu_layout: &TypeLayout, + cpu_layout: &TypeLayout, + gpu_layout: &TypeLayout, skip_stride_check: bool, comment_on_mismatch_error: &str, ) -> Result<(), InvalidReason> { @@ -554,7 +560,8 @@ impl Layoutable for GpuT { } impl GpuLayout for GpuT { - fn gpu_repr() -> Repr { todo!() } + // fn gpu_repr() -> Repr { todo!() } + type GpuRepr = repr::Storage; fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { Some(Ok(( @@ -775,7 +782,6 @@ impl CpuLayout for [T; N] { }), Some(u32::try_from(N).expect("arrays larger than u32::MAX elements are not supported by WGSL")), ), - None, ) } @@ -819,7 +825,6 @@ impl CpuLayout for [T] { }), None, ), - None, ) } diff --git a/shame/src/frontend/rust_types/mat.rs b/shame/src/frontend/rust_types/mat.rs index 4795935..07d3404 100644 --- a/shame/src/frontend/rust_types/mat.rs +++ b/shame/src/frontend/rust_types/mat.rs @@ -10,7 +10,7 @@ use super::{ type_layout::{ self, layoutable::{self, LayoutableSized}, - TypeLayout, + repr, TypeLayout, }, type_traits::{ BindingArgs, EmptyRefFields, GpuAligned, GpuSized, GpuStore, GpuStoreImplCategory, NoAtomics, NoBools, @@ -69,7 +69,7 @@ impl Layoutable for mat { } impl GpuLayout for mat { - fn gpu_repr() -> type_layout::Repr { type_layout::Repr::Storage } + type GpuRepr = repr::Storage; fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { None } } diff --git a/shame/src/frontend/rust_types/packed_vec.rs b/shame/src/frontend/rust_types/packed_vec.rs index 596a0bb..88c536e 100644 --- a/shame/src/frontend/rust_types/packed_vec.rs +++ b/shame/src/frontend/rust_types/packed_vec.rs @@ -25,7 +25,7 @@ use super::{ layout_traits::{from_single_any, ArrayElementsUnsizedError, FromAnys, GpuLayout}, len::LenEven, scalar_type::ScalarType, - type_layout::{self, layoutable, type_layout_internal, TypeLayout, TypeLayoutSemantics}, + type_layout::{self, layoutable, repr, Repr, TypeLayout, TypeLayoutSemantics}, type_traits::{GpuAligned, GpuSized, NoAtomics, NoBools, NoHandles, VertexAttribute}, vec::IsVec, GpuType, @@ -148,13 +148,13 @@ impl Layoutable for PackedVec { } impl GpuLayout for PackedVec { - fn gpu_repr() -> type_layout::Repr { type_layout::Repr::Storage } + type GpuRepr = repr::Storage; fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { let sized_ty = Self::sized_ty_equivalent(); let name = sized_ty.to_string().into(); let sized_ty: layoutable::SizedType = sized_ty.try_into().expect("PackedVec is NoBools and NoHandles"); - let layout = TypeLayout::new_storage_layout_for(sized_ty); + let layout = TypeLayout::new_layout_for(&sized_ty.into(), Repr::Storage); Some(Ok((name, layout.into()))) } } diff --git a/shame/src/frontend/rust_types/struct_.rs b/shame/src/frontend/rust_types/struct_.rs index aaa82aa..f50c185 100644 --- a/shame/src/frontend/rust_types/struct_.rs +++ b/shame/src/frontend/rust_types/struct_.rs @@ -23,7 +23,7 @@ use std::{ }; use super::layout_traits::{GetAllFields, GpuLayout}; -use super::type_layout::{self, layoutable, TypeLayout}; +use super::type_layout::{self, layoutable, repr, TypeLayout}; use super::type_traits::{GpuAligned, GpuSized, GpuStore, GpuStoreImplCategory, NoBools}; use super::{ error::FrontendError, @@ -143,7 +143,7 @@ impl Layoutable for Struct } impl GpuLayout for Struct { - fn gpu_repr() -> type_layout::Repr { T::gpu_repr() } + type GpuRepr = T::GpuRepr; fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { T::cpu_type_name_and_layout().map(|x| x.map(|(name, l)| (format!("Struct<{name}>").into(), l))) diff --git a/shame/src/frontend/rust_types/type_layout/builder.rs b/shame/src/frontend/rust_types/type_layout/builder.rs deleted file mode 100644 index fdaaaa7..0000000 --- a/shame/src/frontend/rust_types/type_layout/builder.rs +++ /dev/null @@ -1,494 +0,0 @@ -use crate::ir::{ir_type::max_u64_po2_dividing, StoreType}; -use super::{ - layoutable::{ - MatrixMajor, RuntimeSizedArray, RuntimeSizedArrayField, SizedField, SizedStruct, SizedType, UnsizedStruct, - }, - *, -}; -use TypeLayoutSemantics as TLS; - -impl TypeLayout { - /// Returns the type layout of the given `LayoutableType` - /// layed out according to wgsl storage layout rules (std430). - pub fn new_storage_layout_for(ty: impl Into) -> Self { - type_layout_internal::cast_unchecked(TypeLayout::new_layout_for(ty, Repr::Storage)) - } - - /// Get the `LayoutableType` this layout is based on. - pub fn layoutable_type(&self) -> &LayoutableType { - self.layoutable_type - .as_ref() - .expect("Storage is always based on a layoutable type") - } -} - -impl TypeLayout { - /// Returns the type layout of the given `LayoutableType` - /// layed out according to wgsl uniform layout rules (std140). - pub fn new_uniform_layout_for(ty: impl Into) -> Self { - type_layout_internal::cast_unchecked(TypeLayout::new_layout_for(ty, Repr::Uniform)) - } - - /// Get the `LayoutableType` this layout is based on. - pub fn layoutable_type(&self) -> &LayoutableType { - self.layoutable_type - .as_ref() - .expect("Uniform is always based on a layoutable type") - } -} - -impl TypeLayout { - /// Returns the type layout of the given `LayoutableType` in packed format. - pub fn new_packed_layout_for(ty: impl Into) -> Self { - type_layout_internal::cast_unchecked(TypeLayout::new_layout_for(ty, Repr::Packed)) - } - - /// Get the `LayoutableType` this layout is based on. - pub fn layoutable_type(&self) -> &LayoutableType { - self.layoutable_type - .as_ref() - .expect("Packed is always based on a layoutable type") - } -} - -impl TypeLayout { - /// Returns the type layout of the given `LayoutableType` - /// layed out according to the given `repr`. - pub fn new_layout_for(ty: impl Into, repr: Repr) -> Self { - match ty.into() { - LayoutableType::Sized(ty) => Self::from_sized_type(ty, repr), - LayoutableType::UnsizedStruct(ty) => Self::from_unsized_struct(ty, repr), - LayoutableType::RuntimeSizedArray(ty) => Self::from_runtime_sized_array(ty, repr), - } - } - - fn from_sized_type(ty: SizedType, repr: Repr) -> Self { - let (size, align, tls) = match &ty { - SizedType::Vector(v) => (v.byte_size(), v.align(repr), TLS::Vector(*v)), - SizedType::Atomic(a) => ( - a.byte_size(), - a.align(repr), - TLS::Vector(Vector::new(a.scalar.into(), ir::Len::X1)), - ), - SizedType::Matrix(m) => ( - m.byte_size(repr, MatrixMajor::Row), - m.align(repr, MatrixMajor::Row), - TLS::Matrix(*m), - ), - SizedType::Array(a) => ( - a.byte_size(repr), - a.align(repr), - TLS::Array( - Rc::new(ElementLayout { - byte_stride: a.byte_stride(repr), - ty: Self::from_sized_type((*a.element).clone(), repr), - }), - Some(a.len.get()), - ), - ), - SizedType::PackedVec(v) => (v.byte_size().as_u64(), v.align(repr), TLS::PackedVector(*v)), - SizedType::Struct(s) => { - let mut field_offsets = s.field_offsets(repr); - let fields = (&mut field_offsets) - .zip(s.fields()) - .map(|(offset, field)| sized_field_to_field_layout(field, offset, repr)) - .collect(); - - ( - field_offsets.byte_size(), - field_offsets.align(), - TLS::Structure(Rc::new(StructLayout { - name: s.name.clone().into(), - fields, - })), - ) - } - }; - - TypeLayout::new(Some(size), align, tls, Some(ty.into())) - } - - fn from_unsized_struct(s: UnsizedStruct, repr: Repr) -> Self { - let mut field_offsets = s.sized_field_offsets(repr); - let mut fields = (&mut field_offsets) - .zip(s.sized_fields.iter()) - .map(|(offset, field)| sized_field_to_field_layout(field, offset, repr)) - .collect::>(); - - let (field_offset, align) = s.last_field_offset_and_struct_align(field_offsets); - fields.push(FieldLayoutWithOffset { - rel_byte_offset: field_offset, - field: FieldLayout { - name: s.last_unsized.name.clone(), - custom_min_size: None.into(), - custom_min_align: s.last_unsized.custom_min_align.into(), - ty: Self::from_runtime_sized_array(s.last_unsized.array.clone(), repr), - }, - }); - - TypeLayout::new( - None, - align, - TLS::Structure(Rc::new(StructLayout { - name: s.name.clone().into(), - fields, - })), - Some(s.into()), - ) - } - - fn from_runtime_sized_array(ty: RuntimeSizedArray, repr: Repr) -> Self { - Self::new( - None, - ty.align(repr), - TLS::Array( - Rc::new(ElementLayout { - byte_stride: ty.byte_stride(repr), - ty: Self::from_sized_type(ty.element.clone(), repr), - }), - None, - ), - Some(ty.into()), - ) - } -} - -fn sized_field_to_field_layout(field: &SizedField, offset: u64, repr: Repr) -> FieldLayoutWithOffset { - FieldLayoutWithOffset { - rel_byte_offset: offset, - field: FieldLayout { - name: field.name.clone(), - custom_min_size: field.custom_min_size.into(), - custom_min_align: field.custom_min_align.into(), - ty: TypeLayout::from_sized_type(field.ty.clone(), repr), - }, - } -} - -impl<'a> TryFrom<&'a TypeLayout> for TypeLayout { - type Error = UniformLayoutError; - - fn try_from(s_layout: &'a TypeLayout) -> Result { - let layoutable_type = s_layout.layoutable_type().clone(); - - // Checking whether it's sized - let sized = match layoutable_type { - LayoutableType::Sized(sized) => sized, - _ => { - return Err(UniformLayoutError::MustBeSized("wgsl", layoutable_type)); - } - }; - - let u_layout = TypeLayout::new_uniform_layout_for(sized); - - // Checking fields - fn check_layout( - s_layout: &TypeLayout, - u_layout: &TypeLayout, - is_top_level: bool, - ) -> Result<(), Mismatch> { - // kinds are the same, because the type layouts are based on the same LayoutableType - match (&s_layout.kind, &u_layout.kind) { - (TLS::Structure(s), TLS::Structure(u)) => { - for (i, (s_field, u_field)) in s.fields.iter().zip(u.fields.iter()).enumerate() { - // Checking field offset - if s_field.rel_byte_offset != u_field.rel_byte_offset { - let struct_type = match s_layout.get_layoutable_type().unwrap() { - LayoutableType::Sized(SizedType::Struct(s)) => StructKind::Sized(s.clone()), - LayoutableType::UnsizedStruct(s) => StructKind::Unsized(s.clone()), - _ => unreachable!("is struct, because tls is struct"), - }; - - return Err(Mismatch::StructureFieldOffset(StructFieldOffsetError { - struct_layout: (**s).clone(), - struct_type, - field_name: s_field.field.name.clone(), - field_index: i, - actual_offset: s_field.rel_byte_offset, - expected_alignment: u_field.field.align(), - is_top_level, - })); - } - - check_layout(&s_field.field.ty, &u_field.field.ty, false)?; - } - } - (TLS::Array(s_ele, _), TLS::Array(u_ele, _)) => { - // As long as the strides are the same, the size must be the same and - // the element align must divide the stride (uniform requirement), because - // u_layout is a valid uniform layout. - if s_ele.byte_stride != u_ele.byte_stride { - let element_ty = match u_ele.ty.get_layoutable_type().unwrap() { - LayoutableType::Sized(s) => s.clone(), - _ => { - unreachable!("elements of an array are always sized for TypeLayout") - } - }; - return Err(Mismatch::ArrayStride(ArrayStrideError { - expected: u_ele.byte_stride, - actual: s_ele.byte_stride, - element_ty, - })); - } - - check_layout(&s_ele.ty, &u_ele.ty, false)?; - } - _ => {} - } - - Ok(()) - } - - - match check_layout(s_layout, &u_layout, true) { - Ok(()) => Ok(u_layout), - Err(e) => { - let ctx = LayoutErrorContext { - s_layout: s_layout.clone(), - u_layout, - // TODO(chronicl) default shouldn't be true? - use_color: Context::try_with(call_info!(), |ctx| ctx.settings().colored_error_messages) - .unwrap_or(false), - }; - - use UniformLayoutError as U; - let e = match e { - Mismatch::ArrayStride(e) => U::ArrayStride(WithContext::new(ctx, e)), - Mismatch::StructureFieldOffset(e) => U::StructureFieldOffset(WithContext::new(ctx, e)), - }; - Err(e) - } - } - } -} - -/// Enum of possible errors during `TypeLayout -> TypeLayout` conversion. -#[derive(thiserror::Error, Debug, Clone)] -pub enum UniformLayoutError { - #[error("{0}")] - ArrayStride(WithContext), - #[error("{0}")] - StructureFieldOffset(WithContext), - #[error( - "The size of `{1}` on the gpu is not known at compile time. `{0}` \ - requires that the size of uniform buffers on the gpu is known at compile time." - )] - MustBeSized(&'static str, LayoutableType), -} - -#[derive(Debug, Clone)] -enum Mismatch { - ArrayStride(ArrayStrideError), - StructureFieldOffset(StructFieldOffsetError), -} - - -#[derive(Debug, Clone)] -pub struct LayoutErrorContext { - s_layout: TypeLayout, - u_layout: TypeLayout, - use_color: bool, -} - -#[derive(Debug, Clone)] -pub struct WithContext { - ctx: LayoutErrorContext, - inner: T, -} - -impl std::ops::Deref for WithContext { - type Target = T; - fn deref(&self) -> &Self::Target { &self.inner } -} - -impl WithContext { - fn new(ctx: LayoutErrorContext, inner: T) -> Self { Self { ctx, inner } } -} - - -#[allow(missing_docs)] -#[derive(Debug, Clone)] -pub struct ArrayStrideError { - expected: u64, - actual: u64, - element_ty: SizedType, -} - -impl std::error::Error for WithContext {} -impl Display for WithContext { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let top_level = self.ctx.s_layout.layoutable_type(); - writeln!( - f, - "array elements within type `{}` do not satisfy uniform layout requirements.", - top_level - )?; - writeln!( - f, - "The array with `{}` elements requires stride {}, but has stride {}.", - self.element_ty, self.expected, self.actual - )?; - writeln!(f, "The full layout of `{}` is:", top_level)?; - self.ctx.s_layout.write("", self.ctx.use_color, f)?; - writeln!(f)?; - writeln!( - f, - "\nfor more information on the layout rules, see https://www.w3.org/TR/WGSL/#address-space-layout-constraints", - )?; - Ok(()) - } -} - -#[allow(missing_docs)] -#[derive(Debug, Clone)] -pub struct StructFieldOffsetError { - pub struct_layout: StructLayout, - pub struct_type: StructKind, - pub field_name: CanonName, - pub field_index: usize, - pub actual_offset: u64, - pub expected_alignment: U32PowerOf2, - pub is_top_level: bool, -} - -#[derive(Debug, Clone)] -pub enum StructKind { - Sized(SizedStruct), - Unsized(UnsizedStruct), -} - -impl std::error::Error for WithContext {} -impl Display for WithContext { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let top_level_type = self.ctx.s_layout.layoutable_type(); - writeln!( - f, - "The type `{top_level_type}` cannot be used as a uniform buffer binding." - )?; - - match self.is_top_level { - true => write!(f, "Struct `{}`", &*self.struct_layout.name)?, - false => write!(f, "It contains a struct `{}`, which", &*self.struct_layout.name)?, - } - writeln!(f, " does not satisfy the uniform memory layout requirements.",)?; - writeln!(f)?; - // TODO(chronicl) structure_def_location - // if let Some(call_info) = structure_def_location { - // writeln!(f, "Definition at {call_info}")?; - // } - - write_struct_layout( - &self.struct_layout, - &self.struct_type, - self.ctx.use_color, - Some(self.field_index), - f, - )?; - - let actual_align = max_u64_po2_dividing(self.actual_offset); - let expected_alignment = self.expected_alignment.as_u32(); - writeln!(f)?; - set_color(f, Some("#508EE3"), false)?; - writeln!( - f, - "Field `{}` needs to be {} byte aligned, but has a byte-offset of {} which is only {actual_align} byte aligned.", - self.field_name, expected_alignment, self.actual_offset - )?; - set_color(f, None, false)?; - writeln!(f)?; - - writeln!(f, "Potential solutions include:")?; - writeln!( - f, - "- add an #[align({})] attribute to the definition of `{}` (not supported by OpenGL/GLSL pipelines)", - expected_alignment, self.field_name - )?; - writeln!(f, "- use a storage binding instead of a uniform binding")?; - writeln!( - f, - "- increase the offset of `{}` until it is divisible by {} by making previous fields larger or adding fields before it", - self.field_name, expected_alignment - )?; - writeln!(f)?; - - writeln!( - f, - "In the uniform address space, structs, arrays and array elements must be at least 16 byte aligned.\nMore info about the uniform address space layout can be found at https://www.w3.org/TR/WGSL/#address-space-layout-constraints" - )?; - Ok(()) - } -} - -/// Panics if s is not the layout of a struct and doesn't contain a cpu-shareable. -fn write_struct_layout( - struct_layout: &StructLayout, - struct_type: &StructKind, - colored: bool, - highlight_field: Option, - f: &mut F, -) -> std::fmt::Result -where - F: Write, -{ - let use_256_color_mode = false; - let color = |f_: &mut F, hex| match colored { - true => set_color(f_, Some(hex), use_256_color_mode), - false => Ok(()), - }; - let reset = |f_: &mut F| match colored { - true => set_color(f_, None, use_256_color_mode), - false => Ok(()), - }; - - let (sized_fields, last_unsized) = match struct_type { - StructKind::Sized(s) => (s.fields(), None), - StructKind::Unsized(s) => (s.sized_fields.as_slice(), Some(&s.last_unsized)), - }; - - let struct_name = &*struct_layout.name; - - let indent = " "; - let field_decl_line = |field: &SizedField| format!("{indent}{}: {},", field.name, field.ty); - let header = format!("struct {} {{", struct_name); - let table_start_column = 1 + sized_fields - .iter() - .map(field_decl_line) - .map(|s| s.len()) - .max() - .unwrap_or(0) - .max(header.chars().count()); - f.write_str(&header)?; - for i in header.len()..table_start_column { - f.write_char(' ')? - } - writeln!(f, "offset align size")?; - for (i, (field, layout)) in sized_fields.iter().zip(struct_layout.fields.iter()).enumerate() { - if Some(i) == highlight_field { - color(f, "#508EE3")?; - } - let (align, size) = (layout.ty.align(), layout.ty.byte_size().expect("is sized field")); - let decl_line = field_decl_line(field); - f.write_str(&decl_line)?; - // write spaces to table on the right - for _ in decl_line.len()..table_start_column { - f.write_char(' ')? - } - writeln!(f, "{:6} {:5} {:4}", layout.rel_byte_offset, align.as_u32(), size)?; - if Some(i) == highlight_field { - reset(f)?; - } - } - if let Some(last_field) = last_unsized { - let layout = struct_layout.fields.last().expect("structs have at least one field"); - - let decl_line = format!("{indent}{}: {},", last_field.name, last_field.array); - f.write_str(&decl_line)?; - // write spaces to table on the right - for _ in decl_line.len()..table_start_column { - f.write_char(' ')? - } - write!(f, "{:6} {:5}", layout.rel_byte_offset, layout.ty.align().as_u32())?; - } - writeln!(f, "}}")?; - Ok(()) -} diff --git a/shame/src/frontend/rust_types/type_layout/construction.rs b/shame/src/frontend/rust_types/type_layout/construction.rs new file mode 100644 index 0000000..77c8178 --- /dev/null +++ b/shame/src/frontend/rust_types/type_layout/construction.rs @@ -0,0 +1,555 @@ +use std::fmt::Formatter; + +use crate::{ + __private::SmallVec, + ir::{ir_type::max_u64_po2_dividing, StoreType}, +}; +use super::{ + layoutable::{ + FieldOffsets, MatrixMajor, RuntimeSizedArray, RuntimeSizedArrayField, SizedField, SizedStruct, SizedType, + UnsizedStruct, + }, + *, +}; +use TypeLayoutSemantics as TLS; + +impl TypeLayout { + /// Returns the type layout of the given `LayoutableType` + /// layed out according to the given `repr`. + pub fn new_layout_for(ty: &LayoutableType, repr: Repr) -> Self { + match ty { + LayoutableType::Sized(ty) => Self::from_sized_type(ty, repr), + LayoutableType::UnsizedStruct(ty) => Self::from_unsized_struct(ty, repr), + LayoutableType::RuntimeSizedArray(ty) => Self::from_runtime_sized_array(ty, repr), + } + } + + fn from_sized_type(ty: &SizedType, repr: Repr) -> Self { + let (size, align, tls) = match &ty { + SizedType::Vector(v) => (v.byte_size(), v.align(repr), TLS::Vector(*v)), + SizedType::Atomic(a) => ( + a.byte_size(), + a.align(repr), + TLS::Vector(Vector::new(a.scalar.into(), ir::Len::X1)), + ), + SizedType::Matrix(m) => ( + m.byte_size(repr, MatrixMajor::Row), + m.align(repr, MatrixMajor::Row), + TLS::Matrix(*m), + ), + SizedType::Array(a) => ( + a.byte_size(repr), + a.align(repr), + TLS::Array( + Rc::new(ElementLayout { + byte_stride: a.byte_stride(repr), + ty: Self::from_sized_type(&a.element, repr), + }), + Some(a.len.get()), + ), + ), + SizedType::PackedVec(v) => (v.byte_size().as_u64(), v.align(repr), TLS::PackedVector(*v)), + SizedType::Struct(s) => { + let mut field_offsets = s.field_offsets(repr); + let fields = (&mut field_offsets) + .zip(s.fields()) + .map(|(offset, field)| sized_field_to_field_layout(field, offset, repr)) + .collect(); + + ( + field_offsets.byte_size(), + field_offsets.align(), + TLS::Structure(Rc::new(StructLayout { + name: s.name.clone().into(), + fields, + })), + ) + } + }; + + TypeLayout::new(Some(size), align, tls) + } + + fn from_unsized_struct(s: &UnsizedStruct, repr: Repr) -> Self { + let mut field_offsets = s.sized_field_offsets(repr); + let mut fields = (&mut field_offsets) + .zip(s.sized_fields.iter()) + .map(|(offset, field)| sized_field_to_field_layout(field, offset, repr)) + .collect::>(); + + let (field_offset, align) = s.last_field_offset_and_struct_align(field_offsets); + fields.push(FieldLayoutWithOffset { + rel_byte_offset: field_offset, + field: FieldLayout { + name: s.last_unsized.name.clone(), + ty: Self::from_runtime_sized_array(&s.last_unsized.array, repr), + }, + }); + + TypeLayout::new( + None, + align, + TLS::Structure(Rc::new(StructLayout { + name: s.name.clone().into(), + fields, + })), + ) + } + + fn from_runtime_sized_array(ty: &RuntimeSizedArray, repr: Repr) -> Self { + Self::new( + None, + ty.align(repr), + TLS::Array( + Rc::new(ElementLayout { + byte_stride: ty.byte_stride(repr), + ty: Self::from_sized_type(&ty.element, repr), + }), + None, + ), + ) + } +} + +fn sized_field_to_field_layout(field: &SizedField, offset: u64, repr: Repr) -> FieldLayoutWithOffset { + FieldLayoutWithOffset { + rel_byte_offset: offset, + field: FieldLayout { + name: field.name.clone(), + ty: TypeLayout::from_sized_type(&field.ty, repr), + }, + } +} + +impl GpuTypeLayout { + /// Creates a new `GpuTypeLayout` where `T` is either `Storage` or `Packed`. + /// + /// All LayoutableType's can be layed out according to storage and packed layout rules, + /// which corresponds to the available #[gpu_repr(packed)] and #[gpu_repr(storage)] + /// attributes when deriving `GpuLayout` for a struct. + /// + /// `GpuTypeLayout` can be acquired via + /// `GpuTypeLayout::::try_from(gpu_type_layout_storage)`. + pub fn new(ty: impl Into) -> Self { + Self { + ty: ty.into(), + _repr: PhantomData, + } + } +} + +impl TryFrom> for GpuTypeLayout { + type Error = LayoutError; + + fn try_from(ty: GpuTypeLayout) -> Result { + check_layout(ty.layoutable_type(), Repr::Storage, Repr::Uniform)?; + Ok(GpuTypeLayout { + ty: ty.ty, + _repr: PhantomData, + }) + } +} + +/// Checks whether the layout of `ty` as `actual_repr` and as `expected_repr` are compatible. +/// Compatible means that all field offsets of structs and all strides of arrays are the same. +/// +/// Another way to say this is, that we are laying `ty` out according to two different +/// layout rules and checking whether the byte representation of those layouts is the same. +fn check_layout(ty: &LayoutableType, actual_repr: Repr, expected_repr: Repr) -> Result<(), LayoutError> { + let ctx = LayoutContext { + top_level_type: ty, + actual_repr, + expected_repr, + use_color: Context::try_with(call_info!(), |ctx| ctx.settings().colored_error_messages).unwrap_or(false), + }; + let is_sized = matches!(ctx.top_level_type, LayoutableType::Sized(_)); + if ctx.expected_repr.is_uniform() && !is_sized { + return Err(LayoutError::UniformBufferMustBeSized( + "wgsl", + ctx.top_level_type.clone(), + )); + } + + match &ctx.top_level_type { + LayoutableType::Sized(s) => check_compare_sized(ctx, s), + LayoutableType::UnsizedStruct(s) => { + let mut actual_offsets = s.sized_field_offsets(ctx.actual_repr); + let mut expected_offsets = s.sized_field_offsets(ctx.expected_repr); + check_sized_fields(ctx, s, &s.sized_fields, &mut actual_offsets, &mut expected_offsets)?; + + let (actual_last_offset, _) = s.last_field_offset_and_struct_align(actual_offsets); + let (expected_last_offset, _) = s.last_field_offset_and_struct_align(expected_offsets); + + if actual_last_offset != expected_last_offset { + let field = &s.last_unsized; + let field_index = s.sized_fields.len(); + return Err(StructFieldOffsetError { + ctx: ctx.into(), + struct_type: s.clone().into(), + field_name: field.name.clone(), + field_index, + actual_offset: actual_last_offset, + expected_alignment: field.align(ctx.expected_repr), + } + .into()); + } + + Ok(()) + } + LayoutableType::RuntimeSizedArray(a) => { + let actual_stride = a.byte_stride(ctx.actual_repr); + let expected_stride = a.byte_stride(ctx.expected_repr); + match actual_stride == expected_stride { + false => Err(ArrayStrideError { + ctx: ctx.into(), + actual_stride, + expected_stride, + element_ty: a.element.clone(), + } + .into()), + true => Ok(()), + } + } + } +} + +fn check_compare_sized(ctx: LayoutContext, ty: &SizedType) -> Result<(), LayoutError> { + match ty { + SizedType::Struct(s) => { + let mut actual_offsets = s.field_offsets(ctx.actual_repr); + let mut expected_offsets = s.field_offsets(ctx.expected_repr); + check_sized_fields(ctx, s, s.fields(), &mut actual_offsets, &mut expected_offsets) + } + SizedType::Array(a) => { + let actual_stride = a.byte_stride(ctx.actual_repr); + let expected_stride = a.byte_stride(ctx.expected_repr); + match actual_stride == expected_stride { + false => Err(ArrayStrideError { + ctx: ctx.into(), + actual_stride, + expected_stride, + element_ty: (*a.element).clone(), + } + .into()), + true => Ok(()), + } + } + SizedType::Vector(_) | SizedType::Matrix(_) | SizedType::Atomic(_) | SizedType::PackedVec(_) => { + assert_eq!(ty.byte_size(ctx.actual_repr), ty.byte_size(ctx.expected_repr)); + Ok(()) + } + } +} + +fn check_sized_fields( + ctx: LayoutContext, + s: &(impl Into + Clone), + fields: &[SizedField], + actual_offsets: &mut FieldOffsets, + expected_offsets: &mut FieldOffsets, +) -> Result<(), LayoutError> { + for (i, (field, (actual_offset, expected_offset))) in + fields.iter().zip(actual_offsets.zip(expected_offsets)).enumerate() + { + if actual_offset != expected_offset { + return Err(StructFieldOffsetError { + ctx: ctx.into(), + struct_type: s.clone().into(), + field_name: field.name.clone(), + field_index: i, + actual_offset, + expected_alignment: field.align(ctx.expected_repr), + } + .into()); + } + + check_compare_sized(ctx, &field.ty)?; + } + + Ok(()) +} + +/// Enum of possible errors during `TypeLayout -> TypeLayout` conversion. +#[derive(thiserror::Error, Debug, Clone)] +pub enum LayoutError { + #[error("{0}")] + ArrayStride(#[from] ArrayStrideError), + #[error("{0}")] + StructureFieldOffset(#[from] StructFieldOffsetError), + #[error( + "The size of `{1}` on the gpu is not known at compile time. `{0}` \ + requires that the size of uniform buffers on the gpu is known at compile time." + )] + UniformBufferMustBeSized(&'static str, LayoutableType), + #[error("{0} contains a `PackedVector`, which are not allowed in {1} address space. ")] + MayNotContainPackedVec(LayoutableType, Repr), +} + + +#[derive(Debug, Clone, Copy)] +pub struct LayoutContext<'a> { + top_level_type: &'a LayoutableType, + actual_repr: Repr, + expected_repr: Repr, + use_color: bool, +} + +#[derive(Debug, Clone)] +pub struct LayoutErrorContext { + top_level_type: LayoutableType, + actual_repr: Repr, + expected_repr: Repr, + use_color: bool, +} + +impl<'a> From> for LayoutErrorContext { + fn from(ctx: LayoutContext) -> Self { + LayoutErrorContext { + top_level_type: ctx.top_level_type.clone(), + actual_repr: ctx.actual_repr, + expected_repr: ctx.expected_repr, + use_color: ctx.use_color, + } + } +} + +impl Repr { + fn more_info_at(&self) -> &str { "https://www.w3.org/TR/WGSL/#memory-layouts" } +} + +#[allow(missing_docs)] +#[derive(Debug, Clone)] +pub struct ArrayStrideError { + ctx: LayoutErrorContext, + expected_stride: u64, + actual_stride: u64, + element_ty: SizedType, +} + +impl std::error::Error for ArrayStrideError {} +impl Display for ArrayStrideError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let top_level = &self.ctx.top_level_type; + writeln!( + f, + "array elements within type `{}` do not satisfy {} layout requirements.", + self.ctx.expected_repr, top_level + )?; + writeln!( + f, + "The array with `{}` elements requires stride {}, but has stride {}.", + self.element_ty, self.expected_stride, self.actual_stride + )?; + writeln!(f, "The full layout of `{}` is:", top_level)?; + let layout = TypeLayout::new_layout_for(&self.ctx.top_level_type, self.ctx.actual_repr); + layout.write("", self.ctx.use_color, f)?; + writeln!(f)?; + writeln!( + f, + "\nfor more information on the layout rules, see {}", + self.ctx.expected_repr.more_info_at() + )?; + Ok(()) + } +} + +#[allow(missing_docs)] +#[derive(Debug, Clone)] +pub struct StructFieldOffsetError { + pub ctx: LayoutErrorContext, + pub struct_type: StructKind, + pub field_name: CanonName, + pub field_index: usize, + pub actual_offset: u64, + pub expected_alignment: U32PowerOf2, +} + +#[derive(Debug, Clone)] +pub enum StructKind { + Sized(SizedStruct), + Unsized(UnsizedStruct), +} + +impl From for StructKind { + fn from(value: SizedStruct) -> Self { StructKind::Sized(value) } +} +impl From for StructKind { + fn from(value: UnsizedStruct) -> Self { StructKind::Unsized(value) } +} + +impl std::error::Error for StructFieldOffsetError {} +impl Display for StructFieldOffsetError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let top_level_type = &self.ctx.top_level_type; + let top_level_name = match top_level_type { + LayoutableType::Sized(SizedType::Struct(s)) => Some(&s.name), + LayoutableType::UnsizedStruct(s) => Some(&s.name), + _ => None, + }; + + let struct_name = match &self.struct_type { + StructKind::Sized(s) => &s.name, + StructKind::Unsized(s) => &s.name, + }; + + let is_top_level = top_level_name == Some(struct_name); + + let structure_def_location = Context::try_with(call_info!(), |ctx| -> Option<_> { + match &self.struct_type { + StructKind::Sized(s) => { + let s: ir::SizedStruct = s.clone().try_into().ok()?; + ctx.struct_registry().get(&*s).map(|def| def.call_info()) + } + StructKind::Unsized(s) => { + let s: ir::BufferBlock = s.clone().try_into().ok()?; + ctx.struct_registry().get(&*s).map(|def| def.call_info()) + } + } + }) + .flatten(); + + + writeln!( + f, + "The type `{top_level_type}` cannot be used as a {} buffer binding.", + self.ctx.expected_repr + )?; + match is_top_level { + true => write!(f, "Struct `{}`", struct_name)?, + false => write!(f, "It contains a struct `{}`, which", struct_name)?, + } + writeln!( + f, + " does not satisfy the {} memory layout requirements.", + self.ctx.expected_repr + )?; + writeln!(f)?; + + if let Some(call_info) = structure_def_location { + writeln!(f, "Definition at {call_info}")?; + } + + write_struct_layout( + &self.struct_type, + self.ctx.actual_repr, + self.ctx.use_color, + Some(self.field_index), + f, + )?; + + let actual_align = max_u64_po2_dividing(self.actual_offset); + let expected_alignment = self.expected_alignment.as_u32(); + writeln!(f)?; + set_color(f, Some("#508EE3"), false)?; + writeln!( + f, + "Field `{}` needs to be {} byte aligned, but has a byte-offset of {} which is only {actual_align} byte aligned.", + self.field_name, expected_alignment, self.actual_offset + )?; + set_color(f, None, false)?; + writeln!(f)?; + + writeln!(f, "Potential solutions include:")?; + writeln!( + f, + "- add an #[align({})] attribute to the definition of `{}` (not supported by OpenGL/GLSL pipelines)", + expected_alignment, self.field_name + )?; + writeln!(f, "- use a storage binding instead of a uniform binding")?; + writeln!( + f, + "- increase the offset of `{}` until it is divisible by {} by making previous fields larger or adding fields before it", + self.field_name, expected_alignment + )?; + writeln!(f)?; + + writeln!( + f, + "In the {} address space, structs, arrays and array elements must be at least 16 byte aligned.", + self.ctx.expected_repr + )?; + writeln!( + f, + "More info about the {} address space layout can be found at https://www.w3.org/TR/WGSL/#address-space-layout-constraints", + self.ctx.expected_repr + )?; + Ok(()) + } +} + +/// Panics if s is not the layout of a struct and doesn't contain a cpu-shareable. +fn write_struct_layout( + struct_type: &StructKind, + repr: Repr, + colored: bool, + highlight_field: Option, + f: &mut F, +) -> std::fmt::Result +where + F: Write, +{ + let use_256_color_mode = false; + let color = |f_: &mut F, hex, field_index| match colored && Some(field_index) == highlight_field { + true => set_color(f_, Some(hex), use_256_color_mode), + false => Ok(()), + }; + let reset = |f_: &mut F, field_index| match colored && Some(field_index) == highlight_field { + true => set_color(f_, None, use_256_color_mode), + false => Ok(()), + }; + + let (struct_name, sized_fields, mut field_offsets) = match struct_type { + StructKind::Sized(s) => (&s.name, s.fields(), s.field_offsets(repr)), + StructKind::Unsized(s) => (&s.name, s.sized_fields.as_slice(), s.sized_field_offsets(repr)), + }; + + let indent = " "; + let field_decl_line = |field: &SizedField| format!("{indent}{}: {},", field.name, field.ty); + let header = format!("struct {} {{", struct_name); + let table_start_column = 1 + sized_fields + .iter() + .map(field_decl_line) + .map(|s| s.len()) + .max() + .unwrap_or(0) + .max(header.chars().count()); + f.write_str(&header)?; + for i in header.len()..table_start_column { + f.write_char(' ')? + } + writeln!(f, "offset align size")?; + for (field_index, (field, field_offset)) in sized_fields.iter().zip(&mut field_offsets).enumerate() { + color(f, "#508EE3", field_index)?; + + let (align, size) = (field.align(repr), field.byte_size(repr)); + let decl_line = field_decl_line(field); + f.write_str(&decl_line)?; + // write spaces to table on the right + for _ in decl_line.len()..table_start_column { + f.write_char(' ')? + } + writeln!(f, "{:6} {:5} {:4}", field_offset, align.as_u32(), size)?; + + reset(f, field_index)?; + } + if let StructKind::Unsized(s) = struct_type { + let field_index = sized_fields.len(); + color(f, "#508EE3", field_index)?; + + let last_field = &s.last_unsized; + let (last_field_offset, _) = s.last_field_offset_and_struct_align(field_offsets); + + let decl_line = format!("{indent}{}: {},", last_field.name, last_field.array); + f.write_str(&decl_line)?; + // write spaces to table on the right + for _ in decl_line.len()..table_start_column { + f.write_char(' ')? + } + write!(f, "{:6} {:5}", last_field_offset, last_field.align(repr).as_u64())?; + + + reset(f, field_index)?; + } + writeln!(f, "}}")?; + Ok(()) +} diff --git a/shame/src/frontend/rust_types/type_layout/eq.rs b/shame/src/frontend/rust_types/type_layout/eq.rs index 5ece418..a7c8275 100644 --- a/shame/src/frontend/rust_types/type_layout/eq.rs +++ b/shame/src/frontend/rust_types/type_layout/eq.rs @@ -4,7 +4,7 @@ use super::*; #[derive(Clone)] pub struct LayoutMismatch { /// 2 (name, layout) pairs - layouts: [(String, TypeLayout); 2], + layouts: [(String, TypeLayout); 2], colored_error: bool, } @@ -65,7 +65,7 @@ impl LayoutMismatch { #[allow(clippy::needless_return)] pub(crate) fn write( indent: &str, - layouts: [(&str, &TypeLayout); 2], + layouts: [(&str, &TypeLayout); 2], colored: bool, f: &mut W, ) -> Result { @@ -431,20 +431,14 @@ impl LayoutMismatch { /// /// if the two layouts are not equal it uses the debug names in the returned /// error to tell the two layouts apart. -pub(crate) fn check_eq( - a: (&str, &TypeLayout), - b: (&str, &TypeLayout), -) -> Result<(), LayoutMismatch> +pub(crate) fn check_eq(a: (&str, &TypeLayout), b: (&str, &TypeLayout)) -> Result<(), LayoutMismatch> where - TypeLayout: PartialEq>, + TypeLayout: PartialEq, { match a.1 == b.1 { true => Ok(()), false => Err(LayoutMismatch { - layouts: [ - (a.0.into(), a.1.to_owned().into_plain()), - (b.0.into(), b.1.to_owned().into_plain()), - ], + layouts: [(a.0.into(), a.1.to_owned()), (b.0.into(), b.1.to_owned())], colored_error: Context::try_with(call_info!(), |ctx| ctx.settings().colored_error_messages) .unwrap_or(false), }), diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index 5213c05..9b9b566 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -43,7 +43,7 @@ impl SizedType { match self { SizedType::Array(a) => a.byte_size(repr), SizedType::Vector(v) => v.byte_size(), - SizedType::Matrix(m) => m.byte_size(repr, MatrixMajor::Row), + SizedType::Matrix(m) => m.byte_size(repr, MatrixMajor::Column), SizedType::Atomic(a) => a.byte_size(), SizedType::PackedVec(v) => u8::from(v.byte_size()) as u64, SizedType::Struct(s) => s.byte_size_and_align(repr).0, @@ -55,7 +55,7 @@ impl SizedType { match self { SizedType::Array(a) => a.align(repr), SizedType::Vector(v) => v.align(repr), - SizedType::Matrix(m) => m.align(repr, MatrixMajor::Row), + SizedType::Matrix(m) => m.align(repr, MatrixMajor::Column), SizedType::Atomic(a) => a.align(repr), SizedType::PackedVec(v) => v.align(repr), SizedType::Struct(s) => s.byte_size_and_align(repr).1, @@ -237,13 +237,14 @@ impl Matrix { pub const fn align(&self, repr: Repr, major: MatrixMajor) -> U32PowerOf2 { let (vec, _) = self.as_vector_array(major); - array_align(vec.align(repr), repr) + // AlignOf(vecR) + vec.align(repr) } const fn as_vector_array(&self, major: MatrixMajor) -> (Vector, NonZeroU32) { let (vec_len, array_len): (Len, NonZeroU32) = match major { - MatrixMajor::Row => (self.rows.as_len(), self.columns.as_non_zero_u32()), - MatrixMajor::Column => (self.columns.as_len(), self.rows.as_non_zero_u32()), + MatrixMajor::Column => (self.rows.as_len(), self.columns.as_non_zero_u32()), + MatrixMajor::Row => (self.columns.as_len(), self.rows.as_non_zero_u32()), }; ( Vector { @@ -309,13 +310,23 @@ impl RuntimeSizedArray { #[allow(missing_docs)] impl SizedField { - pub fn byte_size(&self, repr: Repr) -> u64 { self.ty.byte_size(repr) } - pub fn align(&self, repr: Repr) -> U32PowerOf2 { self.ty.align(repr) } + pub fn byte_size(&self, repr: Repr) -> u64 { + LayoutCalculator::calculate_byte_size(self.ty.byte_size(repr), self.custom_min_size) + } + pub fn align(&self, repr: Repr) -> U32PowerOf2 { + // In case of Repr::Packed, the field's align of 1 is overwritten here by custom_min_align. + // This is intended! + LayoutCalculator::calculate_align(self.ty.align(repr), self.custom_min_align) + } } #[allow(missing_docs)] impl RuntimeSizedArrayField { - pub fn align(&self, repr: Repr) -> U32PowerOf2 { self.array.align(repr) } + pub fn align(&self, repr: Repr) -> U32PowerOf2 { + // In case of Repr::Packed, the field's align of 1 is overwritten here by custom_min_align. + // This is intended! + LayoutCalculator::calculate_align(self.array.align(repr), self.custom_min_align) + } } pub const fn round_up(multiple_of: u64, n: u64) -> u64 { diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs index 16a2c93..031de09 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs @@ -10,7 +10,7 @@ use crate::{ }; pub use crate::ir::{Len, Len2, PackedVector, ScalarTypeFp, ScalarTypeInteger, ir_type::CanonName}; -use super::{builder::StructKind, FieldOptions}; +use super::{construction::StructKind, FieldOptions}; pub(crate) mod align_size; pub(crate) mod builder; diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index 4a1a520..1edd93a 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -23,7 +23,7 @@ use layoutable::{ LayoutableType, Matrix, Vector, }; -pub(crate) mod builder; +pub(crate) mod construction; pub(crate) mod eq; pub(crate) mod layoutable; @@ -97,7 +97,7 @@ pub enum TypeLayoutSemantics { /// ); /// ``` #[derive(Clone)] -pub struct TypeLayout { +pub struct TypeLayout { /// size in bytes (Some), or unsized (None) pub byte_size: Option, /// the byte alignment @@ -106,24 +106,35 @@ pub struct TypeLayout { pub align: U32PowerOf2, /// the type contained in the bytes of this type layout pub kind: TypeLayoutSemantics, - - /// Is some for `constraint::Storage` and `constraint::Uniform`. Can be converted to a `StoreType`. - pub(crate) layoutable_type: Option, - _phantom: PhantomData, } // PartialEq, Eq, Hash for TypeLayout -impl PartialEq> for TypeLayout { - fn eq(&self, other: &TypeLayout) -> bool { self.byte_size() == other.byte_size() && self.kind == other.kind } +impl PartialEq for TypeLayout { + fn eq(&self, other: &TypeLayout) -> bool { self.byte_size() == other.byte_size() && self.kind == other.kind } } -impl Eq for TypeLayout {} -impl Hash for TypeLayout { +impl Eq for TypeLayout {} +impl Hash for TypeLayout { fn hash(&self, state: &mut H) { self.byte_size.hash(state); self.kind.hash(state); } } +/// TODO(chronicl) +#[derive(Debug, Clone)] +pub struct GpuTypeLayout { + ty: LayoutableType, + _repr: PhantomData, +} + +impl GpuTypeLayout { + /// TODO(chronicl) + pub fn layout(&self) -> TypeLayout { TypeLayout::new_layout_for(&self.ty, T::REPR) } + /// TODO(chronicl) + pub fn layoutable_type(&self) -> &LayoutableType { &self.ty } +} + +use repr::TypeReprStorageOrPacked; pub use repr::{TypeRepr, Repr}; /// Module for all restrictions on `TypeLayout`. pub mod repr { @@ -132,7 +143,14 @@ pub mod repr { /// Type representation used by `TypeLayout`. This provides guarantees /// about the alignment rules that the type layout adheres to. /// See [`TypeLayout`] documentation for more details. - pub trait TypeRepr: Clone + PartialEq + Eq {} + pub trait TypeRepr: Clone + PartialEq + Eq { + /// The corresponding enum variant of `Repr`. + const REPR: Repr; + } + /// TODO(chronicl) + pub trait TypeReprStorageOrPacked: TypeRepr {} + impl TypeReprStorageOrPacked for Storage {} + impl TypeReprStorageOrPacked for Packed {} /// Enum of layout rules. #[derive(Debug, Clone, Copy)] @@ -156,64 +174,38 @@ pub mod repr { pub const fn is_packed(self) -> bool { matches!(self, Repr::Packed) } } + impl std::fmt::Display for Repr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Repr::Storage => write!(f, "storage"), + Repr::Uniform => write!(f, "uniform"), + Repr::Packed => write!(f, "packed"), + } + } + } + macro_rules! type_repr { - ($($constraint:ident),*) => { + ($($repr:ident),*) => { $( /// A type representation used by `TypeLayout`. /// See [`TypeLayout`] documentation for more details. #[derive(Clone, PartialEq, Eq, Hash)] - pub struct $constraint; - impl TypeRepr for $constraint {} - )* - }; - } - type_repr!(Plain, Storage, Uniform, Packed); - - macro_rules! impl_from_into { - ($from:ident -> $($into:ident),*) => { - $( - impl From> for TypeLayout<$into> { - fn from(layout: TypeLayout<$from>) -> Self { type_layout_internal::cast_unchecked(layout) } + pub struct $repr; + impl TypeRepr for $repr { + const REPR: Repr = Repr::$repr; } )* }; } - - impl_from_into!(Storage -> Plain); - impl_from_into!(Uniform -> Plain, Storage); - impl_from_into!(Packed -> Plain); + type_repr!(Storage, Uniform, Packed); } impl TypeLayout { - pub(crate) fn new( - byte_size: Option, - byte_align: U32PowerOf2, - kind: TypeLayoutSemantics, - layoutable_type: Option, - ) -> Self { + pub(crate) fn new(byte_size: Option, byte_align: U32PowerOf2, kind: TypeLayoutSemantics) -> Self { TypeLayout { byte_size, align: byte_align, kind, - layoutable_type, - _phantom: PhantomData, - } - } -} - -/// This module offers helper methods that do not adhere to the restriction of `TypeLayout. -/// The caller must uphold these restrictions themselves. -/// The main purpose of this module is to avoid repetition. -pub(in super::super::rust_types) mod type_layout_internal { - use super::*; - - pub fn cast_unchecked(layout: TypeLayout) -> TypeLayout { - TypeLayout { - byte_size: layout.byte_size, - align: layout.align, - kind: layout.kind, - layoutable_type: layout.layoutable_type, - _phantom: PhantomData, } } } @@ -257,10 +249,10 @@ pub struct ElementLayout { /// /// The element layout must be constraint::Plain because it's shared by all constraints. /// `ElementLayout` could possibly be made generic too, but it would complicate a lot. - pub ty: TypeLayout, + pub ty: TypeLayout, } -impl TypeLayout { +impl TypeLayout { /// Returns the byte size of the represented type. /// /// For sized types, this returns Some(size), while for unsized types @@ -270,16 +262,6 @@ impl TypeLayout { /// Returns the alignment requirement of the represented type. pub fn align(&self) -> U32PowerOf2 { self.align } - /// Although all TypeLayout always implement Into, this method - /// is offered to avoid having to declare that as a bound when handling generic TypeLayout. - pub fn into_plain(self) -> TypeLayout { type_layout_internal::cast_unchecked(self) } - - /// Get the `LayoutableType` this layout is based on. - /// - /// This may be `None` for `TypeLayout`. - /// `TypeLayout` may use [`TypeLayout::layoutable_type`]. - pub fn get_layoutable_type(&self) -> Option<&LayoutableType> { self.layoutable_type.as_ref() } - /// a short name for this `TypeLayout`, useful for printing inline pub fn short_name(&self) -> String { match &self.kind { @@ -345,7 +327,6 @@ impl TypeLayout { write!(f, "{indent}{offset:3} {}: ", field.name)?; field.ty.write(&(indent.to_string() + tab), colored, f)?; if let Some(size) = field.ty.byte_size { - let size = size.max(field.custom_min_size.unwrap_or(0)); write!(f, " size={size}")?; } else { write!(f, " size=?")?; @@ -374,7 +355,6 @@ impl TypeLayout { // https://doc.rust-lang.org/reference/type-layout.html#r-layout.properties.align U32PowerOf2::try_from(align_of::() as u32).unwrap(), kind, - None, ) } @@ -383,7 +363,7 @@ impl TypeLayout { store_type: ir::StoreType, ) -> Result { let t: layoutable::LayoutableType = store_type.try_into()?; - Ok(TypeLayout::new_storage_layout_for(t).into()) + Ok(TypeLayout::new_layout_for(&t, Repr::Storage).into()) } } @@ -391,25 +371,17 @@ impl TypeLayout { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct FieldLayout { pub name: CanonName, - // whether size/align is custom doesn't matter for the layout equality. - pub custom_min_size: IgnoreInEqOrdHash>, - pub custom_min_align: IgnoreInEqOrdHash>, - /// The fields layout must be constraint::Plain, - /// because that's the most it can be while supporting all possible constraints. - pub ty: TypeLayout, + pub ty: TypeLayout, } impl FieldLayout { - fn byte_size(&self) -> Option { - self.ty - .byte_size() - .map(|byte_size| LayoutCalculator::calculate_byte_size(byte_size, self.custom_min_size.0)) - } + fn byte_size(&self) -> Option { self.ty.byte_size() } /// The alignment of the field with `custom_min_align` taken into account. - fn align(&self) -> U32PowerOf2 { LayoutCalculator::calculate_align(self.ty.align(), self.custom_min_align.0) } + fn align(&self) -> U32PowerOf2 { self.ty.align() } } +// TODO(chronicl) move into layoutable/builder.rs /// Options for the field of a struct. /// /// If you only want to customize the field's name, you can convert most string types @@ -448,13 +420,13 @@ impl> From for FieldOptions { fn from(name: T) -> Self { Self::new(name, None, None) } } -impl Display for TypeLayout { +impl Display for TypeLayout { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let colored = Context::try_with(call_info!(), |ctx| ctx.settings().colored_error_messages).unwrap_or(false); self.write("", colored, f) } } -impl Debug for TypeLayout { +impl Debug for TypeLayout { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.write("", false, f) } } diff --git a/shame/src/frontend/rust_types/type_traits.rs b/shame/src/frontend/rust_types/type_traits.rs index 4ba3e2d..7880ec3 100644 --- a/shame/src/frontend/rust_types/type_traits.rs +++ b/shame/src/frontend/rust_types/type_traits.rs @@ -3,7 +3,7 @@ use super::{ layout_traits::{FromAnys, GetAllFields, GpuLayout}, mem::{self, AddressSpace}, reference::{AccessMode, AccessModeReadable}, - type_layout::{self, type_layout_internal}, + type_layout::{self}, AsAny, GpuType, ToGpuType, }; use crate::{ diff --git a/shame/src/frontend/rust_types/vec.rs b/shame/src/frontend/rust_types/vec.rs index df5cf8f..4d065e3 100644 --- a/shame/src/frontend/rust_types/vec.rs +++ b/shame/src/frontend/rust_types/vec.rs @@ -7,6 +7,7 @@ use super::{ mem::AddressSpace, reference::{AccessMode, AccessModeReadable}, scalar_type::{dtype_as_scalar_from_f64, ScalarType, ScalarTypeInteger, ScalarTypeNumber}, + type_layout::repr, type_traits::{BindingArgs, GpuAligned, GpuStoreImplCategory, NoAtomics, NoHandles, VertexAttribute}, AsAny, GpuType, To, ToGpuType, }; @@ -592,7 +593,7 @@ impl GpuLayout for vec where vec: NoBools, { - fn gpu_repr() -> layout::Repr { layout::Repr::Storage } + type GpuRepr = repr::Storage; fn cpu_type_name_and_layout() -> Option, TypeLayout), super::layout_traits::ArrayElementsUnsizedError>> diff --git a/shame/src/ir/pipeline/wip_pipeline.rs b/shame/src/ir/pipeline/wip_pipeline.rs index 10bf448..64043f6 100644 --- a/shame/src/ir/pipeline/wip_pipeline.rs +++ b/shame/src/ir/pipeline/wip_pipeline.rs @@ -10,6 +10,7 @@ use thiserror::Error; use super::{PossibleStages, ShaderStage, StageMask}; use crate::{ + any::layout::Repr, call_info, common::{ integer::post_inc_usize, @@ -355,7 +356,7 @@ impl WipPushConstantsField { let sized_struct: layoutable::SizedStruct = sized_struct .try_into() .expect("push constants are NoBools and NoHandles"); - let layout = TypeLayout::new_storage_layout_for(sized_struct); + let layout = TypeLayout::new_layout_for(&sized_struct.into(), Repr::Storage); let layout = match &layout.kind { type_layout::TypeLayoutSemantics::Structure(layout) => &**layout, _ => unreachable!("expected struct layout for type layout of struct"), diff --git a/shame/src/lib.rs b/shame/src/lib.rs index b5dac06..cffa057 100644 --- a/shame/src/lib.rs +++ b/shame/src/lib.rs @@ -467,11 +467,11 @@ pub mod any { use crate::frontend::rust_types::type_layout; // type layout pub use type_layout::TypeLayout; + pub use type_layout::GpuTypeLayout; pub use type_layout::TypeRepr; pub use type_layout::Repr; pub mod repr { use crate::frontend::rust_types::type_layout; - pub use type_layout::repr::Plain; pub use type_layout::repr::Storage; pub use type_layout::repr::Uniform; pub use type_layout::repr::Packed; diff --git a/shame_derive/src/derive_layout.rs b/shame_derive/src/derive_layout.rs index 64a795e..3d4a57d 100644 --- a/shame_derive/src/derive_layout.rs +++ b/shame_derive/src/derive_layout.rs @@ -89,9 +89,8 @@ pub fn impl_for_struct( } let gpu_repr = gpu_repr.map(|(_, repr)| repr).unwrap_or(util::Repr::Storage); let gpu_repr_shame = match gpu_repr { - Repr::Packed => quote!( #re::Repr::Packed ), - Repr::Storage => quote!( #re::Repr::Storage ), - Repr::Uniform => quote!( #re::Repr::Uniform ), + Repr::Packed => quote!( #re::repr::Packed ), + Repr::Storage => quote!( #re::repr::Storage ), }; // #[repr(...)] @@ -260,9 +259,7 @@ pub fn impl_for_struct( #last_field_type: #re::Layoutable, #where_clause_predicates { - fn gpu_repr() -> #re::Repr { - #gpu_repr_shame - } + type GpuRepr = #gpu_repr_shame; fn cpu_type_name_and_layout() -> Option, #re::TypeLayout), #re::ArrayElementsUnsizedError>> { use #re::CpuLayout as _; diff --git a/shame_derive/src/util.rs b/shame_derive/src/util.rs index 0e085ed..4e23ebc 100644 --- a/shame_derive/src/util.rs +++ b/shame_derive/src/util.rs @@ -30,7 +30,6 @@ pub fn find_literal_list_attr( pub enum Repr { Packed, Storage, - Uniform, } pub fn determine_gpu_repr(attribs: &[syn::Attribute]) -> Result> { @@ -44,9 +43,6 @@ pub fn determine_gpu_repr(attribs: &[syn::Attribute]) -> Result Date: Mon, 26 May 2025 07:02:30 +0200 Subject: [PATCH 22/32] Fix FieldLayout construction from LayoutableType --- .../frontend/rust_types/type_layout/construction.rs | 13 +++++++++---- shame/src/frontend/rust_types/type_layout/mod.rs | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/construction.rs b/shame/src/frontend/rust_types/type_layout/construction.rs index 77c8178..24371b6 100644 --- a/shame/src/frontend/rust_types/type_layout/construction.rs +++ b/shame/src/frontend/rust_types/type_layout/construction.rs @@ -112,11 +112,16 @@ impl TypeLayout { } fn sized_field_to_field_layout(field: &SizedField, offset: u64, repr: Repr) -> FieldLayoutWithOffset { + let mut ty = TypeLayout::from_sized_type(&field.ty, repr); + // VERY IMPORTANT: TypeLayout::from_sized_type does not take into account + // custom_min_align and custom_min_size, but field.byte_size and field.align do. + ty.byte_size = Some(field.byte_size(repr)); + ty.align = field.align(repr); FieldLayoutWithOffset { rel_byte_offset: offset, field: FieldLayout { name: field.name.clone(), - ty: TypeLayout::from_sized_type(&field.ty, repr), + ty, }, } } @@ -302,7 +307,7 @@ pub struct LayoutErrorContext { use_color: bool, } -impl<'a> From> for LayoutErrorContext { +impl From> for LayoutErrorContext { fn from(ctx: LayoutContext) -> Self { LayoutErrorContext { top_level_type: ctx.top_level_type.clone(), @@ -398,11 +403,11 @@ impl Display for StructFieldOffsetError { match &self.struct_type { StructKind::Sized(s) => { let s: ir::SizedStruct = s.clone().try_into().ok()?; - ctx.struct_registry().get(&*s).map(|def| def.call_info()) + ctx.struct_registry().get(&s).map(|def| def.call_info()) } StructKind::Unsized(s) => { let s: ir::BufferBlock = s.clone().try_into().ok()?; - ctx.struct_registry().get(&*s).map(|def| def.call_info()) + ctx.struct_registry().get(&s).map(|def| def.call_info()) } } }) diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index 1edd93a..65e7c13 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -363,7 +363,7 @@ impl TypeLayout { store_type: ir::StoreType, ) -> Result { let t: layoutable::LayoutableType = store_type.try_into()?; - Ok(TypeLayout::new_layout_for(&t, Repr::Storage).into()) + Ok(TypeLayout::new_layout_for(&t, Repr::Storage)) } } From c22bfa7621f7a1ad4d18f25bd9c1919878f588fd Mon Sep 17 00:00:00 2001 From: chronicl Date: Mon, 26 May 2025 07:18:28 +0200 Subject: [PATCH 23/32] Change GpuLayout impl of PackedVec --- shame/src/frontend/rust_types/packed_vec.rs | 5 ++--- shame/src/lib.rs | 2 -- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/shame/src/frontend/rust_types/packed_vec.rs b/shame/src/frontend/rust_types/packed_vec.rs index 88c536e..a810c05 100644 --- a/shame/src/frontend/rust_types/packed_vec.rs +++ b/shame/src/frontend/rust_types/packed_vec.rs @@ -151,11 +151,10 @@ impl GpuLayout for PackedVec { type GpuRepr = repr::Storage; fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { - let sized_ty = Self::sized_ty_equivalent(); + let sized_ty: layoutable::SizedType = Self::layoutable_type_sized(); let name = sized_ty.to_string().into(); - let sized_ty: layoutable::SizedType = sized_ty.try_into().expect("PackedVec is NoBools and NoHandles"); let layout = TypeLayout::new_layout_for(&sized_ty.into(), Repr::Storage); - Some(Ok((name, layout.into()))) + Some(Ok((name, layout))) } } diff --git a/shame/src/lib.rs b/shame/src/lib.rs index cffa057..8305b7d 100644 --- a/shame/src/lib.rs +++ b/shame/src/lib.rs @@ -514,8 +514,6 @@ pub mod any { pub use type_layout::layoutable::array_align; pub use type_layout::layoutable::FieldOffsets; // conversion and builder errors - pub use type_layout::layoutable::ir_compat::LayoutableConversionError; - pub use type_layout::layoutable::ir_compat::ContainsBoolsError; pub use type_layout::layoutable::builder::IsUnsizedStructError; pub use type_layout::layoutable::builder::StructFromPartsError; } From 6dc9776a051839363f09180ba820b4c8ef9c5c79 Mon Sep 17 00:00:00 2001 From: chronicl Date: Mon, 26 May 2025 08:02:34 +0200 Subject: [PATCH 24/32] Change gpu_layout --- shame/src/frontend/rust_types/layout_traits.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/shame/src/frontend/rust_types/layout_traits.rs b/shame/src/frontend/rust_types/layout_traits.rs index 6bcc937..ea9a2b0 100644 --- a/shame/src/frontend/rust_types/layout_traits.rs +++ b/shame/src/frontend/rust_types/layout_traits.rs @@ -176,9 +176,7 @@ pub trait GpuLayout: Layoutable { /// println!("OnGpu:\n{}\n", OnGpu::gpu_layout()); /// println!("OnCpu:\n{}\n", OnCpu::cpu_layout()); /// ``` -pub fn gpu_layout() -> TypeLayout { - TypeLayout::new_layout_for(&T::layoutable_type(), ::REPR) -} +pub fn gpu_layout() -> TypeLayout { gpu_type_layout::().layout() } pub fn gpu_type_layout() -> GpuTypeLayout { GpuTypeLayout::new(T::layoutable_type()) From 3d8ddb40d1d3713d26bf057ca147d1b4f79c96fb Mon Sep 17 00:00:00 2001 From: chronicl Date: Mon, 26 May 2025 17:48:31 +0200 Subject: [PATCH 25/32] Fix FieldLayout runtime sized array construction --- shame/src/frontend/rust_types/type_layout/construction.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/shame/src/frontend/rust_types/type_layout/construction.rs b/shame/src/frontend/rust_types/type_layout/construction.rs index 24371b6..e5d8152 100644 --- a/shame/src/frontend/rust_types/type_layout/construction.rs +++ b/shame/src/frontend/rust_types/type_layout/construction.rs @@ -78,11 +78,17 @@ impl TypeLayout { .collect::>(); let (field_offset, align) = s.last_field_offset_and_struct_align(field_offsets); + + let mut ty = Self::from_runtime_sized_array(&s.last_unsized.array, repr); + // VERY IMPORTANT: TypeLayout::from_runtime_sized_array does not take into account + // custom_min_align, but s.last_unsized.align does. + ty.align = s.last_unsized.align(repr); + fields.push(FieldLayoutWithOffset { rel_byte_offset: field_offset, field: FieldLayout { name: s.last_unsized.name.clone(), - ty: Self::from_runtime_sized_array(&s.last_unsized.array, repr), + ty, }, }); From 02dd2c5b762f45b090bc0c8cc0a4298fc51bb1c9 Mon Sep 17 00:00:00 2001 From: chronicl Date: Mon, 26 May 2025 20:14:47 +0200 Subject: [PATCH 26/32] Cleanup and FieldOffsets redone --- shame/src/common/proc_macro_reexports.rs | 2 +- .../rust_types/type_layout/construction.rs | 41 +++--- .../type_layout/layoutable/align_size.rs | 126 ++++++++++-------- .../type_layout/layoutable/builder.rs | 88 ++++++++++++ .../rust_types/type_layout/layoutable/mod.rs | 54 +------- .../frontend/rust_types/type_layout/mod.rs | 62 +++------ shame/src/lib.rs | 2 +- 7 files changed, 210 insertions(+), 165 deletions(-) diff --git a/shame/src/common/proc_macro_reexports.rs b/shame/src/common/proc_macro_reexports.rs index e467fd8..28e46d9 100644 --- a/shame/src/common/proc_macro_reexports.rs +++ b/shame/src/common/proc_macro_reexports.rs @@ -25,7 +25,7 @@ pub use crate::frontend::rust_types::struct_::BufferFields; pub use crate::frontend::rust_types::struct_::SizedFields; pub use crate::frontend::rust_types::type_layout::FieldLayout; pub use crate::frontend::rust_types::type_layout::FieldLayoutWithOffset; -pub use crate::frontend::rust_types::type_layout::FieldOptions; +pub use crate::frontend::rust_types::type_layout::layoutable::FieldOptions; pub use crate::frontend::rust_types::type_layout::StructLayout; pub use crate::frontend::rust_types::type_layout::Repr; pub use crate::frontend::rust_types::type_layout::repr; diff --git a/shame/src/frontend/rust_types/type_layout/construction.rs b/shame/src/frontend/rust_types/type_layout/construction.rs index e5d8152..68947cf 100644 --- a/shame/src/frontend/rust_types/type_layout/construction.rs +++ b/shame/src/frontend/rust_types/type_layout/construction.rs @@ -56,9 +56,10 @@ impl TypeLayout { .map(|(offset, field)| sized_field_to_field_layout(field, offset, repr)) .collect(); + let (byte_size, align) = field_offsets.struct_byte_size_and_align(); ( - field_offsets.byte_size(), - field_offsets.align(), + byte_size, + align, TLS::Structure(Rc::new(StructLayout { name: s.name.clone().into(), fields, @@ -71,13 +72,13 @@ impl TypeLayout { } fn from_unsized_struct(s: &UnsizedStruct, repr: Repr) -> Self { - let mut field_offsets = s.sized_field_offsets(repr); - let mut fields = (&mut field_offsets) + let mut field_offsets = s.field_offsets(repr); + let mut fields = (&mut field_offsets.sized_field_offsets()) .zip(s.sized_fields.iter()) .map(|(offset, field)| sized_field_to_field_layout(field, offset, repr)) .collect::>(); - let (field_offset, align) = s.last_field_offset_and_struct_align(field_offsets); + let (field_offset, align) = field_offsets.last_field_offset_and_struct_align(); let mut ty = Self::from_runtime_sized_array(&s.last_unsized.array, repr); // VERY IMPORTANT: TypeLayout::from_runtime_sized_array does not take into account @@ -184,12 +185,18 @@ fn check_layout(ty: &LayoutableType, actual_repr: Repr, expected_repr: Repr) -> match &ctx.top_level_type { LayoutableType::Sized(s) => check_compare_sized(ctx, s), LayoutableType::UnsizedStruct(s) => { - let mut actual_offsets = s.sized_field_offsets(ctx.actual_repr); - let mut expected_offsets = s.sized_field_offsets(ctx.expected_repr); - check_sized_fields(ctx, s, &s.sized_fields, &mut actual_offsets, &mut expected_offsets)?; + let mut actual_offsets = s.field_offsets(ctx.actual_repr); + let mut expected_offsets = s.field_offsets(ctx.expected_repr); + check_sized_fields( + ctx, + s, + &s.sized_fields, + actual_offsets.sized_field_offsets(), + expected_offsets.sized_field_offsets(), + )?; - let (actual_last_offset, _) = s.last_field_offset_and_struct_align(actual_offsets); - let (expected_last_offset, _) = s.last_field_offset_and_struct_align(expected_offsets); + let (actual_last_offset, _) = actual_offsets.last_field_offset_and_struct_align(); + let (expected_last_offset, _) = expected_offsets.last_field_offset_and_struct_align(); if actual_last_offset != expected_last_offset { let field = &s.last_unsized; @@ -256,8 +263,8 @@ fn check_sized_fields( ctx: LayoutContext, s: &(impl Into + Clone), fields: &[SizedField], - actual_offsets: &mut FieldOffsets, - expected_offsets: &mut FieldOffsets, + actual_offsets: impl Iterator, + expected_offsets: impl Iterator, ) -> Result<(), LayoutError> { for (i, (field, (actual_offset, expected_offset))) in fields.iter().zip(actual_offsets.zip(expected_offsets)).enumerate() @@ -510,8 +517,12 @@ where }; let (struct_name, sized_fields, mut field_offsets) = match struct_type { - StructKind::Sized(s) => (&s.name, s.fields(), s.field_offsets(repr)), - StructKind::Unsized(s) => (&s.name, s.sized_fields.as_slice(), s.sized_field_offsets(repr)), + StructKind::Sized(s) => (&s.name, s.fields(), s.field_offsets(repr).into_sized_fields()), + StructKind::Unsized(s) => ( + &s.name, + s.sized_fields.as_slice(), + s.field_offsets(repr).into_sized_fields(), + ), }; let indent = " "; @@ -548,7 +559,7 @@ where color(f, "#508EE3", field_index)?; let last_field = &s.last_unsized; - let (last_field_offset, _) = s.last_field_offset_and_struct_align(field_offsets); + let (last_field_offset, _) = s.field_offsets(repr).last_field_offset_and_struct_align(); let decl_line = format!("{indent}{}: {},", last_field.name, last_field.array); f.write_str(&decl_line)?; diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index 9b9b566..7421439 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -74,16 +74,10 @@ impl SizedType { impl SizedStruct { /// Returns [`FieldOffsets`], which serves as an iterator over the offsets of the - /// fields of this struct, as well as a `byte_size` and `align` calculator. - /// See the documentation of `FieldOffsets` on how to obtain `byte_size` and `align` - /// of this struct from it. - pub fn field_offsets(&self, repr: Repr) -> FieldOffsets { - FieldOffsets { - fields: &self.fields, - field_index: 0, - calc: LayoutCalculator::new(repr), - repr, - } + /// fields of this struct. `SizedStruct::byte_size_and_align_from_offsets` can be + /// used to efficiently obtain the byte_size + pub fn field_offsets(&self, repr: Repr) -> FieldOffsetsSized { + FieldOffsetsSized(FieldOffsets::new(self.fields(), repr)) } /// Returns (byte_size, align) @@ -93,26 +87,17 @@ impl SizedStruct { /// If you also need field offsets, use [`SizedStruct::field_offsets`] instead and /// read the documentation of [`FieldOffsets`] on how to obtain the byte size and align from it. pub fn byte_size_and_align(&self, repr: Repr) -> (u64, U32PowerOf2) { - let mut field_offsets = self.field_offsets(repr); - (&mut field_offsets).count(); // &mut so it doesn't consume - (field_offsets.byte_size(), field_offsets.align()) + self.field_offsets(repr).struct_byte_size_and_align() } } -/// An iterator over the field offsets of a `SizedStruct` or the sized fields of an `UnsizedStruct`. -/// -/// `FieldOffsets::byte_size` and `FieldOffsets::byte_align` can be used to query the struct's -/// `byte_size` and `byte_align`, but only takes into account the fields that have been iterated over. -/// -/// Use [`UnsizedStruct::last_field_offset_and_struct_align`] to obtain the last field's offset -/// and the struct's align for `UnsizedStruct`s. +/// An iterator over the offsets of sized fields. pub struct FieldOffsets<'a> { fields: &'a [SizedField], field_index: usize, calc: LayoutCalculator, repr: Repr, } - impl Iterator for FieldOffsets<'_> { type Item = u64; @@ -127,49 +112,84 @@ impl Iterator for FieldOffsets<'_> { }) } } +impl<'a> FieldOffsets<'a> { + fn new(fields: &'a [SizedField], repr: Repr) -> Self { + Self { + fields, + field_index: 0, + calc: LayoutCalculator::new(repr), + repr, + } + } +} -impl FieldOffsets<'_> { - /// Returns the byte size of the struct, but ONLY with the fields that have been iterated over so far. - pub const fn byte_size(&self) -> u64 { self.calc.byte_size() } - /// Returns the byte align of the struct, but ONLY with the fields that have been iterated over so far. - pub const fn align(&self) -> U32PowerOf2 { struct_align(self.calc.align(), self.repr) } +/// Iterator over the field offsets of a `SizedStruct`. +// The difference to `FieldOffsets` is that it also offers a `struct_byte_size_and_align` method. +pub struct FieldOffsetsSized<'a>(FieldOffsets<'a>); +impl Iterator for FieldOffsetsSized<'_> { + type Item = u64; + fn next(&mut self) -> Option { self.0.next() } } +impl<'a> FieldOffsetsSized<'a> { + /// Consumes self and calculates the byte size and align of a struct + /// with exactly the sized fields that this FieldOffsets was created with. + pub fn struct_byte_size_and_align(mut self) -> (u64, U32PowerOf2) { + // Finishing layout calculations + (&mut self.0).count(); + (self.0.calc.byte_size(), struct_align(self.0.calc.align(), self.0.repr)) + } + /// Returns the inner iterator over sized fields. + pub fn into_sized_fields(self) -> FieldOffsets<'a> { self.0 } +} -impl UnsizedStruct { - /// Returns [`FieldOffsets`], which serves as an iterator over the offsets of the - /// sized fields of this struct. `FieldOffsets` may be also passed to - /// [`UnsizedStruct::last_field_offset_and_struct_align`] to obtain the last field's offset - /// and the struct's align. - pub fn sized_field_offsets(&self, repr: Repr) -> FieldOffsets { - FieldOffsets { - fields: &self.sized_fields, - field_index: 0, - calc: LayoutCalculator::new(repr), - repr, +/// The field offsets of an `UnsizedStruct`. +/// +/// - Use [`FieldOffsetsUnsized::sized_field_offsets`] for an iterator over the sized field offsets. +/// - Use [`FieldOffsetsUnsized::last_field_offset_and_struct_align`] for the last field's offset +/// and the struct's align +pub struct FieldOffsetsUnsized<'a> { + sized: FieldOffsets<'a>, + last_unsized: &'a RuntimeSizedArrayField, +} + +impl<'a> FieldOffsetsUnsized<'a> { + fn new(sized_fields: &'a [SizedField], last_unsized: &'a RuntimeSizedArrayField, repr: Repr) -> Self { + Self { + sized: FieldOffsets::new(sized_fields, repr), + last_unsized, } } - /// Returns (last field offset, byte align of unsized struct) - /// - /// `sized_field_offsets` must be from the same `UnsizedStruct`, otherwise the returned values - /// are not accurate. - pub fn last_field_offset_and_struct_align(&self, sized_field_offsets: FieldOffsets) -> (u64, U32PowerOf2) { - let mut offsets = sized_field_offsets; - // Iterating over any remaining field offsets to update the layout calculator. - (&mut offsets).count(); - - let array_align = self.last_unsized.array.align(offsets.repr); + /// Returns an iterator over the sized field offsets. + pub fn sized_field_offsets(&mut self) -> &mut FieldOffsets<'a> { &mut self.sized } + + /// Returns the last field's offset and the struct's align. + pub fn last_field_offset_and_struct_align(mut self) -> (u64, U32PowerOf2) { + // Finishing layout calculations + (&mut self.sized).count(); + let array_align = self.last_unsized.array.align(self.sized.repr); let custom_min_align = self.last_unsized.custom_min_align; - let (offset, align) = offsets.calc.extend_unsized(array_align, custom_min_align); - (offset, struct_align(align, offsets.repr)) + let (offset, align) = self.sized.calc.extend_unsized(array_align, custom_min_align); + (offset, struct_align(align, self.sized.repr)) } - /// This is expensive as it calculates the byte align from scratch. - pub fn align(&self, repr: Repr) -> U32PowerOf2 { - let offsets = self.sized_field_offsets(repr); - self.last_field_offset_and_struct_align(offsets).1 + /// Returns the inner iterator over sized fields. + pub fn into_sized_fields(self) -> FieldOffsets<'a> { self.sized } +} + +impl UnsizedStruct { + /// Returns [`FieldOffsetsUnsized`]. + /// + /// - Use [`FieldOffsetsUnsized::sized_field_offsets`] for an iterator over the sized field offsets. + /// - Use [`FieldOffsetsUnsized::last_field_offset_and_struct_align`] for the last field's offset + /// and the struct's align + pub fn field_offsets(&self, repr: Repr) -> FieldOffsetsUnsized { + FieldOffsetsUnsized::new(&self.sized_fields, &self.last_unsized, repr) } + + /// This is expensive as it calculates the byte align from scratch. + pub fn align(&self, repr: Repr) -> U32PowerOf2 { self.field_offsets(repr).last_field_offset_and_struct_align().1 } } const fn struct_align(align: U32PowerOf2, repr: Repr) -> U32PowerOf2 { diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/builder.rs b/shame/src/frontend/rust_types/type_layout/layoutable/builder.rs index 17b67ec..399999f 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/builder.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/builder.rs @@ -156,6 +156,56 @@ impl TryFrom for SizedOrArray { } } +impl SizedField { + /// Creates a new `SizedField`. + pub fn new(options: impl Into, ty: impl Into) -> Self { + let options = options.into(); + Self { + name: options.name, + custom_min_size: options.custom_min_size, + custom_min_align: options.custom_min_align, + ty: ty.into(), + } + } +} + +impl RuntimeSizedArrayField { + /// Creates a new `RuntimeSizedArrayField` given it's field name, + /// an optional custom minimum align and it's element type. + pub fn new( + name: impl Into, + custom_min_align: Option, + element_ty: impl Into, + ) -> Self { + Self { + name: name.into(), + custom_min_align, + array: RuntimeSizedArray { + element: element_ty.into(), + }, + } + } +} + +impl SizedArray { + /// Creates a new `RuntimeSizedArray` from it's element type and length. + pub fn new(element_ty: impl Into, len: NonZeroU32) -> Self { + Self { + element: Rc::new(element_ty.into()), + len, + } + } +} + +impl RuntimeSizedArray { + /// Creates a new `RuntimeSizedArray` from it's element type. + pub fn new(element_ty: impl Into) -> Self { + RuntimeSizedArray { + element: element_ty.into(), + } + } +} + impl> From<(T, SizedType)> for SizedField { fn from(value: (T, SizedType)) -> Self { Self { @@ -176,3 +226,41 @@ impl> From<(T, SizedType)> for RuntimeSizedArrayField { } } } + +/// Options for the field of a struct. +/// +/// If you only want to customize the field's name, you can convert most string types +/// to `FieldOptions` using `Into::into`, but most methods take `impl Into`, +/// meaning you can just pass the string type directly. +#[derive(Debug, Clone)] +pub struct FieldOptions { + /// Name of the field + pub name: CanonName, + /// Custom minimum align of the field. + pub custom_min_align: Option, + /// Custom mininum size of the field. + pub custom_min_size: Option, +} + +impl FieldOptions { + /// Creates new `FieldOptions`. + /// + /// If you only want to customize the field's name, you can convert most string types + /// to `FieldOptions` using `Into::into`, but most methods take `impl Into`, + /// meaning you can just pass the string type directly. + pub fn new( + name: impl Into, + custom_min_align: Option, + custom_min_size: Option, + ) -> Self { + Self { + name: name.into(), + custom_min_align, + custom_min_size, + } + } +} + +impl> From for FieldOptions { + fn from(name: T) -> Self { Self::new(name, None, None) } +} diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs index 031de09..8f92197 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs @@ -10,14 +10,14 @@ use crate::{ }; pub use crate::ir::{Len, Len2, PackedVector, ScalarTypeFp, ScalarTypeInteger, ir_type::CanonName}; -use super::{construction::StructKind, FieldOptions}; +use super::{construction::StructKind}; pub(crate) mod align_size; pub(crate) mod builder; pub(crate) mod ir_compat; pub use align_size::{FieldOffsets, MatrixMajor, LayoutCalculator, array_size, array_stride, array_align}; -pub use builder::SizedOrArray; +pub use builder::{SizedOrArray, FieldOptions}; /// Types that have a defined memory layout. /// @@ -133,56 +133,6 @@ pub struct RuntimeSizedArrayField { pub array: RuntimeSizedArray, } -impl SizedField { - /// Creates a new `SizedField`. - pub fn new(options: impl Into, ty: impl Into) -> Self { - let options = options.into(); - Self { - name: options.name, - custom_min_size: options.custom_min_size, - custom_min_align: options.custom_min_align, - ty: ty.into(), - } - } -} - -impl RuntimeSizedArrayField { - /// Creates a new `RuntimeSizedArrayField` given it's field name, - /// an optional custom minimum align and it's element type. - pub fn new( - name: impl Into, - custom_min_align: Option, - element_ty: impl Into, - ) -> Self { - Self { - name: name.into(), - custom_min_align, - array: RuntimeSizedArray { - element: element_ty.into(), - }, - } - } -} - -impl SizedArray { - /// Creates a new `RuntimeSizedArray` from it's element type and length. - pub fn new(element_ty: impl Into, len: NonZeroU32) -> Self { - Self { - element: Rc::new(element_ty.into()), - len, - } - } -} - -impl RuntimeSizedArray { - /// Creates a new `RuntimeSizedArray` from it's element type. - pub fn new(element_ty: impl Into) -> Self { - RuntimeSizedArray { - element: element_ty.into(), - } - } -} - /// Trait for types that have a well-defined memory layout. pub trait Layoutable { /// Returns the `LayoutableType` representation for this type. diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index 65e7c13..19c9085 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -120,7 +120,21 @@ impl Hash for TypeLayout { } } -/// TODO(chronicl) +/// A `TypeLayout`, but guaranteed to be based on a `LayoutableType` and +/// a `Repr` - it follows the layout rules that correspond to the `Repr`. +/// +/// The actual `TypeLayout` can be obtained via `GpuTypeLayout::layout`. +/// +/// While `GpuTypeLayout` and `GpuTypeLayout` can be freely created +/// from a `LayoutableType`, the only way to get a `GpuTypeLayout` is by +/// using `TryFrom::try_from` on a `GpuTypeLayout`. This is the case, because +/// shame does not support `#[gpu_repr(uniform)]`, meaning we can't lay out a type +/// according to uniform layout rules, we must lay it out according to storage layout rules +/// and then check whether the storage layout also follows the uniform layout rules. +// The reason shame doesn't support #[gpu_repr(uniform)] is that wgsl does not have +// a custom stride attribute, so for example the elements in the `sm::Array` in +// struct A { a: sm::Array } need to have a stride of 16 according to uniform +// layout rules, which we can't enforce. #[derive(Debug, Clone)] pub struct GpuTypeLayout { ty: LayoutableType, @@ -128,9 +142,9 @@ pub struct GpuTypeLayout { } impl GpuTypeLayout { - /// TODO(chronicl) + /// Gets the `TypeLayout`. pub fn layout(&self) -> TypeLayout { TypeLayout::new_layout_for(&self.ty, T::REPR) } - /// TODO(chronicl) + /// Returns the `LayoutableType` this `GpuTypeLayout` is based on. pub fn layoutable_type(&self) -> &LayoutableType { &self.ty } } @@ -147,7 +161,8 @@ pub mod repr { /// The corresponding enum variant of `Repr`. const REPR: Repr; } - /// TODO(chronicl) + /// A subset of the types implementing `TypeRepr`. As the name suggests + /// only `Storage` and `Packed` implement this trait. pub trait TypeReprStorageOrPacked: TypeRepr {} impl TypeReprStorageOrPacked for Storage {} impl TypeReprStorageOrPacked for Packed {} @@ -381,45 +396,6 @@ impl FieldLayout { fn align(&self) -> U32PowerOf2 { self.ty.align() } } -// TODO(chronicl) move into layoutable/builder.rs -/// Options for the field of a struct. -/// -/// If you only want to customize the field's name, you can convert most string types -/// to `FieldOptions` using `Into::into`, but most methods take `impl Into`, -/// meaning you can just pass the string type directly. -#[derive(Debug, Clone)] -pub struct FieldOptions { - /// Name of the field - pub name: CanonName, - /// Custom minimum align of the field. - pub custom_min_align: Option, - /// Custom mininum size of the field. - pub custom_min_size: Option, -} - -impl FieldOptions { - /// Creates new `FieldOptions`. - /// - /// If you only want to customize the field's name, you can convert most string types - /// to `FieldOptions` using `Into::into`, but most methods take `impl Into`, - /// meaning you can just pass the string type directly. - pub fn new( - name: impl Into, - custom_min_align: Option, - custom_min_size: Option, - ) -> Self { - Self { - name: name.into(), - custom_min_align, - custom_min_size, - } - } -} - -impl> From for FieldOptions { - fn from(name: T) -> Self { Self::new(name, None, None) } -} - impl Display for TypeLayout { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let colored = Context::try_with(call_info!(), |ctx| ctx.settings().colored_error_messages).unwrap_or(false); diff --git a/shame/src/lib.rs b/shame/src/lib.rs index 8305b7d..d38dc75 100644 --- a/shame/src/lib.rs +++ b/shame/src/lib.rs @@ -480,7 +480,6 @@ pub mod any { pub use type_layout::StructLayout; pub use type_layout::FieldLayoutWithOffset; pub use type_layout::FieldLayout; - pub use type_layout::FieldOptions; pub use type_layout::ElementLayout; // layoutable traits pub use type_layout::layoutable::Layoutable; @@ -507,6 +506,7 @@ pub mod any { pub use type_layout::layoutable::RuntimeSizedArrayField; pub use type_layout::layoutable::CanonName; pub use type_layout::layoutable::SizedOrArray; + pub use type_layout::layoutable::FieldOptions; // layout calculation utility pub use type_layout::layoutable::LayoutCalculator; pub use type_layout::layoutable::array_stride; From 23af34a96a21411d22b71e9cda629d9e363ad5fa Mon Sep 17 00:00:00 2001 From: chronicl Date: Tue, 27 May 2025 05:51:47 +0200 Subject: [PATCH 27/32] Fix repr_c_struct_layout --- shame/src/common/proc_macro_utils.rs | 6 ++++-- shame/tests/test_layout.rs | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/shame/src/common/proc_macro_utils.rs b/shame/src/common/proc_macro_utils.rs index 164aecb..797f3ec 100644 --- a/shame/src/common/proc_macro_utils.rs +++ b/shame/src/common/proc_macro_utils.rs @@ -100,8 +100,10 @@ pub fn repr_c_struct_layout( let total_struct_size = last_field_size.map(|last_size| round_up(struct_alignment.as_u64(), last_field_offset + last_size)); - let new_size = |layout_size: Option, actual_size: Option| { - (layout_size != actual_size).then_some(actual_size).flatten() + let new_size = |layout_size: Option, actual_size: Option| match (layout_size, actual_size) { + (_, Some(s)) => Some(s), // prefer actual size + (Some(s), None) => Some(s), // but still use layout size if no actual size + (None, None) => None, }; let mut fields = first_fields_with_offsets_and_sizes .iter() diff --git a/shame/tests/test_layout.rs b/shame/tests/test_layout.rs index 041d20d..85115ed 100644 --- a/shame/tests/test_layout.rs +++ b/shame/tests/test_layout.rs @@ -228,6 +228,8 @@ fn unsized_struct_vec3_align_layout_eq() { a: f32x4_align4, // size=16, align=4 } + println!("{}", cpu_layout::()); + // the alignment on the top level of the layout doesn't matter. // two layouts are only considered different if an alignment mismatch // leads to different offsets of fields or array elements From 6c3d170d6f0f3eb56b7cca1577327108cb49f8d5 Mon Sep 17 00:00:00 2001 From: chronicl Date: Thu, 29 May 2025 20:16:28 +0200 Subject: [PATCH 28/32] Some comment cleanup --- .../rust_types/type_layout/construction.rs | 7 ++- .../rust_types/type_layout/layoutable/mod.rs | 2 +- .../frontend/rust_types/type_layout/mod.rs | 43 +++++++++---------- 3 files changed, 24 insertions(+), 28 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/construction.rs b/shame/src/frontend/rust_types/type_layout/construction.rs index 68947cf..e586494 100644 --- a/shame/src/frontend/rust_types/type_layout/construction.rs +++ b/shame/src/frontend/rust_types/type_layout/construction.rs @@ -14,8 +14,8 @@ use super::{ use TypeLayoutSemantics as TLS; impl TypeLayout { - /// Returns the type layout of the given `LayoutableType` - /// layed out according to the given `repr`. + /// Returns the type layout of the `LayoutableType` + /// layed out according to the `repr`. pub fn new_layout_for(ty: &LayoutableType, repr: Repr) -> Self { match ty { LayoutableType::Sized(ty) => Self::from_sized_type(ty, repr), @@ -287,7 +287,7 @@ fn check_sized_fields( Ok(()) } -/// Enum of possible errors during `TypeLayout -> TypeLayout` conversion. +/// Enum of possible errors during comparison of two layouts for the same `LayoutableType`. #[derive(thiserror::Error, Debug, Clone)] pub enum LayoutError { #[error("{0}")] @@ -495,7 +495,6 @@ impl Display for StructFieldOffsetError { } } -/// Panics if s is not the layout of a struct and doesn't contain a cpu-shareable. fn write_struct_layout( struct_type: &StructKind, repr: Repr, diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs index 8f92197..0bbf272 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs @@ -22,7 +22,7 @@ pub use builder::{SizedOrArray, FieldOptions}; /// Types that have a defined memory layout. /// /// `LayoutableType` does not contain any layout information itself, but a layout -/// can be assigned to it using [`TypeLayout`] according to one of the available layout rules: +/// can be assigned to it using [`GpuTypeLayout`] according to one of the available layout rules: /// storage, uniform or packed. #[derive(Debug, Clone)] pub enum LayoutableType { diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index 19c9085..81fac77 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -57,7 +57,6 @@ pub enum TypeLayoutSemantics { /// The following types implementing `TypeRepr` exist and can be found in [`shame::any::repr`]: /// /// ``` -/// struct Plain; /// May not follow any layout rules. This is the default. /// struct Storage; /// wgsl storage address space layout / OpenGL std430 /// struct Uniform; /// wgsl uniform address space layout / OpenGL std140 /// struct Packed; /// Packed layout @@ -67,19 +66,22 @@ pub enum TypeLayoutSemantics { /// /// https://www.w3.org/TR/WGSL/#address-space-layout-constraints /// -/// The following methods exist for creating new type layouts based on a [`LayoutableType`] +/// The following method exists for creating new type layouts based on a [`LayoutableType`] /// ``` /// let layout_type: LayoutableType = f32x1::layoutable_type(); -/// let _ = TypeLayout::new_layout_for(layout_type); -/// let _ = TypeLayout::new_storage_layout_for(layout_type); -/// let _ = TypeLayout::new_uniform_layout_for(layout_type); -/// let _ = TypeLayout::new_packed_layout_for(layout_type); +/// let repr = Repr::Storage; // or Uniform or Packed +/// let _ = TypeLayout::new_layout_for(layout_type, repr); /// ``` /// -/// Non-`Plain` layouts are always based on a [`LayoutableType`], which can be accessed -/// using [`TypeLayout::layoutable_type`]. They also guarantee, that all nested `TypeLayout`s -/// (for example struct fields) are also based on a `LayoutableType`, which can only be accessed -/// through `TypeLayout::get_layoutable_type`. +/// The resulting layout will always follow the layout rules of the `Repr`, however, this +/// can result in layouts that are not representable in wgsl, such as the uniform layout for +/// `shame::Array`, which requires at least a 16 byte stride. The `TypeLayout` will +/// contain information for the correct minimum stride, but since wgsl does not have a custom +/// stride attribute (like `@align` or `@size` but for strides) the type layout can't be +/// translated to wgsl. +/// +/// For the above reason `TypeLayout` exists mainly for internal usage in shame and +/// [`GpuTypeLayout`] is the user interface. See it's documentation for more information. /// /// ### Layout comparison /// @@ -127,14 +129,9 @@ impl Hash for TypeLayout { /// /// While `GpuTypeLayout` and `GpuTypeLayout` can be freely created /// from a `LayoutableType`, the only way to get a `GpuTypeLayout` is by -/// using `TryFrom::try_from` on a `GpuTypeLayout`. This is the case, because -/// shame does not support `#[gpu_repr(uniform)]`, meaning we can't lay out a type -/// according to uniform layout rules, we must lay it out according to storage layout rules -/// and then check whether the storage layout also follows the uniform layout rules. -// The reason shame doesn't support #[gpu_repr(uniform)] is that wgsl does not have -// a custom stride attribute, so for example the elements in the `sm::Array` in -// struct A { a: sm::Array } need to have a stride of 16 according to uniform -// layout rules, which we can't enforce. +/// using `TryFrom::try_from` on a `GpuTypeLayout`, which only checks whether +/// the storage layout also follows the uniform layout rules - it does not change the +/// corresponding `TypeLayout`. #[derive(Debug, Clone)] pub struct GpuTypeLayout { ty: LayoutableType, @@ -150,13 +147,13 @@ impl GpuTypeLayout { use repr::TypeReprStorageOrPacked; pub use repr::{TypeRepr, Repr}; -/// Module for all restrictions on `TypeLayout`. +/// Module for all restrictions on `GpuTypeLayout`. pub mod repr { use super::*; - /// Type representation used by `TypeLayout`. This provides guarantees + /// Type representation used by `GpuTypeLayout`. This provides guarantees /// about the alignment rules that the type layout adheres to. - /// See [`TypeLayout`] documentation for more details. + /// See [`GpuTypeLayout`] documentation for more details. pub trait TypeRepr: Clone + PartialEq + Eq { /// The corresponding enum variant of `Repr`. const REPR: Repr; @@ -202,8 +199,8 @@ pub mod repr { macro_rules! type_repr { ($($repr:ident),*) => { $( - /// A type representation used by `TypeLayout`. - /// See [`TypeLayout`] documentation for more details. + /// A type representation used by `GpuTypeLayout`. + /// See [`GpuTypeLayout`] documentation for more details. #[derive(Clone, PartialEq, Eq, Hash)] pub struct $repr; impl TypeRepr for $repr { From cf1392cd65c179520d0cb3e8990b713bdb6ee082 Mon Sep 17 00:00:00 2001 From: chronicl Date: Tue, 27 May 2025 04:57:41 +0200 Subject: [PATCH 29/32] Begin storage/uniform buffer any api rework --- shame/src/frontend/any/shared_io.rs | 259 +++++++++++------- shame/src/frontend/encoding/binding.rs | 49 ++-- shame/src/frontend/encoding/buffer.rs | 47 +++- shame/src/frontend/encoding/mod.rs | 19 +- .../type_layout/layoutable/ir_compat.rs | 4 +- 5 files changed, 234 insertions(+), 144 deletions(-) diff --git a/shame/src/frontend/any/shared_io.rs b/shame/src/frontend/any/shared_io.rs index b33f80c..e483440 100644 --- a/shame/src/frontend/any/shared_io.rs +++ b/shame/src/frontend/any/shared_io.rs @@ -1,6 +1,7 @@ use std::fmt::Display; use std::num::NonZeroU64; +use crate::any::layout::{repr, GpuTypeLayout, LayoutableType, TypeRepr}; use crate::backend::language::Language; use crate::call_info; use crate::common::po2::U32PowerOf2; @@ -8,6 +9,7 @@ use crate::frontend::any::Any; use crate::frontend::any::{record_node, InvalidReason}; use crate::frontend::encoding::{EncodingErrorKind, EncodingGuard}; use crate::frontend::error::InternalError; +use crate::frontend::rust_types::type_layout::layoutable::ir_compat::IRConversionError; use crate::ir::expr::Binding; use crate::ir::expr::Expr; use crate::ir::ir_type::{ @@ -15,8 +17,8 @@ use crate::ir::ir_type::{ LayoutErrorContext, SamplesPerPixel, }; use crate::ir::pipeline::{PipelineError, StageMask, WipBinding, WipPushConstantsField}; -use crate::ir::recording::Context; -use crate::ir::{self, StoreType, TextureFormatWrapper, TextureSampleUsageType, Type}; +use crate::ir::recording::{Context, MemoryRegion}; +use crate::ir::{self, AddressSpace, StoreType, TextureFormatWrapper, TextureSampleUsageType, Type}; use crate::ir::{ir_type::TextureShape, AccessMode}; use std::collections::btree_map::Entry; use thiserror::Error; @@ -147,125 +149,176 @@ impl Display for SamplingMethod { #[allow(missing_docs)] #[derive(Debug, Error, Clone)] pub enum BindingError { - #[error("invalid type `{0:?}` for binding of kind `{1:?}`")] - InvalidTypeForBinding(ir::StoreType, BindingType), - #[error("the type `{0:?}` cannot be used for a binding of kind `{1:?}` because of its layout.\n{2}")] - TypeHasInvalidLayoutForBinding(ir::StoreType, BindingType, LayoutError), + #[error("the type `{0:?}` cannot be used for a binding of kind `{1:?}` because of:\n{0}")] + TypeHasInvalidLayoutForBinding(LayoutableType, BindingType, IRConversionError), +} + +fn layout_to_store_type( + layout: GpuTypeLayout, + binding_ty: &BindingType, +) -> Result { + let store_type: ir::StoreType = layout.layoutable_type().clone().try_into().map_err(|e| { + BindingError::TypeHasInvalidLayoutForBinding(layout.layoutable_type().clone(), binding_ty.clone(), e) + })?; + + // https://www.w3.org/TR/WGSL/#host-shareable-types + if !store_type.is_host_shareable() { + return Err(InternalError::new( + true, + format!( + "LayoutableType to StoreType conversion did not result in a host-shareable type. LayoutableType:\n{}", + layout.layoutable_type(), + ), + ) + .into()); + } + + Ok(store_type) +} + +fn record_and_register_binding( + ctx: &Context, + path: BindPath, + visibility: StageMask, + binding_ty: BindingType, + store_type: StoreType, + ty: Type, +) -> Result { + let any = record_node( + ctx.latest_user_caller(), + Expr::PipelineIo(PipelineIo::Binding(Binding { bind_path: path, ty })), + &[], + ); + + match ctx.pipeline_layout_mut().bindings.entry(path) { + Entry::Occupied(entry) => Err(PipelineError::DuplicateBindPath(path, entry.get().shader_ty.clone()).into()), + Entry::Vacant(entry) => match any.node() { + Some(node) => { + entry.insert(WipBinding { + call_info: call_info!(), + user_defined_visibility: visibility, + binding_ty, + shader_ty: store_type, + node, + }); + Ok(any) + } + None => Err(InternalError::new(true, format!("binding at `{path}` produced invalid Any object")).into()), + }, + } +} + +#[track_caller] +fn create_any_catch_errors(create_any: impl FnOnce(&Context) -> Result) -> Any { + Context::try_with(call_info!(), |ctx| match create_any(ctx) { + Ok(any) => any, + Err(e) => { + ctx.push_error(e); + Any::new_invalid(InvalidReason::ErrorThatWasPushed) + } + }) + .unwrap_or(Any::new_invalid(InvalidReason::CreatedWithNoActiveEncoding)) } impl Any { - /// import the resource bound at `path` of kind `binding_ty`. - /// the shader type `ty` must correspond to the `binding_ty` according to https://www.w3.org/TR/WGSL/#var-decls - /// (paragraphs on uniform buffer, storage buffer etc.) - /// - /// ----- - /// - /// `buffer_binding_as_ref`: how the resulting `Any` is returned. - /// - `true`: returns `Ref` (`binding_ty` must be `BindingType::Buffer`) - /// - `false`: returns `ty` (`binding_ty` must be a uniform or read-only storage buffer, `ty` must be constructible) - /// - /// if the conditions mentioned above are not met, an `EncodingError` is pushed and an invalid `Any` is returned. + /// Creates a storage buffer binding at the specified bind path. #[track_caller] - pub fn binding( - path: BindPath, + pub fn storage_buffer_binding( + bind_path: BindPath, visibility: StageMask, - ty: ir::StoreType, - binding_ty: BindingType, + layout: GpuTypeLayout, + access: AccessModeReadable, buffer_binding_as_ref: bool, + has_dynamic_offset: bool, ) -> Any { - let record_handle_node = |ty, call_info| { - record_node( - call_info, - Expr::PipelineIo(PipelineIo::Binding(Binding { bind_path: path, ty })), - &[], - ) - }; - // this closure checks whether `binding_ty` and `ty` are compatible let create_any = |ctx: &Context| -> Result { - ctx.push_error_if_outside_encoding_scope("import of bindings"); - let call_info = ctx.latest_user_caller(); + let binding_type = BindingType::Buffer { + ty: BufferBindingType::Storage(access), + has_dynamic_offset, + }; + let store_type = layout_to_store_type(layout, &binding_type)?; + + let ty = match (buffer_binding_as_ref, access) { + (true, _) => Type::Ref( + MemoryRegion::new( + ctx.latest_user_caller(), + store_type.clone(), + None, + None, + access.into(), + AddressSpace::Storage, + )?, + store_type.clone(), + access.into(), + ), + (false, AccessModeReadable::ReadWrite) => { + return Err(LayoutError::NonRefBufferRequiresReadOnlyAndConstructible.into()); + } + (false, AccessModeReadable::Read) => { + if !store_type.is_constructible() { + return Err(LayoutError::NonRefBufferRequiresReadOnlyAndConstructible.into()); + } + Type::Store(store_type.clone()) + } + }; + + record_and_register_binding(ctx, bind_path, visibility, binding_type, store_type, ty) + }; - if buffer_binding_as_ref && !matches!(binding_ty, BindingType::Buffer { .. }) { + create_any_catch_errors(create_any) + } + + /// Creates a uniform buffer binding at the specified bind path. + #[track_caller] + pub fn uniform_buffer_binding( + bind_path: BindPath, + visibility: StageMask, + layout: GpuTypeLayout, + has_dynamic_offset: bool, + ) -> Any { + let create_any = |ctx: &Context| -> Result { + let binding_type = BindingType::Buffer { + ty: BufferBindingType::Uniform, + has_dynamic_offset, + }; + let store_type = layout_to_store_type(layout, &binding_type)?; + if !store_type.is_constructible() { return Err(LayoutError::NonRefBufferRequiresReadOnlyAndConstructible.into()); } - let any = match &binding_ty { - BindingType::Buffer { - ty: buffer_ty, - has_dynamic_offset, - } => { - let ref_or_value_ty = - get_type_for_buffer_binding_type(&ty, *buffer_ty, buffer_binding_as_ref, ctx)?; - Ok(record_handle_node(ref_or_value_ty, ctx.latest_user_caller())) - } - BindingType::Sampler(s1) => match &ty { - StoreType::Handle(HandleType::Sampler(s2)) if s1 == s2 => { - Ok(record_handle_node(Type::Store(ty.clone()), call_info)) - } - _ => Err(BindingError::InvalidTypeForBinding(ty.clone(), binding_ty.clone())), - }, - BindingType::SampledTexture { - shape: s0, - sample_type: t0, - samples_per_pixel: spp0, - } => match &ty { - StoreType::Handle(HandleType::SampledTexture(s1, t1, spp1)) - if (t0 == t1) && (s0 == s1) && (spp0 == spp1) => - { - Ok(record_handle_node(Type::Store(ty.clone()), call_info)) - } + let ty = Type::Store(store_type.clone()); - _ => Err(BindingError::InvalidTypeForBinding(ty.clone(), binding_ty.clone())), - }, - BindingType::StorageTexture { - shape: s0, - format: f0, - access: a0, - } => match &ty { - StoreType::Handle(HandleType::StorageTexture(s1, f1, a1)) - if (a0 == a1) && (f0 == f1) && (s0 == s1) => - { - Ok(record_handle_node(Type::Store(ty.clone()), call_info)) - } + record_and_register_binding(ctx, bind_path, visibility, binding_type, store_type, ty) + }; + + create_any_catch_errors(create_any) + } - _ => Err(BindingError::InvalidTypeForBinding(ty.clone(), binding_ty.clone())), + /// Creates a handle binding at the specified bind path. + #[track_caller] + pub fn handle_binding(bind_path: BindPath, visibility: StageMask, handle_type: HandleType) -> Any { + let create_any = |ctx: &Context| -> Result { + let binding_type = match &handle_type { + HandleType::Sampler(s) => BindingType::Sampler(*s), + HandleType::SampledTexture(s, t, p) => BindingType::SampledTexture { + shape: *s, + sample_type: *t, + samples_per_pixel: *p, + }, + HandleType::StorageTexture(s, f, a) => BindingType::StorageTexture { + shape: *s, + format: f.clone(), + access: *a, }, }; - match ctx.pipeline_layout_mut().bindings.entry(path) { - Entry::Occupied(entry) => { - Err(PipelineError::DuplicateBindPath(path, entry.get().shader_ty.clone()).into()) - } - Entry::Vacant(entry) => match any { - Ok(any) => match any.node() { - Some(node) => { - entry.insert(WipBinding { - call_info, - user_defined_visibility: visibility, - binding_ty, - shader_ty: ty, - node, - }); - Ok(any) - } - None => Err(InternalError::new( - true, - format!("binding at `{path}` produced invalid Any object"), - ) - .into()), - }, - Err(err) => Err(err.into()), - }, - } + let store_type = StoreType::Handle(handle_type); + let ty = Type::Store(store_type.clone()); + + record_and_register_binding(ctx, bind_path, visibility, binding_type, store_type, ty) }; - Context::try_with(call_info!(), |ctx| match create_any(ctx) { - Ok(any) => any, - Err(e) => { - ctx.push_error(e); - Any::new_invalid(InvalidReason::ErrorThatWasPushed) - } - }) - .unwrap_or(Any::new_invalid(InvalidReason::CreatedWithNoActiveEncoding)) + + create_any_catch_errors(create_any) } /// get the next field of type `ty` in the push-constants struct. diff --git a/shame/src/frontend/encoding/binding.rs b/shame/src/frontend/encoding/binding.rs index 893254a..1ac1731 100644 --- a/shame/src/frontend/encoding/binding.rs +++ b/shame/src/frontend/encoding/binding.rs @@ -10,6 +10,7 @@ use crate::frontend::texture::texture_array::{StorageTextureArray, TextureArray} use crate::frontend::texture::texture_traits::{SamplingFormat, Spp, StorageTextureFormat}; use crate::frontend::texture::{Sampler, Texture}; use crate::ir::pipeline::StageMask; +use crate::ir::HandleType; use crate::{ frontend::any::{shared_io::BindPath, shared_io::BindingType}, frontend::{ @@ -69,7 +70,7 @@ where let shape = Coords::SHAPE; let sample_type = Format::SAMPLE_TYPE; let spp = SPP::SAMPLES_PER_PIXEL; - ir::StoreType::Handle(ir::HandleType::SampledTexture( + ir::StoreType::Handle(HandleType::SampledTexture( shape, sample_type.restrict_with_spp(spp), spp, @@ -78,11 +79,13 @@ where #[track_caller] fn new_binding(args: Result) -> Self { + let shape = Coords::SHAPE; + let sample_type = Format::SAMPLE_TYPE; + let spp = SPP::SAMPLES_PER_PIXEL; + let handle_type = HandleType::SampledTexture(shape, sample_type.restrict_with_spp(spp), spp); let any = match args { Err(reason) => Any::new_invalid(reason), - Ok(BindingArgs { path, visibility }) => { - Any::binding(path, visibility, Self::store_ty(), Self::binding_type(), false) - } + Ok(BindingArgs { path, visibility }) => Any::handle_binding(path, visibility, handle_type), }; Texture::from_inner(TextureKind::Standalone(any)) } @@ -103,7 +106,7 @@ impl + SupportsCoords + SupportsCoords Any::new_invalid(reason), - Ok(BindingArgs { path, visibility }) => { - Any::binding(path, visibility, Self::store_ty(), Self::binding_type(), false) - } + Ok(BindingArgs { path, visibility }) => Any::handle_binding(path, visibility, handle_type), }; StorageTexture::from_inner(TextureKind::Standalone(any)) } @@ -139,7 +141,7 @@ where let shape = Coords::ARRAY_SHAPE(Self::NONZERO_N); let sample_type = Format::SAMPLE_TYPE; let spp = ir::SamplesPerPixel::Single; - ir::StoreType::Handle(ir::HandleType::SampledTexture( + ir::StoreType::Handle(HandleType::SampledTexture( shape, sample_type.restrict_with_spp(spp), spp, @@ -148,11 +150,13 @@ where #[track_caller] fn new_binding(args: Result) -> Self { + let shape = Coords::ARRAY_SHAPE(Self::NONZERO_N); + let sample_type = Format::SAMPLE_TYPE; + let spp = ir::SamplesPerPixel::Single; + let handle_type = HandleType::SampledTexture(shape, sample_type.restrict_with_spp(spp), spp); let any = match args { Err(reason) => Any::new_invalid(reason), - Ok(BindingArgs { path, visibility }) => { - Any::binding(path, visibility, Self::store_ty(), Self::binding_type(), false) - } + Ok(BindingArgs { path, visibility }) => Any::handle_binding(path, visibility, handle_type), }; TextureArray::from_inner(any) } @@ -177,16 +181,18 @@ impl< let shape = Coords::ARRAY_SHAPE(Self::NONZERO_N); let access = Access::ACCESS; let format = TextureFormatWrapper::new(Format::id()); - ir::StoreType::Handle(ir::HandleType::StorageTexture(shape, format, access)) + ir::StoreType::Handle(HandleType::StorageTexture(shape, format, access)) } #[track_caller] fn new_binding(args: Result) -> Self { + let shape = Coords::ARRAY_SHAPE(Self::NONZERO_N); + let access = Access::ACCESS; + let format = TextureFormatWrapper::new(Format::id()); + let handle_type = HandleType::StorageTexture(shape, format, access); let any = match args { Err(reason) => Any::new_invalid(reason), - Ok(BindingArgs { path, visibility }) => { - Any::binding(path, visibility, Self::store_ty(), Self::binding_type(), false) - } + Ok(BindingArgs { path, visibility }) => Any::handle_binding(path, visibility, handle_type), }; StorageTextureArray::from_inner(any) } @@ -195,19 +201,14 @@ impl< impl Binding for Sampler { fn binding_type() -> BindingType { BindingType::Sampler(M::SAMPLING_METHOD) } - fn store_ty() -> ir::StoreType { ir::StoreType::Handle(ir::HandleType::Sampler(M::SAMPLING_METHOD)) } + fn store_ty() -> ir::StoreType { ir::StoreType::Handle(HandleType::Sampler(M::SAMPLING_METHOD)) } #[track_caller] fn new_binding(args: Result) -> Self { + let handle_type = HandleType::Sampler(M::SAMPLING_METHOD); let any = match args { Err(reason) => Any::new_invalid(reason), - Ok(BindingArgs { path, visibility }) => Any::binding( - path, - visibility, - ir::StoreType::Handle(ir::HandleType::Sampler(M::SAMPLING_METHOD)), - Self::binding_type(), - false, - ), + Ok(BindingArgs { path, visibility }) => Any::handle_binding(path, visibility, handle_type), }; Self::from_inner(any) } diff --git a/shame/src/frontend/encoding/buffer.rs b/shame/src/frontend/encoding/buffer.rs index c6f3c1c..d08d23a 100644 --- a/shame/src/frontend/encoding/buffer.rs +++ b/shame/src/frontend/encoding/buffer.rs @@ -1,10 +1,12 @@ -use crate::any::layout::LayoutableSized; +use crate::any::layout::{repr, GpuTypeLayout, LayoutableSized}; use crate::common::proc_macro_reexports::GpuStoreImplCategory; use crate::frontend::any::shared_io::{BindPath, BindingType, BufferBindingType}; use crate::frontend::any::{Any, InvalidReason}; use crate::frontend::rust_types::array::{Array, ArrayLen, RuntimeSize, Size}; use crate::frontend::rust_types::atomic::Atomic; -use crate::frontend::rust_types::layout_traits::{get_layout_compare_with_cpu_push_error, FromAnys, GetAllFields}; +use crate::frontend::rust_types::layout_traits::{ + get_layout_compare_with_cpu_push_error, gpu_type_layout, FromAnys, GetAllFields, +}; use crate::frontend::rust_types::len::{Len, Len2}; use crate::frontend::rust_types::mem::{self, AddressSpace, SupportsAccess}; use crate::frontend::rust_types::reference::Ref; @@ -25,6 +27,7 @@ use std::marker::PhantomData; use std::ops::{Deref, Mul}; use super::binding::Binding; +use super::EncodingErrorKind; /// Address spaces used for [`Buffer`] and [`BufferRef`] bindings. /// @@ -148,16 +151,44 @@ pub enum BufferRefInner), } -impl BufferInner { +impl, AS: BufferAddressSpace> + BufferInner +{ #[track_caller] - pub(crate) fn new_plain(args: Result, bind_ty: BindingType) -> Self { + pub(crate) fn new_plain( + args: Result, + bind_ty: BufferBindingType, + has_dynamic_offset: bool, + ) -> Self { let as_ref = false; - let any = match args { - Err(reason) => Any::new_invalid(reason), - Ok(BindingArgs { path, visibility }) => { - Any::binding(path, visibility, ::store_ty(), bind_ty, as_ref) + let create_any = || -> Result { + let BindingArgs { path, visibility } = args?; + + let storage = gpu_type_layout::(); + match bind_ty { + BufferBindingType::Uniform => { + let uniform = GpuTypeLayout::::try_from(storage)?; + Ok(Any::uniform_buffer_binding( + path, + visibility, + uniform, + has_dynamic_offset, + )) + } + BufferBindingType::Storage(access) => Ok(Any::storage_buffer_binding( + path, + visibility, + storage, + access, + false, + has_dynamic_offset, + )), } }; + let any = match create_any() { + Err(reason) => Any::new_invalid(reason), + Ok(any) => any, + }; BufferInner::PlainSized(any.into()) } } diff --git a/shame/src/frontend/encoding/mod.rs b/shame/src/frontend/encoding/mod.rs index fa3ae86..cc43a6b 100644 --- a/shame/src/frontend/encoding/mod.rs +++ b/shame/src/frontend/encoding/mod.rs @@ -15,7 +15,6 @@ use crate::{ rasterizer::{PrimitiveAssembly, VertexStage}, }, ir::{ - ir_type::LayoutError, pipeline::{PipelineError, PipelineKind, StageSolverErrorKind}, recording::{ next_thread_generation, AllocError, BlockError, CallInfo, Context, FnError, NodeRecordingError, StmtError, @@ -30,7 +29,11 @@ use std::{cell::Cell, fmt::Display, marker::PhantomData, rc::Rc}; use super::{ any::{render_io::VertexLayoutError, shared_io::BindingError, ArgumentNotAvailable, InvalidReason}, error::InternalError, - rust_types::{error::FrontendError, len::x3}, + rust_types::{ + error::FrontendError, + len::x3, + type_layout::{construction::LayoutError, layoutable::ir_compat::IRConversionError}, + }, }; pub mod binding; @@ -95,7 +98,7 @@ use crate as shame; /// // `enc` is generic over the pipeline kind, which decided by calling /// // either `enc.new_render_pipeline` or `enc.new_compute_pipeline`. /// // Without this additional call, there will be a compiler error. -/// +/// /// let mut drawcall = enc.new_render_pipeline(sm::Indexing::Incremental); /// /// // ... use `drawcall` to build your pipeline @@ -281,7 +284,7 @@ pub enum EncodingErrorKind { required: PipelineKind, }, #[error("`shame::any::Any` instance is not available. reason: {0}")] - ValueUnavailable(InvalidReason), + ValueUnavailable(#[from] InvalidReason), #[error("{0}")] NodeRecordingError(#[from] NodeRecordingError), #[error("{0}")] @@ -293,6 +296,8 @@ pub enum EncodingErrorKind { #[error("{0}")] LayoutError(#[from] LayoutError), #[error("{0}")] + LayoutableToStoreType(#[from] IRConversionError), + #[error("{0}")] BindingError(#[from] BindingError), #[error("{0}")] StageSolverError(#[from] StageSolverErrorKind), @@ -436,9 +441,9 @@ impl EncodingGuard { /// &drawcall.vertices; // access to vertex-shader related functionality /// &drawcall.bind_groups; // access to bind groups (descriptor-sets) /// &drawcall.push_constants; // access to push constant data - /// + /// /// let fragments = drawcall.vertices.assemble(...).rasterize(...); - /// + /// /// // use fragments object for per-fragment computation and io /// /// enc.finish()? @@ -487,7 +492,7 @@ impl EncodingGuard { /// The compute grid is 3D and all thread positions are 3D vectors /// even though the workgroup is only a flat 2D 8x4 slice. /// Thread indices are still 1D scalars. - /// + /// /// The amount of workgroups dispatched is controlled at runtime by the /// dispatch command. /// diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs b/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs index 81fd987..033d519 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs @@ -3,7 +3,7 @@ use super::*; // Conversions to ir types // /// Errors that can occur when converting IR types to layoutable types. -#[derive(thiserror::Error, Debug)] +#[derive(thiserror::Error, Debug, Clone)] pub enum IRConversionError { /// Packed vectors do not exist in the shader type system. #[error( @@ -16,7 +16,7 @@ pub enum IRConversionError { DuplicateFieldName(#[from] DuplicateFieldNameError), } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct DuplicateFieldNameError { pub struct_type: StructKind, pub first_field: usize, From 64ca485953bcd28e5db9796865d5a25766300120 Mon Sep 17 00:00:00 2001 From: chronicl Date: Tue, 27 May 2025 05:25:50 +0200 Subject: [PATCH 30/32] . --- shame/src/frontend/any/shared_io.rs | 34 ++++++++++++++------------ shame/src/frontend/encoding/io_iter.rs | 16 ++++++++++-- shame/src/frontend/encoding/mod.rs | 2 +- 3 files changed, 33 insertions(+), 19 deletions(-) diff --git a/shame/src/frontend/any/shared_io.rs b/shame/src/frontend/any/shared_io.rs index e483440..beebd65 100644 --- a/shame/src/frontend/any/shared_io.rs +++ b/shame/src/frontend/any/shared_io.rs @@ -9,6 +9,7 @@ use crate::frontend::any::Any; use crate::frontend::any::{record_node, InvalidReason}; use crate::frontend::encoding::{EncodingErrorKind, EncodingGuard}; use crate::frontend::error::InternalError; +use crate::frontend::rust_types::type_layout::layoutable; use crate::frontend::rust_types::type_layout::layoutable::ir_compat::IRConversionError; use crate::ir::expr::Binding; use crate::ir::expr::Expr; @@ -151,6 +152,8 @@ impl Display for SamplingMethod { pub enum BindingError { #[error("the type `{0:?}` cannot be used for a binding of kind `{1:?}` because of:\n{0}")] TypeHasInvalidLayoutForBinding(LayoutableType, BindingType, IRConversionError), + #[error("a non-reference buffer (non `BufferRef`) must be both read-only and constructible")] + NonRefBufferRequiresReadOnlyAndConstructible, } fn layout_to_store_type( @@ -252,11 +255,11 @@ impl Any { access.into(), ), (false, AccessModeReadable::ReadWrite) => { - return Err(LayoutError::NonRefBufferRequiresReadOnlyAndConstructible.into()); + return Err(BindingError::NonRefBufferRequiresReadOnlyAndConstructible.into()); } (false, AccessModeReadable::Read) => { if !store_type.is_constructible() { - return Err(LayoutError::NonRefBufferRequiresReadOnlyAndConstructible.into()); + return Err(BindingError::NonRefBufferRequiresReadOnlyAndConstructible.into()); } Type::Store(store_type.clone()) } @@ -283,7 +286,7 @@ impl Any { }; let store_type = layout_to_store_type(layout, &binding_type)?; if !store_type.is_constructible() { - return Err(LayoutError::NonRefBufferRequiresReadOnlyAndConstructible.into()); + return Err(BindingError::NonRefBufferRequiresReadOnlyAndConstructible.into()); } let ty = Type::Store(store_type.clone()); @@ -338,7 +341,7 @@ impl Any { /// > struct being visible or invisible in a given shader stage. #[track_caller] pub fn next_push_constants_field( - ty: ir::SizedType, + ty: layoutable::SizedType, custom_min_size: Option, custom_min_align: Option, ) -> Any { @@ -346,18 +349,17 @@ impl Any { let mut push_constants = &mut ctx.pipeline_layout_mut().push_constants; let field_index = push_constants.len(); - let store_ty = StoreType::Sized(ty.clone()); - if let Err(e) = check_layout( - &LayoutErrorContext { - binding_type: BufferBindingType::Storage(AccessModeReadable::Read), - expected_constraints: LayoutConstraints::Wgsl(ir::ir_type::WgslBufferLayout::StorageAddressSpace), - top_level_type: store_ty.clone(), - use_color: ctx.settings().colored_error_messages, - }, - &store_ty.clone(), - ) { - ctx.push_error(e.into()); - } + // This is dead code, but makes sure that if we ever decide that a SizedType + // is not trivially layoutable as repr::Storage, it gets caught here. + let _ = GpuTypeLayout::::new(ty.clone()); + + let ty = match ir::SizedType::try_from(ty) { + Ok(ty) => ty, + Err(e) => { + ctx.push_error(e.into()); + return Any::new_invalid(InvalidReason::ErrorThatWasPushed); + } + }; let any = record_node( ctx.latest_user_caller(), diff --git a/shame/src/frontend/encoding/io_iter.rs b/shame/src/frontend/encoding/io_iter.rs index b31c427..da63081 100644 --- a/shame/src/frontend/encoding/io_iter.rs +++ b/shame/src/frontend/encoding/io_iter.rs @@ -19,6 +19,7 @@ use crate::{ }, reference::AccessMode, struct_::SizedFields, + type_layout::layoutable::ir_compat::ContainsBoolsError, type_traits::{BindingArgs, GpuSized, GpuStore, GpuStoreImplCategory, NoAtomics, NoBools}, GpuType, }, @@ -571,8 +572,13 @@ impl PushConstants<'_> { GpuStoreImplCategory::Fields(buffer_block) => match buffer_block.last_unsized_field() { None => { assert_eq!(buffer_block.sized_fields().len(), buffer_block.fields().count()); + let fields = buffer_block.sized_fields().iter().map(|f| { - Any::next_push_constants_field(f.ty.clone(), f.custom_min_size, f.custom_min_align) + let layoutable = match f.ty.clone().try_into() { + Ok(l) => l, + Err(ContainsBoolsError) => unreachable!("No bools ensured by trait bound"), + }; + Any::next_push_constants_field(layoutable, f.custom_min_size, f.custom_min_align) }); T::from_anys(fields) } @@ -587,7 +593,13 @@ impl PushConstants<'_> { }, GpuStoreImplCategory::GpuType(ty) => { let any = match ty { - ir::StoreType::Sized(sized_type) => Any::next_push_constants_field(sized_type, None, None), + ir::StoreType::Sized(sized_type) => { + let layoutable = match sized_type.try_into() { + Ok(l) => l, + Err(ContainsBoolsError) => unreachable!("No bools ensured by trait bound"), + }; + Any::next_push_constants_field(layoutable, None, None) + } _ => { let err = InternalError::new( true, diff --git a/shame/src/frontend/encoding/mod.rs b/shame/src/frontend/encoding/mod.rs index cc43a6b..4f7db48 100644 --- a/shame/src/frontend/encoding/mod.rs +++ b/shame/src/frontend/encoding/mod.rs @@ -284,7 +284,7 @@ pub enum EncodingErrorKind { required: PipelineKind, }, #[error("`shame::any::Any` instance is not available. reason: {0}")] - ValueUnavailable(#[from] InvalidReason), + ValueUnavailable(InvalidReason), #[error("{0}")] NodeRecordingError(#[from] NodeRecordingError), #[error("{0}")] From 8a188c6435cc0a81bb210502fdc2aa720df59f7c Mon Sep 17 00:00:00 2001 From: chronicl Date: Fri, 30 May 2025 07:09:05 +0200 Subject: [PATCH 31/32] MVP storage/uniform buffer any api rework --- shame/src/common/proc_macro_reexports.rs | 1 + shame/src/frontend/any/shared_io.rs | 5 +- shame/src/frontend/encoding/buffer.rs | 194 +- shame/src/frontend/encoding/io_iter.rs | 2 +- shame/src/frontend/encoding/mod.rs | 3 + shame/src/frontend/rust_types/array.rs | 37 +- shame/src/frontend/rust_types/atomic.rs | 15 +- .../src/frontend/rust_types/layout_traits.rs | 41 +- shame/src/frontend/rust_types/mat.rs | 16 +- shame/src/frontend/rust_types/reference.rs | 4 +- shame/src/frontend/rust_types/struct_.rs | 15 +- shame/src/frontend/rust_types/type_traits.rs | 15 +- shame/src/frontend/rust_types/vec.rs | 15 +- shame/src/ir/ir_type/align_size.rs | 2 +- shame/src/ir/ir_type/layout_constraints.rs | 1610 ++++++++--------- shame/src/ir/ir_type/struct_.rs | 87 +- shame_derive/src/derive_layout.rs | 10 +- 17 files changed, 1079 insertions(+), 993 deletions(-) diff --git a/shame/src/common/proc_macro_reexports.rs b/shame/src/common/proc_macro_reexports.rs index 28e46d9..bb780f2 100644 --- a/shame/src/common/proc_macro_reexports.rs +++ b/shame/src/common/proc_macro_reexports.rs @@ -6,6 +6,7 @@ pub use crate::call_info; pub use crate::frontend::any::render_io::VertexAttribFormat; pub use crate::frontend::any::shared_io::BindPath; pub use crate::frontend::any::shared_io::BindingType; +pub use crate::frontend::any::shared_io::BufferBindingType; pub use crate::frontend::any::Any; pub use crate::frontend::any::InvalidReason; pub use crate::frontend::encoding::buffer::BufferAddressSpace; diff --git a/shame/src/frontend/any/shared_io.rs b/shame/src/frontend/any/shared_io.rs index beebd65..4103ce2 100644 --- a/shame/src/frontend/any/shared_io.rs +++ b/shame/src/frontend/any/shared_io.rs @@ -13,10 +13,7 @@ use crate::frontend::rust_types::type_layout::layoutable; use crate::frontend::rust_types::type_layout::layoutable::ir_compat::IRConversionError; use crate::ir::expr::Binding; use crate::ir::expr::Expr; -use crate::ir::ir_type::{ - check_layout, get_type_for_buffer_binding_type, AccessModeReadable, HandleType, LayoutConstraints, LayoutError, - LayoutErrorContext, SamplesPerPixel, -}; +use crate::ir::ir_type::{AccessModeReadable, HandleType, SamplesPerPixel}; use crate::ir::pipeline::{PipelineError, StageMask, WipBinding, WipPushConstantsField}; use crate::ir::recording::{Context, MemoryRegion}; use crate::ir::{self, AddressSpace, StoreType, TextureFormatWrapper, TextureSampleUsageType, Type}; diff --git a/shame/src/frontend/encoding/buffer.rs b/shame/src/frontend/encoding/buffer.rs index d08d23a..27593ad 100644 --- a/shame/src/frontend/encoding/buffer.rs +++ b/shame/src/frontend/encoding/buffer.rs @@ -101,7 +101,7 @@ where impl Buffer where - T: GpuStore + NoHandles + NoAtomics + NoBools + GpuLayout, + T: GpuStore + NoHandles + NoAtomics + NoBools + GpuLayout, AS: BufferAddressSpace, { #[track_caller] @@ -111,14 +111,14 @@ where get_layout_compare_with_cpu_push_error::(ctx, skip_stride_check) }); Self { - inner: T::instantiate_buffer_inner(args, BufferInner::::binding_type(DYN_OFFSET)), + inner: T::instantiate_buffer_inner(args, BufferInner::::binding_type(), DYN_OFFSET), } } } impl BufferRef where - T: GpuStore + NoHandles + NoBools + GpuLayout, + T: GpuStore + NoHandles + NoBools + GpuLayout, AS: BufferAddressSpace, AM: AccessModeReadable, { @@ -129,7 +129,7 @@ where get_layout_compare_with_cpu_push_error::(ctx, skip_stride_check) }); Self { - inner: T::instantiate_buffer_ref_inner(args, BufferRefInner::::binding_type(DYN_OFFSET)), + inner: T::instantiate_buffer_ref_inner(args, BufferRefInner::::binding_type(), DYN_OFFSET), } } } @@ -151,6 +151,41 @@ pub enum BufferRefInner), } +#[track_caller] +fn create_buffer_any>( + args: Result, + bind_ty: BufferBindingType, + has_dynamic_offset: bool, + as_ref: bool, +) -> Any { + let BindingArgs { path, visibility } = match args { + Ok(a) => a, + Err(e) => return Any::new_invalid(e), + }; + + let storage = gpu_type_layout::(); + match bind_ty { + BufferBindingType::Uniform => { + let uniform = match GpuTypeLayout::::try_from(storage) { + Ok(u) => u, + Err(e) => { + let invalid = Context::try_with(call_info!(), |ctx| { + ctx.push_error(e.into()); + InvalidReason::ErrorThatWasPushed + }) + .unwrap_or(InvalidReason::CreatedWithNoActiveEncoding); + + return Any::new_invalid(invalid); + } + }; + Any::uniform_buffer_binding(path, visibility, uniform, has_dynamic_offset) + } + BufferBindingType::Storage(access) => { + Any::storage_buffer_binding(path, visibility, storage, access, as_ref, has_dynamic_offset) + } + } +} + impl, AS: BufferAddressSpace> BufferInner { @@ -161,52 +196,32 @@ impl Self { let as_ref = false; - let create_any = || -> Result { - let BindingArgs { path, visibility } = args?; - - let storage = gpu_type_layout::(); - match bind_ty { - BufferBindingType::Uniform => { - let uniform = GpuTypeLayout::::try_from(storage)?; - Ok(Any::uniform_buffer_binding( - path, - visibility, - uniform, - has_dynamic_offset, - )) - } - BufferBindingType::Storage(access) => Ok(Any::storage_buffer_binding( - path, - visibility, - storage, - access, - false, - has_dynamic_offset, - )), - } - }; - let any = match create_any() { - Err(reason) => Any::new_invalid(reason), - Ok(any) => any, - }; + let any = create_buffer_any::(args, bind_ty, has_dynamic_offset, as_ref); BufferInner::PlainSized(any.into()) } } -impl BufferInner { +impl, AS: BufferAddressSpace> + BufferInner +{ #[track_caller] #[doc(hidden)] - pub fn new_fields(args: Result, bind_ty: BindingType) -> Self { - let init_any = |as_ref, store_ty| match args { - Err(reason) => Any::new_invalid(reason), - Ok(BindingArgs { path, visibility }) => Any::binding(path, visibility, store_ty, bind_ty, as_ref), - }; + pub fn new_fields( + args: Result, + bind_ty: BufferBindingType, + has_dynamic_offset: bool, + ) -> Self { + // let init_any = |as_ref, store_ty| match args { + // Err(reason) => Any::new_invalid(reason), + // Ok(BindingArgs { path, visibility }) => Any::binding(path, visibility, store_ty, bind_ty, as_ref), + // }; let block = T::get_bufferblock_type(); match ir::SizedStruct::try_from(block.clone()) { Ok(struct_) => { - let store_ty = ir::StoreType::Sized(ir::SizedType::Structure(struct_)); - let block_any = init_any(false, store_ty); + let as_ref = false; + // TODO(chronicl) does not require BufferFields. consider unifying with other methods + let block_any = create_buffer_any::(args, bind_ty, has_dynamic_offset, as_ref); let fields_anys = ::fields_as_anys_unchecked(block_any); let fields_anys = (fields_anys.borrow() as &[Any]).iter().cloned(); let fields = ::from_anys(fields_anys); @@ -214,8 +229,9 @@ impl BufferInner< } Err(_) => { // TODO(release) test this! A `Buffer where Foo's last field is a runtime sized array` - let store_ty = ir::StoreType::BufferBlock(block); - let block_any_ref = init_any(true, store_ty); + let as_ref = true; + // TODO(chronicl) does not require BufferFields. consider unifying with other methods + let block_any_ref = create_buffer_any::(args, bind_ty, has_dynamic_offset, as_ref); let fields_anys_refs = ::fields_as_anys_unchecked(block_any_ref); let fields_anys_refs = (fields_anys_refs.borrow() as &[Any]).iter().cloned(); let fields_refs = as FromAnys>::from_anys(fields_anys_refs); @@ -225,67 +241,68 @@ impl BufferInner< } } -impl BufferInner, AS> { +impl + BufferInner, AS> +{ #[track_caller] #[doc(hidden)] - pub(crate) fn new_array(args: Result, bind_ty: BindingType) -> Self { + pub(crate) fn new_array( + args: Result, + bind_ty: BufferBindingType, + has_dynamic_offset: bool, + ) -> Self { let call_info = call_info!(); - let init_any = |ty, as_ref| match args { - Err(reason) => Any::new_invalid(reason), - Ok(BindingArgs { path, visibility }) => Any::binding(path, visibility, ty, bind_ty, as_ref), - }; - let store_ty = as GpuStore>::store_ty(); match L::LEN { // GpuSized Some(_) => { let as_ref = false; - let any = init_any(store_ty, as_ref); + let any = create_buffer_any::>(args, bind_ty, has_dynamic_offset, as_ref); BufferInner::PlainSized(any.into()) } // RuntimeSize None => { let as_ref = true; - let any_ref = init_any(store_ty, as_ref); + let any_ref = create_buffer_any::>(args, bind_ty, has_dynamic_offset, as_ref); BufferInner::RuntimeSizedArray(any_ref.into()) } } } } -impl BufferRefInner { +impl, AS: BufferAddressSpace, AM: AccessModeReadable> + BufferRefInner +{ #[track_caller] - pub(crate) fn new_plain(args: Result, bind_ty: BindingType) -> Self + pub(crate) fn new_plain( + args: Result, + bind_ty: BufferBindingType, + has_dynamic_offset: bool, + ) -> Self where T: GpuType, { let as_ref = true; let store_ty = ::store_ty(); - let any = match args { - Err(reason) => Any::new_invalid(reason), - Ok(BindingArgs { path, visibility }) => Any::binding(path, visibility, store_ty, bind_ty, as_ref), - }; + let any = create_buffer_any::(args, bind_ty, has_dynamic_offset, as_ref); BufferRefInner::Plain(any.into()) } #[track_caller] #[doc(hidden)] - pub fn new_fields(args: Result, bind_ty: BindingType) -> Self + pub fn new_fields( + args: Result, + bind_ty: BufferBindingType, + has_dynamic_offset: bool, + ) -> Self where T: BufferFields, { let as_ref = true; - let block_any_ref = match args { - Err(reason) => Any::new_invalid(reason), - Ok(BindingArgs { path, visibility }) => Any::binding( - path, - visibility, - ir::StoreType::BufferBlock(T::get_bufferblock_type()), - bind_ty, - as_ref, - ), - }; + // TODO(chronicl) this does not require buffer fields. reconsider unifying impls and + // just matching on `LayoutableType` variants. + let block_any_ref = create_buffer_any::(args, bind_ty, has_dynamic_offset, as_ref); let fields_anys_refs = ::fields_as_anys_unchecked(block_any_ref); let fields_anys_refs = (fields_anys_refs.borrow() as &[Any]).iter().cloned(); let fields_refs = as FromAnys>::from_anys(fields_anys_refs); @@ -294,24 +311,22 @@ impl Buff } impl BufferInner { - fn binding_type(has_dynamic_offset: bool) -> BindingType { - let ty = match AS::ADDRESS_SPACE { + fn binding_type() -> BufferBindingType { + match AS::ADDRESS_SPACE { ir::AddressSpace::Uniform => BufferBindingType::Uniform, ir::AddressSpace::Storage => BufferBindingType::Storage(Read::ACCESS_MODE_READABLE), _ => unreachable!("AS: BufferAddressSpace"), - }; - BindingType::Buffer { ty, has_dynamic_offset } + } } } impl BufferRefInner { - fn binding_type(has_dynamic_offset: bool) -> BindingType { - let ty = match AS::ADDRESS_SPACE { + fn binding_type() -> BufferBindingType { + match AS::ADDRESS_SPACE { ir::AddressSpace::Uniform => BufferBindingType::Uniform, ir::AddressSpace::Storage => BufferBindingType::Storage(AM::ACCESS_MODE_READABLE), _ => unreachable!("AS: BufferAddressSpace"), - }; - BindingType::Buffer { ty, has_dynamic_offset } + } } } @@ -326,9 +341,15 @@ impl BufferRefInner #[rustfmt::skip] impl Binding for Buffer where - T: GpuSized+ GpuLayout + T: GpuSized+ GpuLayout { - fn binding_type() -> BindingType { BufferInner::::binding_type(DYN_OFFSET) } + fn binding_type() -> BindingType { + BindingType::Buffer { + ty: BufferInner::::binding_type(), + has_dynamic_offset: DYN_OFFSET + } + } + #[track_caller] fn new_binding(args: Result) -> Self { Buffer::new(args) } @@ -352,7 +373,13 @@ Binding for Buffer, AS, DYN_OFFSET> where T: GpuType + GpuSized + GpuLayout + LayoutableSized { - fn binding_type() -> BindingType { BufferInner::::binding_type(DYN_OFFSET) } + fn binding_type() -> BindingType { + BindingType::Buffer { + ty: BufferInner::::binding_type(), + has_dynamic_offset: DYN_OFFSET + } + } + #[track_caller] fn new_binding(args: Result) -> Self { Buffer::new(args) } @@ -520,13 +547,18 @@ where pub(crate) inner: BufferRefInner, } -#[rustfmt::skip] impl +#[rustfmt::skip] impl, AS, AM, const DYN_OFFSET: bool> Binding for BufferRef where AS: BufferAddressSpace + SupportsAccess, AM: AccessModeReadable + AtomicsRequireWriteable { - fn binding_type() -> BindingType { BufferRefInner::::binding_type(DYN_OFFSET) } + fn binding_type() -> BindingType { + BindingType::Buffer { + ty: BufferInner::::binding_type(), + has_dynamic_offset: DYN_OFFSET + } + } #[track_caller] fn new_binding(args: Result) -> Self { BufferRef::new(args) } diff --git a/shame/src/frontend/encoding/io_iter.rs b/shame/src/frontend/encoding/io_iter.rs index da63081..35f501f 100644 --- a/shame/src/frontend/encoding/io_iter.rs +++ b/shame/src/frontend/encoding/io_iter.rs @@ -34,7 +34,7 @@ use crate::{ }, ir::{ self, - ir_type::{Field, LayoutError}, + ir_type::{Field}, pipeline::{PipelineError, StageMask}, recording::Context, TextureFormatWrapper, diff --git a/shame/src/frontend/encoding/mod.rs b/shame/src/frontend/encoding/mod.rs index 4f7db48..6086470 100644 --- a/shame/src/frontend/encoding/mod.rs +++ b/shame/src/frontend/encoding/mod.rs @@ -31,6 +31,7 @@ use super::{ error::InternalError, rust_types::{ error::FrontendError, + layout_traits::CpuLayoutCompareError, len::x3, type_layout::{construction::LayoutError, layoutable::ir_compat::IRConversionError}, }, @@ -296,6 +297,8 @@ pub enum EncodingErrorKind { #[error("{0}")] LayoutError(#[from] LayoutError), #[error("{0}")] + CpuLayoutCompare(#[from] CpuLayoutCompareError), + #[error("{0}")] LayoutableToStoreType(#[from] IRConversionError), #[error("{0}")] BindingError(#[from] BindingError), diff --git a/shame/src/frontend/rust_types/array.rs b/shame/src/frontend/rust_types/array.rs index 8899dc4..4b58a2a 100644 --- a/shame/src/frontend/rust_types/array.rs +++ b/shame/src/frontend/rust_types/array.rs @@ -5,7 +5,7 @@ use super::len::x1; use super::mem::AddressSpace; use super::reference::{AccessMode, AccessModeReadable, AccessModeWritable, Read}; use super::scalar_type::ScalarTypeInteger; -use super::type_layout::{self, layoutable, repr, ElementLayout, TypeLayout, TypeLayoutSemantics}; +use super::type_layout::{self, layoutable, repr, ElementLayout, Repr, TypeLayout, TypeLayoutSemantics}; use super::type_traits::{ BindingArgs, EmptyRefFields, GpuAligned, GpuSized, GpuStore, GpuStoreImplCategory, NoAtomics, NoBools, NoHandles, }; @@ -13,6 +13,7 @@ use super::vec::{ToInteger, ToVec}; use super::{AsAny, GpuType}; use super::{To, ToGpuType}; use crate::any::layout::{Layoutable, LayoutableSized}; +use crate::any::BufferBindingType; use crate::common::small_vec::SmallVec; use crate::frontend::any::shared_io::{BindPath, BindingType}; use crate::frontend::any::Any; @@ -87,7 +88,7 @@ pub struct Array { phantom: PhantomData<(T, N)>, } -impl GpuType for Array { +impl GpuType for Array { fn ty() -> ir::Type { ir::Type::Store(Self::store_ty()) } fn from_any_unchecked(any: Any) -> Self { @@ -108,28 +109,30 @@ impl Size { } } -impl GpuStore for Array { +impl GpuStore for Array { type RefFields = EmptyRefFields; fn store_ty() -> ir::StoreType { Self::array_store_ty() } fn instantiate_buffer_inner( args: Result, - bind_ty: BindingType, + bind_ty: BufferBindingType, + has_dynamic_offset: bool, ) -> BufferInner where - Self: NoAtomics + NoBools, + Self: NoAtomics + NoBools + GpuLayout, { - BufferInner::new_array(args, bind_ty) + BufferInner::new_array(args, bind_ty, has_dynamic_offset) } fn instantiate_buffer_ref_inner( args: Result, - bind_ty: BindingType, + bind_ty: BufferBindingType, + has_dynamic_offset: bool, ) -> BufferRefInner where Self: NoBools, { - BufferRefInner::new_plain(args, bind_ty) + BufferRefInner::new_plain(args, bind_ty, has_dynamic_offset) } fn impl_category() -> GpuStoreImplCategory { GpuStoreImplCategory::GpuType(Self::store_ty()) } @@ -170,7 +173,7 @@ impl Layoutable for Array< } } -impl ToGpuType for Array { +impl ToGpuType for Array { type Gpu = Self; fn to_gpu(&self) -> Self::Gpu { self.clone() } @@ -276,7 +279,7 @@ impl GpuIndex GpuIndex for Ref, AS, AM> where Idx: ToInteger, - T: GpuType + GpuSized + GpuStore + 'static, + T: GpuType + GpuSized + GpuStore + 'static + GpuLayout + LayoutableSized, AS: AddressSpace + 'static, AM: AccessModeReadable + 'static, N: ArrayLen, @@ -289,7 +292,7 @@ where impl ToGpuType for [T; N] where - T::Gpu: GpuStore + GpuSized, + T::Gpu: GpuStore + GpuSized + GpuLayout + LayoutableSized, { type Gpu = Array>; @@ -301,7 +304,7 @@ where fn as_gpu_type_ref(&self) -> Option<&Self::Gpu> { None } } -impl Array> { +impl Array> { /// (no documentation yet) #[track_caller] pub fn new(fields: [impl To; N]) -> Self { fields.to_gpu() } @@ -317,7 +320,7 @@ impl Array(self, f: impl FnOnce(T) -> R + FlowFn) -> Array> where T: 'static, - R: GpuType + GpuStore + GpuSized + NoAtomics + 'static, + R: GpuType + GpuStore + GpuSized + NoAtomics + 'static + GpuLayout + LayoutableSized, { let result = Array::::zero().cell(); for_range_impl(0..N as u32, move |i| { @@ -353,7 +356,7 @@ fn push_buffer_of_array_has_wrong_variant_error(is_ref: bool, expected_variant: impl GpuIndex for Buffer, AS, DYN_OFFSET> where Idx: ToInteger, - T: GpuType + GpuSized + NoAtomics + NoHandles + GpuStore + 'static, + T: GpuType + GpuSized + NoAtomics + NoHandles + GpuStore + 'static + GpuLayout + LayoutableSized, Array: GpuStore + NoAtomics + NoBools + NoHandles, AS: BufferAddressSpace + 'static, { @@ -372,7 +375,7 @@ where impl GpuIndex for BufferRef, AS, AM, DYN_OFFSET> where Idx: ToInteger, - T: GpuType + GpuSized + GpuStore + 'static, + T: GpuType + GpuSized + GpuStore + 'static + GpuLayout + LayoutableSized, Array: GpuStore + NoBools + NoHandles, AS: BufferAddressSpace + 'static, AM: AccessModeReadable + 'static, @@ -392,7 +395,7 @@ where impl Buffer, AS, DYN_OFFSET> where Array: GpuStore + NoAtomics + NoBools + NoHandles, - T: GpuType + GpuSized + NoAtomics + NoHandles + GpuStore + 'static, + T: GpuType + GpuSized + NoAtomics + NoHandles + GpuStore + 'static + GpuLayout + LayoutableSized, { /// (no documentation yet) #[track_caller] @@ -418,7 +421,7 @@ where impl BufferRef, AS, AM, DYN_OFFSET> where Array: GpuStore + NoBools + NoHandles, - T: GpuStore + GpuType + GpuSized + 'static, + T: GpuStore + GpuType + GpuSized + 'static + GpuLayout + LayoutableSized, AS: BufferAddressSpace + 'static, AM: AccessModeReadable + 'static, { diff --git a/shame/src/frontend/rust_types/atomic.rs b/shame/src/frontend/rust_types/atomic.rs index 0e461c7..61eab93 100644 --- a/shame/src/frontend/rust_types/atomic.rs +++ b/shame/src/frontend/rust_types/atomic.rs @@ -18,7 +18,10 @@ use super::{ vec::vec, AsAny, GpuType, To, ToGpuType, }; -use crate::{any::layout::Layoutable, frontend::rust_types::reference::Ref}; +use crate::{ + any::{layout::Layoutable, BufferBindingType}, + frontend::rust_types::reference::Ref, +}; use crate::{ boolx1, frontend::{ @@ -85,22 +88,24 @@ impl GpuStore for Atomic { fn store_ty() -> ir::StoreType { ir::StoreType::Sized(::sized_ty()) } fn instantiate_buffer_inner( args: Result, - bind_ty: BindingType, + bind_ty: BufferBindingType, + has_dynamic_offset: bool, ) -> BufferInner where Self: NoAtomics + NoBools, { - BufferInner::new_plain(args, bind_ty) + BufferInner::new_plain(args, bind_ty, has_dynamic_offset) } fn instantiate_buffer_ref_inner( args: Result, - bind_ty: BindingType, + bind_ty: BufferBindingType, + has_dynamic_offset: bool, ) -> BufferRefInner where Self: NoBools, { - BufferRefInner::new_plain(args, bind_ty) + BufferRefInner::new_plain(args, bind_ty, has_dynamic_offset) } fn impl_category() -> GpuStoreImplCategory { GpuStoreImplCategory::GpuType(Self::store_ty()) } diff --git a/shame/src/frontend/rust_types/layout_traits.rs b/shame/src/frontend/rust_types/layout_traits.rs index ea9a2b0..f6d34bd 100644 --- a/shame/src/frontend/rust_types/layout_traits.rs +++ b/shame/src/frontend/rust_types/layout_traits.rs @@ -1,4 +1,5 @@ use crate::any::layout::{Layoutable, LayoutableSized, Repr}; +use crate::any::BufferBindingType; use crate::call_info; use crate::common::po2::U32PowerOf2; use crate::common::proc_macro_utils::{self, repr_c_struct_layout, ReprCError, ReprCField}; @@ -12,7 +13,7 @@ use crate::frontend::error::InternalError; use crate::frontend::rust_types::len::*; use crate::ir::ir_type::{ align_of_array, align_of_array_from_element_alignment, byte_size_of_array_from_stride_len, round_up, - stride_of_array_from_element_align_size, CanonName, LayoutError, ScalarTypeFp, ScalarTypeInteger, + stride_of_array_from_element_align_size, CanonName, ScalarTypeFp, ScalarTypeInteger, }; use crate::ir::pipeline::StageMask; use crate::ir::recording::Context; @@ -22,6 +23,7 @@ use super::error::FrontendError; use super::mem::AddressSpace; use super::reference::{AccessMode, AccessModeReadable}; use super::struct_::{BufferFields, SizedFields, Struct}; +use super::type_layout::eq::LayoutMismatch; use super::type_layout::repr::{TypeRepr, TypeReprStorageOrPacked}; use super::type_layout::layoutable::{self, array_stride, Vector}; use super::type_layout::{ @@ -227,15 +229,17 @@ pub(crate) fn check_layout_push_error( comment_on_mismatch_error: &str, ) -> Result<(), InvalidReason> { type_layout::eq::check_eq(("cpu", cpu_layout), ("gpu", gpu_layout)) - .map_err(|e| LayoutError::LayoutMismatch(e, Some(comment_on_mismatch_error.to_string()))) + .map_err(|e| CpuLayoutCompareError::LayoutMismatch(e, Some(comment_on_mismatch_error.to_string()))) .and_then(|_| { if skip_stride_check { Ok(()) } else { // the layout is an element in an array, so the strides need to match too match (cpu_layout.byte_size(), gpu_layout.byte_size()) { - (None, None) | (None, Some(_)) => Err(LayoutError::UnsizedStride { name: cpu_name.into() }), - (Some(_), None) => Err(LayoutError::UnsizedStride { + (None, None) | (None, Some(_)) => { + Err(CpuLayoutCompareError::UnsizedStride { name: cpu_name.into() }) + } + (Some(_), None) => Err(CpuLayoutCompareError::UnsizedStride { name: gpu_layout.short_name(), }), (Some(cpu_size), Some(gpu_size)) => { @@ -243,7 +247,7 @@ pub(crate) fn check_layout_push_error( let gpu_stride = array_stride(gpu_layout.align(), gpu_size); if cpu_stride != gpu_stride { - Err(LayoutError::StrideMismatch { + Err(CpuLayoutCompareError::StrideMismatch { cpu_name: cpu_name.into(), cpu_stride, gpu_name: gpu_layout.short_name(), @@ -262,6 +266,23 @@ pub(crate) fn check_layout_push_error( }) } +#[derive(thiserror::Error, Debug, Clone)] +pub enum CpuLayoutCompareError { + #[error("memory layout mismatch:\n{0}\n{}", if let Some(comment) = .1 {comment.as_str()} else {""})] + LayoutMismatch(LayoutMismatch, Option), + #[error("runtime-sized type {name} cannot be element in an array buffer")] + UnsizedStride { name: String }, + #[error( + "stride mismatch:\n{cpu_name}: {cpu_stride} bytes offset between elements,\n{gpu_name}: {gpu_stride} bytes offset between elements" + )] + StrideMismatch { + cpu_name: String, + cpu_stride: u64, + gpu_name: String, + gpu_stride: u64, + }, +} + /// (no documentation yet) pub trait CpuLayout { // TODO(release) consider making this function "unsafe", since a wrong implementation of it @@ -617,22 +638,24 @@ impl GpuStore for GpuT { fn instantiate_buffer_inner( args: Result, - bind_ty: BindingType, + bind_ty: BufferBindingType, + has_dynamic_offset: bool, ) -> BufferInner where Self: for<'trivial_bound> NoAtomics + for<'trivial_bound> NoBools, { - BufferInner::new_fields(args, bind_ty) + BufferInner::new_fields(args, bind_ty, has_dynamic_offset) } fn instantiate_buffer_ref_inner( args: Result, - bind_ty: BindingType, + bind_ty: BufferBindingType, + has_dynamic_offset: bool, ) -> BufferRefInner where Self: for<'trivial_bound> NoBools, { - BufferRefInner::new_fields(args, bind_ty) + BufferRefInner::new_fields(args, bind_ty, has_dynamic_offset) } fn impl_category() -> GpuStoreImplCategory { GpuStoreImplCategory::Fields(Self::get_bufferblock_type()) } diff --git a/shame/src/frontend/rust_types/mat.rs b/shame/src/frontend/rust_types/mat.rs index 07d3404..3242b5c 100644 --- a/shame/src/frontend/rust_types/mat.rs +++ b/shame/src/frontend/rust_types/mat.rs @@ -19,7 +19,11 @@ use super::{ vec::{scalar, vec, ToInteger}, AsAny, GpuType, To, ToGpuType, }; -use crate::{any::layout::Layoutable, frontend::rust_types::reference::Ref, ir::recording::CallInfoScope}; +use crate::{ + any::{layout::Layoutable, BufferBindingType}, + frontend::rust_types::reference::Ref, + ir::recording::CallInfoScope, +}; use crate::{ call_info, frontend::{ @@ -110,22 +114,24 @@ impl GpuStore for mat { fn instantiate_buffer_inner( args: Result, - bind_ty: BindingType, + bind_ty: BufferBindingType, + has_dynamic_offset: bool, ) -> BufferInner where Self: NoAtomics + NoBools, { - BufferInner::new_plain(args, bind_ty) + BufferInner::new_plain(args, bind_ty, has_dynamic_offset) } fn instantiate_buffer_ref_inner( args: Result, - bind_ty: BindingType, + bind_ty: BufferBindingType, + has_dynamic_offset: bool, ) -> BufferRefInner where Self: NoBools, { - BufferRefInner::new_plain(args, bind_ty) + BufferRefInner::new_plain(args, bind_ty, has_dynamic_offset) } fn impl_category() -> GpuStoreImplCategory { GpuStoreImplCategory::GpuType(Self::store_ty()) } diff --git a/shame/src/frontend/rust_types/reference.rs b/shame/src/frontend/rust_types/reference.rs index cbf5969..432db27 100644 --- a/shame/src/frontend/rust_types/reference.rs +++ b/shame/src/frontend/rust_types/reference.rs @@ -13,7 +13,7 @@ use super::{ vec::ToInteger, AsAny, GpuType, To, }; -use crate::frontend::any::Any; +use crate::{any::layout::LayoutableSized, frontend::any::Any, GpuLayout}; use crate::frontend::rust_types::len::x1; use crate::frontend::rust_types::vec::vec; use crate::{ @@ -228,7 +228,7 @@ where impl Ref, AS, AM> where - T: GpuType + GpuSized + GpuStore + 'static, + T: GpuType + GpuSized + GpuStore + 'static + GpuLayout + LayoutableSized, AS: AddressSpace + 'static, AM: AccessModeReadable + 'static, N: ArrayLen, diff --git a/shame/src/frontend/rust_types/struct_.rs b/shame/src/frontend/rust_types/struct_.rs index f50c185..843093e 100644 --- a/shame/src/frontend/rust_types/struct_.rs +++ b/shame/src/frontend/rust_types/struct_.rs @@ -1,4 +1,5 @@ use crate::any::layout::{Layoutable, LayoutableSized}; +use crate::any::BufferBindingType; use crate::common::small_vec::SmallVec; use crate::frontend::any::shared_io::{BindPath, BindingType}; use crate::frontend::any::{Any, InvalidReason}; @@ -97,22 +98,24 @@ impl GpuStore for Struct { fn store_ty() -> ir::StoreType { ir::StoreType::Sized(::sized_ty()) } fn instantiate_buffer_inner( args: Result, - bind_ty: BindingType, + bind_ty: BufferBindingType, + has_dynamic_offset: bool, ) -> BufferInner where - Self: NoAtomics + NoBools, + Self: NoAtomics + NoBools + GpuLayout, { - BufferInner::new_plain(args, bind_ty) + BufferInner::new_plain(args, bind_ty, has_dynamic_offset) } fn instantiate_buffer_ref_inner( args: Result, - bind_ty: BindingType, + bind_ty: BufferBindingType, + has_dynamic_offset: bool, ) -> BufferRefInner where - Self: NoBools, + Self: NoBools + GpuLayout, { - BufferRefInner::new_plain(args, bind_ty) + BufferRefInner::new_plain(args, bind_ty, has_dynamic_offset) } fn impl_category() -> GpuStoreImplCategory { GpuStoreImplCategory::GpuType(Self::store_ty()) } diff --git a/shame/src/frontend/rust_types/type_traits.rs b/shame/src/frontend/rust_types/type_traits.rs index 7880ec3..730b2c8 100644 --- a/shame/src/frontend/rust_types/type_traits.rs +++ b/shame/src/frontend/rust_types/type_traits.rs @@ -3,10 +3,11 @@ use super::{ layout_traits::{FromAnys, GetAllFields, GpuLayout}, mem::{self, AddressSpace}, reference::{AccessMode, AccessModeReadable}, - type_layout::{self}, + type_layout::{self, repr}, AsAny, GpuType, ToGpuType, }; use crate::{ + any::BufferBindingType, frontend::any::shared_io::{BindPath, BindingType}, TypeLayout, }; @@ -105,10 +106,13 @@ pub trait GpuStore: GpuAligned + GetAllFields + FromAnys { #[doc(hidden)] fn instantiate_buffer_inner( args: Result, - ty: BindingType, + bind_ty: BufferBindingType, + has_dynamic_offset: bool, ) -> BufferInner where - Self: std::marker::Sized /*not GpuSized, this is deliberate*/ + NoAtomics + NoBools; + Self: std::marker::Sized /*not GpuSized, this is deliberate*/ + NoAtomics + + NoBools + + GpuLayout; /// internal function that aids in the construction of `BufferRef` as a `Binding` /// @@ -116,10 +120,11 @@ pub trait GpuStore: GpuAligned + GetAllFields + FromAnys { #[doc(hidden)] fn instantiate_buffer_ref_inner( args: Result, - ty: BindingType, + bind_ty: BufferBindingType, + has_dynamic_offset: bool, ) -> BufferRefInner where - Self: std::marker::Sized /*not GpuSized, this is deliberate*/ + NoBools; + Self: std::marker::Sized /*not GpuSized, this is deliberate*/ + NoBools + GpuLayout; #[doc(hidden)] // runtime api fn store_ty() -> ir::StoreType diff --git a/shame/src/frontend/rust_types/vec.rs b/shame/src/frontend/rust_types/vec.rs index 4d065e3..5974cfb 100644 --- a/shame/src/frontend/rust_types/vec.rs +++ b/shame/src/frontend/rust_types/vec.rs @@ -12,7 +12,10 @@ use super::{ AsAny, GpuType, To, ToGpuType, }; use crate::{ - any::layout::{self, Layoutable, LayoutableSized}, + any::{ + layout::{self, Layoutable, LayoutableSized}, + BufferBindingType, + }, call_info, common::{ proc_macro_utils::{collect_into_array_exact, push_wrong_amount_of_args_error}, @@ -552,22 +555,24 @@ impl GpuStore for vec { fn instantiate_buffer_inner( args: Result, - bind_ty: BindingType, + bind_ty: BufferBindingType, + has_dynamic_offset: bool, ) -> BufferInner where Self: NoAtomics + NoBools, { - BufferInner::new_plain(args, bind_ty) + BufferInner::new_plain(args, bind_ty, has_dynamic_offset) } fn instantiate_buffer_ref_inner( args: Result, - bind_ty: BindingType, + bind_ty: BufferBindingType, + has_dynamic_offset: bool, ) -> BufferRefInner where Self: NoBools, { - BufferRefInner::new_plain(args, bind_ty) + BufferRefInner::new_plain(args, bind_ty, has_dynamic_offset) } fn impl_category() -> GpuStoreImplCategory { GpuStoreImplCategory::GpuType(Self::store_ty()) } diff --git a/shame/src/ir/ir_type/align_size.rs b/shame/src/ir/ir_type/align_size.rs index 1ed703a..0ad319e 100644 --- a/shame/src/ir/ir_type/align_size.rs +++ b/shame/src/ir/ir_type/align_size.rs @@ -6,7 +6,7 @@ use crate::frontend::any::shared_io::BufferBindingType; use thiserror::Error; use super::{CanonName, Len2, ScalarType, ScalarTypeFp, SizedType, StoreType}; -use super::{Len, Std}; +use super::{Len}; pub const fn round_up(multiple_of: u64, n: u64) -> u64 { match multiple_of { diff --git a/shame/src/ir/ir_type/layout_constraints.rs b/shame/src/ir/ir_type/layout_constraints.rs index f2e2182..28cd9a6 100644 --- a/shame/src/ir/ir_type/layout_constraints.rs +++ b/shame/src/ir/ir_type/layout_constraints.rs @@ -1,805 +1,805 @@ -use std::{ - fmt::{Display, Write}, - num::NonZeroU32, - rc::Rc, -}; - -use thiserror::Error; - -use crate::frontend::rust_types::type_layout::{eq::LayoutMismatch, TypeLayout}; -use crate::{ - backend::language::Language, - call_info, - common::prettify::set_color, - frontend::{any::shared_io::BufferBindingType, encoding::EncodingErrorKind}, - ir::{ - ir_type::{max_u64_po2_dividing, AccessModeReadable}, - recording::{Context, MemoryRegion}, - AccessMode, - }, -}; - -use super::{ - align_of_array, round_up, stride_of_array, AddressSpace, BufferBlock, CanonName, Field, SizedField, SizedStruct, - SizedType, StoreType, Type, -}; -use crate::common::integer::IntegerExt; - -/// (slightly modified quote from OpenGL 4.6 spec core, section 7.6.2.2 ) -/// (replaced "basic machine units" with "bytes") -/// rules for std140: -/// -/// 1. If the member is a scalar consuming N bytes, the base alignment is N. -/// 2. If the member is a 2 or 4 component vector with components consuming N bytes the base alignment is 2N or 4N respectively -/// 3. If the member is a 3 component vector with components consuming n bytes, the base alignment is 4N -/// 4. If the member is an array of scalars or vectors, the base alignment and array -/// stride are set to match the base alignment of a single array element according to -/// rules 1. 2. and 3., and rounded up to the base alignment of a `vec4`. The array -/// may have padding at the end; the base offset of the member following the array is -/// rounded up to the next multiple of the base alignment (edit: of the array (this is not explicitly written in the spec, but a best guess based on the end of rule 9)) -/// 5. If the member is a column-major matrix with C columns and R rows, the -/// matrix is stored identically to an array of C column vectors with R components each -/// according to rule 4. . -/// 6. If the member is an array of S column-major matrices with C columns and R rows, -/// the matrix is stored identically to a row(=array???) of S * C column vectors with R -/// components each, according to rule 4. . -/// 7. If the member is a row-major matrix with C columns and R rows, the matrix -/// is stored identically to an array of R row vectors with C components each, -/// according to rules 4. . -/// 8. If the member is an array of S row-major matrices with C columns and R rows, -/// the matrix is stored identically to a row(=array???) of S * R row vectors with C -/// components each, according to rule 4. . -/// 9. If the member is a structure, the base alignment of the structure is N, where -/// N is the largest base alignment value of any of its members and rounded -/// up to the base alignment of a `vec4`. the individual members of this -/// sub-structure are then assigned offset by applying this set of rules -/// recursively, where the base offset of the first member of the sub-structure -/// is equal to the aligned offset of the structure. -/// The structure may have padding at the end; the base offset of the member -/// following the sub-structure is rounded up to the next multiple of the -/// base alignment of the structure. -/// 10. If the member ois an array of S structures, the S elements of the array are -/// laid out in order according to rules 9. . -/// -/// Shader storage blocks also support the std140 layout qualifier as well as a std430 -/// qualifier not supported for uniform blocks. When using the std430 storage layout, -/// shader storage blocks will be laid out in buffer storage identically to uniform -/// and shader storage blocks using the 140 layout except that the base alignment and -/// stride of arrays of scalars and vectors in rule 4 and of structures in rule 9 are -/// not rounded up a multiple of the base alignment of a vec4. -/// -/// summary: -/// -/// SCALAR AND VECTOR RULES: -/// 1. base_align_of `ScalarType`s is its byte-size -/// 2. base_align_of `Vector(len @ (X2 | X4), s)` is `len * base_align_of(s)` -/// 3. base_align_of `Vector(X3, s)` is ` 4 * base_align_of(s)` -/// -/// ARRAY RULES: -/// 4.a(@ std140) base_align_of `Array(e, n)` is `round_up(base_align_of(fvec4), base_align_of(e))` -/// 4.a(@ std430) base_align_of `Array(e, n)` is `base_align_of(e)` -/// 4.b stride_of `Array(e, n)` is round_up(base_align_of `Array(e, n)`, size_of(e)) // this is my interpretation of the word "match" in the spec. -/// 4.c base offset of the member after the array is `offset_of(Array(e, n) + n * stride_of(Array(e, n)))` -/// ^ the empty part after the last element's size end that fills up the stride is referred to as "padding" -/// -/// COLUMN MAJOR TO ARRAY RULES: -/// 5. `col-major Matrix(C, R, t)` stored like `Array(Vector(R, t), C)` -/// 6. `Array(col-major Matrix(C, R, t), S)` stored like `Array(Vector(R, t), S*C)` -/// -/// ROW MAJOR TO ARRAY RULES: -/// 7. `row-major Matrix(C, R, t)` stored like `Array(Vector(C, t), R)` -/// 8. `Array(row-major Matrix(C, R, t), S)` stored like `Array(Vector(C, t), S*R)` -/// -/// STRUCTURE RULES: -/// 9.a(@std140) base_align_of `Struct(fields)` is `round_up(base_align_of(fvec4), base_align_of(fields.map(base_align_of).max()))` -/// 9.a(@std430) base_align_of `Struct(fields)` is `base_align_of(fields.map(base_align_of).max())` -/// -/// 9. If the member is a structure, the base alignment of the structure is N, where -/// N is the largest base alignment value of any of its members and rounded -/// up to the base alignment of a `vec4`. -/// -/// the individual members of this -/// sub-structure are then assigned offset by applying this set of rules -/// recursively, where the base offset of the first member of the sub-structure -/// is equal to the aligned offset of the (outer)structure. -/// -/// The structure may have padding at the end; the base offset of the member -/// following the sub-structure is rounded up to the next multiple of the -/// base alignment of the (outer)structure. -/// -/// 10. If the member is an array of S structures, the S elements of the array are -/// laid out in order according to rules 9. . -/// -/// - storage blocks support std140 and std430 -/// - uniform blocks support std140 only -/// - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Std { - _140, - _430, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum WgslBufferLayout { - UniformAddressSpace, - StorageAddressSpace, -} - -#[derive(Debug, Clone, Copy)] -pub enum LayoutConstraints { - OpenGL(Std), - Wgsl(WgslBufferLayout), -} - -impl Display for LayoutConstraints { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(match self { - LayoutConstraints::OpenGL(std) => match std { - Std::_140 => "std140", - Std::_430 => "std430", - }, - LayoutConstraints::Wgsl(w) => match w { - WgslBufferLayout::UniformAddressSpace => "uniform address-space", - WgslBufferLayout::StorageAddressSpace => "storage address-space", - }, - }) - } -} - -impl LayoutConstraints { - fn more_info_at(&self) -> &'static str { - match self { - LayoutConstraints::OpenGL(_) => { - "section 7.6.2.2 in https://registry.khronos.org/OpenGL/specs/gl/glspec46.core.pdf" - } - LayoutConstraints::Wgsl(_) => "https://www.w3.org/TR/WGSL/#memory-layouts", - } - } -} - -pub fn get_type_for_buffer_binding_type( - ty: &StoreType, - binding_type: BufferBindingType, - as_ref: bool, - ctx: &Context, -) -> Result { - use AccessModeReadable as AM; - use AddressSpace as AS; - - let lang = ctx.settings().lang; - - let access = match binding_type { - BufferBindingType::Uniform => AccessMode::Read, - BufferBindingType::Storage(am) => am.into(), - }; - - if !ty.is_host_shareable() { - return Err(LayoutError::NotHostShareable(ty.clone()).into()); - } - - let result = match as_ref { - true => { - // `binding_ty` must be `BindingType::Buffer` - Type::Ref( - MemoryRegion::new(call_info!(), ty.clone(), None, None, access, AddressSpace::Storage)?, - ty.clone(), - access, - ) - } - false => { - // `binding_ty` must be a uniform or read-only storage buffer, `ty` must be constructible - match binding_type { - BufferBindingType::Uniform => Ok(()), - BufferBindingType::Storage(AccessModeReadable::Read) => Ok(()), - BufferBindingType::Storage(AccessModeReadable::ReadWrite) => { - Err(LayoutError::NonRefBufferRequiresReadOnlyAndConstructible) - } - }?; - if !ty.is_constructible() { - return Err(LayoutError::NonRefBufferRequiresReadOnlyAndConstructible.into()); - } - Type::Store(ty.clone()) - } - }; - - let constraint = match lang { - Language::Wgsl => LayoutConstraints::Wgsl(match binding_type { - BufferBindingType::Uniform => WgslBufferLayout::UniformAddressSpace, - BufferBindingType::Storage(_) => WgslBufferLayout::StorageAddressSpace, - }), - // Language::Glsl => LayoutConstraints::OpenGL(match binding_type { - // BufferBindingType::Uniform => Std::_140, - // BufferBindingType::Storage(_) => Std::_430, - // }), - }; - - check_layout( - &LayoutErrorContext { - binding_type, - expected_constraints: constraint, - top_level_type: ty.clone(), - use_color: ctx.settings().colored_error_messages, - }, - ty, - )?; - Ok(result) -} - -pub fn check_layout(ctx: &LayoutErrorContext, ty: &StoreType) -> Result<(), LayoutError> { - if !ty.is_creation_fixed_footprint() { - match ctx.expected_constraints { - LayoutConstraints::OpenGL(std) => Ok(()), - LayoutConstraints::Wgsl(l) => match l { - WgslBufferLayout::UniformAddressSpace => Err(LayoutError::UniformBufferMustBeSized("wgsl", ty.clone())), - WgslBufferLayout::StorageAddressSpace => Ok(()), - }, - }?; - // WGSL does not allow uniform buffers to have unsized types - } - match ty { - StoreType::Sized(t) => check_sized_type_layout(ctx, t), - StoreType::Handle(_) => Err(LayoutError::NotHostShareable(ty.clone())), - StoreType::RuntimeSizedArray(e) => check_array_layout(ctx, e), - StoreType::BufferBlock(s) => check_structure_layout(ctx, &LayoutStructureKind::BufferBlock(s.clone())), - } -} - -pub fn check_sized_type_layout(ctx: &LayoutErrorContext, ty: &SizedType) -> Result<(), LayoutError> { - match &ty { - SizedType::Vector(_, _) => Ok(()), - SizedType::Matrix(_, _, _) => Ok(()), - SizedType::Atomic(_) => Ok(()), - SizedType::Structure(s) => check_structure_layout(ctx, &LayoutStructureKind::Structure(s.clone())), - SizedType::Array(e, n) => { - let expected_align = match ctx.expected_constraints { - LayoutConstraints::OpenGL(std) => match std { - Std::_140 => round_up(16, align_of_array(e)), - Std::_430 => align_of_array(e), - }, - LayoutConstraints::Wgsl(wbl) => match wbl { - WgslBufferLayout::UniformAddressSpace => round_up(16, align_of_array(e)), - WgslBufferLayout::StorageAddressSpace => align_of_array(e), - }, - }; - let actual_align = align_of_array(e); - if actual_align != expected_align { - Err(LayoutError::ArrayAlignmentError(ArrayAlignmentError { - ctx: ctx.clone(), - expected: expected_align, - actual: actual_align, - element_ty: (**e).clone(), - })) - } else { - check_array_layout(ctx, e) - } - } - } -} - -pub fn check_array_layout(ctx: &LayoutErrorContext, elem: &SizedType) -> Result<(), LayoutError> { - // wgsl spec: Arrays of element type `elem` must have an element - // stride that is a multiple of the RequiredAlignOf(elem, addr_space) - let actual_stride = stride_of_array(elem); - match ctx.expected_constraints { - LayoutConstraints::OpenGL(std) => { - // using wording of the opengl spec - let base_align_of_array = match std { - Std::_140 => round_up(16, elem.align()), - Std::_430 => elem.align(), - }; - let expected_stride = round_up(base_align_of_array, elem.byte_size()); - if actual_stride != expected_stride { - return Err(LayoutError::ArrayStrideError(ArrayStrideError { - ctx: ctx.clone(), - expected: expected_stride, - actual: actual_stride, - element_ty: elem.clone(), - })); - } - } - LayoutConstraints::Wgsl(wbl) => { - let required_element_align = match wbl { - WgslBufferLayout::UniformAddressSpace => round_up(16, align_of_array(elem)), - WgslBufferLayout::StorageAddressSpace => align_of_array(elem), - }; - if !required_element_align.divides(actual_stride) { - return Err(LayoutError::ArrayStrideAlignmentError(ArrayStrideAlignmentError { - ctx: ctx.clone(), - expected_align: required_element_align, - actual_stride, - element_ty: elem.clone(), - })); - } - } - }; - check_sized_type_layout(ctx, elem)?; - Ok(()) -} - -pub fn check_structure_layout(ctx: &LayoutErrorContext, s: &LayoutStructureKind) -> Result<(), LayoutError> { - let structure_name = match s { - LayoutStructureKind::Structure(s) => s.name(), - LayoutStructureKind::BufferBlock(s) => s.name(), - }; - let (sized_fields, last_field) = &match s { - LayoutStructureKind::Structure(s) => (s.sized_fields(), &None), - LayoutStructureKind::BufferBlock(s) => (s.sized_fields(), s.last_unsized_field()), - }; - - let mut offset = 0; - for field in sized_fields.iter() { - // return errors if custom align or size are used in OpenGL constraints. - // warning: removing this check will make the surrounding code wrong. - // for example: the OpenGL spec has a wording which defines std140/std430 - // offsets of elements after arrays directly via their stride. - // this means even if GLSL supports custom size and align in the future, - // the wording of the std140 definition of that situation would need to - // be checked for changes, which would likely justify a rewrite of this - // entire module. - match ctx.expected_constraints { - LayoutConstraints::OpenGL(_) => { - if let Some(custom_align) = field.custom_min_align() { - return Err(LayoutError::LayoutConstriantsDoNotSupportCustomFieldAlign { - ctx: ctx.clone(), - struct_or_block_name: structure_name.clone(), - field_name: field.name().clone(), - custom_align: u64::from(custom_align), - }); - } - if let Some(custom_size) = field.custom_min_size() { - return Err(LayoutError::LayoutConstriantsDoNotSupportCustomFieldSize { - ctx: ctx.clone(), - struct_or_block_name: structure_name.clone(), - field_name: field.name().clone(), - custom_size, - }); - } - } - LayoutConstraints::Wgsl(_) => (), - } - - // the align and size which takes custom user attributes for align and size into account - let (align, size) = (field.align(), field.byte_size()); - // the regular align and size of field.ty - let (ty_align, ty_size) = (field.ty().align(), field.ty().byte_size()); - offset = round_up(field.align(), offset); - - let required_align_of_field = match field.ty() { - SizedType::Vector(_, _) | SizedType::Matrix(_, _, _) | SizedType::Atomic(_) => ty_align, - SizedType::Structure(_) | SizedType::Array(_, _) => match ctx.expected_constraints { - LayoutConstraints::OpenGL(std) => match std { - Std::_140 => round_up(16, ty_align), - Std::_430 => ty_align, - }, - LayoutConstraints::Wgsl(wbl) => match wbl { - WgslBufferLayout::UniformAddressSpace => round_up(16, ty_align), - WgslBufferLayout::StorageAddressSpace => ty_align, - }, - }, - }; - - if !required_align_of_field.divides(offset) { - return Err(LayoutError::Structure(StructureLayoutError { - context: ctx.clone(), - structure: s.clone(), - field_name: field.name().clone(), - actual_offset: offset, - expected_alignment: required_align_of_field, - })); - } - - check_sized_type_layout(ctx, field.ty())?; - - offset += field.byte_size() - } - - if let Some(last_field) = last_field { - let ty_align = align_of_array(last_field.element_ty()); - let ty = StoreType::RuntimeSizedArray(last_field.element_ty().clone()); - - // TODO(low prio) refactor this function so that theres no code duplication here - let required_align_of_array = match ctx.expected_constraints { - LayoutConstraints::OpenGL(std) => match std { - Std::_140 => round_up(16, ty_align), - Std::_430 => ty_align, - }, - LayoutConstraints::Wgsl(wbl) => match wbl { - WgslBufferLayout::UniformAddressSpace => round_up(16, ty_align), - WgslBufferLayout::StorageAddressSpace => ty_align, - }, - }; - - if !required_align_of_array.divides(offset) { - return Err(LayoutError::Structure(StructureLayoutError { - context: ctx.clone(), - structure: s.clone(), - field_name: last_field.name().clone(), - actual_offset: offset, - expected_alignment: required_align_of_array, - })); - } - check_layout(ctx, &ty)?; - } - Ok(()) -} - -#[derive(Error, Debug, Clone)] -pub enum LayoutError { - #[error("array elements may not be runtime-sized types. found array of: {0}")] - ArrayElementsAreUnsized(TypeLayout), - #[error("{0}")] - Structure(#[from] StructureLayoutError), - #[error( - "The size of `{1}` on the gpu is now known at compile time. `{0}` \ - requires that the size of uniform buffers on the gpu is known at compile time." - )] - UniformBufferMustBeSized(&'static str, StoreType), - #[error("{0}")] - ArrayAlignmentError(ArrayAlignmentError), - #[error("{0}")] - ArrayStrideError(ArrayStrideError), - #[error("{0}")] - ArrayStrideAlignmentError(ArrayStrideAlignmentError), - #[error("{} layout constraints do not allow custom struct field byte-alignments. \ -Type `{}` contains type `{struct_or_block_name}` which has a custom byte-alignment of {custom_align} at field `{field_name}`.", ctx.expected_constraints, ctx.top_level_type)] - LayoutConstriantsDoNotSupportCustomFieldAlign { - ctx: LayoutErrorContext, - struct_or_block_name: CanonName, - field_name: CanonName, - custom_align: u64, - }, - #[error("{} layout constraints do not allow custom struct field byte-sizes. \ - Type `{}` contains type `{struct_or_block_name}` which has a custom byte-size of {custom_size} at field `{field_name}`.", ctx.expected_constraints, ctx.top_level_type)] - LayoutConstriantsDoNotSupportCustomFieldSize { - ctx: LayoutErrorContext, - struct_or_block_name: CanonName, - field_name: CanonName, - custom_size: u64, - }, - #[error("a non-reference buffer (non `BufferRef`) must be both read-only and constructible")] - NonRefBufferRequiresReadOnlyAndConstructible, - #[error( - "type {0} does not match the requirements for host-shareable types. See https://www.w3.org/TR/WGSL/#host-shareable-types (In most cases, this is caused by the type containing booleans)" - )] - NotHostShareable(StoreType), - #[error("custom alignment of {custom} is too small. `{ty}` must have an alignment of at least {required}")] - CustomAlignmentTooSmall { custom: u64, required: u64, ty: StoreType }, - #[error("custom size of {custom} is too small. `{ty}` must have a size of at least {required}")] - CustomSizeTooSmall { custom: u64, required: u64, ty: Type }, - #[error("memory layout mismatch:\n{0}\n{}", if let Some(comment) = .1 {comment.as_str()} else {""})] - LayoutMismatch(LayoutMismatch, Option), - #[error("runtime-sized type {name} cannot be element in an array buffer")] - UnsizedStride { name: String }, - #[error( - "stride mismatch:\n{cpu_name}: {cpu_stride} bytes offset between elements,\n{gpu_name}: {gpu_stride} bytes offset between elements" - )] - StrideMismatch { - cpu_name: String, - cpu_stride: u64, - gpu_name: String, - gpu_stride: u64, - }, -} - -#[allow(missing_docs)] -#[derive(Error, Debug, Clone)] -pub struct ArrayStrideAlignmentError { - ctx: LayoutErrorContext, - expected_align: u64, - actual_stride: u64, - element_ty: SizedType, -} - -impl Display for ArrayStrideAlignmentError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - writeln!( - f, - "array elements within type `{}` do not satisfy {} layout requirements.", - self.ctx.top_level_type, self.ctx.expected_constraints - ); - let expected_align = self.expected_align; - let actual_stride = self.actual_stride; - writeln!( - f, - "The array with `{}` elements requires that every element is {expected_align}-byte aligned, but the array has a stride of {actual_stride} bytes, which means subsequent elements are not {expected_align}-byte aligned.", - self.element_ty - ); - if let Ok(layout) = TypeLayout::from_store_ty(self.ctx.top_level_type.clone()) { - writeln!(f, "The full layout of `{}` is:", self.ctx.top_level_type); - layout.write("", self.ctx.use_color, f)?; - writeln!(f); - }; - writeln!( - f, - "\nfor more information on the layout rules, see {}", - self.ctx.expected_constraints.more_info_at() - )?; - Ok(()) - } -} - -#[allow(missing_docs)] -#[derive(Error, Debug, Clone)] -pub struct ArrayStrideError { - ctx: LayoutErrorContext, - expected: u64, - actual: u64, - element_ty: SizedType, -} - -impl Display for ArrayStrideError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - writeln!( - f, - "array elements within type `{}` do not satisfy {} layout requirements.", - self.ctx.top_level_type, self.ctx.expected_constraints - ); - writeln!( - f, - "The array with `{}` elements requires stride {}, but has stride {}.", - self.element_ty, self.expected, self.actual - ); - if let Ok(layout) = TypeLayout::from_store_ty(self.ctx.top_level_type.clone()) { - writeln!(f, "The full layout of `{}` is:", self.ctx.top_level_type); - layout.write("", self.ctx.use_color, f)?; - writeln!(f); - }; - writeln!( - f, - "\nfor more information on the layout rules, see {}", - self.ctx.expected_constraints.more_info_at() - )?; - Ok(()) - } -} - -#[allow(missing_docs)] -#[derive(Error, Debug, Clone)] -pub struct ArrayAlignmentError { - ctx: LayoutErrorContext, - expected: u64, - actual: u64, - element_ty: SizedType, -} - -impl Display for ArrayAlignmentError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - writeln!( - f, - "array elements within type `{}` do not satisfy {} layout requirements.", - self.ctx.top_level_type, self.ctx.expected_constraints - ); - writeln!( - f, - "The array with `{}` elements requires alignment {}, but has alignment {}.", - self.element_ty, self.expected, self.actual - ); - if let Ok(layout) = TypeLayout::from_store_ty(self.ctx.top_level_type.clone()) { - writeln!(f, "The full layout of `{}` is:", self.ctx.top_level_type); - layout.write("", self.ctx.use_color, f)?; - writeln!(f); - }; - writeln!( - f, - "\nfor more information on the layout rules, see {}", - self.ctx.expected_constraints.more_info_at() - )?; - Ok(()) - } -} - - - -#[derive(Debug, Clone)] -pub struct LayoutErrorContext { - pub binding_type: BufferBindingType, - pub expected_constraints: LayoutConstraints, - pub top_level_type: StoreType, - /// whether the error message should be output using console colors - pub use_color: bool, -} - -#[derive(Debug, Clone)] -//TODO(release) this type is obsolete. SizedStruct and BufferBlock now both Deref to `Struct`, use that instead -pub enum LayoutStructureKind { - Structure(SizedStruct), - BufferBlock(BufferBlock), -} - -#[derive(Debug, Clone)] -pub struct StructureLayoutError { - pub context: LayoutErrorContext, - pub structure: LayoutStructureKind, - pub field_name: CanonName, - pub actual_offset: u64, - pub expected_alignment: u64, -} - -impl std::error::Error for StructureLayoutError {} - -impl Display for StructureLayoutError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let custom_align_or_size_supported = match self.context.expected_constraints { - LayoutConstraints::OpenGL(_) => false, - LayoutConstraints::Wgsl(_) => true, - }; - - let structure_name: &CanonName = match &self.structure { - LayoutStructureKind::Structure(s) => s.name(), - LayoutStructureKind::BufferBlock(s) => s.name(), - }; - - let structure_def_location = Context::try_with(call_info!(), |ctx| { - let s = match &self.structure { - LayoutStructureKind::Structure(s) => &**s, - LayoutStructureKind::BufferBlock(s) => &**s, - }; - ctx.struct_registry().get(s).map(|def| def.call_info()) - }) - .flatten(); - - let top_level_type = &self.context.top_level_type; - let binding_type_str = match self.context.binding_type { - BufferBindingType::Storage(_) => "storage", - BufferBindingType::Uniform => "uniform", - }; - writeln!( - f, - "the type `{top_level_type}` cannot be used as a {binding_type_str} buffer binding." - )?; - let ((constraints, short_summary), link) = match self.context.expected_constraints { - LayoutConstraints::OpenGL(std) => ( - match std { - Std::_140 => ( - "std140", - Some( - "In std140 buffers the alignment of structs, arrays and array elements must be at least 16.", - ), - ), - Std::_430 => ("std430", None), - }, - "https://www.khronos.org/opengl/wiki/Interface_Block_(GLSL)#Memory_layout", - ), - LayoutConstraints::Wgsl(x) => ( - match x { - WgslBufferLayout::UniformAddressSpace => ( - "uniform address space", - Some( - "In the uniform address space, structs, arrays and array elements must be at least 16 byte aligned.", - ), - ), - WgslBufferLayout::StorageAddressSpace => ("storage address space", None), - }, - "https://www.w3.org/TR/WGSL/#address-space-layout-constraints", - ), - }; - let actual_align = max_u64_po2_dividing(self.actual_offset); - let nested = self.context.top_level_type.to_string() != **structure_name; - if nested { - write!(f, "It contains a struct `{}`, which", structure_name)?; - } else { - write!(f, "Struct `{}`", structure_name)?; - } - writeln!(f, " does not satisfy the {constraints} memory layout requirements.",)?; - writeln!(f)?; - if let Some(call_info) = structure_def_location { - writeln!(f, "Definition at {call_info}")?; - } - write_struct_layout(&self.structure, self.context.use_color, Some(&self.field_name), f)?; - - writeln!(f)?; - set_color(f, Some("#508EE3"), false)?; - writeln!( - f, - "Field `{}` needs to be {} byte aligned, but has a byte-offset of {} which is only {actual_align} byte aligned.", - self.field_name, self.expected_alignment, self.actual_offset - )?; - set_color(f, None, false)?; - writeln!(f)?; - - writeln!(f, "Potential solutions include:"); - writeln!( - f, - "- add an #[align({})] attribute to the definition of `{}` (not supported by OpenGL/GLSL pipelines)", - self.expected_alignment, self.field_name - ); - writeln!(f, "- use a storage binding instead of a uniform binding"); - writeln!( - f, - "- increase the offset of `{}` until it is divisible by {} by making previous fields larger or adding fields before it", - self.field_name, self.expected_alignment - ); - writeln!(f)?; - - - if let Some(summary) = short_summary { - writeln!(f, "{summary}"); - } - //writeln!(f, "Address space alignment restrictions enable use of more efficient hardware instructions for accessing the values, or satisfy more restrictive hardware requirements.")?; - writeln!(f, "More info about the {constraints} layout can be found at {link}")?; - Ok(()) - } -} - -fn write_struct_layout( - s: &LayoutStructureKind, - colored: bool, - highlight_field: Option<&str>, - f: &mut F, -) -> std::fmt::Result -where - F: Write, -{ - let use_256_color_mode = false; - let color = |f_: &mut F, hex| match colored { - true => set_color(f_, Some(hex), use_256_color_mode), - false => Ok(()), - }; - let reset = |f_: &mut F| match colored { - true => set_color(f_, None, use_256_color_mode), - false => Ok(()), - }; - - let structure_name = match s { - LayoutStructureKind::Structure(s) => s.name(), - LayoutStructureKind::BufferBlock(s) => s.name(), - }; - - let (sized_fields, last_field) = match s { - LayoutStructureKind::Structure(s) => (s.sized_fields(), &None), - LayoutStructureKind::BufferBlock(s) => (s.sized_fields(), s.last_unsized_field()), - }; - - let indent = " "; - let field_decl_line = |field: &SizedField| format!("{indent}{}: {},", field.name(), field.ty()); - let header = format!("struct {} {{", structure_name); - let table_start_column = 1 + sized_fields - .iter() - .map(field_decl_line) - .map(|s| s.len()) - .max() - .unwrap_or(0) - .max(header.chars().count()); - f.write_str(&header)?; - for i in header.len()..table_start_column { - f.write_char(' ')? - } - writeln!(f, "offset align size")?; - let mut offset = 0; - for field in sized_fields.iter() { - if Some(&**field.name()) == highlight_field { - color(f, "#508EE3")?; - } - let (align, size) = (field.align(), field.byte_size()); - offset = round_up(field.align(), offset); - let decl_line = field_decl_line(field); - f.write_str(&decl_line)?; - // write spaces to table on the right - for i in decl_line.len()..table_start_column { - f.write_char(' ')? - } - writeln!(f, "{:6} {:5} {:4}", offset, align, size)?; - if Some(&**field.name()) == highlight_field { - reset(f); - } - offset += field.byte_size() - } - if let Some(last_field) = last_field { - offset = round_up(last_field.align(), offset); - - let decl_line = format!( - "{indent}{}: {},", - last_field.name(), - StoreType::RuntimeSizedArray(last_field.element_ty().clone()) - ); - f.write_str(&decl_line)?; - // write spaces to table on the right - for i in decl_line.len()..table_start_column { - f.write_char(' ')? - } - write!(f, "{:6} {:5}", offset, last_field.align())?; - } - writeln!(f, "}}")?; - Ok(()) -} +// use std::{ +// fmt::{Display, Write}, +// num::NonZeroU32, +// rc::Rc, +// }; + +// use thiserror::Error; + +// use crate::frontend::rust_types::type_layout::{eq::LayoutMismatch, TypeLayout}; +// use crate::{ +// backend::language::Language, +// call_info, +// common::prettify::set_color, +// frontend::{any::shared_io::BufferBindingType, encoding::EncodingErrorKind}, +// ir::{ +// ir_type::{max_u64_po2_dividing, AccessModeReadable}, +// recording::{Context, MemoryRegion}, +// AccessMode, +// }, +// }; + +// use super::{ +// align_of_array, round_up, stride_of_array, AddressSpace, BufferBlock, CanonName, Field, SizedField, SizedStruct, +// SizedType, StoreType, Type, +// }; +// use crate::common::integer::IntegerExt; + +// /// (slightly modified quote from OpenGL 4.6 spec core, section 7.6.2.2 ) +// /// (replaced "basic machine units" with "bytes") +// /// rules for std140: +// /// +// /// 1. If the member is a scalar consuming N bytes, the base alignment is N. +// /// 2. If the member is a 2 or 4 component vector with components consuming N bytes the base alignment is 2N or 4N respectively +// /// 3. If the member is a 3 component vector with components consuming n bytes, the base alignment is 4N +// /// 4. If the member is an array of scalars or vectors, the base alignment and array +// /// stride are set to match the base alignment of a single array element according to +// /// rules 1. 2. and 3., and rounded up to the base alignment of a `vec4`. The array +// /// may have padding at the end; the base offset of the member following the array is +// /// rounded up to the next multiple of the base alignment (edit: of the array (this is not explicitly written in the spec, but a best guess based on the end of rule 9)) +// /// 5. If the member is a column-major matrix with C columns and R rows, the +// /// matrix is stored identically to an array of C column vectors with R components each +// /// according to rule 4. . +// /// 6. If the member is an array of S column-major matrices with C columns and R rows, +// /// the matrix is stored identically to a row(=array???) of S * C column vectors with R +// /// components each, according to rule 4. . +// /// 7. If the member is a row-major matrix with C columns and R rows, the matrix +// /// is stored identically to an array of R row vectors with C components each, +// /// according to rules 4. . +// /// 8. If the member is an array of S row-major matrices with C columns and R rows, +// /// the matrix is stored identically to a row(=array???) of S * R row vectors with C +// /// components each, according to rule 4. . +// /// 9. If the member is a structure, the base alignment of the structure is N, where +// /// N is the largest base alignment value of any of its members and rounded +// /// up to the base alignment of a `vec4`. the individual members of this +// /// sub-structure are then assigned offset by applying this set of rules +// /// recursively, where the base offset of the first member of the sub-structure +// /// is equal to the aligned offset of the structure. +// /// The structure may have padding at the end; the base offset of the member +// /// following the sub-structure is rounded up to the next multiple of the +// /// base alignment of the structure. +// /// 10. If the member ois an array of S structures, the S elements of the array are +// /// laid out in order according to rules 9. . +// /// +// /// Shader storage blocks also support the std140 layout qualifier as well as a std430 +// /// qualifier not supported for uniform blocks. When using the std430 storage layout, +// /// shader storage blocks will be laid out in buffer storage identically to uniform +// /// and shader storage blocks using the 140 layout except that the base alignment and +// /// stride of arrays of scalars and vectors in rule 4 and of structures in rule 9 are +// /// not rounded up a multiple of the base alignment of a vec4. +// /// +// /// summary: +// /// +// /// SCALAR AND VECTOR RULES: +// /// 1. base_align_of `ScalarType`s is its byte-size +// /// 2. base_align_of `Vector(len @ (X2 | X4), s)` is `len * base_align_of(s)` +// /// 3. base_align_of `Vector(X3, s)` is ` 4 * base_align_of(s)` +// /// +// /// ARRAY RULES: +// /// 4.a(@ std140) base_align_of `Array(e, n)` is `round_up(base_align_of(fvec4), base_align_of(e))` +// /// 4.a(@ std430) base_align_of `Array(e, n)` is `base_align_of(e)` +// /// 4.b stride_of `Array(e, n)` is round_up(base_align_of `Array(e, n)`, size_of(e)) // this is my interpretation of the word "match" in the spec. +// /// 4.c base offset of the member after the array is `offset_of(Array(e, n) + n * stride_of(Array(e, n)))` +// /// ^ the empty part after the last element's size end that fills up the stride is referred to as "padding" +// /// +// /// COLUMN MAJOR TO ARRAY RULES: +// /// 5. `col-major Matrix(C, R, t)` stored like `Array(Vector(R, t), C)` +// /// 6. `Array(col-major Matrix(C, R, t), S)` stored like `Array(Vector(R, t), S*C)` +// /// +// /// ROW MAJOR TO ARRAY RULES: +// /// 7. `row-major Matrix(C, R, t)` stored like `Array(Vector(C, t), R)` +// /// 8. `Array(row-major Matrix(C, R, t), S)` stored like `Array(Vector(C, t), S*R)` +// /// +// /// STRUCTURE RULES: +// /// 9.a(@std140) base_align_of `Struct(fields)` is `round_up(base_align_of(fvec4), base_align_of(fields.map(base_align_of).max()))` +// /// 9.a(@std430) base_align_of `Struct(fields)` is `base_align_of(fields.map(base_align_of).max())` +// /// +// /// 9. If the member is a structure, the base alignment of the structure is N, where +// /// N is the largest base alignment value of any of its members and rounded +// /// up to the base alignment of a `vec4`. +// /// +// /// the individual members of this +// /// sub-structure are then assigned offset by applying this set of rules +// /// recursively, where the base offset of the first member of the sub-structure +// /// is equal to the aligned offset of the (outer)structure. +// /// +// /// The structure may have padding at the end; the base offset of the member +// /// following the sub-structure is rounded up to the next multiple of the +// /// base alignment of the (outer)structure. +// /// +// /// 10. If the member is an array of S structures, the S elements of the array are +// /// laid out in order according to rules 9. . +// /// +// /// - storage blocks support std140 and std430 +// /// - uniform blocks support std140 only +// /// + +// #[derive(Debug, Clone, Copy, PartialEq, Eq)] +// pub enum Std { +// _140, +// _430, +// } + +// #[derive(Debug, Clone, Copy, PartialEq, Eq)] +// pub enum WgslBufferLayout { +// UniformAddressSpace, +// StorageAddressSpace, +// } + +// #[derive(Debug, Clone, Copy)] +// pub enum LayoutConstraints { +// OpenGL(Std), +// Wgsl(WgslBufferLayout), +// } + +// impl Display for LayoutConstraints { +// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +// f.write_str(match self { +// LayoutConstraints::OpenGL(std) => match std { +// Std::_140 => "std140", +// Std::_430 => "std430", +// }, +// LayoutConstraints::Wgsl(w) => match w { +// WgslBufferLayout::UniformAddressSpace => "uniform address-space", +// WgslBufferLayout::StorageAddressSpace => "storage address-space", +// }, +// }) +// } +// } + +// impl LayoutConstraints { +// fn more_info_at(&self) -> &'static str { +// match self { +// LayoutConstraints::OpenGL(_) => { +// "section 7.6.2.2 in https://registry.khronos.org/OpenGL/specs/gl/glspec46.core.pdf" +// } +// LayoutConstraints::Wgsl(_) => "https://www.w3.org/TR/WGSL/#memory-layouts", +// } +// } +// } + +// pub fn get_type_for_buffer_binding_type( +// ty: &StoreType, +// binding_type: BufferBindingType, +// as_ref: bool, +// ctx: &Context, +// ) -> Result { +// use AccessModeReadable as AM; +// use AddressSpace as AS; + +// let lang = ctx.settings().lang; + +// let access = match binding_type { +// BufferBindingType::Uniform => AccessMode::Read, +// BufferBindingType::Storage(am) => am.into(), +// }; + +// if !ty.is_host_shareable() { +// return Err(LayoutError::NotHostShareable(ty.clone()).into()); +// } + +// let result = match as_ref { +// true => { +// // `binding_ty` must be `BindingType::Buffer` +// Type::Ref( +// MemoryRegion::new(call_info!(), ty.clone(), None, None, access, AddressSpace::Storage)?, +// ty.clone(), +// access, +// ) +// } +// false => { +// // `binding_ty` must be a uniform or read-only storage buffer, `ty` must be constructible +// match binding_type { +// BufferBindingType::Uniform => Ok(()), +// BufferBindingType::Storage(AccessModeReadable::Read) => Ok(()), +// BufferBindingType::Storage(AccessModeReadable::ReadWrite) => { +// Err(LayoutError::NonRefBufferRequiresReadOnlyAndConstructible) +// } +// }?; +// if !ty.is_constructible() { +// return Err(LayoutError::NonRefBufferRequiresReadOnlyAndConstructible.into()); +// } +// Type::Store(ty.clone()) +// } +// }; + +// let constraint = match lang { +// Language::Wgsl => LayoutConstraints::Wgsl(match binding_type { +// BufferBindingType::Uniform => WgslBufferLayout::UniformAddressSpace, +// BufferBindingType::Storage(_) => WgslBufferLayout::StorageAddressSpace, +// }), +// // Language::Glsl => LayoutConstraints::OpenGL(match binding_type { +// // BufferBindingType::Uniform => Std::_140, +// // BufferBindingType::Storage(_) => Std::_430, +// // }), +// }; + +// check_layout( +// &LayoutErrorContext { +// binding_type, +// expected_constraints: constraint, +// top_level_type: ty.clone(), +// use_color: ctx.settings().colored_error_messages, +// }, +// ty, +// )?; +// Ok(result) +// } + +// pub fn check_layout(ctx: &LayoutErrorContext, ty: &StoreType) -> Result<(), LayoutError> { +// if !ty.is_creation_fixed_footprint() { +// match ctx.expected_constraints { +// LayoutConstraints::OpenGL(std) => Ok(()), +// LayoutConstraints::Wgsl(l) => match l { +// WgslBufferLayout::UniformAddressSpace => Err(LayoutError::UniformBufferMustBeSized("wgsl", ty.clone())), +// WgslBufferLayout::StorageAddressSpace => Ok(()), +// }, +// }?; +// // WGSL does not allow uniform buffers to have unsized types +// } +// match ty { +// StoreType::Sized(t) => check_sized_type_layout(ctx, t), +// StoreType::Handle(_) => Err(LayoutError::NotHostShareable(ty.clone())), +// StoreType::RuntimeSizedArray(e) => check_array_layout(ctx, e), +// StoreType::BufferBlock(s) => check_structure_layout(ctx, &LayoutStructureKind::BufferBlock(s.clone())), +// } +// } + +// pub fn check_sized_type_layout(ctx: &LayoutErrorContext, ty: &SizedType) -> Result<(), LayoutError> { +// match &ty { +// SizedType::Vector(_, _) => Ok(()), +// SizedType::Matrix(_, _, _) => Ok(()), +// SizedType::Atomic(_) => Ok(()), +// SizedType::Structure(s) => check_structure_layout(ctx, &LayoutStructureKind::Structure(s.clone())), +// SizedType::Array(e, n) => { +// let expected_align = match ctx.expected_constraints { +// LayoutConstraints::OpenGL(std) => match std { +// Std::_140 => round_up(16, align_of_array(e)), +// Std::_430 => align_of_array(e), +// }, +// LayoutConstraints::Wgsl(wbl) => match wbl { +// WgslBufferLayout::UniformAddressSpace => round_up(16, align_of_array(e)), +// WgslBufferLayout::StorageAddressSpace => align_of_array(e), +// }, +// }; +// let actual_align = align_of_array(e); +// if actual_align != expected_align { +// Err(LayoutError::ArrayAlignmentError(ArrayAlignmentError { +// ctx: ctx.clone(), +// expected: expected_align, +// actual: actual_align, +// element_ty: (**e).clone(), +// })) +// } else { +// check_array_layout(ctx, e) +// } +// } +// } +// } + +// pub fn check_array_layout(ctx: &LayoutErrorContext, elem: &SizedType) -> Result<(), LayoutError> { +// // wgsl spec: Arrays of element type `elem` must have an element +// // stride that is a multiple of the RequiredAlignOf(elem, addr_space) +// let actual_stride = stride_of_array(elem); +// match ctx.expected_constraints { +// LayoutConstraints::OpenGL(std) => { +// // using wording of the opengl spec +// let base_align_of_array = match std { +// Std::_140 => round_up(16, elem.align()), +// Std::_430 => elem.align(), +// }; +// let expected_stride = round_up(base_align_of_array, elem.byte_size()); +// if actual_stride != expected_stride { +// return Err(LayoutError::ArrayStrideError(ArrayStrideError { +// ctx: ctx.clone(), +// expected: expected_stride, +// actual: actual_stride, +// element_ty: elem.clone(), +// })); +// } +// } +// LayoutConstraints::Wgsl(wbl) => { +// let required_element_align = match wbl { +// WgslBufferLayout::UniformAddressSpace => round_up(16, align_of_array(elem)), +// WgslBufferLayout::StorageAddressSpace => align_of_array(elem), +// }; +// if !required_element_align.divides(actual_stride) { +// return Err(LayoutError::ArrayStrideAlignmentError(ArrayStrideAlignmentError { +// ctx: ctx.clone(), +// expected_align: required_element_align, +// actual_stride, +// element_ty: elem.clone(), +// })); +// } +// } +// }; +// check_sized_type_layout(ctx, elem)?; +// Ok(()) +// } + +// pub fn check_structure_layout(ctx: &LayoutErrorContext, s: &LayoutStructureKind) -> Result<(), LayoutError> { +// let structure_name = match s { +// LayoutStructureKind::Structure(s) => s.name(), +// LayoutStructureKind::BufferBlock(s) => s.name(), +// }; +// let (sized_fields, last_field) = &match s { +// LayoutStructureKind::Structure(s) => (s.sized_fields(), &None), +// LayoutStructureKind::BufferBlock(s) => (s.sized_fields(), s.last_unsized_field()), +// }; + +// let mut offset = 0; +// for field in sized_fields.iter() { +// // return errors if custom align or size are used in OpenGL constraints. +// // warning: removing this check will make the surrounding code wrong. +// // for example: the OpenGL spec has a wording which defines std140/std430 +// // offsets of elements after arrays directly via their stride. +// // this means even if GLSL supports custom size and align in the future, +// // the wording of the std140 definition of that situation would need to +// // be checked for changes, which would likely justify a rewrite of this +// // entire module. +// match ctx.expected_constraints { +// LayoutConstraints::OpenGL(_) => { +// if let Some(custom_align) = field.custom_min_align() { +// return Err(LayoutError::LayoutConstriantsDoNotSupportCustomFieldAlign { +// ctx: ctx.clone(), +// struct_or_block_name: structure_name.clone(), +// field_name: field.name().clone(), +// custom_align: u64::from(custom_align), +// }); +// } +// if let Some(custom_size) = field.custom_min_size() { +// return Err(LayoutError::LayoutConstriantsDoNotSupportCustomFieldSize { +// ctx: ctx.clone(), +// struct_or_block_name: structure_name.clone(), +// field_name: field.name().clone(), +// custom_size, +// }); +// } +// } +// LayoutConstraints::Wgsl(_) => (), +// } + +// // the align and size which takes custom user attributes for align and size into account +// let (align, size) = (field.align(), field.byte_size()); +// // the regular align and size of field.ty +// let (ty_align, ty_size) = (field.ty().align(), field.ty().byte_size()); +// offset = round_up(field.align(), offset); + +// let required_align_of_field = match field.ty() { +// SizedType::Vector(_, _) | SizedType::Matrix(_, _, _) | SizedType::Atomic(_) => ty_align, +// SizedType::Structure(_) | SizedType::Array(_, _) => match ctx.expected_constraints { +// LayoutConstraints::OpenGL(std) => match std { +// Std::_140 => round_up(16, ty_align), +// Std::_430 => ty_align, +// }, +// LayoutConstraints::Wgsl(wbl) => match wbl { +// WgslBufferLayout::UniformAddressSpace => round_up(16, ty_align), +// WgslBufferLayout::StorageAddressSpace => ty_align, +// }, +// }, +// }; + +// if !required_align_of_field.divides(offset) { +// return Err(LayoutError::Structure(StructureLayoutError { +// context: ctx.clone(), +// structure: s.clone(), +// field_name: field.name().clone(), +// actual_offset: offset, +// expected_alignment: required_align_of_field, +// })); +// } + +// check_sized_type_layout(ctx, field.ty())?; + +// offset += field.byte_size() +// } + +// if let Some(last_field) = last_field { +// let ty_align = align_of_array(last_field.element_ty()); +// let ty = StoreType::RuntimeSizedArray(last_field.element_ty().clone()); + +// // TODO(low prio) refactor this function so that theres no code duplication here +// let required_align_of_array = match ctx.expected_constraints { +// LayoutConstraints::OpenGL(std) => match std { +// Std::_140 => round_up(16, ty_align), +// Std::_430 => ty_align, +// }, +// LayoutConstraints::Wgsl(wbl) => match wbl { +// WgslBufferLayout::UniformAddressSpace => round_up(16, ty_align), +// WgslBufferLayout::StorageAddressSpace => ty_align, +// }, +// }; + +// if !required_align_of_array.divides(offset) { +// return Err(LayoutError::Structure(StructureLayoutError { +// context: ctx.clone(), +// structure: s.clone(), +// field_name: last_field.name().clone(), +// actual_offset: offset, +// expected_alignment: required_align_of_array, +// })); +// } +// check_layout(ctx, &ty)?; +// } +// Ok(()) +// } + +// #[derive(Error, Debug, Clone)] +// pub enum LayoutError { +// #[error("array elements may not be runtime-sized types. found array of: {0}")] +// ArrayElementsAreUnsized(TypeLayout), +// #[error("{0}")] +// Structure(#[from] StructureLayoutError), +// #[error( +// "The size of `{1}` on the gpu is now known at compile time. `{0}` \ +// requires that the size of uniform buffers on the gpu is known at compile time." +// )] +// UniformBufferMustBeSized(&'static str, StoreType), +// #[error("{0}")] +// ArrayAlignmentError(ArrayAlignmentError), +// #[error("{0}")] +// ArrayStrideError(ArrayStrideError), +// #[error("{0}")] +// ArrayStrideAlignmentError(ArrayStrideAlignmentError), +// #[error("{} layout constraints do not allow custom struct field byte-alignments. \ +// Type `{}` contains type `{struct_or_block_name}` which has a custom byte-alignment of {custom_align} at field `{field_name}`.", ctx.expected_constraints, ctx.top_level_type)] +// LayoutConstriantsDoNotSupportCustomFieldAlign { +// ctx: LayoutErrorContext, +// struct_or_block_name: CanonName, +// field_name: CanonName, +// custom_align: u64, +// }, +// #[error("{} layout constraints do not allow custom struct field byte-sizes. \ +// Type `{}` contains type `{struct_or_block_name}` which has a custom byte-size of {custom_size} at field `{field_name}`.", ctx.expected_constraints, ctx.top_level_type)] +// LayoutConstriantsDoNotSupportCustomFieldSize { +// ctx: LayoutErrorContext, +// struct_or_block_name: CanonName, +// field_name: CanonName, +// custom_size: u64, +// }, +// #[error("a non-reference buffer (non `BufferRef`) must be both read-only and constructible")] +// NonRefBufferRequiresReadOnlyAndConstructible, +// #[error( +// "type {0} does not match the requirements for host-shareable types. See https://www.w3.org/TR/WGSL/#host-shareable-types (In most cases, this is caused by the type containing booleans)" +// )] +// NotHostShareable(StoreType), +// #[error("custom alignment of {custom} is too small. `{ty}` must have an alignment of at least {required}")] +// CustomAlignmentTooSmall { custom: u64, required: u64, ty: StoreType }, +// #[error("custom size of {custom} is too small. `{ty}` must have a size of at least {required}")] +// CustomSizeTooSmall { custom: u64, required: u64, ty: Type }, +// #[error("memory layout mismatch:\n{0}\n{}", if let Some(comment) = .1 {comment.as_str()} else {""})] +// LayoutMismatch(LayoutMismatch, Option), +// #[error("runtime-sized type {name} cannot be element in an array buffer")] +// UnsizedStride { name: String }, +// #[error( +// "stride mismatch:\n{cpu_name}: {cpu_stride} bytes offset between elements,\n{gpu_name}: {gpu_stride} bytes offset between elements" +// )] +// StrideMismatch { +// cpu_name: String, +// cpu_stride: u64, +// gpu_name: String, +// gpu_stride: u64, +// }, +// } + +// #[allow(missing_docs)] +// #[derive(Error, Debug, Clone)] +// pub struct ArrayStrideAlignmentError { +// ctx: LayoutErrorContext, +// expected_align: u64, +// actual_stride: u64, +// element_ty: SizedType, +// } + +// impl Display for ArrayStrideAlignmentError { +// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +// writeln!( +// f, +// "array elements within type `{}` do not satisfy {} layout requirements.", +// self.ctx.top_level_type, self.ctx.expected_constraints +// ); +// let expected_align = self.expected_align; +// let actual_stride = self.actual_stride; +// writeln!( +// f, +// "The array with `{}` elements requires that every element is {expected_align}-byte aligned, but the array has a stride of {actual_stride} bytes, which means subsequent elements are not {expected_align}-byte aligned.", +// self.element_ty +// ); +// if let Ok(layout) = TypeLayout::from_store_ty(self.ctx.top_level_type.clone()) { +// writeln!(f, "The full layout of `{}` is:", self.ctx.top_level_type); +// layout.write("", self.ctx.use_color, f)?; +// writeln!(f); +// }; +// writeln!( +// f, +// "\nfor more information on the layout rules, see {}", +// self.ctx.expected_constraints.more_info_at() +// )?; +// Ok(()) +// } +// } + +// #[allow(missing_docs)] +// #[derive(Error, Debug, Clone)] +// pub struct ArrayStrideError { +// ctx: LayoutErrorContext, +// expected: u64, +// actual: u64, +// element_ty: SizedType, +// } + +// impl Display for ArrayStrideError { +// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +// writeln!( +// f, +// "array elements within type `{}` do not satisfy {} layout requirements.", +// self.ctx.top_level_type, self.ctx.expected_constraints +// ); +// writeln!( +// f, +// "The array with `{}` elements requires stride {}, but has stride {}.", +// self.element_ty, self.expected, self.actual +// ); +// if let Ok(layout) = TypeLayout::from_store_ty(self.ctx.top_level_type.clone()) { +// writeln!(f, "The full layout of `{}` is:", self.ctx.top_level_type); +// layout.write("", self.ctx.use_color, f)?; +// writeln!(f); +// }; +// writeln!( +// f, +// "\nfor more information on the layout rules, see {}", +// self.ctx.expected_constraints.more_info_at() +// )?; +// Ok(()) +// } +// } + +// #[allow(missing_docs)] +// #[derive(Error, Debug, Clone)] +// pub struct ArrayAlignmentError { +// ctx: LayoutErrorContext, +// expected: u64, +// actual: u64, +// element_ty: SizedType, +// } + +// impl Display for ArrayAlignmentError { +// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +// writeln!( +// f, +// "array elements within type `{}` do not satisfy {} layout requirements.", +// self.ctx.top_level_type, self.ctx.expected_constraints +// ); +// writeln!( +// f, +// "The array with `{}` elements requires alignment {}, but has alignment {}.", +// self.element_ty, self.expected, self.actual +// ); +// if let Ok(layout) = TypeLayout::from_store_ty(self.ctx.top_level_type.clone()) { +// writeln!(f, "The full layout of `{}` is:", self.ctx.top_level_type); +// layout.write("", self.ctx.use_color, f)?; +// writeln!(f); +// }; +// writeln!( +// f, +// "\nfor more information on the layout rules, see {}", +// self.ctx.expected_constraints.more_info_at() +// )?; +// Ok(()) +// } +// } + + + +// #[derive(Debug, Clone)] +// pub struct LayoutErrorContext { +// pub binding_type: BufferBindingType, +// pub expected_constraints: LayoutConstraints, +// pub top_level_type: StoreType, +// /// whether the error message should be output using console colors +// pub use_color: bool, +// } + +// #[derive(Debug, Clone)] +// //TODO(release) this type is obsolete. SizedStruct and BufferBlock now both Deref to `Struct`, use that instead +// pub enum LayoutStructureKind { +// Structure(SizedStruct), +// BufferBlock(BufferBlock), +// } + +// #[derive(Debug, Clone)] +// pub struct StructureLayoutError { +// pub context: LayoutErrorContext, +// pub structure: LayoutStructureKind, +// pub field_name: CanonName, +// pub actual_offset: u64, +// pub expected_alignment: u64, +// } + +// impl std::error::Error for StructureLayoutError {} + +// impl Display for StructureLayoutError { +// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +// let custom_align_or_size_supported = match self.context.expected_constraints { +// LayoutConstraints::OpenGL(_) => false, +// LayoutConstraints::Wgsl(_) => true, +// }; + +// let structure_name: &CanonName = match &self.structure { +// LayoutStructureKind::Structure(s) => s.name(), +// LayoutStructureKind::BufferBlock(s) => s.name(), +// }; + +// let structure_def_location = Context::try_with(call_info!(), |ctx| { +// let s = match &self.structure { +// LayoutStructureKind::Structure(s) => &**s, +// LayoutStructureKind::BufferBlock(s) => &**s, +// }; +// ctx.struct_registry().get(s).map(|def| def.call_info()) +// }) +// .flatten(); + +// let top_level_type = &self.context.top_level_type; +// let binding_type_str = match self.context.binding_type { +// BufferBindingType::Storage(_) => "storage", +// BufferBindingType::Uniform => "uniform", +// }; +// writeln!( +// f, +// "the type `{top_level_type}` cannot be used as a {binding_type_str} buffer binding." +// )?; +// let ((constraints, short_summary), link) = match self.context.expected_constraints { +// LayoutConstraints::OpenGL(std) => ( +// match std { +// Std::_140 => ( +// "std140", +// Some( +// "In std140 buffers the alignment of structs, arrays and array elements must be at least 16.", +// ), +// ), +// Std::_430 => ("std430", None), +// }, +// "https://www.khronos.org/opengl/wiki/Interface_Block_(GLSL)#Memory_layout", +// ), +// LayoutConstraints::Wgsl(x) => ( +// match x { +// WgslBufferLayout::UniformAddressSpace => ( +// "uniform address space", +// Some( +// "In the uniform address space, structs, arrays and array elements must be at least 16 byte aligned.", +// ), +// ), +// WgslBufferLayout::StorageAddressSpace => ("storage address space", None), +// }, +// "https://www.w3.org/TR/WGSL/#address-space-layout-constraints", +// ), +// }; +// let actual_align = max_u64_po2_dividing(self.actual_offset); +// let nested = self.context.top_level_type.to_string() != **structure_name; +// if nested { +// write!(f, "It contains a struct `{}`, which", structure_name)?; +// } else { +// write!(f, "Struct `{}`", structure_name)?; +// } +// writeln!(f, " does not satisfy the {constraints} memory layout requirements.",)?; +// writeln!(f)?; +// if let Some(call_info) = structure_def_location { +// writeln!(f, "Definition at {call_info}")?; +// } +// write_struct_layout(&self.structure, self.context.use_color, Some(&self.field_name), f)?; + +// writeln!(f)?; +// set_color(f, Some("#508EE3"), false)?; +// writeln!( +// f, +// "Field `{}` needs to be {} byte aligned, but has a byte-offset of {} which is only {actual_align} byte aligned.", +// self.field_name, self.expected_alignment, self.actual_offset +// )?; +// set_color(f, None, false)?; +// writeln!(f)?; + +// writeln!(f, "Potential solutions include:"); +// writeln!( +// f, +// "- add an #[align({})] attribute to the definition of `{}` (not supported by OpenGL/GLSL pipelines)", +// self.expected_alignment, self.field_name +// ); +// writeln!(f, "- use a storage binding instead of a uniform binding"); +// writeln!( +// f, +// "- increase the offset of `{}` until it is divisible by {} by making previous fields larger or adding fields before it", +// self.field_name, self.expected_alignment +// ); +// writeln!(f)?; + + +// if let Some(summary) = short_summary { +// writeln!(f, "{summary}"); +// } +// //writeln!(f, "Address space alignment restrictions enable use of more efficient hardware instructions for accessing the values, or satisfy more restrictive hardware requirements.")?; +// writeln!(f, "More info about the {constraints} layout can be found at {link}")?; +// Ok(()) +// } +// } + +// fn write_struct_layout( +// s: &LayoutStructureKind, +// colored: bool, +// highlight_field: Option<&str>, +// f: &mut F, +// ) -> std::fmt::Result +// where +// F: Write, +// { +// let use_256_color_mode = false; +// let color = |f_: &mut F, hex| match colored { +// true => set_color(f_, Some(hex), use_256_color_mode), +// false => Ok(()), +// }; +// let reset = |f_: &mut F| match colored { +// true => set_color(f_, None, use_256_color_mode), +// false => Ok(()), +// }; + +// let structure_name = match s { +// LayoutStructureKind::Structure(s) => s.name(), +// LayoutStructureKind::BufferBlock(s) => s.name(), +// }; + +// let (sized_fields, last_field) = match s { +// LayoutStructureKind::Structure(s) => (s.sized_fields(), &None), +// LayoutStructureKind::BufferBlock(s) => (s.sized_fields(), s.last_unsized_field()), +// }; + +// let indent = " "; +// let field_decl_line = |field: &SizedField| format!("{indent}{}: {},", field.name(), field.ty()); +// let header = format!("struct {} {{", structure_name); +// let table_start_column = 1 + sized_fields +// .iter() +// .map(field_decl_line) +// .map(|s| s.len()) +// .max() +// .unwrap_or(0) +// .max(header.chars().count()); +// f.write_str(&header)?; +// for i in header.len()..table_start_column { +// f.write_char(' ')? +// } +// writeln!(f, "offset align size")?; +// let mut offset = 0; +// for field in sized_fields.iter() { +// if Some(&**field.name()) == highlight_field { +// color(f, "#508EE3")?; +// } +// let (align, size) = (field.align(), field.byte_size()); +// offset = round_up(field.align(), offset); +// let decl_line = field_decl_line(field); +// f.write_str(&decl_line)?; +// // write spaces to table on the right +// for i in decl_line.len()..table_start_column { +// f.write_char(' ')? +// } +// writeln!(f, "{:6} {:5} {:4}", offset, align, size)?; +// if Some(&**field.name()) == highlight_field { +// reset(f); +// } +// offset += field.byte_size() +// } +// if let Some(last_field) = last_field { +// offset = round_up(last_field.align(), offset); + +// let decl_line = format!( +// "{indent}{}: {},", +// last_field.name(), +// StoreType::RuntimeSizedArray(last_field.element_ty().clone()) +// ); +// f.write_str(&decl_line)?; +// // write spaces to table on the right +// for i in decl_line.len()..table_start_column { +// f.write_char(' ')? +// } +// write!(f, "{:6} {:5}", offset, last_field.align())?; +// } +// writeln!(f, "}}")?; +// Ok(()) +// } diff --git a/shame/src/ir/ir_type/struct_.rs b/shame/src/ir/ir_type/struct_.rs index fbfc9f1..9f1cf1a 100644 --- a/shame/src/ir/ir_type/struct_.rs +++ b/shame/src/ir/ir_type/struct_.rs @@ -7,7 +7,7 @@ use std::{ use thiserror::Error; -use super::{align_of_array, canon_name::CanonName, round_up, LayoutError, SizedType, StoreType, Type}; +use super::{align_of_array, canon_name::CanonName, round_up, SizedType, StoreType, Type}; use crate::{ call_info, common::{iterator_ext::IteratorExt, po2::U32PowerOf2, pool::Key}, @@ -591,45 +591,45 @@ impl StructRegistry { /// before `b` in this list. pub fn iter_topo_sorted(&self) -> impl Iterator { self.defs.iter().map(|(_, def)| def) } - - fn validate_custom_align_and_size(s: &Rc) -> Result<(), LayoutError> { - for field in s.sized_fields() { - if let Some(c_align) = field.custom_min_align.map(u64::from) { - let ty_align = field.ty.align(); - if c_align < ty_align { - return Err(LayoutError::CustomAlignmentTooSmall { - custom: c_align, - required: ty_align, - ty: field.ty.clone().into(), - }); - } - } - - if let Some(c_size) = field.custom_min_size { - let ty_size = field.ty.byte_size(); - if c_size < ty_size { - return Err(LayoutError::CustomSizeTooSmall { - custom: c_size, - required: ty_size, - ty: field.ty.clone().into(), - }); - } - } - } - if let Some(field) = &s.last_unsized_field() { - if let Some(c_align) = field.custom_min_align.map(u64::from) { - let ty_align = field.element_ty().align(); - if c_align < ty_align { - return Err(LayoutError::CustomAlignmentTooSmall { - custom: c_align, - required: ty_align, - ty: field.ty().clone(), - }); - } - } - } - Ok(()) - } + // TODO(chronicl) ask about this. how can a min_align/size be too small? + // fn validate_custom_align_and_size(s: &Rc) -> Result<(), LayoutError> { + // for field in s.sized_fields() { + // if let Some(c_align) = field.custom_min_align.map(u64::from) { + // let ty_align = field.ty.align(); + // if c_align < ty_align { + // return Err(LayoutError::CustomAlignmentTooSmall { + // custom: c_align, + // required: ty_align, + // ty: field.ty.clone().into(), + // }); + // } + // } + + // if let Some(c_size) = field.custom_min_size { + // let ty_size = field.ty.byte_size(); + // if c_size < ty_size { + // return Err(LayoutError::CustomSizeTooSmall { + // custom: c_size, + // required: ty_size, + // ty: field.ty.clone().into(), + // }); + // } + // } + // } + // if let Some(field) = &s.last_unsized_field() { + // if let Some(c_align) = field.custom_min_align.map(u64::from) { + // let ty_align = field.element_ty().align(); + // if c_align < ty_align { + // return Err(LayoutError::CustomAlignmentTooSmall { + // custom: c_align, + // required: ty_align, + // ty: field.ty().clone(), + // }); + // } + // } + // } + // Ok(()) + // } /// only registers this one struct (if it wasn't registered before), /// not any structures that are used within that struct's fields. @@ -640,9 +640,10 @@ impl StructRegistry { fn register_single_struct(&mut self, s: &Rc, idents: &mut PoolRefMut, call_info: CallInfo) -> bool { if !self.contains(s) { Context::try_with(call_info, |ctx| { - if let Err(e) = Self::validate_custom_align_and_size(s) { - ctx.push_error(e.into()) - } + // TODO(chronicl) above + // if let Err(e) = Self::validate_custom_align_and_size(s) { + // ctx.push_error(e.into()) + // } }); self.defs .push((s.clone(), StructDef::new_for_struct(s, idents, call_info))); diff --git a/shame_derive/src/derive_layout.rs b/shame_derive/src/derive_layout.rs index 3d4a57d..cf6d070 100644 --- a/shame_derive/src/derive_layout.rs +++ b/shame_derive/src/derive_layout.rs @@ -484,24 +484,26 @@ pub fn impl_for_struct( fn instantiate_buffer_inner( args: Result<#re::BindingArgs, #re::InvalidReason>, - bind_ty: #re::BindingType + bind_ty: #re::BufferBindingType, + has_dynamic_offset: bool, ) -> #re::BufferInner where #triv Self: #re::NoAtomics + #re::NoBools { - #re::BufferInner::new_fields(args, bind_ty) + #re::BufferInner::new_fields(args, bind_ty, has_dynamic_offset) } fn instantiate_buffer_ref_inner( args: Result<#re::BindingArgs, #re::InvalidReason>, - bind_ty: #re::BindingType + bind_ty: #re::BufferBindingType, + has_dynamic_offset: bool, ) -> #re::BufferRefInner where #triv Self: #re::NoBools, { - #re::BufferRefInner::new_fields(args, bind_ty) + #re::BufferRefInner::new_fields(args, bind_ty, has_dynamic_offset) } fn impl_category() -> #re::GpuStoreImplCategory { From 1c6cc5c5f8ec55aecc22db819cad87618f69c189 Mon Sep 17 00:00:00 2001 From: chronicl Date: Fri, 30 May 2025 07:10:43 +0200 Subject: [PATCH 32/32] Remove unused layout_constraints file --- shame/src/ir/ir_type/layout_constraints.rs | 805 --------------------- shame/src/ir/ir_type/mod.rs | 2 - 2 files changed, 807 deletions(-) delete mode 100644 shame/src/ir/ir_type/layout_constraints.rs diff --git a/shame/src/ir/ir_type/layout_constraints.rs b/shame/src/ir/ir_type/layout_constraints.rs deleted file mode 100644 index 28cd9a6..0000000 --- a/shame/src/ir/ir_type/layout_constraints.rs +++ /dev/null @@ -1,805 +0,0 @@ -// use std::{ -// fmt::{Display, Write}, -// num::NonZeroU32, -// rc::Rc, -// }; - -// use thiserror::Error; - -// use crate::frontend::rust_types::type_layout::{eq::LayoutMismatch, TypeLayout}; -// use crate::{ -// backend::language::Language, -// call_info, -// common::prettify::set_color, -// frontend::{any::shared_io::BufferBindingType, encoding::EncodingErrorKind}, -// ir::{ -// ir_type::{max_u64_po2_dividing, AccessModeReadable}, -// recording::{Context, MemoryRegion}, -// AccessMode, -// }, -// }; - -// use super::{ -// align_of_array, round_up, stride_of_array, AddressSpace, BufferBlock, CanonName, Field, SizedField, SizedStruct, -// SizedType, StoreType, Type, -// }; -// use crate::common::integer::IntegerExt; - -// /// (slightly modified quote from OpenGL 4.6 spec core, section 7.6.2.2 ) -// /// (replaced "basic machine units" with "bytes") -// /// rules for std140: -// /// -// /// 1. If the member is a scalar consuming N bytes, the base alignment is N. -// /// 2. If the member is a 2 or 4 component vector with components consuming N bytes the base alignment is 2N or 4N respectively -// /// 3. If the member is a 3 component vector with components consuming n bytes, the base alignment is 4N -// /// 4. If the member is an array of scalars or vectors, the base alignment and array -// /// stride are set to match the base alignment of a single array element according to -// /// rules 1. 2. and 3., and rounded up to the base alignment of a `vec4`. The array -// /// may have padding at the end; the base offset of the member following the array is -// /// rounded up to the next multiple of the base alignment (edit: of the array (this is not explicitly written in the spec, but a best guess based on the end of rule 9)) -// /// 5. If the member is a column-major matrix with C columns and R rows, the -// /// matrix is stored identically to an array of C column vectors with R components each -// /// according to rule 4. . -// /// 6. If the member is an array of S column-major matrices with C columns and R rows, -// /// the matrix is stored identically to a row(=array???) of S * C column vectors with R -// /// components each, according to rule 4. . -// /// 7. If the member is a row-major matrix with C columns and R rows, the matrix -// /// is stored identically to an array of R row vectors with C components each, -// /// according to rules 4. . -// /// 8. If the member is an array of S row-major matrices with C columns and R rows, -// /// the matrix is stored identically to a row(=array???) of S * R row vectors with C -// /// components each, according to rule 4. . -// /// 9. If the member is a structure, the base alignment of the structure is N, where -// /// N is the largest base alignment value of any of its members and rounded -// /// up to the base alignment of a `vec4`. the individual members of this -// /// sub-structure are then assigned offset by applying this set of rules -// /// recursively, where the base offset of the first member of the sub-structure -// /// is equal to the aligned offset of the structure. -// /// The structure may have padding at the end; the base offset of the member -// /// following the sub-structure is rounded up to the next multiple of the -// /// base alignment of the structure. -// /// 10. If the member ois an array of S structures, the S elements of the array are -// /// laid out in order according to rules 9. . -// /// -// /// Shader storage blocks also support the std140 layout qualifier as well as a std430 -// /// qualifier not supported for uniform blocks. When using the std430 storage layout, -// /// shader storage blocks will be laid out in buffer storage identically to uniform -// /// and shader storage blocks using the 140 layout except that the base alignment and -// /// stride of arrays of scalars and vectors in rule 4 and of structures in rule 9 are -// /// not rounded up a multiple of the base alignment of a vec4. -// /// -// /// summary: -// /// -// /// SCALAR AND VECTOR RULES: -// /// 1. base_align_of `ScalarType`s is its byte-size -// /// 2. base_align_of `Vector(len @ (X2 | X4), s)` is `len * base_align_of(s)` -// /// 3. base_align_of `Vector(X3, s)` is ` 4 * base_align_of(s)` -// /// -// /// ARRAY RULES: -// /// 4.a(@ std140) base_align_of `Array(e, n)` is `round_up(base_align_of(fvec4), base_align_of(e))` -// /// 4.a(@ std430) base_align_of `Array(e, n)` is `base_align_of(e)` -// /// 4.b stride_of `Array(e, n)` is round_up(base_align_of `Array(e, n)`, size_of(e)) // this is my interpretation of the word "match" in the spec. -// /// 4.c base offset of the member after the array is `offset_of(Array(e, n) + n * stride_of(Array(e, n)))` -// /// ^ the empty part after the last element's size end that fills up the stride is referred to as "padding" -// /// -// /// COLUMN MAJOR TO ARRAY RULES: -// /// 5. `col-major Matrix(C, R, t)` stored like `Array(Vector(R, t), C)` -// /// 6. `Array(col-major Matrix(C, R, t), S)` stored like `Array(Vector(R, t), S*C)` -// /// -// /// ROW MAJOR TO ARRAY RULES: -// /// 7. `row-major Matrix(C, R, t)` stored like `Array(Vector(C, t), R)` -// /// 8. `Array(row-major Matrix(C, R, t), S)` stored like `Array(Vector(C, t), S*R)` -// /// -// /// STRUCTURE RULES: -// /// 9.a(@std140) base_align_of `Struct(fields)` is `round_up(base_align_of(fvec4), base_align_of(fields.map(base_align_of).max()))` -// /// 9.a(@std430) base_align_of `Struct(fields)` is `base_align_of(fields.map(base_align_of).max())` -// /// -// /// 9. If the member is a structure, the base alignment of the structure is N, where -// /// N is the largest base alignment value of any of its members and rounded -// /// up to the base alignment of a `vec4`. -// /// -// /// the individual members of this -// /// sub-structure are then assigned offset by applying this set of rules -// /// recursively, where the base offset of the first member of the sub-structure -// /// is equal to the aligned offset of the (outer)structure. -// /// -// /// The structure may have padding at the end; the base offset of the member -// /// following the sub-structure is rounded up to the next multiple of the -// /// base alignment of the (outer)structure. -// /// -// /// 10. If the member is an array of S structures, the S elements of the array are -// /// laid out in order according to rules 9. . -// /// -// /// - storage blocks support std140 and std430 -// /// - uniform blocks support std140 only -// /// - -// #[derive(Debug, Clone, Copy, PartialEq, Eq)] -// pub enum Std { -// _140, -// _430, -// } - -// #[derive(Debug, Clone, Copy, PartialEq, Eq)] -// pub enum WgslBufferLayout { -// UniformAddressSpace, -// StorageAddressSpace, -// } - -// #[derive(Debug, Clone, Copy)] -// pub enum LayoutConstraints { -// OpenGL(Std), -// Wgsl(WgslBufferLayout), -// } - -// impl Display for LayoutConstraints { -// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { -// f.write_str(match self { -// LayoutConstraints::OpenGL(std) => match std { -// Std::_140 => "std140", -// Std::_430 => "std430", -// }, -// LayoutConstraints::Wgsl(w) => match w { -// WgslBufferLayout::UniformAddressSpace => "uniform address-space", -// WgslBufferLayout::StorageAddressSpace => "storage address-space", -// }, -// }) -// } -// } - -// impl LayoutConstraints { -// fn more_info_at(&self) -> &'static str { -// match self { -// LayoutConstraints::OpenGL(_) => { -// "section 7.6.2.2 in https://registry.khronos.org/OpenGL/specs/gl/glspec46.core.pdf" -// } -// LayoutConstraints::Wgsl(_) => "https://www.w3.org/TR/WGSL/#memory-layouts", -// } -// } -// } - -// pub fn get_type_for_buffer_binding_type( -// ty: &StoreType, -// binding_type: BufferBindingType, -// as_ref: bool, -// ctx: &Context, -// ) -> Result { -// use AccessModeReadable as AM; -// use AddressSpace as AS; - -// let lang = ctx.settings().lang; - -// let access = match binding_type { -// BufferBindingType::Uniform => AccessMode::Read, -// BufferBindingType::Storage(am) => am.into(), -// }; - -// if !ty.is_host_shareable() { -// return Err(LayoutError::NotHostShareable(ty.clone()).into()); -// } - -// let result = match as_ref { -// true => { -// // `binding_ty` must be `BindingType::Buffer` -// Type::Ref( -// MemoryRegion::new(call_info!(), ty.clone(), None, None, access, AddressSpace::Storage)?, -// ty.clone(), -// access, -// ) -// } -// false => { -// // `binding_ty` must be a uniform or read-only storage buffer, `ty` must be constructible -// match binding_type { -// BufferBindingType::Uniform => Ok(()), -// BufferBindingType::Storage(AccessModeReadable::Read) => Ok(()), -// BufferBindingType::Storage(AccessModeReadable::ReadWrite) => { -// Err(LayoutError::NonRefBufferRequiresReadOnlyAndConstructible) -// } -// }?; -// if !ty.is_constructible() { -// return Err(LayoutError::NonRefBufferRequiresReadOnlyAndConstructible.into()); -// } -// Type::Store(ty.clone()) -// } -// }; - -// let constraint = match lang { -// Language::Wgsl => LayoutConstraints::Wgsl(match binding_type { -// BufferBindingType::Uniform => WgslBufferLayout::UniformAddressSpace, -// BufferBindingType::Storage(_) => WgslBufferLayout::StorageAddressSpace, -// }), -// // Language::Glsl => LayoutConstraints::OpenGL(match binding_type { -// // BufferBindingType::Uniform => Std::_140, -// // BufferBindingType::Storage(_) => Std::_430, -// // }), -// }; - -// check_layout( -// &LayoutErrorContext { -// binding_type, -// expected_constraints: constraint, -// top_level_type: ty.clone(), -// use_color: ctx.settings().colored_error_messages, -// }, -// ty, -// )?; -// Ok(result) -// } - -// pub fn check_layout(ctx: &LayoutErrorContext, ty: &StoreType) -> Result<(), LayoutError> { -// if !ty.is_creation_fixed_footprint() { -// match ctx.expected_constraints { -// LayoutConstraints::OpenGL(std) => Ok(()), -// LayoutConstraints::Wgsl(l) => match l { -// WgslBufferLayout::UniformAddressSpace => Err(LayoutError::UniformBufferMustBeSized("wgsl", ty.clone())), -// WgslBufferLayout::StorageAddressSpace => Ok(()), -// }, -// }?; -// // WGSL does not allow uniform buffers to have unsized types -// } -// match ty { -// StoreType::Sized(t) => check_sized_type_layout(ctx, t), -// StoreType::Handle(_) => Err(LayoutError::NotHostShareable(ty.clone())), -// StoreType::RuntimeSizedArray(e) => check_array_layout(ctx, e), -// StoreType::BufferBlock(s) => check_structure_layout(ctx, &LayoutStructureKind::BufferBlock(s.clone())), -// } -// } - -// pub fn check_sized_type_layout(ctx: &LayoutErrorContext, ty: &SizedType) -> Result<(), LayoutError> { -// match &ty { -// SizedType::Vector(_, _) => Ok(()), -// SizedType::Matrix(_, _, _) => Ok(()), -// SizedType::Atomic(_) => Ok(()), -// SizedType::Structure(s) => check_structure_layout(ctx, &LayoutStructureKind::Structure(s.clone())), -// SizedType::Array(e, n) => { -// let expected_align = match ctx.expected_constraints { -// LayoutConstraints::OpenGL(std) => match std { -// Std::_140 => round_up(16, align_of_array(e)), -// Std::_430 => align_of_array(e), -// }, -// LayoutConstraints::Wgsl(wbl) => match wbl { -// WgslBufferLayout::UniformAddressSpace => round_up(16, align_of_array(e)), -// WgslBufferLayout::StorageAddressSpace => align_of_array(e), -// }, -// }; -// let actual_align = align_of_array(e); -// if actual_align != expected_align { -// Err(LayoutError::ArrayAlignmentError(ArrayAlignmentError { -// ctx: ctx.clone(), -// expected: expected_align, -// actual: actual_align, -// element_ty: (**e).clone(), -// })) -// } else { -// check_array_layout(ctx, e) -// } -// } -// } -// } - -// pub fn check_array_layout(ctx: &LayoutErrorContext, elem: &SizedType) -> Result<(), LayoutError> { -// // wgsl spec: Arrays of element type `elem` must have an element -// // stride that is a multiple of the RequiredAlignOf(elem, addr_space) -// let actual_stride = stride_of_array(elem); -// match ctx.expected_constraints { -// LayoutConstraints::OpenGL(std) => { -// // using wording of the opengl spec -// let base_align_of_array = match std { -// Std::_140 => round_up(16, elem.align()), -// Std::_430 => elem.align(), -// }; -// let expected_stride = round_up(base_align_of_array, elem.byte_size()); -// if actual_stride != expected_stride { -// return Err(LayoutError::ArrayStrideError(ArrayStrideError { -// ctx: ctx.clone(), -// expected: expected_stride, -// actual: actual_stride, -// element_ty: elem.clone(), -// })); -// } -// } -// LayoutConstraints::Wgsl(wbl) => { -// let required_element_align = match wbl { -// WgslBufferLayout::UniformAddressSpace => round_up(16, align_of_array(elem)), -// WgslBufferLayout::StorageAddressSpace => align_of_array(elem), -// }; -// if !required_element_align.divides(actual_stride) { -// return Err(LayoutError::ArrayStrideAlignmentError(ArrayStrideAlignmentError { -// ctx: ctx.clone(), -// expected_align: required_element_align, -// actual_stride, -// element_ty: elem.clone(), -// })); -// } -// } -// }; -// check_sized_type_layout(ctx, elem)?; -// Ok(()) -// } - -// pub fn check_structure_layout(ctx: &LayoutErrorContext, s: &LayoutStructureKind) -> Result<(), LayoutError> { -// let structure_name = match s { -// LayoutStructureKind::Structure(s) => s.name(), -// LayoutStructureKind::BufferBlock(s) => s.name(), -// }; -// let (sized_fields, last_field) = &match s { -// LayoutStructureKind::Structure(s) => (s.sized_fields(), &None), -// LayoutStructureKind::BufferBlock(s) => (s.sized_fields(), s.last_unsized_field()), -// }; - -// let mut offset = 0; -// for field in sized_fields.iter() { -// // return errors if custom align or size are used in OpenGL constraints. -// // warning: removing this check will make the surrounding code wrong. -// // for example: the OpenGL spec has a wording which defines std140/std430 -// // offsets of elements after arrays directly via their stride. -// // this means even if GLSL supports custom size and align in the future, -// // the wording of the std140 definition of that situation would need to -// // be checked for changes, which would likely justify a rewrite of this -// // entire module. -// match ctx.expected_constraints { -// LayoutConstraints::OpenGL(_) => { -// if let Some(custom_align) = field.custom_min_align() { -// return Err(LayoutError::LayoutConstriantsDoNotSupportCustomFieldAlign { -// ctx: ctx.clone(), -// struct_or_block_name: structure_name.clone(), -// field_name: field.name().clone(), -// custom_align: u64::from(custom_align), -// }); -// } -// if let Some(custom_size) = field.custom_min_size() { -// return Err(LayoutError::LayoutConstriantsDoNotSupportCustomFieldSize { -// ctx: ctx.clone(), -// struct_or_block_name: structure_name.clone(), -// field_name: field.name().clone(), -// custom_size, -// }); -// } -// } -// LayoutConstraints::Wgsl(_) => (), -// } - -// // the align and size which takes custom user attributes for align and size into account -// let (align, size) = (field.align(), field.byte_size()); -// // the regular align and size of field.ty -// let (ty_align, ty_size) = (field.ty().align(), field.ty().byte_size()); -// offset = round_up(field.align(), offset); - -// let required_align_of_field = match field.ty() { -// SizedType::Vector(_, _) | SizedType::Matrix(_, _, _) | SizedType::Atomic(_) => ty_align, -// SizedType::Structure(_) | SizedType::Array(_, _) => match ctx.expected_constraints { -// LayoutConstraints::OpenGL(std) => match std { -// Std::_140 => round_up(16, ty_align), -// Std::_430 => ty_align, -// }, -// LayoutConstraints::Wgsl(wbl) => match wbl { -// WgslBufferLayout::UniformAddressSpace => round_up(16, ty_align), -// WgslBufferLayout::StorageAddressSpace => ty_align, -// }, -// }, -// }; - -// if !required_align_of_field.divides(offset) { -// return Err(LayoutError::Structure(StructureLayoutError { -// context: ctx.clone(), -// structure: s.clone(), -// field_name: field.name().clone(), -// actual_offset: offset, -// expected_alignment: required_align_of_field, -// })); -// } - -// check_sized_type_layout(ctx, field.ty())?; - -// offset += field.byte_size() -// } - -// if let Some(last_field) = last_field { -// let ty_align = align_of_array(last_field.element_ty()); -// let ty = StoreType::RuntimeSizedArray(last_field.element_ty().clone()); - -// // TODO(low prio) refactor this function so that theres no code duplication here -// let required_align_of_array = match ctx.expected_constraints { -// LayoutConstraints::OpenGL(std) => match std { -// Std::_140 => round_up(16, ty_align), -// Std::_430 => ty_align, -// }, -// LayoutConstraints::Wgsl(wbl) => match wbl { -// WgslBufferLayout::UniformAddressSpace => round_up(16, ty_align), -// WgslBufferLayout::StorageAddressSpace => ty_align, -// }, -// }; - -// if !required_align_of_array.divides(offset) { -// return Err(LayoutError::Structure(StructureLayoutError { -// context: ctx.clone(), -// structure: s.clone(), -// field_name: last_field.name().clone(), -// actual_offset: offset, -// expected_alignment: required_align_of_array, -// })); -// } -// check_layout(ctx, &ty)?; -// } -// Ok(()) -// } - -// #[derive(Error, Debug, Clone)] -// pub enum LayoutError { -// #[error("array elements may not be runtime-sized types. found array of: {0}")] -// ArrayElementsAreUnsized(TypeLayout), -// #[error("{0}")] -// Structure(#[from] StructureLayoutError), -// #[error( -// "The size of `{1}` on the gpu is now known at compile time. `{0}` \ -// requires that the size of uniform buffers on the gpu is known at compile time." -// )] -// UniformBufferMustBeSized(&'static str, StoreType), -// #[error("{0}")] -// ArrayAlignmentError(ArrayAlignmentError), -// #[error("{0}")] -// ArrayStrideError(ArrayStrideError), -// #[error("{0}")] -// ArrayStrideAlignmentError(ArrayStrideAlignmentError), -// #[error("{} layout constraints do not allow custom struct field byte-alignments. \ -// Type `{}` contains type `{struct_or_block_name}` which has a custom byte-alignment of {custom_align} at field `{field_name}`.", ctx.expected_constraints, ctx.top_level_type)] -// LayoutConstriantsDoNotSupportCustomFieldAlign { -// ctx: LayoutErrorContext, -// struct_or_block_name: CanonName, -// field_name: CanonName, -// custom_align: u64, -// }, -// #[error("{} layout constraints do not allow custom struct field byte-sizes. \ -// Type `{}` contains type `{struct_or_block_name}` which has a custom byte-size of {custom_size} at field `{field_name}`.", ctx.expected_constraints, ctx.top_level_type)] -// LayoutConstriantsDoNotSupportCustomFieldSize { -// ctx: LayoutErrorContext, -// struct_or_block_name: CanonName, -// field_name: CanonName, -// custom_size: u64, -// }, -// #[error("a non-reference buffer (non `BufferRef`) must be both read-only and constructible")] -// NonRefBufferRequiresReadOnlyAndConstructible, -// #[error( -// "type {0} does not match the requirements for host-shareable types. See https://www.w3.org/TR/WGSL/#host-shareable-types (In most cases, this is caused by the type containing booleans)" -// )] -// NotHostShareable(StoreType), -// #[error("custom alignment of {custom} is too small. `{ty}` must have an alignment of at least {required}")] -// CustomAlignmentTooSmall { custom: u64, required: u64, ty: StoreType }, -// #[error("custom size of {custom} is too small. `{ty}` must have a size of at least {required}")] -// CustomSizeTooSmall { custom: u64, required: u64, ty: Type }, -// #[error("memory layout mismatch:\n{0}\n{}", if let Some(comment) = .1 {comment.as_str()} else {""})] -// LayoutMismatch(LayoutMismatch, Option), -// #[error("runtime-sized type {name} cannot be element in an array buffer")] -// UnsizedStride { name: String }, -// #[error( -// "stride mismatch:\n{cpu_name}: {cpu_stride} bytes offset between elements,\n{gpu_name}: {gpu_stride} bytes offset between elements" -// )] -// StrideMismatch { -// cpu_name: String, -// cpu_stride: u64, -// gpu_name: String, -// gpu_stride: u64, -// }, -// } - -// #[allow(missing_docs)] -// #[derive(Error, Debug, Clone)] -// pub struct ArrayStrideAlignmentError { -// ctx: LayoutErrorContext, -// expected_align: u64, -// actual_stride: u64, -// element_ty: SizedType, -// } - -// impl Display for ArrayStrideAlignmentError { -// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { -// writeln!( -// f, -// "array elements within type `{}` do not satisfy {} layout requirements.", -// self.ctx.top_level_type, self.ctx.expected_constraints -// ); -// let expected_align = self.expected_align; -// let actual_stride = self.actual_stride; -// writeln!( -// f, -// "The array with `{}` elements requires that every element is {expected_align}-byte aligned, but the array has a stride of {actual_stride} bytes, which means subsequent elements are not {expected_align}-byte aligned.", -// self.element_ty -// ); -// if let Ok(layout) = TypeLayout::from_store_ty(self.ctx.top_level_type.clone()) { -// writeln!(f, "The full layout of `{}` is:", self.ctx.top_level_type); -// layout.write("", self.ctx.use_color, f)?; -// writeln!(f); -// }; -// writeln!( -// f, -// "\nfor more information on the layout rules, see {}", -// self.ctx.expected_constraints.more_info_at() -// )?; -// Ok(()) -// } -// } - -// #[allow(missing_docs)] -// #[derive(Error, Debug, Clone)] -// pub struct ArrayStrideError { -// ctx: LayoutErrorContext, -// expected: u64, -// actual: u64, -// element_ty: SizedType, -// } - -// impl Display for ArrayStrideError { -// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { -// writeln!( -// f, -// "array elements within type `{}` do not satisfy {} layout requirements.", -// self.ctx.top_level_type, self.ctx.expected_constraints -// ); -// writeln!( -// f, -// "The array with `{}` elements requires stride {}, but has stride {}.", -// self.element_ty, self.expected, self.actual -// ); -// if let Ok(layout) = TypeLayout::from_store_ty(self.ctx.top_level_type.clone()) { -// writeln!(f, "The full layout of `{}` is:", self.ctx.top_level_type); -// layout.write("", self.ctx.use_color, f)?; -// writeln!(f); -// }; -// writeln!( -// f, -// "\nfor more information on the layout rules, see {}", -// self.ctx.expected_constraints.more_info_at() -// )?; -// Ok(()) -// } -// } - -// #[allow(missing_docs)] -// #[derive(Error, Debug, Clone)] -// pub struct ArrayAlignmentError { -// ctx: LayoutErrorContext, -// expected: u64, -// actual: u64, -// element_ty: SizedType, -// } - -// impl Display for ArrayAlignmentError { -// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { -// writeln!( -// f, -// "array elements within type `{}` do not satisfy {} layout requirements.", -// self.ctx.top_level_type, self.ctx.expected_constraints -// ); -// writeln!( -// f, -// "The array with `{}` elements requires alignment {}, but has alignment {}.", -// self.element_ty, self.expected, self.actual -// ); -// if let Ok(layout) = TypeLayout::from_store_ty(self.ctx.top_level_type.clone()) { -// writeln!(f, "The full layout of `{}` is:", self.ctx.top_level_type); -// layout.write("", self.ctx.use_color, f)?; -// writeln!(f); -// }; -// writeln!( -// f, -// "\nfor more information on the layout rules, see {}", -// self.ctx.expected_constraints.more_info_at() -// )?; -// Ok(()) -// } -// } - - - -// #[derive(Debug, Clone)] -// pub struct LayoutErrorContext { -// pub binding_type: BufferBindingType, -// pub expected_constraints: LayoutConstraints, -// pub top_level_type: StoreType, -// /// whether the error message should be output using console colors -// pub use_color: bool, -// } - -// #[derive(Debug, Clone)] -// //TODO(release) this type is obsolete. SizedStruct and BufferBlock now both Deref to `Struct`, use that instead -// pub enum LayoutStructureKind { -// Structure(SizedStruct), -// BufferBlock(BufferBlock), -// } - -// #[derive(Debug, Clone)] -// pub struct StructureLayoutError { -// pub context: LayoutErrorContext, -// pub structure: LayoutStructureKind, -// pub field_name: CanonName, -// pub actual_offset: u64, -// pub expected_alignment: u64, -// } - -// impl std::error::Error for StructureLayoutError {} - -// impl Display for StructureLayoutError { -// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { -// let custom_align_or_size_supported = match self.context.expected_constraints { -// LayoutConstraints::OpenGL(_) => false, -// LayoutConstraints::Wgsl(_) => true, -// }; - -// let structure_name: &CanonName = match &self.structure { -// LayoutStructureKind::Structure(s) => s.name(), -// LayoutStructureKind::BufferBlock(s) => s.name(), -// }; - -// let structure_def_location = Context::try_with(call_info!(), |ctx| { -// let s = match &self.structure { -// LayoutStructureKind::Structure(s) => &**s, -// LayoutStructureKind::BufferBlock(s) => &**s, -// }; -// ctx.struct_registry().get(s).map(|def| def.call_info()) -// }) -// .flatten(); - -// let top_level_type = &self.context.top_level_type; -// let binding_type_str = match self.context.binding_type { -// BufferBindingType::Storage(_) => "storage", -// BufferBindingType::Uniform => "uniform", -// }; -// writeln!( -// f, -// "the type `{top_level_type}` cannot be used as a {binding_type_str} buffer binding." -// )?; -// let ((constraints, short_summary), link) = match self.context.expected_constraints { -// LayoutConstraints::OpenGL(std) => ( -// match std { -// Std::_140 => ( -// "std140", -// Some( -// "In std140 buffers the alignment of structs, arrays and array elements must be at least 16.", -// ), -// ), -// Std::_430 => ("std430", None), -// }, -// "https://www.khronos.org/opengl/wiki/Interface_Block_(GLSL)#Memory_layout", -// ), -// LayoutConstraints::Wgsl(x) => ( -// match x { -// WgslBufferLayout::UniformAddressSpace => ( -// "uniform address space", -// Some( -// "In the uniform address space, structs, arrays and array elements must be at least 16 byte aligned.", -// ), -// ), -// WgslBufferLayout::StorageAddressSpace => ("storage address space", None), -// }, -// "https://www.w3.org/TR/WGSL/#address-space-layout-constraints", -// ), -// }; -// let actual_align = max_u64_po2_dividing(self.actual_offset); -// let nested = self.context.top_level_type.to_string() != **structure_name; -// if nested { -// write!(f, "It contains a struct `{}`, which", structure_name)?; -// } else { -// write!(f, "Struct `{}`", structure_name)?; -// } -// writeln!(f, " does not satisfy the {constraints} memory layout requirements.",)?; -// writeln!(f)?; -// if let Some(call_info) = structure_def_location { -// writeln!(f, "Definition at {call_info}")?; -// } -// write_struct_layout(&self.structure, self.context.use_color, Some(&self.field_name), f)?; - -// writeln!(f)?; -// set_color(f, Some("#508EE3"), false)?; -// writeln!( -// f, -// "Field `{}` needs to be {} byte aligned, but has a byte-offset of {} which is only {actual_align} byte aligned.", -// self.field_name, self.expected_alignment, self.actual_offset -// )?; -// set_color(f, None, false)?; -// writeln!(f)?; - -// writeln!(f, "Potential solutions include:"); -// writeln!( -// f, -// "- add an #[align({})] attribute to the definition of `{}` (not supported by OpenGL/GLSL pipelines)", -// self.expected_alignment, self.field_name -// ); -// writeln!(f, "- use a storage binding instead of a uniform binding"); -// writeln!( -// f, -// "- increase the offset of `{}` until it is divisible by {} by making previous fields larger or adding fields before it", -// self.field_name, self.expected_alignment -// ); -// writeln!(f)?; - - -// if let Some(summary) = short_summary { -// writeln!(f, "{summary}"); -// } -// //writeln!(f, "Address space alignment restrictions enable use of more efficient hardware instructions for accessing the values, or satisfy more restrictive hardware requirements.")?; -// writeln!(f, "More info about the {constraints} layout can be found at {link}")?; -// Ok(()) -// } -// } - -// fn write_struct_layout( -// s: &LayoutStructureKind, -// colored: bool, -// highlight_field: Option<&str>, -// f: &mut F, -// ) -> std::fmt::Result -// where -// F: Write, -// { -// let use_256_color_mode = false; -// let color = |f_: &mut F, hex| match colored { -// true => set_color(f_, Some(hex), use_256_color_mode), -// false => Ok(()), -// }; -// let reset = |f_: &mut F| match colored { -// true => set_color(f_, None, use_256_color_mode), -// false => Ok(()), -// }; - -// let structure_name = match s { -// LayoutStructureKind::Structure(s) => s.name(), -// LayoutStructureKind::BufferBlock(s) => s.name(), -// }; - -// let (sized_fields, last_field) = match s { -// LayoutStructureKind::Structure(s) => (s.sized_fields(), &None), -// LayoutStructureKind::BufferBlock(s) => (s.sized_fields(), s.last_unsized_field()), -// }; - -// let indent = " "; -// let field_decl_line = |field: &SizedField| format!("{indent}{}: {},", field.name(), field.ty()); -// let header = format!("struct {} {{", structure_name); -// let table_start_column = 1 + sized_fields -// .iter() -// .map(field_decl_line) -// .map(|s| s.len()) -// .max() -// .unwrap_or(0) -// .max(header.chars().count()); -// f.write_str(&header)?; -// for i in header.len()..table_start_column { -// f.write_char(' ')? -// } -// writeln!(f, "offset align size")?; -// let mut offset = 0; -// for field in sized_fields.iter() { -// if Some(&**field.name()) == highlight_field { -// color(f, "#508EE3")?; -// } -// let (align, size) = (field.align(), field.byte_size()); -// offset = round_up(field.align(), offset); -// let decl_line = field_decl_line(field); -// f.write_str(&decl_line)?; -// // write spaces to table on the right -// for i in decl_line.len()..table_start_column { -// f.write_char(' ')? -// } -// writeln!(f, "{:6} {:5} {:4}", offset, align, size)?; -// if Some(&**field.name()) == highlight_field { -// reset(f); -// } -// offset += field.byte_size() -// } -// if let Some(last_field) = last_field { -// offset = round_up(last_field.align(), offset); - -// let decl_line = format!( -// "{indent}{}: {},", -// last_field.name(), -// StoreType::RuntimeSizedArray(last_field.element_ty().clone()) -// ); -// f.write_str(&decl_line)?; -// // write spaces to table on the right -// for i in decl_line.len()..table_start_column { -// f.write_char(' ')? -// } -// write!(f, "{:6} {:5}", offset, last_field.align())?; -// } -// writeln!(f, "}}")?; -// Ok(()) -// } diff --git a/shame/src/ir/ir_type/mod.rs b/shame/src/ir/ir_type/mod.rs index 3a4e50d..6c56a37 100644 --- a/shame/src/ir/ir_type/mod.rs +++ b/shame/src/ir/ir_type/mod.rs @@ -5,7 +5,6 @@ mod align_size; mod canon_name; mod categories; -mod layout_constraints; mod memory_view; mod struct_; mod tensor; @@ -15,7 +14,6 @@ mod ty; pub use align_size::*; pub use canon_name::*; -pub use layout_constraints::*; pub use memory_view::*; pub use struct_::*; pub use tensor::*;