use crate::grid::grid::{GpuGrid, WgGrid};
use crate::grid::prefix_sum::{PrefixSumWorkspace, WgPrefixSum};
use crate::grid::sort::WgSort;
use crate::solver::{GpuBoundaryCondition, GpuImpulses, GpuMaterials, GpuParticleModelData, GpuParticles, GpuRigidParticles, GpuSimulationParams, GpuTimestepBounds, Particle, SimulationParams, WgG2P, WgG2PCdf, WgGridUpdate, WgGridUpdateCdf, WgGridUpdateCollide, WgP2G, WgP2GCdf, WgParticleUpdate, WgRigidImpulses, WgRigidParticleUpdate, WgTimestepBounds};
use crate::rbd::dynamics::GpuBodySet;
use crate::rbd::dynamics::body::{BodyCoupling, BodyCouplingEntry};
use crate::math::{GpuSim, Vector};
use rapier::dynamics::RigidBodySet;
use rapier::geometry::{ColliderHandle, ColliderSet};
use slang_hal::backend::{Backend, Encoder, GpuTimestamps};
use slang_hal::{BufferUsages, Shader, SlangCompiler};
use std::any::Any;
use std::marker::PhantomData;
use stensor::tensor::{GpuScalar, GpuTensor, GpuVector};
pub struct MpmPipeline<B: Backend, GpuModel: GpuParticleModelData> {
grid: WgGrid<B>,
prefix_sum: WgPrefixSum<B>,
sort: WgSort<B>,
p2g: WgP2G<B>,
p2g_cdf: WgP2GCdf<B>,
grid_update_cdf: WgGridUpdateCdf<B>,
grid_update_collide: WgGridUpdateCollide<B>,
grid_update: WgGridUpdate<B>,
particles_update: WgParticleUpdate<B>,
g2p: WgG2P<B>,
g2p_cdf: WgG2PCdf<B>,
rigid_particles_update: WgRigidParticleUpdate<B>,
pub timestep_bounds: WgTimestepBounds<B>,
pub impulses: WgRigidImpulses<B>,
_phantom: PhantomData<GpuModel>,
}
pub trait MpmPipelineHooks<B: Backend, GpuModel: GpuParticleModelData> {
fn after_particle_sort(
&mut self,
_backend: &B,
_encoder: &mut B::Encoder,
_data: &mut MpmData<B, GpuModel>,
_state: &mut dyn Any,
_timestamps: Option<&mut GpuTimestamps>,
) -> Result<(), B::Error> {
Ok(())
}
fn after_p2g(
&mut self,
_backend: &B,
_encoder: &mut B::Encoder,
_data: &mut MpmData<B, GpuModel>,
_state: &mut dyn Any,
_timestamps: Option<&mut GpuTimestamps>,
) -> Result<(), B::Error> {
Ok(())
}
fn after_grid_update(
&mut self,
_backend: &B,
_encoder: &mut B::Encoder,
_data: &mut MpmData<B, GpuModel>,
_state: &mut dyn Any,
_timestamps: Option<&mut GpuTimestamps>,
) -> Result<(), B::Error> {
Ok(())
}
fn after_g2p(
&mut self,
_backend: &B,
_encoder: &mut B::Encoder,
_data: &mut MpmData<B, GpuModel>,
_state: &mut dyn Any,
_timestamps: Option<&mut GpuTimestamps>,
) -> Result<(), B::Error> {
Ok(())
}
fn after_particles_update(
&mut self,
_backend: &B,
_encoder: &mut B::Encoder,
_data: &mut MpmData<B, GpuModel>,
_state: &mut dyn Any,
_timestamps: Option<&mut GpuTimestamps>,
) -> Result<(), B::Error> {
Ok(())
}
}
impl<B: Backend, GpuModel: GpuParticleModelData> MpmPipelineHooks<B, GpuModel> for () {}
pub struct MpmData<B: Backend, GpuModel: GpuParticleModelData> {
pub base_dt: f32,
pub gravity: Vector<f32>,
pub sim_params: GpuSimulationParams<B>,
pub grid: GpuGrid<B>,
pub particles: GpuParticles<B, GpuModel>, pub rigid_particles: GpuRigidParticles<B>,
pub bodies: GpuBodySet<B>,
pub body_materials: GpuMaterials<B>,
pub impulses: GpuImpulses<B>,
pub poses_staging: GpuVector<GpuSim, B>,
pub timestep_bounds: GpuScalar<GpuTimestepBounds, B>,
pub timestep_bounds_staging: GpuScalar<GpuTimestepBounds, B>,
prefix_sum: PrefixSumWorkspace<B>,
coupling: Vec<BodyCouplingEntry>,
}
pub struct MpmSpecializations {
pub particle_model: Vec<String>,
}
impl<B: Backend, GpuModel: GpuParticleModelData> MpmData<B, GpuModel> {
pub fn new(
backend: &B,
params: SimulationParams,
particles: &[Particle<GpuModel::Model>],
bodies: &RigidBodySet,
colliders: &ColliderSet,
materials: &[(ColliderHandle, GpuBoundaryCondition)],
cell_width: f32,
grid_capacity: u32,
) -> Result<Self, B::Error> {
let coupling: Vec<_> = colliders
.iter()
.filter_map(|(co_handle, co)| {
let rb_handle = co.parent()?;
Some(BodyCouplingEntry {
body: rb_handle,
collider: co_handle,
mode: BodyCoupling::OneWay,
})
})
.collect();
let materials: Vec<_> = coupling
.iter()
.map(|c| {
materials
.iter()
.find(|e| e.0 == c.collider)
.map(|e| e.1)
.unwrap_or_default()
})
.collect();
Self::with_select_coupling(
backend,
params,
particles,
bodies,
colliders,
coupling,
&materials,
cell_width,
grid_capacity,
)
}
pub fn with_select_coupling(
backend: &B,
params: SimulationParams,
particles: &[Particle<GpuModel::Model>],
bodies: &RigidBodySet,
colliders: &ColliderSet,
coupling: Vec<BodyCouplingEntry>,
materials: &[GpuBoundaryCondition], cell_width: f32,
grid_capacity: u32,
) -> Result<Self, B::Error> {
assert_eq!(coupling.len(), materials.len());
let sampling_step = cell_width; let bodies = GpuBodySet::from_rapier(backend, bodies, colliders, &coupling)?;
let body_materials = GpuMaterials::new(backend, materials)?;
let sim_params = GpuSimulationParams::new(backend, params)?;
let particles = GpuParticles::from_particles(backend, particles)?;
let rigid_particles =
GpuRigidParticles::from_rapier(backend, colliders, &bodies, &coupling, sampling_step)?;
let grid = GpuGrid::with_capacity(backend, grid_capacity, cell_width)?;
let prefix_sum = PrefixSumWorkspace::with_capacity(backend, grid_capacity)?;
let impulses = GpuImpulses::new(backend)?;
let poses_staging = GpuVector::vector_uninit(
backend,
bodies.len(),
BufferUsages::COPY_DST | BufferUsages::MAP_READ,
)?;
let bounds = GpuTimestepBounds::new();
let timestep_bounds = GpuTensor::scalar(
backend,
bounds,
BufferUsages::STORAGE | BufferUsages::COPY_SRC,
)?;
let timestep_bounds_staging = GpuTensor::scalar(
backend,
bounds,
BufferUsages::COPY_DST | BufferUsages::MAP_READ,
)?;
Ok(Self {
sim_params,
particles,
gravity: params.gravity,
rigid_particles,
bodies,
body_materials,
impulses,
grid,
prefix_sum,
poses_staging,
coupling,
timestep_bounds,
timestep_bounds_staging,
base_dt: params.dt,
})
}
pub fn coupling(&self) -> &[BodyCouplingEntry] {
&self.coupling
}
}
impl<B: Backend, GpuModel: GpuParticleModelData> MpmPipeline<B, GpuModel> {
pub fn new(backend: &B, compiler: &SlangCompiler) -> Result<Self, B::Error> {
Ok(Self {
grid: WgGrid::from_backend(backend, compiler)?,
prefix_sum: WgPrefixSum::from_backend(backend, compiler)?,
sort: WgSort::from_backend(backend, compiler)?,
p2g: WgP2G::from_backend(backend, compiler)?,
p2g_cdf: WgP2GCdf::from_backend(backend, compiler)?,
grid_update: WgGridUpdate::from_backend(backend, compiler)?,
grid_update_cdf: WgGridUpdateCdf::from_backend(backend, compiler)?,
grid_update_collide: WgGridUpdateCollide::from_backend(backend, compiler)?,
#[cfg(feature = "comptime")]
particles_update: WgParticleUpdate::from_backend(backend, compiler)?,
#[cfg(feature = "runtime")]
particles_update: WgParticleUpdate::with_specializations(
backend,
compiler,
&GpuModel::specialization_modules(),
)?,
rigid_particles_update: WgRigidParticleUpdate::from_backend(backend, compiler)?,
g2p: WgG2P::from_backend(backend, compiler)?,
g2p_cdf: WgG2PCdf::from_backend(backend, compiler)?,
impulses: WgRigidImpulses::from_backend(backend, compiler)?,
#[cfg(feature = "comptime")]
timestep_bounds: WgTimestepBounds::from_backend(backend, compiler)?,
#[cfg(feature = "runtime")]
timestep_bounds: WgTimestepBounds::with_specializations(
backend,
compiler,
&GpuModel::specialization_modules(),
)?,
_phantom: PhantomData,
})
}
pub async fn launch_step(
&self,
backend: &B,
encoder: &mut B::Encoder,
data: &mut MpmData<B, GpuModel>,
hooks: &mut dyn MpmPipelineHooks<B, GpuModel>,
hooks_state: &mut dyn Any,
mut timestamps: Option<&mut GpuTimestamps>,
) -> Result<(), B::Error> {
{
let mut pass = encoder.begin_pass("grid_sort", timestamps.as_deref_mut());
data.grid.swap_buffers();
self.grid.launch_sort(
backend,
&mut pass,
&data.particles,
&data.rigid_particles,
&data.grid,
&mut data.prefix_sum,
&self.sort,
&self.prefix_sum,
)?;
}
hooks.after_particle_sort(backend, encoder, data, hooks_state, timestamps.as_deref_mut())?;
{
let mut pass = encoder.begin_pass("p2g", timestamps.as_deref_mut());
self.p2g.launch(
backend,
&mut pass,
&data.grid,
&data.particles,
&data.impulses,
&data.bodies,
&data.body_materials,
)?;
}
hooks.after_p2g(backend, encoder, data, hooks_state, timestamps.as_deref_mut())?;
{
let mut pass = encoder.begin_pass("grid_update", timestamps.as_deref_mut());
self.grid_update
.launch(backend, &mut pass, &data.sim_params, &data.grid)?;
self.grid_update_collide
.launch(backend, &mut pass, &data.sim_params, &data.grid, &data.bodies, &data.body_materials)?;
}
hooks.after_grid_update(backend, encoder, data, hooks_state, timestamps.as_deref_mut())?;
{
let mut pass = encoder.begin_pass("g2p", timestamps.as_deref_mut());
self.g2p.launch(
backend,
&mut pass,
&data.sim_params,
&data.grid,
&data.particles,
&data.bodies,
&data.body_materials,
)?;
}
hooks.after_g2p(backend, encoder, data, hooks_state, timestamps.as_deref_mut())?;
{
let mut pass = encoder.begin_pass("particles_update", timestamps.as_deref_mut());
self.particles_update.launch(
backend,
&mut pass,
&data.sim_params,
&data.grid,
&data.particles,
&data.bodies,
)?;
}
hooks.after_particles_update(backend, encoder, data, hooks_state, timestamps.as_deref_mut())?;
{
let mut pass = encoder.begin_pass("integrate_bodies", timestamps.as_deref_mut());
self.impulses.launch(
backend,
&mut pass,
&data.grid,
&data.sim_params,
&data.impulses,
&data.bodies,
)?;
}
Ok(())
}
}