diff --git a/assets/shaders/plexus_link.wesl b/assets/shaders/plexus_link.wesl index 9b375f0b..e38366a2 100644 --- a/assets/shaders/plexus_link.wesl +++ b/assets/shaders/plexus_link.wesl @@ -1,11 +1,4 @@ -struct GridParams { - grid_min: vec3, - cell_size: f32, - dims_x: u32, - dims_y: u32, - dims_z: u32, - _pad: u32, -} +import processing::particles::{Grid, cell_coords, cell_index}; struct Params { connection_radius: f32, @@ -15,30 +8,17 @@ struct Params { } // Auto-bound by apply(): names must be position/color. -@group(0) @binding(0) var position: array; -@group(0) @binding(1) var color: array; -@group(0) @binding(2) var edge_pos: array; -@group(0) @binding(3) var edge_col: array; -@group(0) @binding(4) var indices: array; -@group(0) @binding(5) var draw_args: array>; -// Bound by grid.bind(). -@group(0) @binding(6) var offsets: array; -@group(0) @binding(7) var sorted: array; -@group(0) @binding(8) var params: Params; -@group(0) @binding(9) var gp: GridParams; - -fn cell_coords(p: vec3, grid_min: vec3, cell_size: f32, dims: vec3) -> vec3 { - let rel = (p - grid_min) / cell_size; - return vec3( - clamp(i32(floor(rel.x)), 0, i32(dims.x) - 1), - clamp(i32(floor(rel.y)), 0, i32(dims.y) - 1), - clamp(i32(floor(rel.z)), 0, i32(dims.z) - 1), - ); -} - -fn cell_index(c: vec3, dims: vec3) -> u32 { - return c.x + c.y * dims.x + c.z * dims.x * dims.y; -} +@group(0) @binding(0) var position: array; +@group(0) @binding(1) var color: array; +@group(0) @binding(2) var edge_pos: array; +@group(0) @binding(3) var edge_col: array; +@group(0) @binding(4) var indices: array; +@group(0) @binding(5) var draw_args: array>; +// set with link.set(grid=grid) +@group(0) @binding(6) var grid_offsets: array; +@group(0) @binding(7) var grid_sorted: array; +@group(0) @binding(8) var params: Params; +@group(0) @binding(9) var grid: Grid; fn load_pos(i: u32) -> vec3 { return vec3(position[i * 3u], position[i * 3u + 1u], position[i * 3u + 2u]); @@ -70,26 +50,26 @@ fn main(@builtin(global_invocation_id) gid: vec3) { let rgb = load_rgb(i); let radius = params.connection_radius; let r2 = radius * radius; - let dims = vec3(gp.dims_x, gp.dims_y, gp.dims_z); - let base = cell_coords(pos, gp.grid_min, gp.cell_size, dims); + let dims = grid.dims; + let base = cell_coords(pos, grid.origin, grid.cell_size, dims); - let reach = max(1, i32(ceil(radius / gp.cell_size))); + let reach = max(1, i32(ceil(radius / grid.cell_size))); let z0 = max(base.z - reach, 0); - let z1 = min(base.z + reach, i32(gp.dims_z) - 1); + let z1 = min(base.z + reach, i32(grid.dims.z) - 1); let y0 = max(base.y - reach, 0); - let y1 = min(base.y + reach, i32(gp.dims_y) - 1); + let y1 = min(base.y + reach, i32(grid.dims.y) - 1); let x0 = max(base.x - reach, 0); - let x1 = min(base.x + reach, i32(gp.dims_x) - 1); + let x1 = min(base.x + reach, i32(grid.dims.x) - 1); var emitted = 0u; for (var cz = z0; cz <= z1; cz++) { for (var cy = y0; cy <= y1; cy++) { for (var cx = x0; cx <= x1; cx++) { let cell = cell_index(vec3(u32(cx), u32(cy), u32(cz)), dims); - let start = offsets[cell]; - let end = offsets[cell + 1u]; + let start = grid_offsets[cell]; + let end = grid_offsets[cell + 1u]; for (var s = start; s < end; s++) { - let j = sorted[s]; + let j = grid_sorted[s]; if j <= i { continue; } // one direction per edge let pj = load_pos(j); let diff = pos - pj; diff --git a/crates/processing_core/src/error.rs b/crates/processing_core/src/error.rs index 41510f34..e4dbdad4 100644 --- a/crates/processing_core/src/error.rs +++ b/crates/processing_core/src/error.rs @@ -60,6 +60,8 @@ pub enum ProcessingError { PipelineNotReady(u32), #[error("Particles not found")] ParticlesNotFound, + #[error("Grid not found")] + GridNotFound, #[error("Font not found")] FontNotFound, #[error("Font load error: {0}")] diff --git a/crates/processing_ffi/src/lib.rs b/crates/processing_ffi/src/lib.rs index c81d3c6f..2e4b49fa 100644 --- a/crates/processing_ffi/src/lib.rs +++ b/crates/processing_ffi/src/lib.rs @@ -3442,6 +3442,27 @@ pub unsafe extern "C" fn processing_shader_set_buffer( }); } +/// Binds `{name}_offsets`, `{name}_sorted` and the `{name}` uniform. +/// +/// # Safety +/// - `name` must be non-null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn processing_shader_set_grid( + entity: u64, + name: *const std::ffi::c_char, + grid_id: u64, +) { + error::clear_error(); + error::check(|| { + let name = unsafe { cstr_to_str(name) }?; + shader_set( + Entity::from_bits(entity), + name, + ShaderValue::Grid(Entity::from_bits(grid_id)), + ) + }); +} + #[unsafe(no_mangle)] pub extern "C" fn processing_compute_dispatch(compute_id: u64, x: u32, y: u32, z: u32) { error::clear_error(); @@ -3826,6 +3847,51 @@ pub extern "C" fn processing_particles_apply(particles_id: u64, compute_id: u64) }); } +/// Returns the grid id, or 0 on error. +#[unsafe(no_mangle)] +pub extern "C" fn processing_particles_grid_create( + particles_id: u64, + min_x: f32, + min_y: f32, + min_z: f32, + cell_size: f32, + dims_x: u32, + dims_y: u32, + dims_z: u32, +) -> u64 { + error::clear_error(); + error::check(|| { + let capacity = particles_capacity(Entity::from_bits(particles_id))?; + grid_create( + GridParams { + min: [min_x, min_y, min_z], + cell_size, + dims: [dims_x, dims_y, dims_z], + }, + capacity, + ) + }) + .map(|e| e.to_bits()) + .unwrap_or(0) +} + +#[unsafe(no_mangle)] +pub extern "C" fn processing_particles_grid_build(grid_id: u64, position_buf_id: u64) { + error::clear_error(); + error::check(|| { + grid_build( + Entity::from_bits(grid_id), + Entity::from_bits(position_buf_id), + ) + }); +} + +#[unsafe(no_mangle)] +pub extern "C" fn processing_particles_grid_destroy(grid_id: u64) { + error::clear_error(); + error::check(|| grid_destroy(Entity::from_bits(grid_id))); +} + #[unsafe(no_mangle)] pub extern "C" fn processing_particles_draw(graphics_id: u64, particles_id: u64, geometry_id: u64) { error::clear_error(); diff --git a/crates/processing_pyo3/examples/particles_lissajous.py b/crates/processing_pyo3/examples/particles_lissajous.py index ae8e4220..886c3408 100644 --- a/crates/processing_pyo3/examples/particles_lissajous.py +++ b/crates/processing_pyo3/examples/particles_lissajous.py @@ -73,7 +73,7 @@ def draw(): line_alpha=LINE_ALPHA, max_links=MAX_LINKS, ) - grid.bind(link) + link.set(grid=grid) p.apply(link) blend_mode(ALPHA_OVER) diff --git a/crates/processing_pyo3/examples/particles_plexus.py b/crates/processing_pyo3/examples/particles_plexus.py index 00ad6fd9..027d3712 100644 --- a/crates/processing_pyo3/examples/particles_plexus.py +++ b/crates/processing_pyo3/examples/particles_plexus.py @@ -65,7 +65,7 @@ def draw(): grid.build(p.buffer("position")) p.reset_indices() link.set(indices=idx, draw_args=args, link_distance=LINK_DIST, max_links=MAX_LINKS) - grid.bind(link) + link.set(grid=grid) p.apply(link) particles(p, topology="lines") diff --git a/crates/processing_pyo3/src/compute.rs b/crates/processing_pyo3/src/compute.rs index bac55d3b..9f7208fb 100644 --- a/crates/processing_pyo3/src/compute.rs +++ b/crates/processing_pyo3/src/compute.rs @@ -291,8 +291,9 @@ fn shader_value_to_py<'py>(py: Python<'py>, sv: &ShaderValue) -> PyResult Err(PyRuntimeError::new_err( - "cannot convert Texture/Buffer/Mesh* to Python value", + | ShaderValue::MeshIndex(_) + | ShaderValue::Grid(_) => Err(PyRuntimeError::new_err( + "cannot convert Texture/Buffer/Mesh*/Grid to Python value", )), } } diff --git a/crates/processing_pyo3/src/material.rs b/crates/processing_pyo3/src/material.rs index e0d4c8b1..95cf57a9 100644 --- a/crates/processing_pyo3/src/material.rs +++ b/crates/processing_pyo3/src/material.rs @@ -43,6 +43,9 @@ pub(crate) fn py_to_shader_value(value: &Bound<'_, PyAny>) -> PyResult>() { return Ok(shader_value::ShaderValue::Buffer(buf.entity)); } + if let Ok(grid) = value.extract::>() { + return Ok(shader_value::ShaderValue::Grid(grid.entity)); + } if let Ok(v) = value.extract::<[f32; 4]>() { return Ok(shader_value::ShaderValue::Float4(v)); diff --git a/crates/processing_pyo3/src/particles.rs b/crates/processing_pyo3/src/particles.rs index bc169d06..4676c1cc 100644 --- a/crates/processing_pyo3/src/particles.rs +++ b/crates/processing_pyo3/src/particles.rs @@ -117,23 +117,33 @@ fn parse_falloff(s: &str) -> PyResult { #[pyclass(unsendable)] pub struct Grid { - pub(crate) inner: processing_render::particles::grid::Grid, + pub(crate) entity: Entity, } #[pymethods] impl Grid { + /// Opaque id for this object. + pub fn id(&self) -> u64 { + self.entity.to_bits() + } + pub fn build(&self, position: &Buffer) -> PyResult<()> { - grid_build(&self.inner, position.entity) + grid_build(self.entity, position.entity) .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) } - pub fn bind(&self, compute: &Compute) -> PyResult<()> { - grid_bind(&self.inner, compute.entity).map_err(|e| PyRuntimeError::new_err(format!("{e}"))) + #[getter] + pub fn cell_size(&self) -> PyResult { + Ok(grid_get(self.entity) + .map_err(|e| PyRuntimeError::new_err(format!("{e}")))? + .params + .cell_size) } +} - #[getter] - pub fn cell_size(&self) -> f32 { - self.inner.params.cell_size +impl Drop for Grid { + fn drop(&mut self) { + let _ = grid_destroy(self.entity); } } @@ -673,7 +683,7 @@ impl Particles { Some(v) => parse_falloff(&v.extract::()?)?, None => FALLOFF_SMOOTHSTEP, }; - let cell = grid.inner.params.cell_size; + let cell = grid.cell_size()?; let radius = kw_f32(kwargs, "radius", cell)?.min(cell); let (out, out_comp) = self.operand(kwargs, "out")?; @@ -704,7 +714,7 @@ impl Particles { }; particles_gather( self.entity, - &grid.inner, + grid.entity, a, out, op, @@ -944,9 +954,9 @@ impl Particles { cell_size, dims, }; - let inner = + let entity = grid_create(params, capacity).map_err(|e| PyRuntimeError::new_err(format!("{e}")))?; - Ok(Grid { inner }) + Ok(Grid { entity }) } #[pyo3(signature = (grid, **kwargs))] @@ -955,7 +965,7 @@ impl Particles { if let Some(kwargs) = kwargs { crate::compute::set_compute_kwargs(flock, kwargs)?; } - let cell = grid.inner.params.cell_size; + let cell = grid.cell_size()?; let neighbor_distance = kw_f32(kwargs, "neighbor_distance", cell)?.min(cell); compute_set( flock, @@ -963,7 +973,7 @@ impl Particles { shader_value::ShaderValue::Float(neighbor_distance), ) .map_err(|e| PyRuntimeError::new_err(format!("{e}")))?; - particles_flock(self.entity, flock, &grid.inner) + particles_flock(self.entity, flock, grid.entity) .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) } diff --git a/crates/processing_render/shaders/processing/particles.wesl b/crates/processing_render/shaders/processing/particles.wesl index 9545b2b3..66984c73 100644 --- a/crates/processing_render/shaders/processing/particles.wesl +++ b/crates/processing_render/shaders/processing/particles.wesl @@ -10,6 +10,13 @@ fn falloff(d: f32, radius: f32, mode: u32) -> f32 { } } +// a spatial hash grid, bound by `set(grid=...)` as `grid_offsets`, `grid_sorted` and `grid` +struct Grid { + origin: vec3, + cell_size: f32, + dims: vec3, +} + fn cell_coords(p: vec3, grid_min: vec3, cell_size: f32, dims: vec3) -> vec3 { let rel = (p - grid_min) / cell_size; return vec3( diff --git a/crates/processing_render/src/lib.rs b/crates/processing_render/src/lib.rs index 67280944..d20e26d4 100644 --- a/crates/processing_render/src/lib.rs +++ b/crates/processing_render/src/lib.rs @@ -28,7 +28,7 @@ pub use particles::algebra::{ generate, lookup, map, mix, pack, reduce_components, }; pub use particles::compact::compact; -pub use particles::grid::{Grid, GridParams, grid_bind, grid_build, grid_create}; +pub use particles::grid::{Grid, GridParams, grid_build, grid_create, grid_destroy, grid_get}; pub use particles::reduce::{REDUCE_OP_MAX, REDUCE_OP_MIN, REDUCE_OP_SUM, reduce}; pub use particles::sort::bitonic_sort_by_key; pub use particles::{ @@ -2443,7 +2443,7 @@ fn buffer_write_range( .get::(entity) .ok_or(error::ProcessingError::BufferNotFound)? .synced; - // next read will refresh + // next read will refresh if on_gpu && !synced { return Ok(()); } diff --git a/crates/processing_render/src/material/custom.rs b/crates/processing_render/src/material/custom.rs index f39de9d7..7563ece1 100644 --- a/crates/processing_render/src/material/custom.rs +++ b/crates/processing_render/src/material/custom.rs @@ -335,7 +335,7 @@ pub(crate) fn apply_reflect_field( Err(ProcessingError::UnknownShaderProperty(name.to_string())) } -fn apply_field_coerced(field: &mut dyn PartialReflect, value: &dyn PartialReflect) { +pub(crate) fn apply_field_coerced(field: &mut dyn PartialReflect, value: &dyn PartialReflect) { if let Some(coerced) = coerce_numeric(field, value) { field.apply(coerced.as_ref()); } else { @@ -495,9 +495,10 @@ pub(crate) fn shader_value_to_reflect(value: &ShaderValue) -> Result { + | ShaderValue::MeshIndex(_) + | ShaderValue::Grid(_) => { return Err(ProcessingError::InvalidArgument( - "Texture/Buffer/Mesh* must be bound via set_property, not as a uniform value" + "Texture/Buffer/Mesh*/Grid must be bound via set_property, not as a uniform value" .to_string(), )); } diff --git a/crates/processing_render/src/particles/emit.rs b/crates/processing_render/src/particles/emit.rs index f31fb5aa..16e3ca27 100644 --- a/crates/processing_render/src/particles/emit.rs +++ b/crates/processing_render/src/particles/emit.rs @@ -6,7 +6,7 @@ use processing_core::app_mut; use processing_core::error; use crate::geometry; -use crate::particles::grid::{Grid, grid_bind, grid_build}; +use crate::particles::grid::grid_build; use crate::particles::kernels::KernelRequires; use crate::particles::{Particles, particles_ensure_attribute}; use crate::shader_value::ShaderValue; @@ -146,7 +146,7 @@ pub fn particles_emit( pub fn particles_flock( particles_entity: Entity, flock_entity: Entity, - grid: &Grid, + grid: Entity, ) -> error::Result<()> { let position = app_mut(|app| { let world = app.world(); @@ -167,7 +167,7 @@ pub fn particles_flock( })?; grid_build(grid, position)?; - grid_bind(grid, flock_entity)?; + compute_set(flock_entity, "grid", ShaderValue::Grid(grid))?; particles_apply(particles_entity, flock_entity) } @@ -186,7 +186,7 @@ fn neighbor_compute() -> error::Result { pub fn particles_gather( particles_entity: Entity, - grid: &Grid, + grid: Entity, source: Entity, out: Entity, op: u32, @@ -222,7 +222,7 @@ pub fn particles_gather( grid_build(grid, position)?; let neighbor = neighbor_compute()?; - grid_bind(grid, neighbor)?; + compute_set(neighbor, "grid", ShaderValue::Grid(grid))?; compute_set(neighbor, "position", ShaderValue::Buffer(position))?; compute_set(neighbor, "source", ShaderValue::Buffer(source))?; compute_set(neighbor, "out", ShaderValue::Buffer(out))?; diff --git a/crates/processing_render/src/particles/grid.rs b/crates/processing_render/src/particles/grid.rs index 0e778111..cedc5de7 100644 --- a/crates/processing_render/src/particles/grid.rs +++ b/crates/processing_render/src/particles/grid.rs @@ -1,12 +1,16 @@ use std::sync::Mutex; -use bevy::prelude::Entity; +use bevy::prelude::{Component, Entity}; -use processing_core::error::Result; +use processing_core::app_mut; +use processing_core::error::{ProcessingError, Result}; use crate::particles::scan::prefix_sum_u32; use crate::shader_value::ShaderValue; -use crate::{buffer_create, compute_create, compute_dispatch_no_update, compute_set, shader_load}; +use crate::{ + buffer_create, buffer_destroy, compute_create, compute_dispatch_no_update, compute_set, + shader_load, +}; const CLEAR_SHADER: &str = "embedded://processing_render/particles/kernels/grid_clear.wgsl"; const COUNT_SHADER: &str = "embedded://processing_render/particles/kernels/grid_count.wgsl"; @@ -41,7 +45,7 @@ impl GridParams { } } -#[derive(Clone, Copy)] +#[derive(Component, Clone, Copy)] pub struct Grid { pub offsets: Entity, pub cursor: Entity, @@ -54,25 +58,36 @@ const CLEAR_WG: u32 = 256; const PARTICLE_WG: u32 = 64; const COPY_WG: u32 = 256; -pub fn grid_create(params: GridParams, capacity: u32) -> Result { +pub fn grid_create(params: GridParams, capacity: u32) -> Result { let num_cells = params.num_cells(); - let offsets = buffer_create(((num_cells + 1) as u64) * 4)?; - let cursor = buffer_create((num_cells as u64) * 4)?; - let sorted = buffer_create((capacity.max(1) as u64) * 4)?; - Ok(Grid { - offsets, - cursor, - sorted, + let grid = Grid { + offsets: buffer_create(((num_cells + 1) as u64) * 4)?, + cursor: buffer_create((num_cells as u64) * 4)?, + sorted: buffer_create((capacity.max(1) as u64) * 4)?, params, capacity, + }; + app_mut(|app| Ok(app.world_mut().spawn(grid).id())) +} + +pub fn grid_get(entity: Entity) -> Result { + app_mut(|app| { + app.world() + .get::(entity) + .copied() + .ok_or(ProcessingError::GridNotFound) }) } -pub fn grid_bind(grid: &Grid, compute: Entity) -> Result<()> { - compute_set(compute, "offsets", ShaderValue::Buffer(grid.offsets))?; - compute_set(compute, "sorted", ShaderValue::Buffer(grid.sorted))?; - set_domain(compute, &grid.params)?; - Ok(()) +pub fn grid_destroy(entity: Entity) -> Result<()> { + let grid = grid_get(entity)?; + buffer_destroy(grid.offsets)?; + buffer_destroy(grid.cursor)?; + buffer_destroy(grid.sorted)?; + app_mut(|app| { + app.world_mut().despawn(entity); + Ok(()) + }) } fn set_domain(compute: Entity, params: &GridParams) -> Result<()> { @@ -84,7 +99,8 @@ fn set_domain(compute: Entity, params: &GridParams) -> Result<()> { Ok(()) } -pub fn grid_build(grid: &Grid, position: Entity) -> Result<()> { +pub fn grid_build(entity: Entity, position: Entity) -> Result<()> { + let grid = grid_get(entity)?; let (clear, count, copy, scatter) = grid_computes()?; let num_cells = grid.params.num_cells(); diff --git a/crates/processing_render/src/particles/kernels/flock.wgsl b/crates/processing_render/src/particles/kernels/flock.wgsl index 56040c17..b1e75ec3 100644 --- a/crates/processing_render/src/particles/kernels/flock.wgsl +++ b/crates/processing_render/src/particles/kernels/flock.wgsl @@ -1,4 +1,4 @@ -import processing::particles::{cell_coords, cell_index}; +import processing::particles::{Grid, cell_coords, cell_index}; struct FlockParams { sep_distance: f32, @@ -11,21 +11,12 @@ struct FlockParams { min_speed: f32, } -struct GridParams { - grid_min: vec3, - cell_size: f32, - dims_x: u32, - dims_y: u32, - dims_z: u32, - _pad: u32, -} - @group(0) @binding(0) var position: array; @group(0) @binding(1) var velocity: array; -@group(0) @binding(2) var offsets: array; -@group(0) @binding(3) var sorted: array; +@group(0) @binding(2) var grid_offsets: array; +@group(0) @binding(3) var grid_sorted: array; @group(0) @binding(4) var fp: FlockParams; -@group(0) @binding(5) var gp: GridParams; +@group(0) @binding(5) var grid: Grid; fn limit_mag(v: vec3, m: f32) -> vec3 { let len2 = dot(v, v); @@ -59,8 +50,8 @@ fn main(@builtin(global_invocation_id) gid: vec3) { let sep_d2 = fp.sep_distance * fp.sep_distance; let neighbor_d2 = fp.neighbor_distance * fp.neighbor_distance; - let dims = vec3(gp.dims_x, gp.dims_y, gp.dims_z); - let base = cell_coords(pos, gp.grid_min, gp.cell_size, dims); + let dims = grid.dims; + let base = cell_coords(pos, grid.origin, grid.cell_size, dims); let bx = base.x; let by = base.y; let bz = base.z; @@ -73,19 +64,19 @@ fn main(@builtin(global_invocation_id) gid: vec3) { for (var dz = -1; dz <= 1; dz++) { let cz = bz + dz; - if cz < 0 || cz >= i32(gp.dims_z) { continue; } + if cz < 0 || cz >= i32(grid.dims.z) { continue; } for (var dy = -1; dy <= 1; dy++) { let cy = by + dy; - if cy < 0 || cy >= i32(gp.dims_y) { continue; } + if cy < 0 || cy >= i32(grid.dims.y) { continue; } for (var dx = -1; dx <= 1; dx++) { let cx = bx + dx; - if cx < 0 || cx >= i32(gp.dims_x) { continue; } + if cx < 0 || cx >= i32(grid.dims.x) { continue; } let cell = cell_index(vec3(u32(cx), u32(cy), u32(cz)), dims); - let start = offsets[cell]; - let end = offsets[cell + 1u]; + let start = grid_offsets[cell]; + let end = grid_offsets[cell + 1u]; for (var s = start; s < end; s++) { - let j = sorted[s]; + let j = grid_sorted[s]; if j == i { continue; } let diff = pos - load_pos(j); diff --git a/crates/processing_render/src/particles/kernels/neighbor.wgsl b/crates/processing_render/src/particles/kernels/neighbor.wgsl index 966b412f..723c68e1 100644 --- a/crates/processing_render/src/particles/kernels/neighbor.wgsl +++ b/crates/processing_render/src/particles/kernels/neighbor.wgsl @@ -1,13 +1,4 @@ -import processing::particles::{cell_coords, cell_index, falloff}; - -struct GridParams { - grid_min: vec3, - cell_size: f32, - dims_x: u32, - dims_y: u32, - dims_z: u32, - _pad: u32, -} +import processing::particles::{Grid, cell_coords, cell_index, falloff}; struct Params { radius: f32, @@ -20,13 +11,13 @@ const OP_SUM: u32 = 0u; const OP_MEAN: u32 = 1u; const OP_COUNT: u32 = 2u; -@group(0) @binding(0) var position: array; -@group(0) @binding(1) var source: array; -@group(0) @binding(2) var out: array; -@group(0) @binding(3) var offsets: array; -@group(0) @binding(4) var sorted: array; -@group(0) @binding(5) var params: Params; -@group(0) @binding(6) var gp: GridParams; +@group(0) @binding(0) var position: array; +@group(0) @binding(1) var source: array; +@group(0) @binding(2) var out: array; +@group(0) @binding(3) var grid_offsets: array; +@group(0) @binding(4) var grid_sorted: array; +@group(0) @binding(5) var params: Params; +@group(0) @binding(6) var grid: Grid; fn load_pos(i: u32) -> vec3 { return vec3(position[i * 3u], position[i * 3u + 1u], position[i * 3u + 2u]); @@ -42,27 +33,27 @@ fn main(@builtin(global_invocation_id) gid: vec3) { let r2 = params.radius * params.radius; let comps = params.components; - let dims = vec3(gp.dims_x, gp.dims_y, gp.dims_z); - let base = cell_coords(pos, gp.grid_min, gp.cell_size, dims); + let dims = grid.dims; + let base = cell_coords(pos, grid.origin, grid.cell_size, dims); var value = array(0.0, 0.0, 0.0, 0.0); var weight_sum = 0.0; for (var dz = -1; dz <= 1; dz++) { let cz = base.z + dz; - if cz < 0 || cz >= i32(gp.dims_z) { continue; } + if cz < 0 || cz >= i32(grid.dims.z) { continue; } for (var dy = -1; dy <= 1; dy++) { let cy = base.y + dy; - if cy < 0 || cy >= i32(gp.dims_y) { continue; } + if cy < 0 || cy >= i32(grid.dims.y) { continue; } for (var dx = -1; dx <= 1; dx++) { let cx = base.x + dx; - if cx < 0 || cx >= i32(gp.dims_x) { continue; } + if cx < 0 || cx >= i32(grid.dims.x) { continue; } let cell = cell_index(vec3(u32(cx), u32(cy), u32(cz)), dims); - let start = offsets[cell]; - let end = offsets[cell + 1u]; + let start = grid_offsets[cell]; + let end = grid_offsets[cell + 1u]; for (var s = start; s < end; s++) { - let j = sorted[s]; + let j = grid_sorted[s]; let diff = pos - load_pos(j); let d2 = dot(diff, diff); if d2 <= r2 { diff --git a/crates/processing_render/src/particles/mod.rs b/crates/processing_render/src/particles/mod.rs index c415c9b4..5088726d 100644 --- a/crates/processing_render/src/particles/mod.rs +++ b/crates/processing_render/src/particles/mod.rs @@ -23,7 +23,7 @@ pub use compact::compact; pub use emit::{ particles_apply, particles_emit, particles_emit_gpu, particles_flock, particles_gather, }; -pub use grid::{Grid, GridParams, grid_bind, grid_build, grid_create}; +pub use grid::{Grid, GridParams, grid_build, grid_create, grid_destroy, grid_get}; pub use kernels::{ BOUNDS_CLAMP, BOUNDS_REFLECT, BOUNDS_SOFT, BOUNDS_WRAP, COMBINE_ADD, COMBINE_DIV, COMBINE_MAX, COMBINE_MIN, COMBINE_MUL, COMBINE_POW, COMBINE_SUB, FALLOFF_CONST, FALLOFF_CUBIC, diff --git a/crates/processing_render/src/shader_property.rs b/crates/processing_render/src/shader_property.rs index 7d76b73c..a48d1f51 100644 --- a/crates/processing_render/src/shader_property.rs +++ b/crates/processing_render/src/shader_property.rs @@ -1,10 +1,12 @@ use bevy::prelude::*; +use bevy::reflect::ReflectMut; use bevy_naga_reflect::dynamic_shader::DynamicShader; use bevy_naga_reflect::reflect::ParameterCategory; use crate::compute::{Buffer, Compute, MeshBindingRef}; use crate::image::Image as PImage; -use crate::material::custom::{apply_reflect_field, shader_value_to_reflect}; +use crate::material::custom::{apply_field_coerced, apply_reflect_field, shader_value_to_reflect}; +use crate::particles::grid::Grid; use crate::render::filter::Filter; use crate::shader_value::ShaderValue; use processing_core::error::{ProcessingError, Result}; @@ -104,27 +106,81 @@ pub(crate) fn apply_shader_value( } } +/// Binds `{name}_{suffix}` buffers and the fields of the `{name}` uniform struct. +fn bind_struct( + shader: &mut DynamicShader, + name: &str, + type_name: &str, + buffers: &[(&str, Entity)], + values: &[(&str, ShaderValue)], + p_buffers: &mut Query<&mut Buffer>, + p_images: &Query<&PImage>, +) -> Result<()> { + for (suffix, buffer) in buffers { + let binding = format!("{name}_{suffix}"); + apply_shader_value( + shader, + &binding, + ShaderValue::Buffer(*buffer), + p_buffers, + p_images, + )?; + } + let wrong_type = || { + ProcessingError::InvalidArgument(format!( + "`{name}` must be a `particles::{type_name}` uniform" + )) + }; + let param = shader + .field_mut(name) + .ok_or_else(|| ProcessingError::UnknownShaderProperty(name.to_string()))?; + let ReflectMut::Struct(fields) = param.reflect_mut() else { + return Err(wrong_type()); + }; + for (field, value) in values { + let target = fields.field_mut(field).ok_or_else(wrong_type)?; + apply_field_coerced(target, &*shader_value_to_reflect(value)?); + } + Ok(()) +} + pub fn set_property( In((entity, name, value)): In<(Entity, String, ShaderValue)>, mut computes: Query<&mut Compute>, mut filters: Query<&mut Filter>, mut p_buffers: Query<&mut Buffer>, p_images: Query<&PImage>, + grids: Query<&Grid>, ) -> Result { - if let Ok(mut compute) = computes.get_mut(entity) { - match value { - ShaderValue::MeshAttribute(..) | ShaderValue::MeshIndex(..) => { - bind_compute_mesh(&mut compute, name, value)?; - } - other => { - apply_shader_value(&mut compute.shader, &name, other, &mut p_buffers, &p_images)?; - } + let shader = if let Ok(compute) = computes.get_mut(entity) { + if let ShaderValue::MeshAttribute(..) | ShaderValue::MeshIndex(..) = value { + bind_compute_mesh(compute.into_inner(), name, value)?; + return Ok(true); } - return Ok(true); - } - if let Ok(mut filter) = filters.get_mut(entity) { - apply_shader_value(&mut filter.shader, &name, value, &mut p_buffers, &p_images)?; - return Ok(true); + &mut compute.into_inner().shader + } else if let Ok(filter) = filters.get_mut(entity) { + &mut filter.into_inner().shader + } else { + return Ok(false); + }; + match value { + ShaderValue::Grid(grid) => { + let grid = grids.get(grid).map_err(|_| ProcessingError::GridNotFound)?; + bind_struct( + shader, + &name, + "Grid", + &[("offsets", grid.offsets), ("sorted", grid.sorted)], + &[ + ("origin", ShaderValue::Float3(grid.params.min)), + ("cell_size", ShaderValue::Float(grid.params.cell_size)), + ("dims", ShaderValue::UInt3(grid.params.dims)), + ], + &mut p_buffers, + &p_images, + )?; + } + other => apply_shader_value(shader, &name, other, &mut p_buffers, &p_images)?, } - Ok(false) + Ok(true) } diff --git a/crates/processing_render/src/shader_value.rs b/crates/processing_render/src/shader_value.rs index 2f1d4049..5e1f7ce8 100644 --- a/crates/processing_render/src/shader_value.rs +++ b/crates/processing_render/src/shader_value.rs @@ -19,6 +19,8 @@ pub enum ShaderValue { Buffer(Entity), MeshAttribute(Entity, Entity), MeshIndex(Entity), + /// binds `{name}_offsets`, `{name}_sorted` and the `{name}` uniform + Grid(Entity), } impl ShaderValue { @@ -40,7 +42,8 @@ impl ShaderValue { ShaderValue::Texture(_) | ShaderValue::Buffer(_) | ShaderValue::MeshAttribute(..) - | ShaderValue::MeshIndex(_) => None, + | ShaderValue::MeshIndex(_) + | ShaderValue::Grid(_) => None, } } @@ -54,7 +57,8 @@ impl ShaderValue { ShaderValue::Texture(_) | ShaderValue::Buffer(_) | ShaderValue::MeshAttribute(..) - | ShaderValue::MeshIndex(_) => None, + | ShaderValue::MeshIndex(_) + | ShaderValue::Grid(_) => None, } } @@ -103,7 +107,8 @@ impl ShaderValue { ShaderValue::Texture(_) | ShaderValue::Buffer(_) | ShaderValue::MeshAttribute(..) - | ShaderValue::MeshIndex(_) => None, + | ShaderValue::MeshIndex(_) + | ShaderValue::Grid(_) => None, } } }