use std::{
any::Any,
borrow::Cow,
fmt::Display,
num::{NonZeroU32, NonZeroU64},
};
use bevy::{
math::{Vec2, Vec3, Vec4},
reflect::{
structs::{FieldIter, Struct, StructInfo},
utility::{GenericTypePathCell, NonGenericTypeInfoCell},
ApplyError, FromReflect, FromType, GetTypeRegistration, NamedField, PartialReflect,
Reflect, ReflectDeserialize, ReflectFromReflect, ReflectMut, ReflectOwned, ReflectRef,
ReflectSerialize, TypeInfo, TypePath, TypeRegistration, Typed,
},
};
use serde::{Deserialize, Serialize};
use crate::{
graph::{ScalarValue, Value, VectorValue},
ToWgslString,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
#[non_exhaustive]
pub enum ScalarType {
Bool,
Float,
Int,
Uint,
}
impl Display for ScalarType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Bool => write!(f, "bool"),
Self::Float => write!(f, "f32"),
Self::Int => write!(f, "i32"),
Self::Uint => write!(f, "u32"),
}
}
}
impl ScalarType {
pub fn is_numeric(&self) -> bool {
!(matches!(self, ScalarType::Bool))
}
pub const fn size(&self) -> usize {
4
}
pub const fn align(&self) -> usize {
4
}
}
impl ToWgslString for ScalarType {
fn to_wgsl_string(&self) -> String {
match self {
ScalarType::Bool => "bool",
ScalarType::Float => "f32",
ScalarType::Int => "i32",
ScalarType::Uint => "u32",
}
.to_string()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
pub struct VectorType {
elem_type: ScalarType,
count: u8,
}
impl Display for VectorType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "vec{}<{}>", self.count, self.elem_type)
}
}
impl VectorType {
pub const VEC2B: VectorType = VectorType::new(ScalarType::Bool, 2);
pub const VEC3B: VectorType = VectorType::new(ScalarType::Bool, 3);
pub const VEC4B: VectorType = VectorType::new(ScalarType::Bool, 4);
pub const VEC2F: VectorType = VectorType::new(ScalarType::Float, 2);
pub const VEC3F: VectorType = VectorType::new(ScalarType::Float, 3);
pub const VEC4F: VectorType = VectorType::new(ScalarType::Float, 4);
pub const VEC2I: VectorType = VectorType::new(ScalarType::Int, 2);
pub const VEC3I: VectorType = VectorType::new(ScalarType::Int, 3);
pub const VEC4I: VectorType = VectorType::new(ScalarType::Int, 4);
pub const VEC2U: VectorType = VectorType::new(ScalarType::Uint, 2);
pub const VEC3U: VectorType = VectorType::new(ScalarType::Uint, 3);
pub const VEC4U: VectorType = VectorType::new(ScalarType::Uint, 4);
pub const fn new(elem_type: ScalarType, count: u8) -> Self {
assert!(count >= 2 && count <= 4);
Self { elem_type, count }
}
pub const fn elem_type(&self) -> ScalarType {
self.elem_type
}
pub const fn count(&self) -> usize {
self.count as usize
}
pub fn is_numeric(&self) -> bool {
self.elem_type.is_numeric()
}
pub const fn size(&self) -> usize {
self.count() * self.elem_type.size()
}
pub const fn align(&self) -> usize {
if self.count >= 3 {
4 * self.elem_type.align()
} else {
2 * self.elem_type.align()
}
}
}
impl ToWgslString for VectorType {
fn to_wgsl_string(&self) -> String {
format!("vec{}<{}>", self.count, self.elem_type.to_wgsl_string())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
pub struct MatrixType {
rows: u8,
cols: u8,
}
impl Display for MatrixType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "mat{}x{}<f32>", self.cols, self.rows)
}
}
impl MatrixType {
pub const MAT2X2F: MatrixType = MatrixType::new(2, 2);
pub const MAT3X2F: MatrixType = MatrixType::new(3, 2);
pub const MAT4X2F: MatrixType = MatrixType::new(4, 2);
pub const MAT2X3F: MatrixType = MatrixType::new(2, 3);
pub const MAT3X3F: MatrixType = MatrixType::new(3, 3);
pub const MAT4X3F: MatrixType = MatrixType::new(4, 3);
pub const MAT2X4F: MatrixType = MatrixType::new(2, 4);
pub const MAT3X4F: MatrixType = MatrixType::new(3, 4);
pub const MAT4X4F: MatrixType = MatrixType::new(4, 4);
pub const fn new(cols: u8, rows: u8) -> Self {
assert!(cols >= 2 && cols <= 4);
assert!(rows >= 2 && rows <= 4);
Self { cols, rows }
}
pub const fn cols(&self) -> usize {
self.cols as usize
}
pub const fn rows(&self) -> usize {
self.rows as usize
}
pub const fn size(&self) -> usize {
if self.rows >= 3 {
self.cols() * VectorType::VEC4F.size()
} else {
self.cols() * VectorType::VEC2F.size()
}
}
pub const fn align(&self) -> usize {
VectorType::new(ScalarType::Float, self.rows).align()
}
}
impl ToWgslString for MatrixType {
fn to_wgsl_string(&self) -> String {
format!(
"mat{}x{}<{}>",
self.cols,
self.rows,
ScalarType::Float.to_wgsl_string()
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
#[non_exhaustive]
pub enum ValueType {
Scalar(ScalarType),
Vector(VectorType),
Matrix(MatrixType),
}
impl Display for ValueType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ValueType::Scalar(s) => s.fmt(f),
ValueType::Vector(v) => v.fmt(f),
ValueType::Matrix(m) => m.fmt(f),
}
}
}
impl ValueType {
pub fn is_numeric(&self) -> bool {
match self {
ValueType::Scalar(s) => s.is_numeric(),
ValueType::Vector(v) => v.is_numeric(),
ValueType::Matrix(_) => true,
}
}
pub fn is_scalar(&self) -> bool {
matches!(self, ValueType::Scalar(_))
}
pub fn is_vector(&self) -> bool {
matches!(self, ValueType::Vector(_))
}
pub fn is_matrix(&self) -> bool {
matches!(self, ValueType::Matrix(_))
}
pub fn size(&self) -> usize {
match self {
ValueType::Scalar(s) => s.size(),
ValueType::Vector(v) => v.size(),
ValueType::Matrix(m) => m.size(),
}
}
pub fn align(&self) -> usize {
match self {
ValueType::Scalar(s) => s.align(),
ValueType::Vector(v) => v.align(),
ValueType::Matrix(m) => m.align(),
}
}
}
impl From<ScalarType> for ValueType {
fn from(value: ScalarType) -> Self {
ValueType::Scalar(value)
}
}
impl From<VectorType> for ValueType {
fn from(value: VectorType) -> Self {
ValueType::Vector(value)
}
}
impl From<MatrixType> for ValueType {
fn from(value: MatrixType) -> Self {
ValueType::Matrix(value)
}
}
impl ToWgslString for ValueType {
fn to_wgsl_string(&self) -> String {
match self {
ValueType::Scalar(s) => s.to_wgsl_string(),
ValueType::Vector(v) => v.to_wgsl_string(),
ValueType::Matrix(m) => m.to_wgsl_string(),
}
}
}
#[derive(Debug, Clone, Reflect)]
pub(crate) struct AttributeInner {
name: Cow<'static, str>,
default_value: Value,
}
impl PartialEq for AttributeInner {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
}
}
impl Eq for AttributeInner {}
impl std::hash::Hash for AttributeInner {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.name.hash(state);
}
}
macro_rules! declare_custom_attr_inner {
($t:ident, $T:ty, $name:literal, $new_fn:ident) => {
pub const $t: &'static AttributeInner = &AttributeInner::new(
Cow::Borrowed($name),
Value::Vector(VectorValue::$new_fn(<$T>::ZERO)),
);
};
}
macro_rules! declare_custom_attr_u32_inner {
($t:ident, $name:literal, $scalar_type:ident) => {
pub const $t: &'static AttributeInner = &AttributeInner::new(
Cow::Borrowed($name),
Value::Scalar(ScalarValue::$scalar_type(0)),
);
};
}
impl AttributeInner {
pub const ID: &'static AttributeInner =
&AttributeInner::new(Cow::Borrowed("id"), Value::Scalar(ScalarValue::Uint(0)));
pub const PARTICLE_COUNTER: &'static AttributeInner = &AttributeInner::new(
Cow::Borrowed("particle_counter"),
Value::Scalar(ScalarValue::Uint(0)),
);
pub const POSITION: &'static AttributeInner = &AttributeInner::new(
Cow::Borrowed("position"),
Value::Vector(VectorValue::new_vec3(Vec3::ZERO)),
);
pub const VELOCITY: &'static AttributeInner = &AttributeInner::new(
Cow::Borrowed("velocity"),
Value::Vector(VectorValue::new_vec3(Vec3::ZERO)),
);
pub const AGE: &'static AttributeInner =
&AttributeInner::new(Cow::Borrowed("age"), Value::Scalar(ScalarValue::Float(0.)));
pub const LIFETIME: &'static AttributeInner = &AttributeInner::new(
Cow::Borrowed("lifetime"),
Value::Scalar(ScalarValue::Float(1.)),
);
pub const COLOR: &'static AttributeInner = &AttributeInner::new(
Cow::Borrowed("color"),
Value::Scalar(ScalarValue::Uint(0xFFFFFFFFu32)),
);
pub const HDR_COLOR: &'static AttributeInner = &AttributeInner::new(
Cow::Borrowed("hdr_color"),
Value::Vector(VectorValue::new_vec4(Vec4::ONE)),
);
pub const ALPHA: &'static AttributeInner = &AttributeInner::new(
Cow::Borrowed("alpha"),
Value::Scalar(ScalarValue::Float(1.)),
);
pub const SIZE: &'static AttributeInner =
&AttributeInner::new(Cow::Borrowed("size"), Value::Scalar(ScalarValue::Float(1.)));
pub const SIZE2: &'static AttributeInner = &AttributeInner::new(
Cow::Borrowed("size2"),
Value::Vector(VectorValue::new_vec2(Vec2::ONE)),
);
pub const SIZE3: &'static AttributeInner = &AttributeInner::new(
Cow::Borrowed("size3"),
Value::Vector(VectorValue::new_vec3(Vec3::ONE)),
);
pub const PREV: &'static AttributeInner = &AttributeInner::new(
Cow::Borrowed("prev"),
Value::Scalar(ScalarValue::Uint(!0u32)),
);
pub const NEXT: &'static AttributeInner = &AttributeInner::new(
Cow::Borrowed("next"),
Value::Scalar(ScalarValue::Uint(!0u32)),
);
pub const AXIS_X: &'static AttributeInner = &AttributeInner::new(
Cow::Borrowed("axis_x"),
Value::Vector(VectorValue::new_vec3(Vec3::X)),
);
pub const AXIS_Y: &'static AttributeInner = &AttributeInner::new(
Cow::Borrowed("axis_y"),
Value::Vector(VectorValue::new_vec3(Vec3::Y)),
);
pub const AXIS_Z: &'static AttributeInner = &AttributeInner::new(
Cow::Borrowed("axis_z"),
Value::Vector(VectorValue::new_vec3(Vec3::Z)),
);
pub const SPRITE_INDEX: &'static AttributeInner = &AttributeInner::new(
Cow::Borrowed("sprite_index"),
Value::Scalar(ScalarValue::Int(0)),
);
pub const F32_0: &'static AttributeInner = &AttributeInner::new(
Cow::Borrowed("f32_0"),
Value::Scalar(ScalarValue::Float(0.)),
);
pub const F32_1: &'static AttributeInner = &AttributeInner::new(
Cow::Borrowed("f32_1"),
Value::Scalar(ScalarValue::Float(0.)),
);
pub const F32_2: &'static AttributeInner = &AttributeInner::new(
Cow::Borrowed("f32_2"),
Value::Scalar(ScalarValue::Float(0.)),
);
pub const F32_3: &'static AttributeInner = &AttributeInner::new(
Cow::Borrowed("f32_3"),
Value::Scalar(ScalarValue::Float(0.)),
);
declare_custom_attr_inner!(F32X2_0, Vec2, "f32x2_0", new_vec2);
declare_custom_attr_inner!(F32X2_1, Vec2, "f32x2_1", new_vec2);
declare_custom_attr_inner!(F32X2_2, Vec2, "f32x2_2", new_vec2);
declare_custom_attr_inner!(F32X2_3, Vec2, "f32x2_3", new_vec2);
declare_custom_attr_inner!(F32X3_0, Vec3, "f32x3_0", new_vec3);
declare_custom_attr_inner!(F32X3_1, Vec3, "f32x3_1", new_vec3);
declare_custom_attr_inner!(F32X3_2, Vec3, "f32x3_2", new_vec3);
declare_custom_attr_inner!(F32X3_3, Vec3, "f32x3_3", new_vec3);
declare_custom_attr_inner!(F32X4_0, Vec4, "f32x4_0", new_vec4);
declare_custom_attr_inner!(F32X4_1, Vec4, "f32x4_1", new_vec4);
declare_custom_attr_inner!(F32X4_2, Vec4, "f32x4_2", new_vec4);
declare_custom_attr_inner!(F32X4_3, Vec4, "f32x4_3", new_vec4);
declare_custom_attr_u32_inner!(U32_0, "u32_0", Uint);
declare_custom_attr_u32_inner!(U32_1, "u32_1", Uint);
declare_custom_attr_u32_inner!(U32_2, "u32_2", Uint);
declare_custom_attr_u32_inner!(U32_3, "u32_3", Uint);
pub const RIBBON_ID: &'static AttributeInner = &AttributeInner::new(
Cow::Borrowed("ribbon_id"),
Value::Scalar(ScalarValue::Uint(0u32)),
);
pub(crate) const PAD0: &'static AttributeInner =
&AttributeInner::new(Cow::Borrowed("pad0"), Value::Scalar(ScalarValue::Uint(0)));
pub(crate) const PAD1: &'static AttributeInner =
&AttributeInner::new(Cow::Borrowed("pad1"), Value::Scalar(ScalarValue::Uint(0)));
pub(crate) const PAD2: &'static AttributeInner =
&AttributeInner::new(Cow::Borrowed("pad2"), Value::Scalar(ScalarValue::Uint(0)));
pub(crate) const PAD3: &'static AttributeInner =
&AttributeInner::new(Cow::Borrowed("pad3"), Value::Scalar(ScalarValue::Uint(0)));
pub(crate) const PAD4: &'static AttributeInner =
&AttributeInner::new(Cow::Borrowed("pad4"), Value::Scalar(ScalarValue::Uint(0)));
#[inline]
pub(crate) const fn new(name: Cow<'static, str>, default_value: Value) -> Self {
Self {
name,
default_value,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(try_from = "&str", into = "&'static str")]
pub struct Attribute(pub(crate) &'static AttributeInner);
impl TryFrom<&str> for Attribute {
type Error = &'static str;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Attribute::from_name(value).ok_or("Unknown attribute name.")
}
}
impl From<Attribute> for &'static str {
fn from(value: Attribute) -> Self {
value.name()
}
}
impl TypePath for Attribute {
fn type_path() -> &'static str {
static CELL: GenericTypePathCell = GenericTypePathCell::new();
CELL.get_or_insert::<Self, _>(|| "bevy_hanabi::attribute::Attribute".to_owned())
}
fn short_type_path() -> &'static str {
static CELL: GenericTypePathCell = GenericTypePathCell::new();
CELL.get_or_insert::<Self, _>(|| "Attribute".to_owned())
}
fn type_ident() -> Option<&'static str> {
Some("Attribute")
}
fn crate_name() -> Option<&'static str> {
Some("bevy_hanabi")
}
fn module_path() -> Option<&'static str> {
Some("bevy_hanabi::attribute")
}
}
impl Typed for Attribute {
fn type_info() -> &'static TypeInfo {
static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new();
CELL.get_or_set(|| {
let fields = [
NamedField::new::<Cow<str>>("name"),
NamedField::new::<Value>("default_value"),
];
let info = StructInfo::new::<Self>(&fields);
TypeInfo::Struct(info)
})
}
}
impl Struct for Attribute {
fn field(&self, name: &str) -> Option<&dyn PartialReflect> {
match name {
"name" => Some(&self.0.name),
"default_value" => Some(&self.0.default_value),
_ => None,
}
}
fn field_mut(&mut self, _name: &str) -> Option<&mut dyn PartialReflect> {
None
}
fn field_at(&self, index: usize) -> Option<&dyn PartialReflect> {
match index {
0 => Some(&self.0.name),
1 => Some(&self.0.default_value),
_ => None,
}
}
fn field_at_mut(&mut self, _index: usize) -> Option<&mut dyn PartialReflect> {
None
}
fn name_at(&self, index: usize) -> Option<&str> {
match index {
0 => Some("name"),
1 => Some("default_value"),
_ => None,
}
}
fn index_of_name(&self, name: &str) -> Option<usize> {
match name {
"name" => Some(0),
"default_value" => Some(1),
_ => None,
}
}
fn field_len(&self) -> usize {
2
}
fn iter_fields(&self) -> FieldIter<'_> {
FieldIter::new(self)
}
}
impl GetTypeRegistration for Attribute {
fn get_type_registration() -> TypeRegistration {
let mut registration = TypeRegistration::of::<Self>();
registration.insert::<ReflectDeserialize>(FromType::<Self>::from_type());
registration.insert::<ReflectSerialize>(FromType::<Self>::from_type());
registration.insert::<ReflectFromReflect>(FromType::<Self>::from_type());
registration
}
}
impl PartialReflect for Attribute {
fn get_represented_type_info(&self) -> Option<&'static TypeInfo> {
Some(<Self as Typed>::type_info())
}
#[inline]
fn into_partial_reflect(self: Box<Self>) -> Box<dyn PartialReflect> {
self
}
#[inline]
fn as_partial_reflect(&self) -> &dyn PartialReflect {
self
}
#[inline]
fn as_partial_reflect_mut(&mut self) -> &mut dyn PartialReflect {
self
}
#[inline]
fn try_into_reflect(self: Box<Self>) -> Result<Box<dyn Reflect>, Box<dyn PartialReflect>> {
Ok(self)
}
#[inline]
fn try_as_reflect(&self) -> Option<&dyn Reflect> {
Some(self)
}
#[inline]
fn try_as_reflect_mut(&mut self) -> Option<&mut dyn Reflect> {
Some(self)
}
fn try_apply(&mut self, value: &dyn PartialReflect) -> Result<(), ApplyError> {
if let Some(value) = value.try_downcast_ref::<Self>() {
*self = *value;
Ok(())
} else {
Err(ApplyError::MismatchedTypes {
from_type: value.reflect_type_path().into(),
to_type: Self::type_path().into(),
})
}
}
#[inline]
fn reflect_ref(&self) -> ReflectRef<'_> {
ReflectRef::Struct(self)
}
#[inline]
fn reflect_mut(&mut self) -> ReflectMut<'_> {
ReflectMut::Struct(self)
}
#[inline]
fn reflect_owned(self: Box<Self>) -> ReflectOwned {
ReflectOwned::Struct(self)
}
}
impl Reflect for Attribute {
#[inline]
fn into_any(self: Box<Self>) -> Box<dyn Any> {
self
}
#[inline]
fn as_any(&self) -> &dyn Any {
self
}
#[inline]
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
#[inline]
fn into_reflect(self: Box<Self>) -> Box<dyn Reflect> {
self
}
#[inline]
fn as_reflect(&self) -> &dyn Reflect {
self
}
#[inline]
fn as_reflect_mut(&mut self) -> &mut dyn Reflect {
self
}
#[inline]
fn set(&mut self, value: Box<dyn Reflect>) -> Result<(), Box<dyn Reflect>> {
*self = value.take()?;
Ok(())
}
}
impl FromReflect for Attribute {
fn from_reflect(reflect: &dyn PartialReflect) -> Option<Self> {
Attribute::from_name(
reflect
.try_as_reflect()?
.as_any()
.downcast_ref::<String>()?,
)
}
}
macro_rules! declare_custom_attr_pub {
($t: ident, $name: literal, $count: literal, $vector_type: ident) => {
#[doc = concat!("A generic vector float attribute with ", $count, " components.\n\n This attribute can be used for anything. It has no specific meaning. You can store whatever per-particle value you want in it (for example, at spawn time) and read it back later.\n\n# Name\n\n`", $name, "`\n\n# Type\n\n[`VectorType::", stringify!($vector_type), "`]")]
pub const $t: Attribute = Attribute(AttributeInner::$t);
};
}
macro_rules! declare_custom_attr_u32_pub {
($t: ident, $name: literal, $scalar_type: ident) => {
#[doc = concat!("A generic scalar uint attribute.\n\n This attribute can be used for anything. It has no specific meaning. You can store whatever per-particle value you want in it (for example, at spawn time) and read it back later.\n\n# Name\n\n`", $name, "`\n\n# Type\n\n[`ScalarType::", stringify!($scalar_type), "`]")]
pub const $t: Attribute = Attribute(AttributeInner::$t);
};
}
impl Attribute {
pub const ID: Attribute = Attribute(AttributeInner::ID);
pub const PARTICLE_COUNTER: Attribute = Attribute(AttributeInner::PARTICLE_COUNTER);
pub const POSITION: Attribute = Attribute(AttributeInner::POSITION);
pub const VELOCITY: Attribute = Attribute(AttributeInner::VELOCITY);
pub const AGE: Attribute = Attribute(AttributeInner::AGE);
pub const LIFETIME: Attribute = Attribute(AttributeInner::LIFETIME);
pub const COLOR: Attribute = Attribute(AttributeInner::COLOR);
pub const HDR_COLOR: Attribute = Attribute(AttributeInner::HDR_COLOR);
pub const ALPHA: Attribute = Attribute(AttributeInner::ALPHA);
pub const SIZE: Attribute = Attribute(AttributeInner::SIZE);
pub const SIZE2: Attribute = Attribute(AttributeInner::SIZE2);
pub const SIZE3: Attribute = Attribute(AttributeInner::SIZE3);
pub const PREV: Attribute = Attribute(AttributeInner::PREV);
pub const NEXT: Attribute = Attribute(AttributeInner::NEXT);
pub const AXIS_X: Attribute = Attribute(AttributeInner::AXIS_X);
pub const AXIS_Y: Attribute = Attribute(AttributeInner::AXIS_Y);
pub const AXIS_Z: Attribute = Attribute(AttributeInner::AXIS_Z);
pub const SPRITE_INDEX: Attribute = Attribute(AttributeInner::SPRITE_INDEX);
pub const F32_0: Attribute = Attribute(AttributeInner::F32_0);
pub const F32_1: Attribute = Attribute(AttributeInner::F32_1);
pub const F32_2: Attribute = Attribute(AttributeInner::F32_2);
pub const F32_3: Attribute = Attribute(AttributeInner::F32_3);
declare_custom_attr_pub!(F32X2_0, "f32x2_0", 2, VEC2F);
declare_custom_attr_pub!(F32X2_1, "f32x2_1", 2, VEC2F);
declare_custom_attr_pub!(F32X2_2, "f32x2_2", 2, VEC2F);
declare_custom_attr_pub!(F32X2_3, "f32x2_3", 2, VEC2F);
declare_custom_attr_pub!(F32X3_0, "f32x3_0", 3, VEC3F);
declare_custom_attr_pub!(F32X3_1, "f32x3_1", 3, VEC3F);
declare_custom_attr_pub!(F32X3_2, "f32x3_2", 3, VEC3F);
declare_custom_attr_pub!(F32X3_3, "f32x3_3", 3, VEC3F);
declare_custom_attr_pub!(F32X4_0, "f32x4_0", 4, VEC4F);
declare_custom_attr_pub!(F32X4_1, "f32x4_1", 4, VEC4F);
declare_custom_attr_pub!(F32X4_2, "f32x4_2", 4, VEC4F);
declare_custom_attr_pub!(F32X4_3, "f32x4_3", 4, VEC4F);
declare_custom_attr_u32_pub!(U32_0, "u32_0", Uint);
declare_custom_attr_u32_pub!(U32_1, "u32_1", Uint);
declare_custom_attr_u32_pub!(U32_2, "u32_2", Uint);
declare_custom_attr_u32_pub!(U32_3, "u32_3", Uint);
pub const RIBBON_ID: Attribute = Attribute(AttributeInner::RIBBON_ID);
const ALL: [Attribute; 39] = [
Attribute::ID,
Attribute::PARTICLE_COUNTER,
Attribute::POSITION,
Attribute::VELOCITY,
Attribute::AGE,
Attribute::LIFETIME,
Attribute::COLOR,
Attribute::HDR_COLOR,
Attribute::ALPHA,
Attribute::SIZE,
Attribute::SIZE2,
Attribute::SIZE3,
Attribute::PREV,
Attribute::NEXT,
Attribute::AXIS_X,
Attribute::AXIS_Y,
Attribute::AXIS_Z,
Attribute::SPRITE_INDEX,
Attribute::F32_0,
Attribute::F32_1,
Attribute::F32_2,
Attribute::F32_3,
Attribute::F32X2_0,
Attribute::F32X2_1,
Attribute::F32X2_2,
Attribute::F32X2_3,
Attribute::F32X3_0,
Attribute::F32X3_1,
Attribute::F32X3_2,
Attribute::F32X3_3,
Attribute::F32X4_0,
Attribute::F32X4_1,
Attribute::F32X4_2,
Attribute::F32X4_3,
Attribute::U32_0,
Attribute::U32_1,
Attribute::U32_2,
Attribute::U32_3,
Attribute::RIBBON_ID,
];
pub(crate) const PAD0: Attribute = Attribute(AttributeInner::PAD0);
pub(crate) const PAD1: Attribute = Attribute(AttributeInner::PAD1);
pub(crate) const PAD2: Attribute = Attribute(AttributeInner::PAD2);
pub(crate) const PAD3: Attribute = Attribute(AttributeInner::PAD3);
pub(crate) const PAD4: Attribute = Attribute(AttributeInner::PAD4);
pub fn from_name(name: &str) -> Option<Attribute> {
Attribute::ALL
.iter()
.find(|&attr| attr.name() == name)
.copied()
}
pub fn all() -> &'static [Attribute] {
&Self::ALL
}
#[inline]
pub fn name(&self) -> &'static str {
self.0.name.as_ref()
}
#[inline]
pub fn default_value(&self) -> Value {
self.0.default_value
}
#[inline]
pub fn value_type(&self) -> ValueType {
self.0.default_value.value_type()
}
#[inline]
pub fn size(&self) -> usize {
self.value_type().size()
}
#[inline]
pub fn align(&self) -> usize {
self.value_type().align()
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct AttributeLayout {
pub attribute: Attribute,
pub offset: u32,
}
impl std::fmt::Debug for AttributeLayout {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!(
"(+{}) {}: {}",
self.offset,
self.attribute.name(),
self.attribute.value_type().to_wgsl_string(),
))
}
}
#[derive(Debug, Default, Clone)]
pub struct ParticleLayoutBuilder {
layout: Vec<AttributeLayout>,
}
impl ParticleLayoutBuilder {
pub fn append(mut self, attribute: Attribute) -> Self {
self.layout.push(AttributeLayout {
attribute,
offset: 0, });
self
}
pub fn build(mut self) -> ParticleLayout {
let pads = [
Attribute::PAD0,
Attribute::PAD1,
Attribute::PAD2,
Attribute::PAD3,
Attribute::PAD4,
];
let mut next_pad = 0;
self.layout.sort_unstable_by_key(|la| la.attribute.name());
self.layout.dedup_by_key(|la| la.attribute.name());
self.layout.sort_unstable_by_key(|la| la.attribute.size());
let unpadded_len = self.layout.len() as u32;
let mut layout = vec![];
let mut offset = 0;
let mut align = 4;
let index4 = self
.layout
.partition_point(|attr| attr.attribute.size() < 16);
for i in index4..self.layout.len() {
let mut attr = self.layout[i];
attr.offset = offset;
offset += 16;
layout.push(attr);
}
let num4 = self.layout.len() - index4;
if num4 > 0 {
align = 16;
}
let index2 = self
.layout
.partition_point(|attr| attr.attribute.size() < 8);
let num1 = index2;
let index3 = self
.layout
.partition_point(|attr| attr.attribute.size() < 12);
let num2 = (index2..index3).len();
let num3 = (index3..index4).len();
if num3 > 0 {
align = 16;
} else if num2 > 0 {
align = align.max(8);
}
let num_pairs = num1.min(num3);
for i in 0..num_pairs {
let mut attr = self.layout[index3 + i];
attr.offset = offset;
offset += 12;
layout.push(attr);
let mut attr = self.layout[i];
attr.offset = offset;
offset += 4;
layout.push(attr);
}
let index1 = num_pairs;
let index3 = index3 + num_pairs;
let num1 = num1 - num_pairs;
let num3 = num3 - num_pairs;
for i in 0..(num2 / 2) {
for j in 0..2 {
let mut attr = self.layout[index2 + i * 2 + j];
attr.offset = offset;
offset += 8;
layout.push(attr);
}
}
let index2 = index2 + (num2 / 2) * 2;
let num2 = num2 % 2;
if num3 > 0 {
debug_assert_eq!(num1, 0);
for i in 0..num3 {
let mut attr = self.layout[index3 + i];
attr.offset = offset;
layout.push(attr);
let pad = AttributeLayout {
attribute: pads[next_pad],
offset: offset + 12,
};
next_pad += 1;
layout.push(pad);
offset += 16;
}
}
if num2 > 0 {
debug_assert_eq!(num2, 1);
let mut attr = self.layout[index2];
attr.offset = offset;
offset += 8;
layout.push(attr);
}
for i in 0..num1 {
let mut attr = self.layout[index1 + i];
attr.offset = offset;
layout.push(attr);
offset += 4;
}
let rem = offset.next_multiple_of(align) - offset;
if rem > 0 {
debug_assert_eq!(rem % 4, 0);
let num = rem / 4;
for _ in 0..num {
debug_assert!(next_pad < 3);
let pad = AttributeLayout {
attribute: pads[next_pad],
offset,
};
layout.push(pad);
next_pad += 1;
offset += 4;
}
}
ParticleLayout {
layout,
align,
unpadded_len,
}
}
}
impl From<&ParticleLayout> for ParticleLayoutBuilder {
fn from(layout: &ParticleLayout) -> Self {
Self {
layout: layout.layout.clone(),
}
}
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct ParticleLayout {
layout: Vec<AttributeLayout>,
align: u32,
unpadded_len: u32,
}
impl std::fmt::Debug for ParticleLayout {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_list().entries(self.layout.iter()).finish()
}
}
impl Default for ParticleLayout {
fn default() -> Self {
ParticleLayout::new()
.append(Attribute::POSITION)
.append(Attribute::AGE)
.append(Attribute::VELOCITY)
.append(Attribute::LIFETIME)
.build()
}
}
impl ParticleLayout {
pub const fn empty() -> ParticleLayout {
Self {
layout: vec![],
align: 4,
unpadded_len: 0,
}
}
#[allow(clippy::new_ret_no_self)]
pub fn new() -> ParticleLayoutBuilder {
ParticleLayoutBuilder::default()
}
pub fn merged_with(
&self,
attributes: &[Attribute],
) -> ParticleLayout {
let mut builder = ParticleLayoutBuilder::from(self);
for attr in attributes {
builder = builder.append(*attr);
}
builder.build()
}
pub fn is_empty(&self) -> bool {
self.unpadded_len == 0
}
pub fn len(&self) -> u32 {
self.unpadded_len
}
pub fn size(&self) -> u32 {
if self.layout.is_empty() {
0
} else {
let last_attr = self.layout.last().unwrap();
last_attr.offset + last_attr.attribute.size() as u32
}
}
pub fn align(&self) -> u32 {
self.align
}
pub fn min_binding_size(&self) -> NonZeroU64 {
let size = self.size();
NonZeroU64::new(size.next_multiple_of(self.align) as u64).unwrap()
}
pub fn min_binding_size32(&self) -> NonZeroU32 {
let size = self.size();
NonZeroU32::new(size.next_multiple_of(self.align)).unwrap()
}
pub fn attributes(&self) -> impl ExactSizeIterator<Item = &AttributeLayout> {
self.layout.iter()
}
pub fn contains(&self, attribute: Attribute) -> bool {
self.layout
.iter()
.any(|&entry| entry.attribute.name() == attribute.name())
}
pub fn byte_offset(&self, attribute: Attribute) -> Option<u32> {
self.layout.iter().find_map(|&entry| {
if entry.attribute.name() == attribute.name() {
Some(entry.offset)
} else {
None
}
})
}
pub fn generate_code(&self) -> String {
self.layout
.iter()
.map(|entry| {
format!(
" {}: {},",
entry.attribute.name(),
entry.attribute.value_type().to_wgsl_string()
)
})
.fold(String::new(), |mut a, b| {
a.reserve(b.len() + 1);
a.push_str(&b);
a.push('\n');
a
})
}
}
#[cfg(test)]
mod tests {
use bevy::reflect::TypeRegistration;
use naga::{front::wgsl::Frontend, proc::Layouter};
use super::*;
#[test]
fn value_type_align() {
let mut frontend = Frontend::new();
for (value_type, value) in &[
(
ValueType::Scalar(ScalarType::Float),
Value::Scalar(ScalarValue::Float(0.)),
),
(
ValueType::Scalar(ScalarType::Int),
Value::Scalar(ScalarValue::Int(-42)),
),
(
ValueType::Scalar(ScalarType::Uint),
Value::Scalar(ScalarValue::Uint(999)),
),
(
ValueType::Vector(VectorType {
elem_type: ScalarType::Float,
count: 2,
}),
Value::Vector(VectorValue::new_vec2(Vec2::new(-0.5, 3.458))),
),
(
ValueType::Vector(VectorType {
elem_type: ScalarType::Float,
count: 3,
}),
Value::Vector(VectorValue::new_vec3(Vec3::new(-0.5, 3.458, -53.))),
),
(
ValueType::Vector(VectorType {
elem_type: ScalarType::Float,
count: 4,
}),
Value::Vector(VectorValue::new_vec4(Vec4::new(-0.5, 3.458, 0., -53.))),
),
] {
let src = format!("fn main() {{\nlet x = {};\n}}", value.to_wgsl_string());
let res = frontend.parse(&src);
if let Err(err) = &res {
println!("Error: {:?}", err);
}
assert!(res.is_ok());
let m = res.unwrap();
let (_main_handle, main) = m
.functions
.iter()
.find(|c| c.1.name == Some("main".to_string()))
.unwrap();
let (expr_handle, _expr_name) =
main.named_expressions.iter().find(|c| c.1 == "x").unwrap();
let expr = main.expressions.try_get(*expr_handle).unwrap();
match expr {
naga::ir::Expression::Literal(lit) => {
assert_eq!(lit.width(), value_type.size() as u8);
assert_eq!(lit.width(), value_type.align() as u8);
}
naga::ir::Expression::Compose { ty, .. } => {
let (size, align) = {
let mut layouter = Layouter::default();
assert!(layouter.update(m.to_ctx()).is_ok());
let layout = layouter[*ty];
(layout.size, layout.alignment)
};
assert_eq!(size, value_type.size() as u32);
assert_eq!(
align,
naga::proc::Alignment::new(value_type.align() as u32).unwrap()
);
}
_ => panic!(),
};
}
}
#[test]
fn value_type_is_numeric() {
assert!(!ScalarType::Bool.is_numeric());
assert!(ScalarType::Float.is_numeric());
assert!(ScalarType::Int.is_numeric());
assert!(ScalarType::Uint.is_numeric());
assert!(!VectorType::VEC2B.is_numeric());
assert!(!VectorType::VEC3B.is_numeric());
assert!(!VectorType::VEC4B.is_numeric());
assert!(VectorType::VEC2F.is_numeric());
assert!(VectorType::VEC3F.is_numeric());
assert!(VectorType::VEC4F.is_numeric());
assert!(VectorType::VEC2I.is_numeric());
assert!(VectorType::VEC3I.is_numeric());
assert!(VectorType::VEC4I.is_numeric());
assert!(VectorType::VEC2U.is_numeric());
assert!(VectorType::VEC3U.is_numeric());
assert!(VectorType::VEC4U.is_numeric());
assert!(!ValueType::Scalar(ScalarType::Bool).is_numeric());
assert!(ValueType::Scalar(ScalarType::Float).is_numeric());
assert!(ValueType::Scalar(ScalarType::Int).is_numeric());
assert!(ValueType::Scalar(ScalarType::Uint).is_numeric());
assert!(!ValueType::Vector(VectorType::VEC2B).is_numeric());
assert!(!ValueType::Vector(VectorType::VEC3B).is_numeric());
assert!(!ValueType::Vector(VectorType::VEC4B).is_numeric());
assert!(ValueType::Vector(VectorType::VEC2F).is_numeric());
assert!(ValueType::Vector(VectorType::VEC3F).is_numeric());
assert!(ValueType::Vector(VectorType::VEC4F).is_numeric());
assert!(ValueType::Vector(VectorType::VEC2I).is_numeric());
assert!(ValueType::Vector(VectorType::VEC3I).is_numeric());
assert!(ValueType::Vector(VectorType::VEC4I).is_numeric());
assert!(ValueType::Vector(VectorType::VEC2U).is_numeric());
assert!(ValueType::Vector(VectorType::VEC3U).is_numeric());
assert!(ValueType::Vector(VectorType::VEC4U).is_numeric());
assert!(ValueType::Matrix(MatrixType::MAT2X2F).is_numeric());
assert!(ValueType::Matrix(MatrixType::MAT3X2F).is_numeric());
assert!(ValueType::Matrix(MatrixType::MAT4X2F).is_numeric());
assert!(ValueType::Matrix(MatrixType::MAT2X3F).is_numeric());
assert!(ValueType::Matrix(MatrixType::MAT3X3F).is_numeric());
assert!(ValueType::Matrix(MatrixType::MAT4X3F).is_numeric());
assert!(ValueType::Matrix(MatrixType::MAT2X4F).is_numeric());
assert!(ValueType::Matrix(MatrixType::MAT3X4F).is_numeric());
assert!(ValueType::Matrix(MatrixType::MAT4X4F).is_numeric());
}
#[test]
#[should_panic]
fn vector_type_invalid_rank_1() {
let _ = VectorType::new(ScalarType::Float, 1);
}
#[test]
#[should_panic]
fn vector_type_invalid_rank_5() {
let _ = VectorType::new(ScalarType::Float, 5);
}
#[test]
#[should_panic]
fn matrix_type_invalid_cols_1() {
let _ = MatrixType::new(1, 3);
}
#[test]
#[should_panic]
fn matrix_type_invalid_cols_5() {
let _ = MatrixType::new(5, 3);
}
#[test]
#[should_panic]
fn matrix_type_invalid_rows_1() {
let _ = MatrixType::new(3, 1);
}
#[test]
#[should_panic]
fn matrix_type_invalid_rows_5() {
let _ = MatrixType::new(3, 5);
}
#[test]
fn matrix_type_size() {
assert_eq!(MatrixType::MAT2X2F.size(), 16);
assert_eq!(MatrixType::MAT3X2F.size(), 24);
assert_eq!(MatrixType::MAT4X2F.size(), 32);
assert_eq!(MatrixType::MAT2X3F.size(), 32);
assert_eq!(MatrixType::MAT3X3F.size(), 48);
assert_eq!(MatrixType::MAT4X3F.size(), 64);
assert_eq!(MatrixType::MAT2X4F.size(), 32);
assert_eq!(MatrixType::MAT3X4F.size(), 48);
assert_eq!(MatrixType::MAT4X4F.size(), 64);
}
#[test]
fn matrix_type_align() {
assert_eq!(MatrixType::MAT2X2F.align(), 8);
assert_eq!(MatrixType::MAT3X2F.align(), 8);
assert_eq!(MatrixType::MAT4X2F.align(), 8);
assert_eq!(MatrixType::MAT2X3F.align(), 16);
assert_eq!(MatrixType::MAT3X3F.align(), 16);
assert_eq!(MatrixType::MAT4X3F.align(), 16);
assert_eq!(MatrixType::MAT2X4F.align(), 16);
assert_eq!(MatrixType::MAT3X4F.align(), 16);
assert_eq!(MatrixType::MAT4X4F.align(), 16);
}
#[test]
fn value_type_is_type() {
for t in [
ScalarType::Bool,
ScalarType::Float,
ScalarType::Int,
ScalarType::Uint,
] {
assert!(ValueType::Scalar(t).is_scalar());
assert!(!ValueType::Scalar(t).is_vector());
assert!(!ValueType::Scalar(t).is_matrix());
assert_eq!(ValueType::Scalar(t).size(), t.size());
assert_eq!(ValueType::Scalar(t).align(), t.align());
}
for t in [
VectorType::VEC2B,
VectorType::VEC3B,
VectorType::VEC4B,
VectorType::VEC2F,
VectorType::VEC3F,
VectorType::VEC4F,
VectorType::VEC2I,
VectorType::VEC3I,
VectorType::VEC4I,
VectorType::VEC2U,
VectorType::VEC3U,
VectorType::VEC4U,
] {
assert!(!ValueType::Vector(t).is_scalar());
assert!(ValueType::Vector(t).is_vector());
assert!(!ValueType::Vector(t).is_matrix());
assert_eq!(ValueType::Vector(t).size(), t.size());
assert_eq!(ValueType::Vector(t).align(), t.align());
}
for t in [
MatrixType::MAT2X2F,
MatrixType::MAT3X2F,
MatrixType::MAT4X2F,
MatrixType::MAT2X3F,
MatrixType::MAT3X3F,
MatrixType::MAT4X3F,
MatrixType::MAT2X4F,
MatrixType::MAT3X4F,
MatrixType::MAT4X4F,
] {
assert!(!ValueType::Matrix(t).is_scalar());
assert!(!ValueType::Matrix(t).is_vector());
assert!(ValueType::Matrix(t).is_matrix());
assert_eq!(ValueType::Matrix(t).size(), t.size());
assert_eq!(ValueType::Matrix(t).align(), t.align());
}
}
const TEST_ATTR_NAME: &str = "test_attr";
const TEST_ATTR_INNER: &AttributeInner = &AttributeInner::new(
Cow::Borrowed(TEST_ATTR_NAME),
Value::Vector(VectorValue::new_vec3(Vec3::ONE)),
);
#[test]
fn attr_new() {
let attr = Attribute(TEST_ATTR_INNER);
assert_eq!(attr.name(), TEST_ATTR_NAME);
assert_eq!(attr.size(), 12);
assert_eq!(attr.align(), 16);
assert_eq!(
attr.value_type(),
ValueType::Vector(VectorType {
elem_type: ScalarType::Float,
count: 3
})
);
assert_eq!(
attr.default_value(),
Value::Vector(VectorValue::new_vec3(Vec3::ONE))
);
}
#[test]
fn attr_from_name() {
for attr in Attribute::all() {
assert_eq!(Attribute::from_name(attr.name()), Some(*attr));
}
}
#[test]
fn attr_reflect() {
let mut attr = Attribute(TEST_ATTR_INNER);
let r = attr.as_reflect();
assert_eq!(TypeRegistration::of::<Attribute>().type_id(), r.type_id());
match r.reflect_ref() {
ReflectRef::Struct(s) => {
assert_eq!(2, s.field_len());
assert_eq!(Some("name"), s.name_at(0));
assert_eq!(Some("default_value"), s.name_at(1));
assert_eq!(None, s.name_at(2));
assert_eq!(None, s.name_at(9999));
assert_eq!(
Some("alloc::borrow::Cow<str>"),
s.field("name")
.map(|f| f.get_represented_type_info().unwrap().type_path())
);
assert_eq!(
Some("bevy_hanabi::graph::Value"),
s.field("default_value")
.map(|f| f.get_represented_type_info().unwrap().type_path())
);
assert!(s.field("DUMMY").is_none());
assert!(s.field("").is_none());
for (_, f) in s.iter_fields() {
let tp = f.get_represented_type_info().unwrap().type_path();
assert!(
tp.contains("alloc::borrow::Cow<str>")
|| tp.contains("bevy_hanabi::graph::Value")
);
}
let d = s.to_dynamic_struct();
assert_eq!(
TypeRegistration::of::<Attribute>().type_id(),
d.get_represented_type_info().unwrap().type_id()
);
assert_eq!(Some(0), d.index_of_name("name"));
assert_eq!(Some(1), d.index_of_name("default_value"));
}
_ => panic!("Attribute should be reflected as a Struct"),
}
let r = attr.as_reflect_mut();
match r.reflect_mut() {
ReflectMut::Struct(s) => {
assert!(s.field_mut("name").is_none());
assert!(s.field_mut("default_value").is_none());
assert!(s.field_at_mut(0).is_none());
assert!(s.field_at_mut(1).is_none());
}
_ => panic!("Attribute should be reflected as a Struct"),
}
}
#[test]
fn attr_from_reflect() {
for attr in Attribute::ALL {
let s: String = attr.name().into();
let r = s.as_partial_reflect();
let r_attr = Attribute::from_reflect(r).expect(
"Cannot find
attribute by name",
);
assert_eq!(r_attr, attr);
}
assert_eq!(
None,
Attribute::from_reflect("test".to_string().as_partial_reflect())
);
}
#[test]
fn attr_serde() {
for attr in Attribute::ALL {
let ron = ron::to_string(&attr).unwrap();
assert_eq!(ron, format!("\"{}\"", attr.name()));
let s: Attribute = ron::from_str(&ron).unwrap();
assert_eq!(s, attr);
}
assert!(ron::from_str::<Attribute>("\"\"").is_err());
assert!(ron::from_str::<Attribute>("\"UNKNOWN\"").is_err());
}
const F1_INNER: &AttributeInner =
&AttributeInner::new(Cow::Borrowed("F1"), Value::Scalar(ScalarValue::Float(3.)));
const F1B_INNER: &AttributeInner =
&AttributeInner::new(Cow::Borrowed("F1B"), Value::Scalar(ScalarValue::Float(5.)));
const F2_INNER: &AttributeInner = &AttributeInner::new(
Cow::Borrowed("F2"),
Value::Vector(VectorValue::new_vec2(Vec2::ZERO)),
);
const F2B_INNER: &AttributeInner = &AttributeInner::new(
Cow::Borrowed("F2B"),
Value::Vector(VectorValue::new_vec2(Vec2::ONE)),
);
const F3_INNER: &AttributeInner = &AttributeInner::new(
Cow::Borrowed("F3"),
Value::Vector(VectorValue::new_vec3(Vec3::ZERO)),
);
const F3B_INNER: &AttributeInner = &AttributeInner::new(
Cow::Borrowed("F3B"),
Value::Vector(VectorValue::new_vec3(Vec3::ONE)),
);
const F4_INNER: &AttributeInner = &AttributeInner::new(
Cow::Borrowed("F4"),
Value::Vector(VectorValue::new_vec4(Vec4::ZERO)),
);
const F4B_INNER: &AttributeInner = &AttributeInner::new(
Cow::Borrowed("F4B"),
Value::Vector(VectorValue::new_vec4(Vec4::ONE)),
);
const F1: Attribute = Attribute(F1_INNER);
const F1B: Attribute = Attribute(F1B_INNER);
const F2: Attribute = Attribute(F2_INNER);
const F2B: Attribute = Attribute(F2B_INNER);
const F3: Attribute = Attribute(F3_INNER);
const F3B: Attribute = Attribute(F3B_INNER);
const F4: Attribute = Attribute(F4_INNER);
const F4B: Attribute = Attribute(F4B_INNER);
fn calc_raw_align(layout: &ParticleLayout) -> u32 {
if layout.layout.is_empty() {
0
} else {
layout
.layout
.iter()
.map(|attr| attr.attribute.value_type().align())
.max()
.unwrap() as u32
}
}
#[test]
fn test_layout_build() {
let layout = ParticleLayout::new().build();
assert_eq!(layout.layout.len(), 0);
assert_eq!(layout.generate_code(), String::new());
for attr in Attribute::ALL {
let layout = ParticleLayout::new().append(attr).build();
assert_eq!(layout.len(), 1);
assert!(!layout.layout.is_empty());
let size = layout.size();
let aligned_size = size.next_multiple_of(layout.align());
assert_eq!(aligned_size % attr.align() as u32, 0);
let attr_size = attr.size() as u32;
if aligned_size != attr_size {
assert!(aligned_size > attr_size);
let pad_size = aligned_size - attr_size;
assert_eq!(pad_size % 4, 0);
let num_pad = pad_size / 4;
assert_eq!(layout.layout.len() as u32, 1 + num_pad);
} else {
assert_eq!(layout.layout.len(), 1);
}
let attr0 = &layout.layout[0];
assert_eq!(attr0.offset, 0);
assert!(layout.generate_code().starts_with(&format!(
" {}: {},\n",
attr0.attribute.name(),
attr0.attribute.value_type().to_wgsl_string()
)));
assert_eq!(layout.align(), calc_raw_align(&layout));
}
for attr in [F1, F2, F3, F4] {
let mut layout = ParticleLayout::new();
for _ in 0..3 {
layout = layout.append(attr);
}
let layout = layout.build();
assert_eq!(layout.len(), 1); let attr = &layout.layout[0];
assert_eq!(attr.offset, 0);
}
for attrs in [[F1, F1B], [F2, F2B], [F3, F3B], [F4, F4B]] {
let mut layout = ParticleLayout::new();
for &attr in &attrs {
layout = layout.append(attr);
}
let layout = layout.build();
assert_eq!(layout.len(), 2);
let attr_0 = &layout.layout[0];
let size = attr_0.attribute.size();
assert_eq!(attr_0.offset as usize, 0);
if attr_0.attribute.size() != attr_0.attribute.align() {
let attr_1 = &layout.layout[2]; assert_eq!(
attr_1.offset as usize,
size.next_multiple_of(attr_0.attribute.align())
);
assert_eq!(attr_1.attribute.size(), size);
} else {
let attr_1 = &layout.layout[1];
assert_eq!(
attr_1.offset as usize,
size.next_multiple_of(attr_0.attribute.align())
);
assert_eq!(attr_1.attribute.size(), size);
}
}
{
let mut layout = ParticleLayout::new();
for &attr in &[F1, F3, F2, F3B] {
layout = layout.append(attr);
}
let layout = layout.build();
assert_eq!(layout.len(), 4);
assert_eq!(layout.layout.len(), 7);
assert_eq!(layout.size(), 48);
assert_eq!(layout.align(), 16);
for (i, (off, a)) in [
(0, F3),
(12, F1),
(16, F3B),
(28, Attribute::PAD0),
(32, F2),
(40, Attribute::PAD1),
(44, Attribute::PAD2),
]
.iter()
.enumerate()
{
let attr_i = layout.layout[i];
assert_eq!(attr_i.offset, *off);
assert_eq!(attr_i.attribute, *a);
}
}
{
let mut layout = ParticleLayout::new();
for &attr in &[F1, F4, F3, F2, F2B, F3B] {
layout = layout.append(attr);
}
let layout = layout.build();
assert_eq!(layout.len(), 6);
assert_eq!(layout.layout.len(), 7);
assert_eq!(layout.size(), 64);
assert_eq!(layout.align(), 16);
for (i, (off, a)) in [
(0, F4),
(16, F3),
(28, F1),
(32, F2),
(40, F2B),
(48, F3B),
(60, Attribute::PAD0),
]
.iter()
.enumerate()
{
let attr_i = layout.layout[i];
assert_eq!(attr_i.offset, *off);
assert_eq!(attr_i.attribute, *a);
}
}
}
}