use std::{cell::RefCell, num::NonZeroU32, rc::Rc};
use bevy::{
platform::collections::HashSet,
prelude::default,
reflect::{Reflect, ReflectDeserialize, ReflectSerialize},
};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use super::Value;
use crate::{
Attribute, ModifierContext, ParticleLayout, Property, PropertyLayout, ScalarType,
TextureLayout, TextureSlot, ToWgslString, ValueType, VectorType,
};
type Id = NonZeroU32;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Reflect)]
#[reflect(Serialize, Deserialize)]
#[repr(transparent)]
pub struct ExprHandle {
id: Id,
}
impl serde::Serialize for ExprHandle {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&format!("#{}", self.id.get()))
}
}
impl<'de> serde::Deserialize<'de> for ExprHandle {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct IdVisitor;
impl<'de2> serde::de::Visitor<'de2> for IdVisitor {
type Value = ExprHandle;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str(
"a string of the form \"#<N>\" where <N> is a non-zero expression index",
)
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
if v.len() > 1 && v.chars().nth(0) == Some('#') {
let id = v[1..]
.parse::<u32>()
.map_err(|_| serde::de::Error::custom("Failed to parse ID value"))?;
NonZeroU32::try_from(id)
.map(|id| ExprHandle { id })
.map_err(|_| serde::de::Error::custom("Invalid ID value"))
} else {
Err(serde::de::Error::custom(
"Invalid ID format (expected '#N')",
))
}
}
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_str(&v[..])
}
}
deserializer.deserialize_any(IdVisitor)
}
}
impl ExprHandle {
#[allow(dead_code)]
fn new(id: Id) -> Self {
Self { id }
}
#[allow(unsafe_code)]
unsafe fn new_unchecked(id: usize) -> Self {
debug_assert!(id != 0);
Self {
id: NonZeroU32::new_unchecked(id as u32),
}
}
pub fn index(&self) -> usize {
(self.id.get() - 1) as usize
}
}
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Reflect, Serialize, Deserialize,
)]
#[repr(transparent)]
#[serde(transparent)]
pub struct PropertyHandle {
id: Id,
}
impl PropertyHandle {
#[allow(dead_code)]
fn new(id: Id) -> Self {
Self { id }
}
#[allow(unsafe_code)]
unsafe fn new_unchecked(id: usize) -> Self {
debug_assert!(id != 0);
Self {
id: NonZeroU32::new_unchecked(id as u32),
}
}
pub fn index(&self) -> usize {
(self.id.get() - 1) as usize
}
}
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Reflect, Serialize, Deserialize,
)]
#[repr(transparent)]
#[serde(transparent)]
pub struct TextureHandle {
id: Id,
}
impl TextureHandle {
#[allow(dead_code)]
fn new(id: Id) -> Self {
Self { id }
}
#[allow(unsafe_code)]
unsafe fn new_unchecked(id: usize) -> Self {
debug_assert!(id != 0);
Self {
id: NonZeroU32::new_unchecked(id as u32),
}
}
#[allow(dead_code)]
fn index(&self) -> usize {
(self.id.get() - 1) as usize
}
}
#[derive(Debug, Default, Clone, PartialEq, Hash, Reflect, Serialize, Deserialize)]
pub struct Module {
expressions: Vec<Expr>,
properties: Vec<Property>,
texture_layout: TextureLayout,
}
macro_rules! impl_module_unary {
($t: ident, $T: ident) => {
#[doc = concat!("Build a [`UnaryOperator::", stringify!($T), "`](crate::graph::expr::UnaryOperator::", stringify!($T),") unary expression and append it to the module.\n\nThis is a shortcut for [`unary(UnaryOperator::", stringify!($T), ", inner)`](crate::graph::expr::Module::unary).")]
#[inline]
pub fn $t(&mut self, inner: ExprHandle) -> ExprHandle {
self.unary(UnaryOperator::$T, inner)
}
};
}
macro_rules! impl_module_binary {
($t: ident, $T: ident) => {
#[doc = concat!("Build a [`BinaryOperator::", stringify!($T), "`](crate::graph::expr::BinaryOperator::", stringify!($T),") binary expression and append it to the module.\n\nThis is a shortcut for [`binary(BinaryOperator::", stringify!($T), ", left, right)`](crate::graph::expr::Module::binary).")]
#[inline]
pub fn $t(&mut self, left: ExprHandle, right: ExprHandle) -> ExprHandle {
self.binary(BinaryOperator::$T, left, right)
}
};
}
macro_rules! impl_module_ternary {
($t: ident, $T: ident) => {
#[doc = concat!("Build a [`TernaryOperator::", stringify!($T), "`](crate::graph::expr::TernaryOperator::", stringify!($T),") ternary expression and append it to the module.\n\nThis is a shortcut for [`ternary(TernaryOperator::", stringify!($T), ", first, second, third)`](crate::graph::expr::Module::ternary).")]
#[inline]
pub fn $t(&mut self, first: ExprHandle, second: ExprHandle, third: ExprHandle) -> ExprHandle {
self.ternary(TernaryOperator::$T, first, second, third)
}
};
}
impl Module {
pub fn from_raw(expr: Vec<Expr>) -> Self {
Self {
expressions: expr,
properties: vec![],
texture_layout: default(),
}
}
pub fn expressions(&self) -> impl Iterator<Item = (ExprHandle, &Expr)> {
self.expressions.iter().enumerate().map(|(index, expr)| {
(
#[allow(unsafe_code)]
unsafe {
ExprHandle::new_unchecked(index + 1)
},
expr,
)
})
}
pub fn add_expr(&mut self, expr: impl Into<Expr>) -> ExprHandle {
self.expressions.push(expr.into());
#[allow(unsafe_code)]
unsafe {
ExprHandle::new_unchecked(self.expressions.len())
}
}
pub fn add_property(
&mut self,
name: impl Into<String>,
default_value: Value,
) -> PropertyHandle {
let name = name.into();
assert!(!self.properties.iter().any(|p| p.name() == name));
self.properties.push(Property::new(name, default_value));
#[allow(unsafe_code)]
unsafe {
PropertyHandle::new_unchecked(self.properties.len())
}
}
pub fn get_property(&self, property: PropertyHandle) -> Option<&Property> {
self.properties.get(property.index())
}
pub fn get_property_by_name(&self, name: &str) -> Option<PropertyHandle> {
self.properties
.iter()
.enumerate()
.find(|(_, prop)| prop.name() == name)
.map(|(index, _)| PropertyHandle::new(NonZeroU32::new(index as u32 + 1).unwrap()))
}
pub fn properties(&self) -> &[Property] {
&self.properties
}
pub fn add_texture_slot(&mut self, name: impl Into<String>) -> TextureHandle {
let name = name.into();
assert!(!self.texture_layout.layout.iter().any(|t| t.name == name));
self.texture_layout.layout.push(TextureSlot { name });
#[allow(unsafe_code)]
unsafe {
TextureHandle::new_unchecked(self.texture_layout.layout.len())
}
}
pub fn gather_attributes(&self, set: &mut HashSet<Attribute>) {
for expr in &self.expressions {
if let Expr::Attribute(attr) = expr {
set.insert(attr.attr);
}
}
}
#[inline]
pub fn lit<V>(&mut self, value: V) -> ExprHandle
where
Value: From<V>,
{
self.add_expr(Expr::Literal(LiteralExpr::new(value)))
}
#[inline]
pub fn attr(&mut self, attr: Attribute) -> ExprHandle {
self.add_expr(Expr::Attribute(AttributeExpr::new(attr)))
}
#[inline]
pub fn parent_attr(&mut self, attr: Attribute) -> ExprHandle {
self.add_expr(Expr::ParentAttribute(AttributeExpr::new(attr)))
}
#[inline]
pub fn prop(&mut self, property: PropertyHandle) -> ExprHandle {
self.add_expr(Expr::Property(PropertyExpr::new(property)))
}
#[inline]
pub fn builtin(&mut self, op: BuiltInOperator) -> ExprHandle {
self.add_expr(Expr::BuiltIn(BuiltInExpr::new(op)))
}
#[inline]
pub fn unary(&mut self, op: UnaryOperator, inner: ExprHandle) -> ExprHandle {
assert!(inner.index() < self.expressions.len());
self.add_expr(Expr::Unary { op, expr: inner })
}
impl_module_unary!(abs, Abs);
impl_module_unary!(acos, Acos);
impl_module_unary!(asin, Asin);
impl_module_unary!(atan, Atan);
impl_module_unary!(all, All);
impl_module_unary!(any, Any);
impl_module_unary!(ceil, Ceil);
impl_module_unary!(cos, Cos);
impl_module_unary!(exp, Exp);
impl_module_unary!(exp2, Exp2);
impl_module_unary!(floor, Floor);
impl_module_unary!(fract, Fract);
impl_module_unary!(inverse_sqrt, InvSqrt);
impl_module_unary!(length, Length);
impl_module_unary!(log, Log);
impl_module_unary!(log2, Log2);
impl_module_unary!(normalize, Normalize);
impl_module_unary!(pack4x8snorm, Pack4x8snorm);
impl_module_unary!(pack4x8unorm, Pack4x8unorm);
impl_module_unary!(round, Round);
impl_module_unary!(saturate, Saturate);
impl_module_unary!(sign, Sign);
impl_module_unary!(sin, Sin);
impl_module_unary!(sqrt, Sqrt);
impl_module_unary!(tan, Tan);
impl_module_unary!(unpack4x8snorm, Unpack4x8snorm);
impl_module_unary!(unpack4x8unorm, Unpack4x8unorm);
impl_module_unary!(w, W);
impl_module_unary!(x, X);
impl_module_unary!(y, Y);
impl_module_unary!(z, Z);
#[inline]
pub fn binary(
&mut self,
op: BinaryOperator,
left: ExprHandle,
right: ExprHandle,
) -> ExprHandle {
assert!(left.index() < self.expressions.len());
assert!(right.index() < self.expressions.len());
self.add_expr(Expr::Binary { op, left, right })
}
impl_module_binary!(add, Add);
impl_module_binary!(atan2, Atan2);
impl_module_binary!(cross, Cross);
impl_module_binary!(distance, Distance);
impl_module_binary!(div, Div);
impl_module_binary!(dot, Dot);
impl_module_binary!(ge, GreaterThanOrEqual);
impl_module_binary!(gt, GreaterThan);
impl_module_binary!(le, LessThanOrEqual);
impl_module_binary!(lt, LessThan);
impl_module_binary!(max, Max);
impl_module_binary!(min, Min);
impl_module_binary!(mul, Mul);
impl_module_binary!(rem, Remainder);
impl_module_binary!(step, Step);
impl_module_binary!(sub, Sub);
impl_module_binary!(uniform, UniformRand);
impl_module_binary!(normal, NormalRand);
impl_module_binary!(vec2, Vec2);
impl_module_binary!(vec4_xyz_w, Vec4XyzW);
#[inline]
pub fn ternary(
&mut self,
op: TernaryOperator,
first: ExprHandle,
second: ExprHandle,
third: ExprHandle,
) -> ExprHandle {
assert!(first.index() < self.expressions.len());
assert!(second.index() < self.expressions.len());
assert!(third.index() < self.expressions.len());
self.add_expr(Expr::Ternary {
op,
first,
second,
third,
})
}
impl_module_ternary!(mix, Mix);
impl_module_ternary!(clamp, Clamp);
impl_module_ternary!(smoothstep, SmoothStep);
impl_module_ternary!(vec3, Vec3);
pub fn cast(&mut self, expr: ExprHandle, target: impl Into<ValueType>) -> ExprHandle {
assert!(expr.index() < self.expressions.len());
let target = target.into();
let expr = CastExpr::new(expr, target);
if let Some(valid) = expr.is_valid(self) {
assert!(valid);
}
self.add_expr(Expr::Cast(expr))
}
#[inline]
pub fn get(&self, expr: ExprHandle) -> Option<&Expr> {
let index = expr.index();
self.expressions.get(index)
}
#[inline]
pub fn get_mut(&mut self, expr: ExprHandle) -> Option<&mut Expr> {
let index = expr.index();
self.expressions.get_mut(index)
}
#[inline]
pub fn try_get(&self, expr: ExprHandle) -> Result<&Expr, ExprError> {
let index = expr.index();
self.expressions
.get(index)
.ok_or(ExprError::InvalidExprHandleError(format!(
"Cannot find expression with handle {:?} in the current module. Check that the Module used to build the expression was the same used in the EvalContext or the original EffectAsset.", expr)))
}
#[inline]
pub fn try_get_mut(&mut self, expr: ExprHandle) -> Result<&mut Expr, ExprError> {
let index = expr.index();
self.expressions
.get_mut(index)
.ok_or(ExprError::InvalidExprHandleError(format!(
"Cannot find expression with handle {:?} in the current module. Check that the Module used to build the expression was the same used in the EvalContext or the original EffectAsset.", expr)))
}
#[inline]
pub fn is_const(&self, expr: ExprHandle) -> bool {
let expr = self.get(expr).unwrap();
expr.is_const(self)
}
pub fn has_side_effect(&self, expr: ExprHandle) -> bool {
let expr = self.get(expr).unwrap();
expr.has_side_effect(self)
}
pub fn texture_layout(&self) -> TextureLayout {
self.texture_layout.clone()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum ExprError {
#[error("Type error: {0}")]
TypeError(String),
#[error("Syntax error: {0}")]
SyntaxError(String),
#[error("Graph evaluation error: {0}")]
GraphEvalError(String),
#[error("Property error: {0}")]
PropertyError(String),
#[error("Invalid expression handle: {0}")]
InvalidExprHandleError(String),
#[error("Invalid modifier context {0}, expected {1} instead.")]
InvalidModifierContext(ModifierContext, ModifierContext),
}
pub trait EvalContext {
fn modifier_context(&self) -> ModifierContext;
fn particle_layout(&self) -> &ParticleLayout;
fn property_layout(&self) -> &PropertyLayout;
fn eval(&mut self, module: &Module, handle: ExprHandle) -> Result<String, ExprError>;
fn make_local_var(&mut self) -> String;
fn push_stmt(&mut self, stmt: &str);
fn make_fn(
&mut self,
func_name: &str,
args: &str,
module: &mut Module,
f: &mut dyn FnMut(&mut Module, &mut dyn EvalContext) -> Result<String, ExprError>,
) -> Result<(), ExprError>;
fn is_attribute_pointer(&self) -> bool;
}
#[derive(Debug, Clone, Copy, PartialEq, Hash, Reflect, Serialize, Deserialize)]
pub enum Expr {
BuiltIn(BuiltInExpr),
Literal(LiteralExpr),
Property(PropertyExpr),
Attribute(AttributeExpr),
ParentAttribute(AttributeExpr),
Unary {
op: UnaryOperator,
expr: ExprHandle,
},
Binary {
op: BinaryOperator,
left: ExprHandle,
right: ExprHandle,
},
Ternary {
op: TernaryOperator,
first: ExprHandle,
second: ExprHandle,
third: ExprHandle,
},
Cast(CastExpr),
TextureSample(TextureSampleExpr),
}
impl Expr {
pub fn is_const(&self, module: &Module) -> bool {
match self {
Expr::BuiltIn(expr) => expr.is_const(),
Expr::Literal(expr) => expr.is_const(),
Expr::Property(expr) => expr.is_const(),
Expr::Attribute(expr) => expr.is_const(),
Expr::ParentAttribute(expr) => expr.is_const(),
Expr::Unary { expr, .. } => module.is_const(*expr),
Expr::Binary { left, right, .. } => module.is_const(*left) && module.is_const(*right),
Expr::Ternary {
first,
second,
third,
..
} => module.is_const(*first) && module.is_const(*second) && module.is_const(*third),
Expr::Cast(expr) => module.is_const(expr.inner),
Expr::TextureSample(_) => false,
}
}
pub fn has_side_effect(&self, _: &Module) -> bool {
match self {
Expr::BuiltIn(expr) => expr.has_side_effect(),
Expr::Literal(_) => false,
Expr::Property(_) => false,
Expr::Attribute(_) => false,
Expr::ParentAttribute(_) => false,
Expr::Unary { .. } => false,
Expr::Binary { op, .. } => {
*op == BinaryOperator::UniformRand || *op == BinaryOperator::NormalRand
}
Expr::Ternary { .. } => false,
Expr::Cast(_) => false,
Expr::TextureSample(_) => false,
}
}
pub fn value_type(&self) -> Option<ValueType> {
match self {
Expr::BuiltIn(expr) => Some(expr.value_type()),
Expr::Literal(expr) => Some(expr.value_type()),
Expr::Property(_) => None,
Expr::Attribute(expr) => Some(expr.value_type()),
Expr::ParentAttribute(expr) => Some(expr.value_type()),
Expr::Unary { .. } => None,
Expr::Binary { .. } => None,
Expr::Ternary { .. } => None,
Expr::Cast(expr) => Some(expr.value_type()),
Expr::TextureSample(expr) => Some(expr.value_type()),
}
}
pub fn eval(
&self,
module: &Module,
context: &mut dyn EvalContext,
) -> Result<String, ExprError> {
match self {
Expr::BuiltIn(expr) => expr.eval(context),
Expr::Literal(expr) => expr.eval(context),
Expr::Property(expr) => expr.eval(module, context),
Expr::Attribute(expr) => expr.eval(context, false),
Expr::ParentAttribute(expr) => expr.eval(context, true),
Expr::Unary { op, expr } => {
let expr = context.eval(module, *expr)?;
Ok(if op.is_functional() {
format!("{}({})", op.to_wgsl_string(), expr)
} else {
format!("{}.{}", expr, op.to_wgsl_string())
})
}
Expr::Binary { op, left, right } => {
let compiled_left = context.eval(module, *left)?;
let compiled_right = context.eval(module, *right)?;
let body = if op.is_functional() {
if op.needs_type_suffix() {
let lhs_type = module.get(*left).and_then(|arg| arg.value_type());
let rhs_type = module.get(*right).and_then(|arg| arg.value_type());
if lhs_type.is_none() || rhs_type.is_none() {
return Err(ExprError::TypeError(
"Can't determine the type of the operand".to_string(),
));
}
if lhs_type != rhs_type {
return Err(ExprError::TypeError("Mismatched types".to_string()));
}
let value_type = lhs_type.unwrap();
let suffix = match value_type {
ValueType::Scalar(ScalarType::Float) => "f",
ValueType::Vector(vector_type)
if vector_type.elem_type() == ScalarType::Float =>
{
match vector_type.count() {
2 => "vec2",
3 => "vec3",
4 => "vec4",
_ => unreachable!(),
}
}
_ => {
return Err(ExprError::TypeError("Unsupported type".to_string()));
}
};
Ok(format!(
"{}_{}({}, {})",
op.to_wgsl_string(),
suffix,
compiled_left,
compiled_right
))
} else {
Ok(format!(
"{}({}, {})",
op.to_wgsl_string(),
compiled_left,
compiled_right
))
}
} else {
Ok(format!(
"({}) {} ({})",
compiled_left,
op.to_wgsl_string(),
compiled_right
))
};
body.map(|body| {
check_side_effects_and_create_local_if_needed(
context,
body,
self.has_side_effect(module),
)
})
}
Expr::Ternary {
op,
first,
second,
third,
} => {
let first = context.eval(module, *first)?;
let second = context.eval(module, *second)?;
let third = context.eval(module, *third)?;
Ok(format!(
"{}({}, {}, {})",
op.to_wgsl_string(),
first,
second,
third
))
}
Expr::Cast(expr) => {
let inner = context.eval(module, expr.inner)?;
Ok(format!("{}({})", expr.target.to_wgsl_string(), inner))
}
Expr::TextureSample(expr) => expr.eval(module, context),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Hash, Reflect, Serialize, Deserialize)]
#[serde(transparent)]
pub struct LiteralExpr {
pub value: Value,
}
impl LiteralExpr {
pub fn new<V>(value: V) -> Self
where
Value: From<V>,
{
Self {
value: value.into(),
}
}
pub fn is_const(&self) -> bool {
true
}
pub fn value_type(&self) -> ValueType {
self.value.value_type()
}
pub fn eval(&self, _context: &dyn EvalContext) -> Result<String, ExprError> {
Ok(self.value.to_wgsl_string())
}
}
impl ToWgslString for LiteralExpr {
fn to_wgsl_string(&self) -> String {
self.value.to_wgsl_string()
}
}
impl From<&Value> for LiteralExpr {
fn from(value: &Value) -> Self {
Self { value: *value }
}
}
impl<T: Into<Value>> From<T> for LiteralExpr {
fn from(value: T) -> Self {
Self {
value: value.into(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
pub struct AttributeExpr {
pub attr: Attribute,
}
impl AttributeExpr {
#[inline]
pub fn new(attr: Attribute) -> Self {
Self { attr }
}
pub fn is_const(&self) -> bool {
false
}
pub fn value_type(&self) -> ValueType {
self.attr.value_type()
}
pub fn eval(&self, context: &dyn EvalContext, parent: bool) -> Result<String, ExprError> {
if self.attr == Attribute::ID {
Ok(if parent {
"parent_particle_index"
} else {
"particle_index"
}
.to_string())
} else if self.attr == Attribute::PARTICLE_COUNTER {
Ok("particle_counter".to_string())
} else {
let owner = if parent {
"parent_particle"
} else {
"particle"
};
if context.is_attribute_pointer() {
Ok(format!("(*{}).{}", owner, self.attr.name()))
} else {
Ok(format!("{}.{}", owner, self.attr.name()))
}
}
}
}
impl ToWgslString for AttributeExpr {
fn to_wgsl_string(&self) -> String {
format!("particle.{}", self.attr.name())
}
}
impl From<Attribute> for AttributeExpr {
fn from(value: Attribute) -> Self {
AttributeExpr::new(value)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
#[repr(transparent)]
#[serde(transparent)]
pub struct PropertyExpr {
pub property: PropertyHandle,
}
impl PropertyExpr {
#[inline]
pub fn new(property: PropertyHandle) -> Self {
Self { property }
}
pub fn is_const(&self) -> bool {
false
}
pub fn eval(&self, module: &Module, context: &dyn EvalContext) -> Result<String, ExprError> {
let prop = module
.get_property(self.property)
.ok_or(ExprError::PropertyError(format!(
"Unknown property handle {:?} in evaluation module.",
self.property
)))?;
if !context.property_layout().contains(prop.name()) {
return Err(ExprError::PropertyError(format!(
"Unknown property '{}' in evaluation layout.",
prop.name()
)));
}
Ok(prop.to_wgsl_string())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
pub struct CastExpr {
pub inner: ExprHandle,
pub target: ValueType,
}
impl CastExpr {
#[inline]
pub fn new(inner: ExprHandle, target: impl Into<ValueType>) -> Self {
Self {
inner,
target: target.into(),
}
}
pub fn value_type(&self) -> ValueType {
self.target
}
pub fn is_valid(&self, module: &Module) -> Option<bool> {
let Some(inner) = module.get(self.inner) else {
return Some(false);
};
if let Some(inner_type) = inner.value_type() {
match self.target {
ValueType::Scalar(_) => {
Some(matches!(inner_type, ValueType::Scalar(_)))
}
ValueType::Vector(_) => {
Some(!matches!(inner_type, ValueType::Matrix(_)))
}
ValueType::Matrix(_) => {
Some(matches!(inner_type, ValueType::Matrix(_)))
}
}
} else {
None
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
pub struct TextureSampleExpr {
pub image: ExprHandle,
pub coordinates: ExprHandle,
}
impl TextureSampleExpr {
#[inline]
pub fn new(image: ExprHandle, coordinates: ExprHandle) -> Self {
Self { image, coordinates }
}
pub fn value_type(&self) -> ValueType {
ValueType::Vector(VectorType::VEC4F)
}
pub fn is_valid(&self, module: &Module) -> Option<bool> {
let Some(_image) = module.get(self.image) else {
return Some(false);
};
let Some(_coordinates) = module.get(self.coordinates) else {
return Some(false);
};
Some(true)
}
pub fn eval(
&self,
module: &Module,
context: &mut dyn EvalContext,
) -> Result<String, ExprError> {
let image = module.try_get(self.image)?;
let image = image.eval(module, context)?;
let coordinates = module.try_get(self.coordinates)?;
let coordinates = coordinates.eval(module, context)?;
Ok(format!(
"textureSample(material_texture_{image}, material_sampler_{image}, {coordinates})",
))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
pub enum BuiltInOperator {
Time,
DeltaTime,
VirtualTime,
VirtualDeltaTime,
RealTime,
RealDeltaTime,
Rand(ValueType),
AlphaCutoff,
IsAlive,
}
impl BuiltInOperator {
pub fn name(&self) -> &str {
match self {
BuiltInOperator::Time => "time",
BuiltInOperator::DeltaTime => "delta_time",
BuiltInOperator::VirtualTime => "virtual_time",
BuiltInOperator::VirtualDeltaTime => "virtual_delta_time",
BuiltInOperator::RealTime => "real_time",
BuiltInOperator::RealDeltaTime => "real_delta_time",
BuiltInOperator::Rand(value_type) => match value_type {
ValueType::Scalar(s) => match s {
ScalarType::Bool => "brand",
ScalarType::Float => "frand",
ScalarType::Int => "irand",
ScalarType::Uint => "urand",
},
ValueType::Vector(vector_type) => {
match (vector_type.elem_type(), vector_type.count()) {
(ScalarType::Bool, 2) => "brand2",
(ScalarType::Bool, 3) => "brand3",
(ScalarType::Bool, 4) => "brand4",
(ScalarType::Float, 2) => "frand2",
(ScalarType::Float, 3) => "frand3",
(ScalarType::Float, 4) => "frand4",
(ScalarType::Int, 2) => "irand2",
(ScalarType::Int, 3) => "irand3",
(ScalarType::Int, 4) => "irand4",
(ScalarType::Uint, 2) => "urand2",
(ScalarType::Uint, 3) => "urand3",
(ScalarType::Uint, 4) => "urand4",
_ => panic!("Invalid vector type {:?}", vector_type),
}
}
ValueType::Matrix(_) => panic!("Invalid BuiltInOperator::Rand(ValueType::Matrix)."),
},
BuiltInOperator::AlphaCutoff => "alpha_cutoff",
BuiltInOperator::IsAlive => "is_alive",
}
}
pub fn value_type(&self) -> ValueType {
match self {
BuiltInOperator::Time => ValueType::Scalar(ScalarType::Float),
BuiltInOperator::DeltaTime => ValueType::Scalar(ScalarType::Float),
BuiltInOperator::VirtualTime => ValueType::Scalar(ScalarType::Float),
BuiltInOperator::VirtualDeltaTime => ValueType::Scalar(ScalarType::Float),
BuiltInOperator::RealTime => ValueType::Scalar(ScalarType::Float),
BuiltInOperator::RealDeltaTime => ValueType::Scalar(ScalarType::Float),
BuiltInOperator::Rand(value_type) => *value_type,
BuiltInOperator::AlphaCutoff => ValueType::Scalar(ScalarType::Float),
BuiltInOperator::IsAlive => ValueType::Scalar(ScalarType::Bool),
}
}
}
impl ToWgslString for BuiltInOperator {
fn to_wgsl_string(&self) -> String {
match self {
BuiltInOperator::Rand(_) => format!("{}()", self.name()),
BuiltInOperator::IsAlive => "is_alive".to_string(),
_ => format!("sim_params.{}", self.name()),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
pub struct BuiltInExpr {
pub operator: BuiltInOperator,
}
impl BuiltInExpr {
#[inline]
pub fn new(operator: BuiltInOperator) -> Self {
if let BuiltInOperator::Rand(value_type) = operator {
assert!(!matches!(value_type, ValueType::Matrix(_)));
}
Self { operator }
}
pub fn is_const(&self) -> bool {
false
}
pub fn has_side_effect(&self) -> bool {
matches!(self.operator, BuiltInOperator::Rand(_))
}
pub fn value_type(&self) -> ValueType {
self.operator.value_type()
}
pub fn eval(&self, context: &mut dyn EvalContext) -> Result<String, ExprError> {
Ok(check_side_effects_and_create_local_if_needed(
context,
self.to_wgsl_string(),
self.has_side_effect(),
))
}
}
impl ToWgslString for BuiltInExpr {
fn to_wgsl_string(&self) -> String {
self.operator.to_wgsl_string()
}
}
fn check_side_effects_and_create_local_if_needed(
context: &mut dyn EvalContext,
compiled_code: String,
has_side_effect: bool,
) -> String {
if has_side_effect {
let var_name = context.make_local_var();
context.push_stmt(&format!("let {} = {};", var_name, compiled_code));
var_name
} else {
compiled_code
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
pub enum UnaryOperator {
Abs,
Acos,
Asin,
Atan,
All,
Any,
Ceil,
Cos,
Exp,
Exp2,
Floor,
Fract,
InvSqrt,
Length,
Log,
Log2,
Normalize,
Pack4x8snorm,
Pack4x8unorm,
Round,
Saturate,
Sign,
Sin,
Sqrt,
Tan,
Unpack4x8snorm,
Unpack4x8unorm,
W,
X,
Y,
Z,
}
impl UnaryOperator {
pub fn is_functional(&self) -> bool {
!matches!(
*self,
UnaryOperator::X | UnaryOperator::Y | UnaryOperator::Z | UnaryOperator::W
)
}
}
impl ToWgslString for UnaryOperator {
fn to_wgsl_string(&self) -> String {
match *self {
UnaryOperator::Abs => "abs".to_string(),
UnaryOperator::Acos => "acos".to_string(),
UnaryOperator::Asin => "asin".to_string(),
UnaryOperator::Atan => "atan".to_string(),
UnaryOperator::All => "all".to_string(),
UnaryOperator::Any => "any".to_string(),
UnaryOperator::Ceil => "ceil".to_string(),
UnaryOperator::Cos => "cos".to_string(),
UnaryOperator::Exp => "exp".to_string(),
UnaryOperator::Exp2 => "exp2".to_string(),
UnaryOperator::Floor => "floor".to_string(),
UnaryOperator::Fract => "fract".to_string(),
UnaryOperator::InvSqrt => "inverseSqrt".to_string(),
UnaryOperator::Length => "length".to_string(),
UnaryOperator::Log => "log".to_string(),
UnaryOperator::Log2 => "log2".to_string(),
UnaryOperator::Normalize => "normalize".to_string(),
UnaryOperator::Pack4x8snorm => "pack4x8snorm".to_string(),
UnaryOperator::Pack4x8unorm => "pack4x8unorm".to_string(),
UnaryOperator::Round => "round".to_string(),
UnaryOperator::Saturate => "saturate".to_string(),
UnaryOperator::Sign => "sign".to_string(),
UnaryOperator::Sin => "sin".to_string(),
UnaryOperator::Sqrt => "sqrt".to_string(),
UnaryOperator::Tan => "tan".to_string(),
UnaryOperator::Unpack4x8snorm => "unpack4x8snorm".to_string(),
UnaryOperator::Unpack4x8unorm => "unpack4x8unorm".to_string(),
UnaryOperator::W => "w".to_string(),
UnaryOperator::X => "x".to_string(),
UnaryOperator::Y => "y".to_string(),
UnaryOperator::Z => "z".to_string(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
pub enum BinaryOperator {
Add,
Atan2,
Cross,
Distance,
Div,
Dot,
GreaterThan,
GreaterThanOrEqual,
LessThan,
LessThanOrEqual,
Max,
Min,
Mul,
Remainder,
Step,
Sub,
UniformRand,
NormalRand,
Vec2,
Vec4XyzW,
}
impl BinaryOperator {
pub fn is_functional(&self) -> bool {
match *self {
BinaryOperator::Add
| BinaryOperator::Div
| BinaryOperator::GreaterThan
| BinaryOperator::GreaterThanOrEqual
| BinaryOperator::LessThan
| BinaryOperator::LessThanOrEqual
| BinaryOperator::Mul
| BinaryOperator::Remainder
| BinaryOperator::Sub => false,
BinaryOperator::Atan2
| BinaryOperator::Cross
| BinaryOperator::Distance
| BinaryOperator::Dot
| BinaryOperator::Max
| BinaryOperator::Min
| BinaryOperator::Step
| BinaryOperator::UniformRand
| BinaryOperator::NormalRand
| BinaryOperator::Vec2
| BinaryOperator::Vec4XyzW => true,
}
}
pub fn needs_type_suffix(&self) -> bool {
matches!(
*self,
BinaryOperator::UniformRand | BinaryOperator::NormalRand
)
}
}
impl ToWgslString for BinaryOperator {
fn to_wgsl_string(&self) -> String {
match *self {
BinaryOperator::Add => "+".to_string(),
BinaryOperator::Atan2 => "atan2".to_string(),
BinaryOperator::Cross => "cross".to_string(),
BinaryOperator::Distance => "distance".to_string(),
BinaryOperator::Div => "/".to_string(),
BinaryOperator::Dot => "dot".to_string(),
BinaryOperator::GreaterThan => ">".to_string(),
BinaryOperator::GreaterThanOrEqual => ">=".to_string(),
BinaryOperator::LessThan => "<".to_string(),
BinaryOperator::LessThanOrEqual => "<=".to_string(),
BinaryOperator::Max => "max".to_string(),
BinaryOperator::Min => "min".to_string(),
BinaryOperator::Mul => "*".to_string(),
BinaryOperator::Remainder => "%".to_string(),
BinaryOperator::Step => "step".to_string(),
BinaryOperator::Sub => "-".to_string(),
BinaryOperator::UniformRand => "rand_uniform".to_string(),
BinaryOperator::NormalRand => "rand_normal".to_string(),
BinaryOperator::Vec2 => "vec2".to_string(),
BinaryOperator::Vec4XyzW => "vec4".to_string(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
pub enum TernaryOperator {
Mix,
Clamp,
SmoothStep,
Vec3,
}
impl ToWgslString for TernaryOperator {
fn to_wgsl_string(&self) -> String {
match *self {
TernaryOperator::Mix => "mix".to_string(),
TernaryOperator::Clamp => "clamp".to_string(),
TernaryOperator::SmoothStep => "smoothstep".to_string(),
TernaryOperator::Vec3 => "vec3".to_string(),
}
}
}
#[derive(Debug, Default, Clone)]
pub struct ExprWriter {
module: Rc<RefCell<Module>>,
}
#[allow(dead_code)]
impl ExprWriter {
pub fn new() -> Self {
Self {
module: Rc::new(RefCell::new(Module::default())),
}
}
pub fn from_module(module: Rc<RefCell<Module>>) -> Self {
Self { module }
}
pub fn add_property(&self, name: impl Into<String>, default_value: Value) -> PropertyHandle {
self.module.borrow_mut().add_property(name, default_value)
}
pub fn push(&self, expr: impl Into<Expr>) -> WriterExpr {
let expr = {
let mut m = self.module.borrow_mut();
m.add_expr(expr.into())
};
WriterExpr {
expr,
module: Rc::clone(&self.module),
}
}
pub fn lit(&self, value: impl Into<Value>) -> WriterExpr {
self.push(Expr::Literal(LiteralExpr {
value: value.into(),
}))
}
pub fn attr(&self, attr: Attribute) -> WriterExpr {
self.push(Expr::Attribute(AttributeExpr::new(attr)))
}
pub fn parent_attr(&self, attr: Attribute) -> WriterExpr {
self.push(Expr::ParentAttribute(AttributeExpr::new(attr)))
}
pub fn prop(&self, handle: PropertyHandle) -> WriterExpr {
self.push(Expr::Property(PropertyExpr::new(handle)))
}
pub fn time(&self) -> WriterExpr {
self.push(Expr::BuiltIn(BuiltInExpr::new(BuiltInOperator::Time)))
}
pub fn delta_time(&self) -> WriterExpr {
self.push(Expr::BuiltIn(BuiltInExpr::new(BuiltInOperator::DeltaTime)))
}
pub fn rand(&self, value_type: impl Into<ValueType>) -> WriterExpr {
self.push(Expr::BuiltIn(BuiltInExpr::new(BuiltInOperator::Rand(
value_type.into(),
))))
}
pub fn alpha_cutoff(&self) -> WriterExpr {
self.push(Expr::BuiltIn(BuiltInExpr::new(
BuiltInOperator::AlphaCutoff,
)))
}
pub fn finish(self) -> Module {
self.module.take()
}
}
#[derive(Debug, Clone)]
pub struct WriterExpr {
expr: ExprHandle,
module: Rc<RefCell<Module>>,
}
impl WriterExpr {
fn unary_op(self, op: UnaryOperator) -> Self {
let expr = self.module.borrow_mut().add_expr(Expr::Unary {
op,
expr: self.expr,
});
WriterExpr {
expr,
module: self.module,
}
}
#[inline]
pub fn abs(self) -> Self {
self.unary_op(UnaryOperator::Abs)
}
#[inline]
pub fn all(self) -> Self {
self.unary_op(UnaryOperator::All)
}
#[inline]
pub fn any(self) -> Self {
self.unary_op(UnaryOperator::Any)
}
#[inline]
pub fn acos(self) -> Self {
self.unary_op(UnaryOperator::Acos)
}
#[inline]
pub fn asin(self) -> Self {
self.unary_op(UnaryOperator::Asin)
}
#[inline]
pub fn atan(self) -> Self {
self.unary_op(UnaryOperator::Atan)
}
#[inline]
pub fn ceil(self) -> Self {
self.unary_op(UnaryOperator::Ceil)
}
#[inline]
pub fn cos(self) -> Self {
self.unary_op(UnaryOperator::Cos)
}
#[inline]
pub fn exp(self) -> Self {
self.unary_op(UnaryOperator::Exp)
}
#[inline]
pub fn exp2(self) -> Self {
self.unary_op(UnaryOperator::Exp2)
}
#[inline]
pub fn floor(self) -> Self {
self.unary_op(UnaryOperator::Floor)
}
#[inline]
pub fn fract(self) -> Self {
self.unary_op(UnaryOperator::Fract)
}
#[inline]
pub fn inverse_sqrt(self) -> Self {
self.unary_op(UnaryOperator::InvSqrt)
}
#[inline]
pub fn length(self) -> Self {
self.unary_op(UnaryOperator::Length)
}
#[inline]
pub fn log(self) -> Self {
self.unary_op(UnaryOperator::Log)
}
#[inline]
pub fn log2(self) -> Self {
self.unary_op(UnaryOperator::Log2)
}
#[inline]
pub fn normalized(self) -> Self {
self.unary_op(UnaryOperator::Normalize)
}
#[inline]
pub fn pack4x8snorm(self) -> Self {
self.unary_op(UnaryOperator::Pack4x8snorm)
}
#[inline]
pub fn pack4x8unorm(self) -> Self {
self.unary_op(UnaryOperator::Pack4x8unorm)
}
#[inline]
pub fn round(self) -> Self {
self.unary_op(UnaryOperator::Round)
}
#[inline]
pub fn sign(self) -> Self {
self.unary_op(UnaryOperator::Sign)
}
#[inline]
pub fn sin(self) -> Self {
self.unary_op(UnaryOperator::Sin)
}
#[inline]
pub fn sqrt(self) -> Self {
self.unary_op(UnaryOperator::Sqrt)
}
#[inline]
pub fn tan(self) -> Self {
self.unary_op(UnaryOperator::Tan)
}
#[inline]
pub fn unpack4x8snorm(self) -> Self {
self.unary_op(UnaryOperator::Unpack4x8snorm)
}
#[inline]
pub fn unpack4x8unorm(self) -> Self {
self.unary_op(UnaryOperator::Unpack4x8unorm)
}
#[inline]
pub fn saturate(self) -> Self {
self.unary_op(UnaryOperator::Saturate)
}
#[inline]
pub fn x(self) -> Self {
self.unary_op(UnaryOperator::X)
}
#[inline]
pub fn y(self) -> Self {
self.unary_op(UnaryOperator::Y)
}
#[inline]
pub fn z(self) -> Self {
self.unary_op(UnaryOperator::Z)
}
#[inline]
pub fn w(self) -> Self {
self.unary_op(UnaryOperator::W)
}
fn binary_op(self, other: Self, op: BinaryOperator) -> Self {
assert_eq!(self.module, other.module);
let left = self.expr;
let right = other.expr;
let expr = self
.module
.borrow_mut()
.add_expr(Expr::Binary { op, left, right });
WriterExpr {
expr,
module: self.module,
}
}
#[allow(clippy::should_implement_trait)]
#[inline]
pub fn add(self, other: Self) -> Self {
self.binary_op(other, BinaryOperator::Add)
}
#[inline]
pub fn atan2(self, other: Self) -> Self {
self.binary_op(other, BinaryOperator::Atan2)
}
#[inline]
pub fn cross(self, other: Self) -> Self {
self.binary_op(other, BinaryOperator::Cross)
}
#[inline]
pub fn dot(self, other: Self) -> Self {
self.binary_op(other, BinaryOperator::Dot)
}
#[inline]
pub fn distance(self, other: Self) -> Self {
self.binary_op(other, BinaryOperator::Distance)
}
#[allow(clippy::should_implement_trait)]
#[inline]
pub fn div(self, other: Self) -> Self {
self.binary_op(other, BinaryOperator::Div)
}
#[inline]
pub fn ge(self, other: Self) -> Self {
self.binary_op(other, BinaryOperator::GreaterThanOrEqual)
}
#[inline]
pub fn gt(self, other: Self) -> Self {
self.binary_op(other, BinaryOperator::GreaterThan)
}
#[inline]
pub fn le(self, other: Self) -> Self {
self.binary_op(other, BinaryOperator::LessThanOrEqual)
}
#[inline]
pub fn lt(self, other: Self) -> Self {
self.binary_op(other, BinaryOperator::LessThan)
}
#[inline]
pub fn max(self, other: Self) -> Self {
self.binary_op(other, BinaryOperator::Max)
}
#[inline]
pub fn min(self, other: Self) -> Self {
self.binary_op(other, BinaryOperator::Min)
}
#[allow(clippy::should_implement_trait)]
#[inline]
pub fn mul(self, other: Self) -> Self {
self.binary_op(other, BinaryOperator::Mul)
}
#[inline]
pub fn normal(self, other: Self) -> Self {
self.binary_op(other, BinaryOperator::NormalRand)
}
#[allow(clippy::should_implement_trait)]
#[inline]
pub fn rem(self, other: Self) -> Self {
self.binary_op(other, BinaryOperator::Remainder)
}
#[allow(clippy::should_implement_trait)]
#[inline]
pub fn step(self, edge: Self) -> Self {
edge.binary_op(self, BinaryOperator::Step)
}
#[allow(clippy::should_implement_trait)]
#[inline]
pub fn sub(self, other: Self) -> Self {
self.binary_op(other, BinaryOperator::Sub)
}
#[inline]
pub fn uniform(self, other: Self) -> Self {
self.binary_op(other, BinaryOperator::UniformRand)
}
fn ternary_op(self, second: Self, third: Self, op: TernaryOperator) -> Self {
assert_eq!(self.module, second.module);
assert_eq!(self.module, third.module);
let first = self.expr;
let second = second.expr;
let third = third.expr;
let expr = self.module.borrow_mut().add_expr(Expr::Ternary {
op,
first,
second,
third,
});
WriterExpr {
expr,
module: self.module,
}
}
#[inline]
pub fn mix(self, other: Self, fraction: Self) -> Self {
self.ternary_op(other, fraction, TernaryOperator::Mix)
}
#[inline]
pub fn clamp(self, lo: Self, hi: Self) -> Self {
self.ternary_op(lo, hi, TernaryOperator::Clamp)
}
#[inline]
pub fn smoothstep(self, low: Self, high: Self) -> Self {
low.ternary_op(high, self, TernaryOperator::SmoothStep)
}
#[inline]
pub fn vec2(self, y: Self) -> Self {
self.binary_op(y, BinaryOperator::Vec2)
}
#[inline]
pub fn vec3(self, y: Self, z: Self) -> Self {
self.ternary_op(y, z, TernaryOperator::Vec3)
}
#[inline]
pub fn vec4_xyz_w(self, w: Self) -> Self {
self.binary_op(w, BinaryOperator::Vec4XyzW)
}
pub fn cast(self, target: impl Into<ValueType>) -> Self {
let target = target.into();
let expr = self
.module
.borrow_mut()
.add_expr(Expr::Cast(CastExpr::new(self.expr, target)));
WriterExpr {
expr,
module: self.module,
}
}
#[inline]
pub fn expr(self) -> ExprHandle {
self.expr
}
}
impl std::ops::Add<WriterExpr> for WriterExpr {
type Output = WriterExpr;
#[inline]
fn add(self, rhs: WriterExpr) -> Self::Output {
self.add(rhs)
}
}
impl std::ops::Sub<WriterExpr> for WriterExpr {
type Output = WriterExpr;
#[inline]
fn sub(self, rhs: WriterExpr) -> Self::Output {
self.sub(rhs)
}
}
impl std::ops::Mul<WriterExpr> for WriterExpr {
type Output = WriterExpr;
#[inline]
fn mul(self, rhs: WriterExpr) -> Self::Output {
self.mul(rhs)
}
}
impl std::ops::Div<WriterExpr> for WriterExpr {
type Output = WriterExpr;
#[inline]
fn div(self, rhs: WriterExpr) -> Self::Output {
self.div(rhs)
}
}
impl std::ops::Rem<WriterExpr> for WriterExpr {
type Output = WriterExpr;
#[inline]
fn rem(self, rhs: WriterExpr) -> Self::Output {
self.rem(rhs)
}
}
#[cfg(test)]
mod tests {
use bevy::{platform::collections::HashSet, prelude::*};
use ron::ser::PrettyConfig;
use super::*;
use crate::{MatrixType, ScalarValue, ShaderWriter, VectorType};
#[test]
fn module() {
let mut m = Module::default();
#[allow(unsafe_code)]
let unknown = unsafe { ExprHandle::new_unchecked(1) };
assert!(m.get(unknown).is_none());
assert!(m.get_mut(unknown).is_none());
assert!(matches!(
m.try_get(unknown),
Err(ExprError::InvalidExprHandleError(_))
));
assert!(matches!(
m.try_get_mut(unknown),
Err(ExprError::InvalidExprHandleError(_))
));
let x = m.lit(5.);
let mut expected = Expr::Literal(LiteralExpr::new(5.));
assert_eq!(m.get(x), Some(&expected));
assert_eq!(m.get_mut(x), Some(&mut expected));
assert_eq!(m.try_get(x), Ok(&expected));
assert_eq!(m.try_get_mut(x), Ok(&mut expected));
}
#[test]
fn local_var() {
let property_layout = PropertyLayout::default();
let particle_layout = ParticleLayout::default();
let mut ctx =
ShaderWriter::new(ModifierContext::Update, &property_layout, &particle_layout);
let mut h = HashSet::new();
for _ in 0..100 {
let v = ctx.make_local_var();
assert!(h.insert(v));
}
}
#[test]
fn make_fn() {
let property_layout = PropertyLayout::default();
let particle_layout = ParticleLayout::default();
let mut ctx =
ShaderWriter::new(ModifierContext::Update, &property_layout, &particle_layout);
let mut module = Module::default();
let func_name = "my_func";
let args = "arg0: i32, arg1: f32";
assert!(ctx
.make_fn(func_name, args, &mut module, &mut |m, ctx| {
m.lit(3.);
let v = ctx.make_local_var();
assert_eq!(v, "var0");
let code = String::new();
Ok(code)
})
.is_ok());
let v = ctx.make_local_var();
assert_eq!(v, "var0");
assert!(!module.expressions.is_empty());
}
#[test]
fn property() {
let mut m = Module::default();
let _my_prop = m.add_property("my_prop", Value::Scalar(345_u32.into()));
let _other_prop = m.add_property(
"other_prop",
Value::Vector(Vec3::new(3., -7.5, 42.42).into()),
);
assert!(m.properties().iter().any(|p| p.name() == "my_prop"));
assert!(m.properties().iter().any(|p| p.name() == "other_prop"));
assert!(!m.properties().iter().any(|p| p.name() == "do_not_exist"));
}
#[test]
fn writer() {
let w = ExprWriter::new();
let my_prop = w.add_property("my_prop", 3.0.into());
let x = w.lit(3.).abs().max(w.attr(Attribute::POSITION) * w.lit(2.))
+ w.lit(-4.).min(w.prop(my_prop));
let x = x.expr();
let property_layout =
PropertyLayout::new(&[Property::new("my_prop", ScalarValue::Float(3.))]);
let particle_layout = ParticleLayout::default();
let m = w.finish();
let mut context =
ShaderWriter::new(ModifierContext::Update, &property_layout, &particle_layout);
let x = m.try_get(x).unwrap();
let s = x.eval(&m, &mut context).unwrap();
assert_eq!(
"(max(abs(3.), (particle.position) * (2.))) + (min(-4., properties[properties_array_index].my_prop))"
.to_string(),
s
);
}
#[test]
fn type_error() {
let l = Value::Scalar(3.5_f32.into());
let r: Result<Vec2, ExprError> = l.try_into();
assert!(r.is_err());
assert!(matches!(r, Err(ExprError::TypeError(_))));
}
#[test]
fn math_expr() {
let mut m = Module::default();
let x = m.attr(Attribute::POSITION);
let y = m.lit(Vec3::ONE);
let add = m.add(x, y);
let sub = m.sub(x, y);
let mul = m.mul(x, y);
let div = m.div(x, y);
let rem = m.rem(x, y);
let lt = m.lt(x, y);
let le = m.le(x, y);
let gt = m.gt(x, y);
let ge = m.ge(x, y);
let property_layout = PropertyLayout::default();
let particle_layout = ParticleLayout::default();
let mut ctx =
ShaderWriter::new(ModifierContext::Update, &property_layout, &particle_layout);
for (expr, op) in [
(add, "+"),
(sub, "-"),
(mul, "*"),
(div, "/"),
(rem, "%"),
(lt, "<"),
(le, "<="),
(gt, ">"),
(ge, ">="),
] {
let expr = ctx.eval(&m, expr);
assert!(expr.is_ok());
let expr = expr.unwrap();
assert_eq!(
expr,
format!(
"(particle.{}) {} (vec3<f32>(1.,1.,1.))",
Attribute::POSITION.name(),
op,
)
);
}
}
#[test]
fn builtin_expr() {
let mut m = Module::default();
let builtin_ops = [
BuiltInOperator::Time,
BuiltInOperator::DeltaTime,
BuiltInOperator::VirtualTime,
BuiltInOperator::VirtualDeltaTime,
BuiltInOperator::RealTime,
BuiltInOperator::RealDeltaTime,
];
for op in builtin_ops {
let handle = m.builtin(op);
{
let expr = m.get(handle);
assert!(expr.is_some());
let expr = expr.unwrap();
assert!(matches!(*expr, Expr::BuiltIn(_)));
if let Expr::BuiltIn(op) = expr {
assert!(builtin_ops.contains(&op.operator));
}
}
let property_layout = PropertyLayout::default();
let particle_layout = ParticleLayout::default();
let mut ctx =
ShaderWriter::new(ModifierContext::Update, &property_layout, &particle_layout);
let expr = ctx.eval(&m, handle);
assert!(expr.is_ok());
let expr = expr.unwrap();
assert_eq!(expr, format!("sim_params.{}", op.name()));
}
for (handle, expr) in m.expressions() {
assert!(handle.index() < m.expressions.len());
assert!(matches!(*expr, Expr::BuiltIn(_)));
if let Expr::BuiltIn(op) = expr {
assert!(builtin_ops.contains(&op.operator));
}
}
{
let value = m.builtin(BuiltInOperator::IsAlive);
let property_layout = PropertyLayout::default();
let particle_layout = ParticleLayout::default();
let mut ctx =
ShaderWriter::new(ModifierContext::Update, &property_layout, &particle_layout);
let expr = ctx.eval(&m, value);
assert!(expr.is_ok());
let expr = expr.unwrap();
assert_eq!(expr, "is_alive");
}
for (scalar_type, prefix) in [
(ScalarType::Bool, "b"),
(ScalarType::Float, "f"),
(ScalarType::Int, "i"),
(ScalarType::Uint, "u"),
] {
let value = m.builtin(BuiltInOperator::Rand(scalar_type.into()));
{
let property_layout = PropertyLayout::default();
let particle_layout = ParticleLayout::default();
let mut ctx =
ShaderWriter::new(ModifierContext::Update, &property_layout, &particle_layout);
let expr = ctx.eval(&m, value);
assert!(expr.is_ok());
let expr = expr.unwrap();
assert_eq!(expr, "var0");
assert_eq!(ctx.main_code, format!("let var0 = {}rand();\n", prefix));
}
for count in 2..=4 {
let vec = m.builtin(BuiltInOperator::Rand(
VectorType::new(scalar_type, count).into(),
));
let property_layout = PropertyLayout::default();
let particle_layout = ParticleLayout::default();
let mut ctx =
ShaderWriter::new(ModifierContext::Update, &property_layout, &particle_layout);
let expr = ctx.eval(&m, vec);
assert!(expr.is_ok());
let expr = expr.unwrap();
assert_eq!(expr, "var0");
assert_eq!(
ctx.main_code,
format!("let var0 = {}rand{}();\n", prefix, count)
);
}
}
}
#[test]
fn unary_expr() {
let mut m = Module::default();
let x = m.attr(Attribute::POSITION);
let y = m.lit(Vec3::new(1., -3.1, 6.99));
let z = m.lit(BVec3::new(false, true, false));
let w = m.lit(Vec4::W);
let v = m.lit(Vec4::new(-1., 1., 0., 7.2));
let us = m.lit(0x0u32);
let uu = m.lit(0x0u32);
let abs = m.abs(x);
let acos = m.acos(w);
let all = m.all(z);
let any = m.any(z);
let asin = m.asin(w);
let atan = m.atan(w);
let ceil = m.ceil(y);
let cos = m.cos(y);
let exp = m.exp(y);
let exp2 = m.exp2(y);
let floor = m.floor(y);
let fract = m.fract(y);
let inv_sqrt = m.inverse_sqrt(y);
let length = m.length(y);
let log = m.log(y);
let log2 = m.log2(y);
let norm = m.normalize(y);
let pack4x8snorm = m.pack4x8snorm(v);
let pack4x8unorm = m.pack4x8unorm(v);
let round = m.round(y);
let saturate = m.saturate(y);
let sign = m.sign(y);
let sin = m.sin(y);
let sqrt = m.sqrt(y);
let tan = m.tan(y);
let unpack4x8snorm = m.unpack4x8snorm(us);
let unpack4x8unorm = m.unpack4x8unorm(uu);
let comp_x = m.x(w);
let comp_y = m.y(w);
let comp_z = m.z(w);
let comp_w = m.w(w);
let property_layout = PropertyLayout::default();
let particle_layout = ParticleLayout::default();
let mut ctx =
ShaderWriter::new(ModifierContext::Update, &property_layout, &particle_layout);
for (expr, op, inner) in [
(
abs,
"abs",
&format!("particle.{}", Attribute::POSITION.name())[..],
),
(acos, "acos", "vec4<f32>(0.,0.,0.,1.)"),
(all, "all", "vec3<bool>(false,true,false)"),
(any, "any", "vec3<bool>(false,true,false)"),
(asin, "asin", "vec4<f32>(0.,0.,0.,1.)"),
(atan, "atan", "vec4<f32>(0.,0.,0.,1.)"),
(ceil, "ceil", "vec3<f32>(1.,-3.1,6.99)"),
(cos, "cos", "vec3<f32>(1.,-3.1,6.99)"),
(exp, "exp", "vec3<f32>(1.,-3.1,6.99)"),
(exp2, "exp2", "vec3<f32>(1.,-3.1,6.99)"),
(floor, "floor", "vec3<f32>(1.,-3.1,6.99)"),
(fract, "fract", "vec3<f32>(1.,-3.1,6.99)"),
(inv_sqrt, "inverseSqrt", "vec3<f32>(1.,-3.1,6.99)"),
(length, "length", "vec3<f32>(1.,-3.1,6.99)"),
(log, "log", "vec3<f32>(1.,-3.1,6.99)"),
(log2, "log2", "vec3<f32>(1.,-3.1,6.99)"),
(norm, "normalize", "vec3<f32>(1.,-3.1,6.99)"),
(pack4x8snorm, "pack4x8snorm", "vec4<f32>(-1.,1.,0.,7.2)"),
(pack4x8unorm, "pack4x8unorm", "vec4<f32>(-1.,1.,0.,7.2)"),
(round, "round", "vec3<f32>(1.,-3.1,6.99)"),
(saturate, "saturate", "vec3<f32>(1.,-3.1,6.99)"),
(sign, "sign", "vec3<f32>(1.,-3.1,6.99)"),
(sin, "sin", "vec3<f32>(1.,-3.1,6.99)"),
(sqrt, "sqrt", "vec3<f32>(1.,-3.1,6.99)"),
(tan, "tan", "vec3<f32>(1.,-3.1,6.99)"),
(unpack4x8snorm, "unpack4x8snorm", "0u"),
(unpack4x8unorm, "unpack4x8unorm", "0u"),
] {
let expr = ctx.eval(&m, expr);
assert!(expr.is_ok());
let expr = expr.unwrap();
assert_eq!(expr, format!("{}({})", op, inner));
}
for (expr, op, inner) in [
(comp_x, "x", "vec4<f32>(0.,0.,0.,1.)"),
(comp_y, "y", "vec4<f32>(0.,0.,0.,1.)"),
(comp_z, "z", "vec4<f32>(0.,0.,0.,1.)"),
(comp_w, "w", "vec4<f32>(0.,0.,0.,1.)"),
] {
let expr = ctx.eval(&m, expr);
assert!(expr.is_ok());
let expr = expr.unwrap();
assert_eq!(expr, format!("{}.{}", inner, op));
}
}
#[test]
fn binary_expr() {
let mut m = Module::default();
let x = m.attr(Attribute::POSITION);
let y = m.lit(Vec3::ONE);
let z = m.lit(1.45);
let atan2 = m.atan2(x, y);
let cross = m.cross(x, y);
let dist = m.distance(x, y);
let dot = m.dot(x, y);
let min = m.min(x, y);
let max = m.max(x, y);
let step = m.step(x, y);
let vec4_xyz_w = m.vec4_xyz_w(x, z);
let property_layout = PropertyLayout::default();
let particle_layout = ParticleLayout::default();
let mut ctx =
ShaderWriter::new(ModifierContext::Update, &property_layout, &particle_layout);
for (expr, op) in [
(atan2, "atan2"),
(cross, "cross"),
(dist, "distance"),
(dot, "dot"),
(min, "min"),
(max, "max"),
(step, "step"),
] {
let expr = ctx.eval(&m, expr);
assert!(expr.is_ok());
let expr = expr.unwrap();
assert_eq!(
expr,
format!(
"{}(particle.{}, vec3<f32>(1.,1.,1.))",
op,
Attribute::POSITION.name(),
)
);
}
{
let expr = ctx.eval(&m, vec4_xyz_w);
assert!(expr.is_ok());
let expr = expr.unwrap();
let z = ctx.eval(&m, z).unwrap();
assert_eq!(
expr,
format!("vec4(particle.{}, {})", Attribute::POSITION.name(), z)
);
}
}
#[test]
fn ternary_expr() {
let mut m = Module::default();
let x = m.attr(Attribute::POSITION);
let y = m.lit(Vec3::ONE);
let z = m.lit(Vec3::splat(2.));
let t = m.lit(0.3);
let a = m.lit(-4.2);
let b = m.lit(53.09);
let mix = m.mix(x, y, t);
let clamp = m.clamp(x, y, z);
let smoothstep = m.smoothstep(x, y, x);
let vecthree = m.vec3(a, b, t);
let property_layout = PropertyLayout::default();
let particle_layout = ParticleLayout::default();
let mut ctx =
ShaderWriter::new(ModifierContext::Update, &property_layout, &particle_layout);
for (expr, op, third) in [
(mix, "mix", t),
(clamp, "clamp", z),
(smoothstep, "smoothstep", x),
] {
let expr = ctx.eval(&m, expr);
assert!(expr.is_ok());
let expr = expr.unwrap();
let third = ctx.eval(&m, third).unwrap();
assert_eq!(
expr,
format!(
"{}(particle.{}, vec3<f32>(1.,1.,1.), {})",
op,
Attribute::POSITION.name(),
third
)
);
}
{
let expr = ctx.eval(&m, vecthree);
assert!(expr.is_ok());
let expr = expr.unwrap();
let a = ctx.eval(&m, a).unwrap();
let b = ctx.eval(&m, b).unwrap();
let t = ctx.eval(&m, t).unwrap();
assert_eq!(expr, format!("vec3({}, {}, {})", a, b, t));
}
}
#[test]
fn cast_expr() {
let mut m = Module::default();
let x = m.attr(Attribute::POSITION);
let y = m.lit(IVec2::ONE);
let z = m.lit(0.3);
let w = m.lit(false);
let cx = m.cast(x, VectorType::VEC3I);
let cy = m.cast(y, VectorType::VEC2U);
let cz = m.cast(z, ScalarType::Int);
let cw = m.cast(w, ScalarType::Uint);
let property_layout = PropertyLayout::default();
let particle_layout = ParticleLayout::default();
let mut ctx =
ShaderWriter::new(ModifierContext::Update, &property_layout, &particle_layout);
for (expr, cast, target) in [
(x, cx, ValueType::Vector(VectorType::VEC3I)),
(y, cy, VectorType::VEC2U.into()),
(z, cz, ScalarType::Int.into()),
(w, cw, ScalarType::Uint.into()),
] {
let expr = ctx.eval(&m, expr);
assert!(expr.is_ok());
let expr = expr.unwrap();
let cast = ctx.eval(&m, cast);
assert!(cast.is_ok());
let cast = cast.unwrap();
assert_eq!(cast, format!("{}({})", target.to_wgsl_string(), expr));
}
}
#[test]
fn attribute_pointer() {
let mut m = Module::default();
let x = m.attr(Attribute::POSITION);
let property_layout = PropertyLayout::default();
let particle_layout = ParticleLayout::default();
let mut ctx =
ShaderWriter::new(ModifierContext::Update, &property_layout, &particle_layout);
let res = ctx.eval(&m, x);
assert!(res.is_ok());
let xx = res.ok().unwrap();
assert_eq!(xx, format!("particle.{}", Attribute::POSITION.name()));
let mut ctx =
ShaderWriter::new(ModifierContext::Update, &property_layout, &particle_layout)
.with_attribute_pointer();
let res = ctx.eval(&m, x);
assert!(res.is_ok());
let xx = res.ok().unwrap();
assert_eq!(xx, format!("(*particle).{}", Attribute::POSITION.name()));
}
#[test]
#[should_panic]
fn invalid_cast_vector_to_scalar() {
let mut m = Module::default();
let x = m.lit(Vec2::ONE);
let _ = m.cast(x, ScalarType::Float);
}
#[test]
#[should_panic]
fn invalid_cast_matrix_to_scalar() {
let mut m = Module::default();
let x = m.lit(Value::Matrix(Mat4::ZERO.into()));
let _ = m.cast(x, ScalarType::Float);
}
#[test]
#[should_panic]
fn invalid_cast_matrix_to_vector() {
let mut m = Module::default();
let x = m.lit(Value::Matrix(Mat4::ZERO.into()));
let _ = m.cast(x, VectorType::VEC4F);
}
#[test]
#[should_panic]
fn invalid_cast_scalar_to_matrix() {
let mut m = Module::default();
let x = m.lit(3.);
let _ = m.cast(x, MatrixType::MAT3X3F);
}
#[test]
#[should_panic]
fn invalid_cast_vector_to_matrix() {
let mut m = Module::default();
let x = m.lit(Vec3::ZERO);
let _ = m.cast(x, MatrixType::MAT2X4F);
}
#[test]
fn cast_expr_new() {
let mut m = Module::default();
let x = m.attr(Attribute::POSITION);
let c = CastExpr::new(x, VectorType::VEC3F);
assert_eq!(c.value_type(), ValueType::Vector(VectorType::VEC3F));
assert_eq!(c.is_valid(&m), Some(true));
let x = m.attr(Attribute::POSITION);
let c = CastExpr::new(x, ScalarType::Bool);
assert_eq!(c.value_type(), ValueType::Scalar(ScalarType::Bool));
assert_eq!(c.is_valid(&m), Some(false));
let p = m.add_property("my_prop", 3.0.into());
let y = m.prop(p);
let c = CastExpr::new(y, MatrixType::MAT2X3F);
assert_eq!(c.value_type(), ValueType::Matrix(MatrixType::MAT2X3F));
assert_eq!(c.is_valid(&m), None); }
#[test]
fn side_effect() {
let mut m = Module::default();
let r = m.builtin(BuiltInOperator::Rand(ScalarType::Float.into()));
let r2 = r;
let r3 = r2;
let a = m.add(r, r2);
let b = m.mix(r, r2, r3);
let c = m.abs(a);
{
let property_layout = PropertyLayout::default();
let particle_layout = ParticleLayout::default();
let mut ctx =
ShaderWriter::new(ModifierContext::Update, &property_layout, &particle_layout);
let value = ctx.eval(&m, a).unwrap();
assert_eq!(value, "(var0) + (var0)");
assert_eq!(ctx.main_code, "let var0 = frand();\n");
}
{
let property_layout = PropertyLayout::default();
let particle_layout = ParticleLayout::default();
let mut ctx =
ShaderWriter::new(ModifierContext::Update, &property_layout, &particle_layout);
let value = ctx.eval(&m, b).unwrap();
assert_eq!(value, "mix(var0, var0, var0)");
assert_eq!(ctx.main_code, "let var0 = frand();\n");
}
{
let property_layout = PropertyLayout::default();
let particle_layout = ParticleLayout::default();
let mut ctx =
ShaderWriter::new(ModifierContext::Update, &property_layout, &particle_layout);
let value = ctx.eval(&m, c).unwrap();
assert_eq!(value, "abs((var0) + (var0))");
assert_eq!(ctx.main_code, "let var0 = frand();\n");
}
}
#[test]
fn expr_handle_serde() {
let handle = ExprHandle {
id: NonZeroU32::new(42).unwrap(),
};
let s = ron::ser::to_string_pretty(&handle, PrettyConfig::default()).unwrap();
eprintln!("{}", s);
{
let mut de = ron::de::Deserializer::from_str(&s).unwrap();
let serde_handle = ExprHandle::deserialize(&mut de).unwrap();
assert_eq!(handle, serde_handle);
}
{
let mut de = ron::de::Deserializer::from_str("\"#42\"").unwrap();
let serde_handle = ExprHandle::deserialize(&mut de).unwrap();
assert_eq!(handle, serde_handle);
}
{
let mut de = ron::de::Deserializer::from_str("\"invalid\"").unwrap();
let ret = ExprHandle::deserialize(&mut de);
assert!(ret.is_err());
}
{
let mut de = ron::de::Deserializer::from_str("33").unwrap();
let ret = ExprHandle::deserialize(&mut de);
assert!(ret.is_err());
}
{
let mut de = ron::de::Deserializer::from_str("\"#0\"").unwrap();
let ret = ExprHandle::deserialize(&mut de);
assert!(ret.is_err());
}
{
let mut de = ron::de::Deserializer::from_str("\"#-5\"").unwrap();
let ret = ExprHandle::deserialize(&mut de);
assert!(ret.is_err());
}
{
let mut de = ron::de::Deserializer::from_str("\"#4294967295\"").unwrap();
let serde_handle = ExprHandle::deserialize(&mut de).unwrap();
assert_eq!(serde_handle.id.get(), u32::MAX);
}
{
let mut de = ron::de::Deserializer::from_str("\"#4294967296\"").unwrap();
let ret = ExprHandle::deserialize(&mut de);
assert!(ret.is_err());
}
}
}