use crate::grid::grid::{
GpuActiveBlockHeader, GpuGrid, GpuGridHashMapEntry, GpuGridMetadata, GpuGridNode,
GpuNodeCollision,
};
use crate::math::{Matrix, Vector};
use crate::rbd::dynamics::GpuBodySet;
use crate::solver::{
GpuBoundaryCondition, GpuMaterials, GpuParticleModelData, GpuParticles, GpuSimulationParams,
Kinematics, ParticlePosition, ParticleProperties, SimulationParams,
};
use bytemuck::{Pod, Zeroable};
use encase::ShaderType;
use slang_hal::backend::Backend;
use slang_hal::function::GpuFunction;
use slang_hal::{BufferUsages, Shader, ShaderArgs};
use stensor::tensor::{GpuScalar, GpuTensor, GpuVector};
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
pub enum LineSearchCriterion {
#[default]
Energy,
Residual,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct ImplicitSolverParams {
pub max_newton_iters: u32,
pub newton_rel_tol: f32,
pub max_cg_iters: u32,
pub cg_rel_tol: f32,
pub line_search_steps: u32,
pub line_search: LineSearchCriterion,
}
impl Default for ImplicitSolverParams {
fn default() -> Self {
Self {
max_newton_iters: 3,
newton_rel_tol: 1.0e-2,
max_cg_iters: 30,
cg_rel_tol: 1.0e-3,
line_search_steps: 4,
line_search: LineSearchCriterion::Energy,
}
}
}
impl ImplicitSolverParams {
pub fn semi_implicit() -> Self {
Self {
max_newton_iters: 1,
line_search_steps: 0,
..Self::default()
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Default)]
pub enum MpmIntegrator {
#[default]
Explicit,
Implicit(ImplicitSolverParams),
}
const ARMIJO: f32 = 1.0e-4;
#[derive(Copy, Clone, Debug, PartialEq, Default, Pod, Zeroable)]
#[repr(C)]
pub struct GpuCgNode {
pub rhs: Vector,
#[cfg(feature = "dim3")]
pub _pad0: f32,
pub x: Vector,
#[cfg(feature = "dim3")]
pub _pad1: f32,
pub dx: Vector,
#[cfg(feature = "dim3")]
pub _pad2: f32,
pub v: Vector,
#[cfg(feature = "dim3")]
pub _pad3: f32,
pub g: Vector,
#[cfg(feature = "dim3")]
pub _pad4: f32,
pub r: Vector,
#[cfg(feature = "dim3")]
pub _pad5: f32,
pub z: Vector,
#[cfg(feature = "dim3")]
pub _pad6: f32,
pub p: Vector,
#[cfg(feature = "dim3")]
pub _pad7: f32,
pub ap: Vector,
#[cfg(feature = "dim3")]
pub _pad8: f32,
pub normal: Vector,
pub mass: f32,
pub bc: u32,
#[cfg(feature = "dim3")]
pub _pad9: [u32; 3],
}
#[cfg(feature = "dim3")]
static_assertions::assert_eq_size!(GpuCgNode, [u8; 176]);
#[cfg(feature = "dim2")]
static_assertions::assert_eq_size!(GpuCgNode, [u8; 88]);
#[derive(Copy, Clone, Debug, PartialEq, Default, ShaderType)]
#[repr(C)]
pub struct GpuImplicitParticle {
pub stress: Matrix,
pub trial_def_grad: Matrix,
pub coeff: f32,
pub init_volume: f32,
pub energy: f32,
pub padding: u32,
}
#[derive(Copy, Clone, Debug, PartialEq, Default, Pod, Zeroable)]
#[repr(C)]
pub struct CgScalars {
pub rz: f32,
pub rz0: f32,
pub p_ap: f32,
pub alpha: f32,
pub beta: f32,
pub cg_converged: u32,
pub cg_iters: u32,
pub g_sq: f32,
pub g0_sq: f32,
pub energy: f32,
pub energy_trial: f32,
pub gdx: f32,
pub ls_alpha: f32,
pub ls_state: u32,
pub ls_trial: u32,
pub newton_iters: u32,
pub newton_done: u32,
pub padding: u32,
}
#[derive(Copy, Clone, Debug, PartialEq, Default, Pod, Zeroable)]
#[repr(C)]
pub struct CgParams {
pub cg_rel_tol_sq: f32,
pub newton_rel_tol_sq: f32,
pub armijo: f32,
pub line_search_steps: u32,
pub criterion: u32,
pub padding0: u32,
pub padding1: u32,
pub padding2: u32,
}
impl CgParams {
fn from_params(params: &ImplicitSolverParams) -> Self {
Self {
cg_rel_tol_sq: params.cg_rel_tol * params.cg_rel_tol,
newton_rel_tol_sq: params.newton_rel_tol * params.newton_rel_tol,
armijo: ARMIJO,
line_search_steps: params.line_search_steps,
criterion: match params.line_search {
LineSearchCriterion::Energy => 0,
LineSearchCriterion::Residual => 1,
},
padding0: 0,
padding1: 0,
padding2: 0,
}
}
}
pub struct ImplicitWorkspace<B: Backend> {
pub cg_nodes: GpuVector<GpuCgNode, B>,
pub particles: GpuVector<GpuImplicitParticle, B>,
pub partials: GpuVector<f32, B>,
pub partials_particles: GpuVector<f32, B>,
pub scalars: GpuScalar<CgScalars, B>,
scalars_staging: GpuScalar<CgScalars, B>,
pub cg_params: GpuScalar<CgParams, B>,
pub newton_dispatch: GpuScalar<[u32; 3], B>,
pub cg_dispatch: GpuScalar<[u32; 3], B>,
pub ls_dispatch: GpuScalar<[u32; 3], B>,
pub ls_particle_dispatch: GpuScalar<[u32; 3], B>,
uploaded_params: CgParams,
}
impl<B: Backend> ImplicitWorkspace<B> {
const NODE_USAGES: BufferUsages = BufferUsages::STORAGE
.union(BufferUsages::COPY_SRC)
.union(BufferUsages::COPY_DST);
pub fn new(backend: &B) -> Result<Self, B::Error> {
let uploaded_params = CgParams::from_params(&ImplicitSolverParams::default());
let dispatch = |backend: &B| {
GpuTensor::scalar(
backend,
[0u32, 1, 1],
BufferUsages::STORAGE | BufferUsages::INDIRECT,
)
};
Ok(Self {
cg_nodes: GpuVector::vector_uninit(backend, 1, Self::NODE_USAGES)?,
particles: GpuVector::vector_uninit_encased(backend, 1, BufferUsages::STORAGE)?,
partials: GpuVector::vector_uninit(backend, 1, Self::NODE_USAGES)?,
partials_particles: GpuVector::vector_uninit(backend, 1, Self::NODE_USAGES)?,
scalars: GpuTensor::scalar(
backend,
CgScalars::default(),
BufferUsages::STORAGE | BufferUsages::COPY_SRC,
)?,
scalars_staging: GpuTensor::scalar(
backend,
CgScalars::default(),
BufferUsages::COPY_DST | BufferUsages::MAP_READ,
)?,
cg_params: GpuTensor::scalar(
backend,
uploaded_params,
BufferUsages::UNIFORM | BufferUsages::COPY_DST,
)?,
newton_dispatch: dispatch(backend)?,
cg_dispatch: dispatch(backend)?,
ls_dispatch: dispatch(backend)?,
ls_particle_dispatch: dispatch(backend)?,
uploaded_params,
})
}
pub fn ensure_capacity<GpuModel: GpuParticleModelData>(
&mut self,
backend: &B,
grid: &GpuGrid<B>,
particles: &GpuParticles<B, GpuModel>,
) -> Result<(), B::Error> {
let num_nodes = grid.nodes.len();
if self.cg_nodes.len() < num_nodes {
self.cg_nodes = GpuVector::vector_uninit(backend, num_nodes as u32, Self::NODE_USAGES)?;
}
let num_blocks = grid.active_blocks.len();
if self.partials.len() < num_blocks {
self.partials =
GpuVector::vector_uninit(backend, num_blocks as u32, Self::NODE_USAGES)?;
}
let num_particles = particles.len().max(1) as u64;
if self.particles.len() < num_particles {
self.particles = GpuVector::vector_uninit_encased(
backend,
num_particles as u32,
BufferUsages::STORAGE,
)?;
}
let num_groups = num_particles.div_ceil(64);
if self.partials_particles.len() < num_groups {
self.partials_particles =
GpuVector::vector_uninit(backend, num_groups as u32, Self::NODE_USAGES)?;
}
Ok(())
}
fn set_params(&mut self, backend: &B, params: &ImplicitSolverParams) -> Result<(), B::Error> {
let cg_params = CgParams::from_params(params);
if cg_params != self.uploaded_params {
self.uploaded_params = cg_params;
backend.write_buffer(self.cg_params.buffer_mut(), 0, &[cg_params])?;
}
Ok(())
}
pub async fn read_scalars(&mut self, backend: &B) -> Result<CgScalars, B::Error> {
let mut encoder = backend.begin_encoding();
self.scalars_staging
.copy_from_view(&mut encoder, &self.scalars)?;
backend.submit(encoder)?;
let mut result = [CgScalars::default()];
backend
.read_buffer(self.scalars_staging.buffer(), &mut result)
.await?;
Ok(result[0])
}
}
#[derive(Shader)]
#[shader(
module = "slosh::solver::implicit::gather",
specialize = ["slosh::models::specializations"]
)]
pub struct WgImplicitGather<B: Backend> {
gather_operator: GpuFunction<B>,
gather_residual: GpuFunction<B>,
}
#[derive(Shader)]
#[shader(module = "slosh::solver::implicit::scatter")]
pub struct WgImplicitScatter<B: Backend> {
scatter_operator: GpuFunction<B>,
scatter_residual: GpuFunction<B>,
}
#[derive(Shader)]
#[shader(module = "slosh::solver::implicit::cg")]
pub struct WgImplicitCg<B: Backend> {
cg_reset: GpuFunction<B>,
prepare_particles: GpuFunction<B>,
seed_from_grid: GpuFunction<B>,
fixed_nodes: GpuFunction<B>,
apply_boundary_conditions: GpuFunction<B>,
cg_init_residual: GpuFunction<B>,
cg_update_solution: GpuFunction<B>,
cg_update_direction: GpuFunction<B>,
ls_prepare: GpuFunction<B>,
ls_trial: GpuFunction<B>,
ls_finalize: GpuFunction<B>,
energy_particles: GpuFunction<B>,
cg_reduce_init: GpuFunction<B>,
cg_reduce_alpha: GpuFunction<B>,
cg_reduce_beta: GpuFunction<B>,
newton_check: GpuFunction<B>,
ls_reduce_prepare: GpuFunction<B>,
ls_reduce_energy: GpuFunction<B>,
ls_reduce_residual: GpuFunction<B>,
init_energy: GpuFunction<B>,
}
#[derive(ShaderArgs)]
struct ImplicitArgs<'a, B: Backend, GpuModel: GpuParticleModelData> {
params: &'a GpuScalar<SimulationParams, B>,
grid: &'a GpuScalar<GpuGridMetadata, B>,
hmap_entries: &'a GpuVector<GpuGridHashMapEntry, B>,
active_blocks: &'a GpuVector<GpuActiveBlockHeader, B>,
nodes: &'a GpuVector<GpuGridNode, B>,
node_collisions: &'a GpuVector<GpuNodeCollision, B>,
body_materials: &'a GpuVector<GpuBoundaryCondition, B>,
sorted_particle_ids: &'a GpuVector<u32, B>,
particles_pos: &'a GpuVector<ParticlePosition, B>,
particles_kin: &'a GpuVector<Kinematics, B>,
particles_props: &'a GpuVector<ParticleProperties, B>,
particles_def_grad: &'a GpuTensor<Matrix, B>,
particles_model: &'a GpuTensor<GpuModel, B>,
particles_len: &'a GpuScalar<u32, B>,
cg_nodes: &'a GpuVector<GpuCgNode, B>,
implicit_particles: &'a GpuVector<GpuImplicitParticle, B>,
partials: &'a GpuVector<f32, B>,
partials_particles: &'a GpuVector<f32, B>,
scalars: &'a GpuScalar<CgScalars, B>,
cg_params: &'a GpuScalar<CgParams, B>,
cg_dispatch: &'a GpuScalar<[u32; 3], B>,
newton_dispatch: &'a GpuScalar<[u32; 3], B>,
ls_dispatch: &'a GpuScalar<[u32; 3], B>,
ls_particle_dispatch: &'a GpuScalar<[u32; 3], B>,
}
pub struct WgImplicitSolver<B: Backend> {
gather: WgImplicitGather<B>,
scatter: WgImplicitScatter<B>,
cg: WgImplicitCg<B>,
}
impl<B: Backend> WgImplicitSolver<B> {
pub fn with_specializations(
backend: &B,
compiler: &slang_hal::SlangCompiler,
specializations: &[String],
) -> Result<Self, B::Error> {
Ok(Self {
gather: WgImplicitGather::with_specializations(backend, compiler, specializations)?,
scatter: WgImplicitScatter::from_backend(backend, compiler)?,
cg: WgImplicitCg::from_backend(backend, compiler)?,
})
}
fn args<'a, GpuModel: GpuParticleModelData>(
sim_params: &'a GpuSimulationParams<B>,
grid: &'a GpuGrid<B>,
particles: &'a GpuParticles<B, GpuModel>,
body_materials: &'a GpuMaterials<B>,
workspace: &'a ImplicitWorkspace<B>,
) -> ImplicitArgs<'a, B, GpuModel> {
ImplicitArgs {
params: &sim_params.params,
grid: &grid.meta,
hmap_entries: &grid.hmap_entries,
active_blocks: &grid.active_blocks,
nodes: &grid.nodes,
node_collisions: &grid.node_collisions,
body_materials: &body_materials.materials,
sorted_particle_ids: particles.sorted_ids(),
particles_pos: particles.positions(),
particles_kin: &particles.kinematics,
particles_props: &particles.properties,
particles_def_grad: &particles.def_grad,
particles_model: particles.models(),
particles_len: particles.gpu_len(),
cg_nodes: &workspace.cg_nodes,
implicit_particles: &workspace.particles,
partials: &workspace.partials,
partials_particles: &workspace.partials_particles,
scalars: &workspace.scalars,
cg_params: &workspace.cg_params,
cg_dispatch: &workspace.cg_dispatch,
newton_dispatch: &workspace.newton_dispatch,
ls_dispatch: &workspace.ls_dispatch,
ls_particle_dispatch: &workspace.ls_particle_dispatch,
}
}
#[allow(clippy::too_many_arguments)]
pub fn launch_solve<GpuModel: GpuParticleModelData>(
&self,
backend: &B,
pass: &mut B::Pass,
solver_params: &ImplicitSolverParams,
sim_params: &GpuSimulationParams<B>,
grid: &GpuGrid<B>,
particles: &GpuParticles<B, GpuModel>,
_bodies: &GpuBodySet<B>,
body_materials: &GpuMaterials<B>,
workspace: &mut ImplicitWorkspace<B>,
) -> Result<(), B::Error> {
workspace.ensure_capacity(backend, grid, particles)?;
workspace.set_params(backend, solver_params)?;
let num_particles = particles.len() as u32;
let args = Self::args(sim_params, grid, particles, body_materials, workspace);
let grid_dispatch = grid.indirect_n_g2p_p2g_groups.buffer();
let residual_criterion = solver_params.line_search == LineSearchCriterion::Residual;
self.cg.cg_reset.launch(backend, pass, &args, [1, 1, 1])?;
self.cg
.prepare_particles
.launch_capped(backend, pass, &args, num_particles)?;
self.cg
.seed_from_grid
.launch_indirect(backend, pass, &args, grid_dispatch)?;
self.cg
.fixed_nodes
.launch_capped(backend, pass, &args, num_particles)?;
self.gather
.gather_residual
.launch_indirect(backend, pass, &args, grid_dispatch)?;
self.cg
.ls_trial
.launch_indirect(backend, pass, &args, grid_dispatch)?;
self.cg
.energy_particles
.launch(backend, pass, &args, [num_particles, 1, 1])?;
self.cg
.init_energy
.launch(backend, pass, &args, [256, 1, 1])?;
for _ in 0..solver_params.max_newton_iters.max(1) {
self.scatter.scatter_residual.launch_indirect(
backend,
pass,
&args,
workspace.newton_dispatch.buffer(),
)?;
self.cg
.newton_check
.launch(backend, pass, &args, [256, 1, 1])?;
self.cg.cg_init_residual.launch_indirect(
backend,
pass,
&args,
workspace.cg_dispatch.buffer(),
)?;
self.cg
.cg_reduce_init
.launch(backend, pass, &args, [256, 1, 1])?;
for _ in 0..solver_params.max_cg_iters {
self.gather.gather_operator.launch_indirect(
backend,
pass,
&args,
workspace.cg_dispatch.buffer(),
)?;
self.scatter.scatter_operator.launch_indirect(
backend,
pass,
&args,
workspace.cg_dispatch.buffer(),
)?;
self.cg
.cg_reduce_alpha
.launch(backend, pass, &args, [256, 1, 1])?;
self.cg.cg_update_solution.launch_indirect(
backend,
pass,
&args,
workspace.cg_dispatch.buffer(),
)?;
self.cg
.cg_reduce_beta
.launch(backend, pass, &args, [256, 1, 1])?;
self.cg.cg_update_direction.launch_indirect(
backend,
pass,
&args,
workspace.cg_dispatch.buffer(),
)?;
}
self.cg.ls_prepare.launch_indirect(
backend,
pass,
&args,
workspace.newton_dispatch.buffer(),
)?;
self.cg
.ls_reduce_prepare
.launch(backend, pass, &args, [256, 1, 1])?;
for _ in 0..solver_params.line_search_steps.max(1) {
self.cg.ls_trial.launch_indirect(
backend,
pass,
&args,
workspace.ls_dispatch.buffer(),
)?;
self.gather.gather_residual.launch_indirect(
backend,
pass,
&args,
workspace.ls_dispatch.buffer(),
)?;
if residual_criterion {
self.scatter.scatter_residual.launch_indirect(
backend,
pass,
&args,
workspace.ls_dispatch.buffer(),
)?;
self.cg
.ls_reduce_residual
.launch(backend, pass, &args, [256, 1, 1])?;
} else {
self.cg.energy_particles.launch_indirect(
backend,
pass,
&args,
workspace.ls_particle_dispatch.buffer(),
)?;
self.cg
.ls_reduce_energy
.launch(backend, pass, &args, [256, 1, 1])?;
}
}
self.cg
.ls_finalize
.launch_indirect(backend, pass, &args, grid_dispatch)?;
}
self.cg
.apply_boundary_conditions
.launch_indirect(backend, pass, &args, grid_dispatch)
}
#[allow(clippy::too_many_arguments)]
pub fn launch_prepare<GpuModel: GpuParticleModelData>(
&self,
backend: &B,
pass: &mut B::Pass,
sim_params: &GpuSimulationParams<B>,
grid: &GpuGrid<B>,
particles: &GpuParticles<B, GpuModel>,
body_materials: &GpuMaterials<B>,
workspace: &mut ImplicitWorkspace<B>,
) -> Result<(), B::Error> {
workspace.ensure_capacity(backend, grid, particles)?;
let num_particles = particles.len() as u32;
let args = Self::args(sim_params, grid, particles, body_materials, workspace);
let grid_dispatch = grid.indirect_n_g2p_p2g_groups.buffer();
self.cg.cg_reset.launch(backend, pass, &args, [1, 1, 1])?;
self.cg
.prepare_particles
.launch_capped(backend, pass, &args, num_particles)?;
self.cg
.seed_from_grid
.launch_indirect(backend, pass, &args, grid_dispatch)?;
self.cg
.fixed_nodes
.launch_capped(backend, pass, &args, num_particles)?;
self.gather
.gather_residual
.launch_indirect(backend, pass, &args, grid_dispatch)
}
pub fn launch_apply<GpuModel: GpuParticleModelData>(
&self,
backend: &B,
pass: &mut B::Pass,
sim_params: &GpuSimulationParams<B>,
grid: &GpuGrid<B>,
particles: &GpuParticles<B, GpuModel>,
body_materials: &GpuMaterials<B>,
workspace: &ImplicitWorkspace<B>,
) -> Result<(), B::Error> {
let args = Self::args(sim_params, grid, particles, body_materials, workspace);
let grid_dispatch = grid.indirect_n_g2p_p2g_groups.buffer();
self.gather
.gather_operator
.launch_indirect(backend, pass, &args, grid_dispatch)?;
self.scatter
.scatter_operator
.launch_indirect(backend, pass, &args, grid_dispatch)
}
}