diff --git a/examples/api_showcase/src/main.rs b/examples/api_showcase/src/main.rs index a1b3eb8..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, 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/shame_wgpu/src/conversion.rs b/examples/shame_wgpu/src/conversion.rs index 16c7ad6..3bb0ff7 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::any::layout::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..dd64124 --- /dev/null +++ b/examples/type_layout/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "type_layout" +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..35830bc --- /dev/null +++ b/examples/type_layout/src/main.rs @@ -0,0 +1,57 @@ +#![allow(dead_code, unused)] +//! Demonstration of the TypeLayout and TypeLayout Builder API. + +use layout::{repr, Repr, SizedStruct}; +use shame::{ + any::{ + self, + layout::{ + self, FieldOptions, LayoutableSized, Len, RuntimeSizedArrayField, ScalarType, SizedField, SizedType, + UnsizedStruct, Vector, GpuTypeLayout, + }, + U32PowerOf2, + }, + boolx1, f32x1, f32x2, f32x3, f32x4, gpu_layout, Array, GpuLayout, GpuSized, TypeLayout, VertexAttribute, + VertexLayout, +}; + +fn main() { + // We'll start by replicating this struct using `any::layout` types. + #[derive(GpuLayout)] + struct Vertex { + position: f32x3, + normal: f32x3, + uv: f32x2, + } + + // SizedStruct::new immediately takes the first field of the struct, because + // structs need to have at least one field. + let sized_struct = SizedStruct::new("Vertex", "position", f32x3::layoutable_type_sized()) + .extend("normal", f32x3::layoutable_type_sized()) + .extend("uv", f32x1::layoutable_type_sized()); + + let storage = GpuTypeLayout::::new(sized_struct.clone()); + let packed = GpuTypeLayout::::new(sized_struct); + assert_ne!(storage.layout(), packed.layout()); + + // Does not exist: + // let uniform = GpuTypeLayout::::new(sized_struct.clone()); + + // However we can try to upgrade a GpuTypeLayout:: + let uniform = GpuTypeLayout::::try_from(storage.clone()).unwrap(); + + // Which if it succeeds, guarantees: + assert_eq!(storage.layout(), uniform.layout()); + + // // 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 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/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..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; @@ -25,10 +26,11 @@ 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::layoutable::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::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::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; @@ -40,6 +42,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 f392681..797f3ec 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::{ @@ -56,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() { @@ -95,33 +94,39 @@ 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 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() - .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::>(); 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/any/render_io.rs b/shame/src/frontend/any/render_io.rs index 5640f27..93e817d 100644 --- a/shame/src/frontend/any/render_io.rs +++ b/shame/src/frontend/any/render_io.rs @@ -3,7 +3,7 @@ use std::{fmt::Display, rc::Rc}; use thiserror::Error; 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, @@ -108,7 +108,7 @@ impl Any { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum VertexAttribFormat { /// regular [`crate::vec`] types - Fine(Len, ScalarType), + Fine(Len, layoutable::ScalarType), /// packed [`crate::packed::PackedVec`] types Coarse(PackedVector), } @@ -238,7 +238,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 +251,16 @@ impl Attrib { ) -> Option<(Box<[Attrib]>, u64)> { let stride = { let size = layout.byte_size()?; - stride_of_array_from_element_align_size(layout.align(), size) + layoutable::array_stride(layout.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 +274,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/any/shared_io.rs b/shame/src/frontend/any/shared_io.rs index b33f80c..4103ce2 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,15 +9,14 @@ 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; -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; -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 +147,178 @@ 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), + #[error("a non-reference buffer (non `BufferRef`) must be both read-only and constructible")] + NonRefBufferRequiresReadOnlyAndConstructible, +} + +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(); - - if buffer_binding_as_ref && !matches!(binding_ty, BindingType::Buffer { .. }) { - return Err(LayoutError::NonRefBufferRequiresReadOnlyAndConstructible.into()); - } + let binding_type = BindingType::Buffer { + ty: BufferBindingType::Storage(access), + has_dynamic_offset, + }; + let store_type = layout_to_store_type(layout, &binding_type)?; - 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())) + 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(BindingError::NonRefBufferRequiresReadOnlyAndConstructible.into()); } - 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)) + (false, AccessModeReadable::Read) => { + if !store_type.is_constructible() { + return Err(BindingError::NonRefBufferRequiresReadOnlyAndConstructible.into()); } + 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) + }; - _ => Err(BindingError::InvalidTypeForBinding(ty.clone(), binding_ty.clone())), - }, + 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(BindingError::NonRefBufferRequiresReadOnlyAndConstructible.into()); + } - 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 ty = Type::Store(store_type.clone()); + + record_and_register_binding(ctx, bind_path, visibility, binding_type, store_type, ty) + }; + + create_any_catch_errors(create_any) + } + + /// 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, + }, + }; + + 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. @@ -285,7 +338,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 { @@ -293,18 +346,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/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 50ad927..27593ad 100644 --- a/shame/src/frontend/encoding/buffer.rs +++ b/shame/src/frontend/encoding/buffer.rs @@ -1,9 +1,12 @@ +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; @@ -17,13 +20,14 @@ 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::{self as shame, call_info, ir, GpuLayout}; use std::borrow::Borrow; use std::marker::PhantomData; use std::ops::{Deref, Mul}; use super::binding::Binding; +use super::EncodingErrorKind; /// Address spaces used for [`Buffer`] and [`BufferRef`] bindings. /// @@ -97,7 +101,7 @@ where impl Buffer where - T: GpuStore + NoHandles + NoAtomics + NoBools, + T: GpuStore + NoHandles + NoAtomics + NoBools + GpuLayout, AS: BufferAddressSpace, { #[track_caller] @@ -107,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, + T: GpuStore + NoHandles + NoBools + GpuLayout, AS: BufferAddressSpace, AM: AccessModeReadable, { @@ -125,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), } } } @@ -147,34 +151,77 @@ pub enum BufferRefInner), } -impl BufferInner { +#[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 +{ #[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 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); @@ -182,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); @@ -193,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); @@ -262,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 } + } } } @@ -294,9 +341,15 @@ impl BufferRefInner #[rustfmt::skip] impl Binding for Buffer where - T: GpuSized + 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) } @@ -316,11 +369,17 @@ 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 + 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) } @@ -464,7 +523,7 @@ where /// /// // field access returns references /// let world: sm::Ref = buffer.world; -/// +/// /// // get fields via `.get()` /// let matrix: f32x4x4 = buffer.world.get(); /// @@ -488,16 +547,21 @@ 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) } - + 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..35f501f 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, }, @@ -33,7 +34,7 @@ use crate::{ }, ir::{ self, - ir_type::{Field, LayoutError}, + ir_type::{Field}, pipeline::{PipelineError, StageMask}, recording::Context, TextureFormatWrapper, @@ -454,13 +455,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 +559,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(); @@ -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 fa3ae86..6086470 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,12 @@ 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, + layout_traits::CpuLayoutCompareError, + len::x3, + type_layout::{construction::LayoutError, layoutable::ir_compat::IRConversionError}, + }, }; pub mod binding; @@ -95,7 +99,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 @@ -293,6 +297,10 @@ pub enum EncodingErrorKind { #[error("{0}")] LayoutError(#[from] LayoutError), #[error("{0}")] + CpuLayoutCompare(#[from] CpuLayoutCompareError), + #[error("{0}")] + LayoutableToStoreType(#[from] IRConversionError), + #[error("{0}")] BindingError(#[from] BindingError), #[error("{0}")] StageSolverError(#[from] StageSolverErrorKind), @@ -436,9 +444,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 +495,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/array.rs b/shame/src/frontend/rust_types/array.rs index 853e6e1..4b58a2a 100644 --- a/shame/src/frontend/rust_types/array.rs +++ b/shame/src/frontend/rust_types/array.rs @@ -5,13 +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, repr, ElementLayout, Repr, 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::any::BufferBindingType; use crate::common::small_vec::SmallVec; use crate::frontend::any::shared_io::{BindPath, BindingType}; use crate::frontend::any::Any; @@ -86,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 { @@ -107,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()) } @@ -155,19 +159,30 @@ impl GpuSized for Array> { #[rustfmt::skip] impl NoHandles for Array {} #[rustfmt::skip] impl NoAtomics for Array {} #[rustfmt::skip] impl NoBools for Array {} +impl LayoutableSized for Array> { + fn layoutable_type_sized() -> layoutable::SizedType { + layoutable::SizedArray::new(T::layoutable_type_sized(), Size::::nonzero()).into() + } +} +impl Layoutable for Array { + fn layoutable_type() -> layoutable::LayoutableType { + match N::LEN { + Some(n) => layoutable::SizedArray::new(T::layoutable_type_sized(), n).into(), + None => layoutable::RuntimeSizedArray::new(T::layoutable_type_sized()).into(), + } + } +} -impl ToGpuType for Array { +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 { + 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()? { @@ -190,7 +205,7 @@ impl GpuLayout for Array { t_cpu_layout.align(), TypeLayoutSemantics::Array( Rc::new(ElementLayout { - byte_stride: stride_of_array_from_element_align_size(t_cpu_layout.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), @@ -264,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, @@ -277,7 +292,7 @@ where impl ToGpuType for [T; N] where - T::Gpu: GpuStore + GpuSized, + T::Gpu: GpuStore + GpuSized + GpuLayout + LayoutableSized, { type Gpu = Array>; @@ -289,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() } @@ -305,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| { @@ -341,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, { @@ -360,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, @@ -380,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] @@ -406,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 e391b6b..61eab93 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::{TypeLayout, TypeLayoutRules}, + type_layout::{ + self, + layoutable::{self, LayoutableSized}, + repr, TypeLayout, + }, type_traits::{ BindingArgs, EmptyRefFields, GpuAligned, GpuSized, GpuStore, GpuStoreImplCategory, NoAtomics, NoBools, NoHandles, @@ -14,7 +18,10 @@ use super::{ vec::vec, AsAny, GpuType, To, ToGpuType, }; -use crate::frontend::rust_types::reference::Ref; +use crate::{ + any::{layout::Layoutable, BufferBindingType}, + frontend::rust_types::reference::Ref, +}; use crate::{ boolx1, frontend::{ @@ -81,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()) } @@ -129,8 +138,20 @@ impl GetAllFields for Atomic { fn fields_as_anys_unchecked(self_as_any: Any) -> impl std::borrow::Borrow<[Any]> { [] } } +impl LayoutableSized for Atomic { + fn layoutable_type_sized() -> layoutable::SizedType { + layoutable::Atomic { + scalar: T::SCALAR_TYPE_INTEGER, + } + .into() + } +} +impl Layoutable for Atomic { + fn layoutable_type() -> layoutable::LayoutableType { Self::layoutable_type_sized().into() } +} + impl GpuLayout for Atomic { - fn gpu_layout() -> TypeLayout { TypeLayout::from_sized_ty(TypeLayoutRules::Wgsl, &::sized_ty()) } + 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 96e9373..f6d34bd 100644 --- a/shame/src/frontend/rust_types/layout_traits.rs +++ b/shame/src/frontend/rust_types/layout_traits.rs @@ -1,3 +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}; @@ -11,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; @@ -21,8 +23,11 @@ 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::{ - ElementLayout, FieldLayout, FieldLayoutWithOffset, StructLayout, TypeLayout, TypeLayoutError, TypeLayoutRules, + self, repr, ElementLayout, FieldLayout, FieldLayoutWithOffset, GpuTypeLayout, StructLayout, TypeLayout, TypeLayoutSemantics, }; use super::type_traits::{ @@ -114,7 +119,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,28 +140,10 @@ use std::rc::Rc; /// [`Texture`]: crate::Texture /// [`StorageTexture`]: crate::StorageTexture /// -pub trait GpuLayout { - /// 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; +pub trait GpuLayout: Layoutable { + /// Returns the `Repr` of the `TypeLayout` from `GpuLayout::gpu_layout`. + // 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. @@ -171,6 +158,38 @@ pub trait GpuLayout { 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 { gpu_type_layout::().layout() } + +pub fn gpu_type_layout() -> GpuTypeLayout { + GpuTypeLayout::new(T::layoutable_type()) +} + +/// (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, @@ -194,7 +213,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(); } @@ -209,24 +228,26 @@ pub(crate) fn check_layout_push_error( skip_stride_check: bool, comment_on_mismatch_error: &str, ) -> Result<(), InvalidReason> { - TypeLayout::check_eq(("cpu", cpu_layout), ("gpu", gpu_layout)) - .map_err(|e| LayoutError::LayoutMismatch(e, Some(comment_on_mismatch_error.to_string()))) + type_layout::eq::check_eq(("cpu", cpu_layout), ("gpu", gpu_layout)) + .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)) => { - 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.align(), cpu_size); + 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(), @@ -245,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 @@ -533,28 +571,16 @@ where } } +impl LayoutableSized for GpuT { + fn layoutable_type_sized() -> layoutable::SizedType { todo!() } +} +impl Layoutable for GpuT { + fn layoutable_type() -> layoutable::LayoutableType { 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!() } + type GpuRepr = repr::Storage; fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { Some(Ok(( @@ -612,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()) } @@ -701,7 +729,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( + layoutable::ScalarType::F32, + layoutable::Len::X1, + ))) } // fn gpu_type_layout() -> Option> { // Some(Ok(vec::::gpu_layout())) @@ -710,29 +741,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( + layoutable::ScalarType::F64, + layoutable::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( + layoutable::ScalarType::U32, + layoutable::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( + layoutable::ScalarType::I32, + layoutable::Len::X1, + ))) } - // fn gpu_type_layout() -> Option> { - // Some(Ok(vec::::gpu_layout())) - // } } /// (no documentation yet) @@ -760,7 +791,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), @@ -803,7 +834,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, diff --git a/shame/src/frontend/rust_types/mat.rs b/shame/src/frontend/rust_types/mat.rs index 08c72ab..3242b5c 100644 --- a/shame/src/frontend/rust_types/mat.rs +++ b/shame/src/frontend/rust_types/mat.rs @@ -7,7 +7,11 @@ use super::{ mem::AddressSpace, reference::{AccessMode, AccessModeReadable}, scalar_type::{ScalarType, ScalarTypeFp}, - type_layout::{TypeLayout, TypeLayoutRules}, + type_layout::{ + self, + layoutable::{self, LayoutableSized}, + repr, TypeLayout, + }, type_traits::{ BindingArgs, EmptyRefFields, GpuAligned, GpuSized, GpuStore, GpuStoreImplCategory, NoAtomics, NoBools, NoHandles, @@ -15,7 +19,11 @@ use super::{ vec::{scalar, vec, ToInteger}, AsAny, GpuType, To, ToGpuType, }; -use crate::{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::{ @@ -50,8 +58,22 @@ impl Default for mat { } } +impl LayoutableSized for mat { + fn layoutable_type_sized() -> layoutable::SizedType { + layoutable::Matrix { + columns: C::LEN2, + rows: R::LEN2, + scalar: T::SCALAR_TYPE_FP, + } + .into() + } +} +impl Layoutable for mat { + fn layoutable_type() -> layoutable::LayoutableType { Self::layoutable_type_sized().into() } +} + impl GpuLayout for mat { - fn gpu_layout() -> TypeLayout { TypeLayout::from_sized_ty(TypeLayoutRules::Wgsl, &::sized_ty()) } + type GpuRepr = repr::Storage; fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { None } } @@ -92,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()) } @@ -181,7 +205,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 +259,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 +520,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..a810c05 100644 --- a/shame/src/frontend/rust_types/packed_vec.rs +++ b/shame/src/frontend/rust_types/packed_vec.rs @@ -5,9 +5,12 @@ use std::{ }; use crate::{ - any::{AsAny, DataPackingFn}, + any::{ + layout::{Layoutable, LayoutableSized}, + 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; @@ -22,7 +25,7 @@ use super::{ layout_traits::{from_single_any, ArrayElementsUnsizedError, FromAnys, GpuLayout}, len::LenEven, scalar_type::ScalarType, - type_layout::{TypeLayout, TypeLayoutRules, TypeLayoutSemantics}, + type_layout::{self, layoutable, repr, Repr, TypeLayout, TypeLayoutSemantics}, type_traits::{GpuAligned, GpuSized, NoAtomics, NoBools, NoHandles, VertexAttribute}, vec::IsVec, GpuType, @@ -104,7 +107,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,21 +133,27 @@ impl GpuAligned for PackedVec { impl NoBools for PackedVec {} impl NoHandles for PackedVec {} impl NoAtomics for PackedVec {} +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, + } + .into() + } +} +impl Layoutable for PackedVec { + fn layoutable_type() -> layoutable::LayoutableType { Self::layoutable_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::()), - ) - } + 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 layout = TypeLayout::from_sized_ty(TypeLayoutRules::Wgsl, &sized_ty); + let layout = TypeLayout::new_layout_for(&sized_ty.into(), Repr::Storage); Some(Ok((name, layout))) } } @@ -162,7 +171,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/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 f5615dc..843093e 100644 --- a/shame/src/frontend/rust_types/struct_.rs +++ b/shame/src/frontend/rust_types/struct_.rs @@ -1,3 +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}; @@ -22,7 +24,7 @@ use std::{ }; use super::layout_traits::{GetAllFields, GpuLayout}; -use super::type_layout::TypeLayout; +use super::type_layout::{self, layoutable, repr, TypeLayout}; use super::type_traits::{GpuAligned, GpuSized, GpuStore, GpuStoreImplCategory, NoBools}; use super::{ error::FrontendError, @@ -96,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()) } @@ -134,8 +138,15 @@ impl Deref for Struct { fn deref(&self) -> &Self::Target { &self.fields } } -impl GpuLayout for Struct { - fn gpu_layout() -> TypeLayout { T::gpu_layout() } +impl LayoutableSized for Struct { + fn layoutable_type_sized() -> layoutable::SizedType { T::layoutable_type_sized() } +} +impl Layoutable for Struct { + fn layoutable_type() -> layoutable::LayoutableType { T::layoutable_type() } +} + +impl GpuLayout for Struct { + 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.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/construction.rs b/shame/src/frontend/rust_types/type_layout/construction.rs new file mode 100644 index 0000000..e586494 --- /dev/null +++ b/shame/src/frontend/rust_types/type_layout/construction.rs @@ -0,0 +1,576 @@ +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 `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), + 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(); + + let (byte_size, align) = field_offsets.struct_byte_size_and_align(); + ( + byte_size, + 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.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) = 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 + // 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, + }, + }); + + 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 { + 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, + }, + } +} + +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.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, _) = 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; + 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: 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() + { + 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 comparison of two layouts for the same `LayoutableType`. +#[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 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(()) + } +} + +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).into_sized_fields()), + StructKind::Unsized(s) => ( + &s.name, + s.sized_fields.as_slice(), + s.field_offsets(repr).into_sized_fields(), + ), + }; + + 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.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)?; + // 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 new file mode 100644 index 0000000..a7c8275 --- /dev/null +++ b/shame/src/frontend/rust_types/type_layout/eq.rs @@ -0,0 +1,446 @@ +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.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.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.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().as_u32()); + color_b(f); + 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.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()), (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 new file mode 100644 index 0000000..7421439 --- /dev/null +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -0,0 +1,492 @@ +use super::super::{Repr}; +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 { + 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 { + match self { + SizedType::Array(a) => a.byte_size(repr), + SizedType::Vector(v) => v.byte_size(), + 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, + } + } + + /// 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(repr), + 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, + } + } + + /// 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.align(repr)), + } + } +} + + +impl SizedStruct { + /// Returns [`FieldOffsets`], which serves as an iterator over the offsets of the + /// 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) + /// + /// ### 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, repr: Repr) -> (u64, U32PowerOf2) { + self.field_offsets(repr).struct_byte_size_and_align() + } +} + +/// 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; + + fn next(&mut self) -> Option { + self.field_index += 1; + self.fields.get(self.field_index - 1).map(|field| { + 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, is_struct) + }) + } +} +impl<'a> FieldOffsets<'a> { + fn new(fields: &'a [SizedField], repr: Repr) -> Self { + Self { + fields, + field_index: 0, + calc: LayoutCalculator::new(repr), + 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 } +} + +/// 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 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) = self.sized.calc.extend_unsized(array_align, custom_min_align); + (offset, struct_align(align, self.sized.repr)) + } + + /// 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 { + match repr { + // Packedness is ensured by the `LayoutCalculator`. + Repr::Storage => align, + Repr::Uniform => round_up_align(U32PowerOf2::_16, align), + Repr::Packed => PACKED_ALIGN, + } +} + +#[allow(missing_docs)] +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, 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() + } +} + +#[allow(missing_docs)] +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, + } + } +} + +#[allow(missing_docs)] +#[derive(Debug, Clone, Copy)] +pub enum MatrixMajor { + Row, + Column, +} + +#[allow(missing_docs)] +impl Matrix { + 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(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); + // 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::Column => (self.rows.as_len(), self.columns.as_non_zero_u32()), + MatrixMajor::Row => (self.columns.as_len(), self.rows.as_non_zero_u32()), + }; + ( + Vector { + len: vec_len, + scalar: self.scalar.as_scalar_type(), + }, + array_len, + ) + } +} + +#[allow(missing_docs)] +impl Atomic { + pub const fn byte_size(&self) -> u64 { self.scalar.as_scalar_type().byte_size() } + pub const fn align(&self, repr: Repr) -> U32PowerOf2 { + if repr.is_packed() { + return PACKED_ALIGN; + } + 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) } + + 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); + array_stride(element_align, element_size) + } +} + +/// 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 } + +/// 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 => element_align, + Repr::Uniform => round_up_align(U32PowerOf2::_16, element_align), + Repr::Packed => PACKED_ALIGN, + } +} + +/// 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 align(&self, repr: Repr) -> U32PowerOf2 { array_align(self.element.align(repr), 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 { + 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 { + // 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 { + 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() +} + +/// `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/builder.rs b/shame/src/frontend/rust_types/type_layout/layoutable/builder.rs new file mode 100644 index 0000000..399999f --- /dev/null +++ b/shame/src/frontend/rust_types/type_layout/layoutable/builder.rs @@ -0,0 +1,266 @@ +use super::*; + +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, + ) -> Result { + use StructFromPartsError::*; + + enum Field { + Sized(SizedField), + Unsized(RuntimeSizedArrayField), + } + + let mut fields = fields + .into_iter() + .map(|(options, ty)| { + Ok(match ty { + LayoutableType::Sized(s) => Field::Sized(SizedField::new(options, s)), + LayoutableType::RuntimeSizedArray(a) => Field::Unsized(RuntimeSizedArrayField::new( + options.name, + options.custom_min_align, + a.element, + )), + LayoutableType::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()) + } + } +} + +#[allow(missing_docs)] +#[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)], + } + } + + /// 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 `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) -> LayoutableType { + 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(), + } + } + + /// 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, + } + } +} + +#[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 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 { + 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, + } + } +} + +/// 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/ir_compat.rs b/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs new file mode 100644 index 0000000..033d519 --- /dev/null +++ b/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs @@ -0,0 +1,399 @@ +use super::*; + +// Conversions to ir types // + +/// Errors that can occur when converting IR types to layoutable types. +#[derive(thiserror::Error, Debug, Clone)] +pub enum IRConversionError { + /// Packed vectors do not exist in the shader type system. + #[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, Clone)] +pub struct DuplicateFieldNameError { + pub struct_type: StructKind, + pub first_field: usize, + pub second_field: usize, + 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( + 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; + for (i, field1) in sized_fields.iter().enumerate() { + for (j, field2) in sized_fields.iter().skip(i + 1).enumerate() { + if field1.name == field2.name { + duplicate_fields = Some((i, i + 1 + j)); + break; + } + } + if let Some(last_unsized) = last_unsized { + if field1.name == last_unsized.name { + duplicate_fields = Some((i, sized_fields.len())); + break; + } + } + } + duplicate_fields +} + +#[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 { + 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()?)), + } + } +} + +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()?), + }) + } +} + +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(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 // + +/// 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()) + } +} + + +#[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))); +} diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs new file mode 100644 index 0000000..0bbf272 --- /dev/null +++ b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs @@ -0,0 +1,285 @@ +//! This module defines types that can be laid out in memory. + +use std::{fmt::Formatter, num::NonZeroU32, rc::Rc}; + +use crate::{ + any::U32PowerOf2, + 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::{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, 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 [`GpuTypeLayout`] according to one of the available layout rules: +/// storage, uniform or packed. +#[derive(Debug, Clone)] +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), + Matrix(Matrix), + Array(SizedArray), + Atomic(Atomic), + PackedVec(PackedVector), + 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, + pub columns: Len2, + 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, +} + +/// 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, + F32, + U32, + I32, + 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, + pub custom_min_size: Option, + pub custom_min_align: Option, + pub ty: SizedType, +} + +#[allow(missing_docs)] +#[derive(Debug, Clone)] +pub struct RuntimeSizedArrayField { + pub name: CanonName, + pub custom_min_align: Option, + pub array: RuntimeSizedArray, +} + +/// Trait for types that have a well-defined memory layout. +pub trait Layoutable { + /// Returns the `LayoutableType` representation for this type. + fn layoutable_type() -> LayoutableType; +} +/// 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; +} + +// Conversions to ScalarType, SizedType and LayoutableType // + +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 [`LayoutableType`] + pub const fn into_layoutable_type(self) -> LayoutableType { + LayoutableType::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 LayoutableType +where + SizedType: From, +{ + fn from(value: T) -> Self { LayoutableType::Sized(SizedType::from(value)) } +} + +impl From for LayoutableType { + fn from(s: UnsizedStruct) -> Self { LayoutableType::UnsizedStruct(s) } +} +impl From for LayoutableType { + fn from(a: RuntimeSizedArray) -> Self { LayoutableType::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() } +} + +// Display impls + +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 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 std::fmt::Display for Vector { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}x{}", self.scalar, self.len) } +} + +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 std::fmt::Display for SizedArray { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "Array<{}, {}>", &*self.element, self.len) } +} + +impl std::fmt::Display for Atomic { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "Atomic<{}>", ScalarType::from(self.scalar)) } +} + +impl std::fmt::Display for SizedStruct { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.name) } +} + +impl std::fmt::Display for UnsizedStruct { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.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 Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + ScalarType::F16 => "f16", + ScalarType::F32 => "f32", + ScalarType::F64 => "f64", + ScalarType::U32 => "u32", + ScalarType::I32 => "i32", + }) + } +} 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..81fac77 --- /dev/null +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -0,0 +1,405 @@ +//! 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}, + recording::Context, + Len, + }, +}; +use layoutable::{ + align_size::{LayoutCalculator, PACKED_ALIGN}, + LayoutableType, Matrix, Vector, +}; + +pub(crate) mod construction; +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 { + /// `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. +/// +/// ### 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`]: +/// +/// ``` +/// 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 method exists for creating new type layouts based on a [`LayoutableType`] +/// ``` +/// let layout_type: LayoutableType = f32x1::layoutable_type(); +/// let repr = Repr::Storage; // or Uniform or Packed +/// let _ = TypeLayout::new_layout_for(layout_type, repr); +/// ``` +/// +/// 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 +/// +/// 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)] +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 align: U32PowerOf2, + /// the type contained in the bytes of this type layout + pub kind: TypeLayoutSemantics, +} + +// 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); + } +} + +/// 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`, 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, + _repr: PhantomData, +} + +impl GpuTypeLayout { + /// Gets the `TypeLayout`. + pub fn layout(&self) -> TypeLayout { TypeLayout::new_layout_for(&self.ty, T::REPR) } + /// Returns the `LayoutableType` this `GpuTypeLayout` is based on. + pub fn layoutable_type(&self) -> &LayoutableType { &self.ty } +} + +use repr::TypeReprStorageOrPacked; +pub use repr::{TypeRepr, Repr}; +/// Module for all restrictions on `GpuTypeLayout`. +pub mod repr { + use super::*; + + /// Type representation used by `GpuTypeLayout`. This provides guarantees + /// about the alignment rules that the type layout adheres to. + /// See [`GpuTypeLayout`] documentation for more details. + pub trait TypeRepr: Clone + PartialEq + Eq { + /// The corresponding enum variant of `Repr`. + const REPR: Repr; + } + /// 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 {} + + /// 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, + } + + 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) } + } + + 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 { + ($($repr:ident),*) => { + $( + /// A type representation used by `GpuTypeLayout`. + /// See [`GpuTypeLayout`] documentation for more details. + #[derive(Clone, PartialEq, Eq, Hash)] + pub struct $repr; + impl TypeRepr for $repr { + const REPR: Repr = Repr::$repr; + } + )* + }; + } + type_repr!(Storage, Uniform, Packed); +} + +impl TypeLayout { + pub(crate) fn new(byte_size: Option, byte_align: U32PowerOf2, kind: TypeLayoutSemantics) -> Self { + TypeLayout { + byte_size, + align: byte_align, + kind, + } + } +} + +/// a sized or unsized struct type with 0 or more fields +#[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, +} + +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 { + /// The layout information for the field + pub field: FieldLayout, + /// 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 { + 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 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 { + /// 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 } + + /// Returns the alignment requirement of the represented type. + pub fn align(&self) -> U32PowerOf2 { self.align } + + /// 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 { + write!(f, " size={size}")?; + } else { + write!(f, " size=?")?; + } + writeln!(f, ",")?; + } + } + write!(f, "{indent}}}")?; + write!(f, " align={}", self.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, + ) + } + + // 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: layoutable::LayoutableType = store_type.try_into()?; + Ok(TypeLayout::new_layout_for(&t, Repr::Storage)) + } +} + +#[allow(missing_docs)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct FieldLayout { + pub name: CanonName, + pub ty: TypeLayout, +} + +impl FieldLayout { + 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 { self.ty.align() } +} + +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_traits.rs b/shame/src/frontend/rust_types/type_traits.rs index 0157f57..730b2c8 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, repr}, AsAny, GpuType, ToGpuType, }; -use crate::frontend::any::shared_io::{BindPath, BindingType}; +use crate::{ + any::BufferBindingType, + frontend::any::shared_io::{BindPath, BindingType}, + TypeLayout, +}; use crate::{ call_info, common::proc_macro_utils::push_wrong_amount_of_args_error, @@ -101,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` /// @@ -112,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 @@ -168,7 +177,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 +195,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 +206,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 +216,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..5974cfb 100644 --- a/shame/src/frontend/rust_types/vec.rs +++ b/shame/src/frontend/rust_types/vec.rs @@ -7,11 +7,15 @@ use super::{ mem::AddressSpace, reference::{AccessMode, AccessModeReadable}, scalar_type::{dtype_as_scalar_from_f64, ScalarType, ScalarTypeInteger, ScalarTypeNumber}, - type_layout::TypeLayoutRules, + type_layout::repr, type_traits::{BindingArgs, GpuAligned, GpuStoreImplCategory, NoAtomics, NoHandles, VertexAttribute}, AsAny, GpuType, To, ToGpuType, }; use crate::{ + any::{ + layout::{self, Layoutable, LayoutableSized}, + BufferBindingType, + }, call_info, common::{ proc_macro_utils::{collect_into_array_exact, push_wrong_amount_of_args_error}, @@ -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); @@ -551,29 +555,50 @@ 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()) } } -impl GpuLayout for vec { - fn gpu_layout() -> TypeLayout { TypeLayout::from_sized_ty(TypeLayoutRules::Wgsl, &::sized_ty()) } + +impl LayoutableSized for vec +where + vec: NoBools, +{ + fn layoutable_type_sized() -> layout::SizedType { + layout::Vector::new(T::SCALAR_TYPE.try_into().expect("no bools"), L::LEN).into() + } +} +impl Layoutable for vec +where + vec: NoBools, +{ + fn layoutable_type() -> layout::LayoutableType { Self::layoutable_type_sized().into() } +} + +impl GpuLayout for vec +where + vec: NoBools, +{ + type GpuRepr = repr::Storage; fn cpu_type_name_and_layout() -> Option, TypeLayout), super::layout_traits::ArrayElementsUnsizedError>> @@ -1095,7 +1120,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.try_into().expect("no bools vec")) + } } 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..0ad319e 100644 --- a/shame/src/ir/ir_type/align_size.rs +++ b/shame/src/ir/ir_type/align_size.rs @@ -6,13 +6,13 @@ 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 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 deleted file mode 100644 index 8d7b80f..0000000 --- a/shame/src/ir/ir_type/layout_constraints.rs +++ /dev/null @@ -1,807 +0,0 @@ -use std::{ - fmt::{Display, Write}, - num::NonZeroU32, - rc::Rc, -}; - -use thiserror::Error; - -use crate::{common::proc_macro_reexports::TypeLayoutRules, frontend::rust_types::type_layout::TypeLayout}; -use crate::{ - backend::language::Language, - call_info, - common::prettify::set_color, - frontend::{ - any::shared_io::BufferBindingType, encoding::EncodingErrorKind, rust_types::type_layout::LayoutMismatch, - }, - 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(TypeLayoutRules::Wgsl, &self.ctx.top_level_type) { - 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(TypeLayoutRules::Wgsl, &self.ctx.top_level_type) { - 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(TypeLayoutRules::Wgsl, &self.ctx.top_level_type) { - 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::*; 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/src/ir/ir_type/tensor.rs b/shame/src/ir/ir_type/tensor.rs index 9933b87..9783eaf 100644 --- a/shame/src/ir/ir_type/tensor.rs +++ b/shame/src/ir/ir_type/tensor.rs @@ -1,7 +1,9 @@ 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, }; @@ -73,23 +75,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 +92,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) @@ -442,6 +480,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, @@ -458,6 +497,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 +530,17 @@ impl PackedVector { } } - pub fn align(&self) -> u64 { - match self.byte_size() { + 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(), 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/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 4e9e1f0..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, @@ -32,7 +33,7 @@ use crate::{ error::InternalError, rust_types::{ len::x3, - type_layout::{StructLayout, TypeLayoutRules}, + type_layout::{self, layoutable, StructLayout}, }, }, ir::{ @@ -43,7 +44,7 @@ use crate::{ StructureFieldNamesMustBeUnique, TextureFormatWrapper, Type, }, results::DepthStencilState, - BindingIter, DepthLhs, StencilMasking, Test, + BindingIter, DepthLhs, StencilMasking, Test, TypeLayout, }; @@ -71,7 +72,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 +353,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: layoutable::SizedStruct = sized_struct + .try_into() + .expect("push constants are NoBools and NoHandles"); + 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"), + }; let mut ranges = ByteRangesPerStage::default(); @@ -408,7 +416,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..d38dc75 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, @@ -315,10 +315,10 @@ 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; -pub use frontend::rust_types::type_layout::TypeLayoutError; -pub use frontend::rust_types::layout_traits::ArrayElementsUnsizedError; // derived traits pub use frontend::rust_types::type_traits::GpuStore; @@ -331,6 +331,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; @@ -462,6 +463,61 @@ pub mod any { pub use crate::ir::ir_type::StructureDefinitionError; pub use crate::ir::ir_type::StructureFieldNamesMustBeUnique; + pub mod layout { + 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::Storage; + pub use type_layout::repr::Uniform; + pub use type_layout::repr::Packed; + } + pub use type_layout::TypeLayoutSemantics; + pub use type_layout::StructLayout; + pub use type_layout::FieldLayoutWithOffset; + pub use type_layout::FieldLayout; + pub use type_layout::ElementLayout; + // layoutable traits + pub use type_layout::layoutable::Layoutable; + pub use type_layout::layoutable::LayoutableSized; + // layoutable types + 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 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; + pub use type_layout::layoutable::FieldOptions; + // layout calculation utility + 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; + pub use type_layout::layoutable::FieldOffsets; + // conversion and builder errors + pub use type_layout::layoutable::builder::IsUnsizedStructError; + pub use 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 b137a23..85115ed 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] @@ -228,10 +228,12 @@ 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 - assert_eq!(OnGpu::gpu_layout(), OnCpu::cpu_layout()); + assert_eq!(gpu_layout::(), cpu_layout::()); } #[test] @@ -246,11 +248,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() == 16); - assert!(OnCpu::cpu_layout().byte_size() == Some(12)); - assert!(OnCpu::cpu_layout().align() == 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 +285,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 +320,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 +338,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 +360,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 +387,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 +410,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 +429,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 {} } } diff --git a/shame_derive/src/derive_layout.rs b/shame_derive/src/derive_layout.rs index c9fe351..cf6d070 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,16 @@ 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(...)] let repr_c_attr = util::try_parse_repr(&input.attrs)?; @@ -194,35 +199,67 @@ 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 layoutable_type_fn = quote! { + let result = #re::LayoutableType::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::Layoutable>::layoutable_type() + ),)* + ] + ); + + match result { + 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 + // 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_layoutable = quote! { + impl<#generics_decl> #re::Layoutable 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, 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 layoutable_type() -> #re::LayoutableType { + #layoutable_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::LayoutableSized for #derive_struct_ident<#(#idents_of_generics),*> + where + #(#field_type: #re::NoBools + #re::NoHandles + #triv #re::LayoutableSized,)* + #where_clause_predicates + { + fn layoutable_type_sized() -> #re::SizedType { + match { #layoutable_type_fn } { + #re::LayoutableType::Sized(s) => s, + _ => unreachable!("ensured by LayoutableSized 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::LayoutableSized,)* + #last_field_type: #re::Layoutable, + #where_clause_predicates + { + type GpuRepr = #gpu_repr_shame; fn cpu_type_name_and_layout() -> Option, #re::TypeLayout), #re::ArrayElementsUnsizedError>> { use #re::CpuLayout as _; @@ -243,8 +280,7 @@ pub fn impl_for_struct( where #(#triv #field_type: #re::VertexAttribute,)* #where_clause_predicates - { - } + { } }; @@ -322,10 +358,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_layoutable #impl_gpu_layout #impl_vertex_buffer_layout #impl_fake_auto_traits @@ -334,12 +371,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_layoutable #impl_gpu_layout #impl_vertex_buffer_layout #impl_fake_auto_traits @@ -446,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 { diff --git a/shame_derive/src/util.rs b/shame_derive/src/util.rs index afa6930..4e23ebc 100644 --- a/shame_derive/src/util.rs +++ b/shame_derive/src/util.rs @@ -27,16 +27,28 @@ pub fn find_literal_list_attr( Ok(None) } -pub fn find_gpu_repr_packed(attribs: &[syn::Attribute]) -> Result> { +pub enum Repr { + Packed, + Storage, +} + +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(()); } + 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)