diff --git a/crates/processing_core/src/error.rs b/crates/processing_core/src/error.rs index e4dbdad..5926576 100644 --- a/crates/processing_core/src/error.rs +++ b/crates/processing_core/src/error.rs @@ -58,6 +58,8 @@ pub enum ProcessingError { PipelineCompileError(String), #[error("Pipeline not ready after {0} frames")] PipelineNotReady(u32), + #[error("`{0}` isn't on the GPU yet")] + ResourceNotReady(String), #[error("Particles not found")] ParticlesNotFound, #[error("Grid not found")] diff --git a/crates/processing_render/src/compute.rs b/crates/processing_render/src/compute.rs index 9e593ed..e11a281 100644 --- a/crates/processing_render/src/compute.rs +++ b/crates/processing_render/src/compute.rs @@ -359,6 +359,18 @@ pub fn dispatch( .clone(); let reflection = shader.reflection(); + // a missing GPU resource drops its binding, and wgpu panics on an incomplete bind group + for param in reflection.parameters() { + let Some(name) = param.name() else { continue }; + let ready = match (shader.buffer_handle(name), shader.image_handle(name)) { + (Some(handle), _) => gpu_buffers.get(handle).is_some(), + (_, Some(handle)) => gpu_images.get(handle).is_some(), + _ => true, + }; + if !ready { + return Err(ProcessingError::ResourceNotReady(name.to_string())); + } + } let mut bind_groups = Vec::new(); for (group, desc) in &layout_descriptors { diff --git a/crates/processing_render/src/lib.rs b/crates/processing_render/src/lib.rs index d20e26d..31b22b3 100644 --- a/crates/processing_render/src/lib.rs +++ b/crates/processing_render/src/lib.rs @@ -2553,7 +2553,10 @@ pub(crate) fn compute_dispatch_no_update( z: u32, ) -> error::Result<()> { app_mut(|app| match dispatch_inner(app, entity, x, y, z) { - Err(error::ProcessingError::PipelineNotReady(_)) => { + Err( + error::ProcessingError::PipelineNotReady(_) + | error::ProcessingError::ResourceNotReady(_), + ) => { app.update(); dispatch_inner(app, entity, x, y, z) } diff --git a/crates/processing_render/src/material/custom.rs b/crates/processing_render/src/material/custom.rs index 7563ece..bc9535c 100644 --- a/crates/processing_render/src/material/custom.rs +++ b/crates/processing_render/src/material/custom.rs @@ -322,11 +322,27 @@ pub(crate) fn apply_reflect_field( return Ok(()); } - let param_name = find_param_containing_field(shader, name); - if let Some(param_name) = param_name - && let Some(param) = shader.field_mut(¶m_name) + // `struct.field` picks a struct explicitly + let (param_name, field_name) = match name.split_once('.') { + Some((param, field)) => (param.to_string(), field), + None => { + let params = params_containing_field(shader, name); + if params.len() > 1 { + return Err(ProcessingError::InvalidArgument(format!( + "`{name}` is a field of {}; name one, e.g. `{}.{name}`", + params.join(" and "), + params[0], + ))); + } + let Some(param) = params.into_iter().next() else { + return Err(ProcessingError::UnknownShaderProperty(name.to_string())); + }; + (param, name) + } + }; + if let Some(param) = shader.field_mut(¶m_name) && let ReflectMut::Struct(s) = param.reflect_mut() - && let Some(field) = s.field_mut(name) + && let Some(field) = s.field_mut(field_name) { apply_field_coerced(field, value); return Ok(()); @@ -509,15 +525,20 @@ pub(crate) fn find_param_containing_field( shader: &DynamicShader, field_name: &str, ) -> Option { - for i in 0..shader.field_len() { - if let Some(field) = shader.field_at(i) - && let ReflectRef::Struct(s) = field.reflect_ref() - && s.field(field_name).is_some() - { - return shader.name_at(i).map(|s: &str| s.to_string()); - } - } - None + params_containing_field(shader, field_name) + .into_iter() + .next() +} + +fn params_containing_field(shader: &DynamicShader, field_name: &str) -> Vec { + (0..shader.field_len()) + .filter(|&i| { + shader.field_at(i).is_some_and(|field| { + matches!(field.reflect_ref(), ReflectRef::Struct(s) if s.field(field_name).is_some()) + }) + }) + .filter_map(|i| shader.name_at(i).map(str::to_string)) + .collect() } pub struct CustomMaterialPlugin; diff --git a/crates/processing_render/src/particles/algebra.rs b/crates/processing_render/src/particles/algebra.rs index 7500040..313ff98 100644 --- a/crates/processing_render/src/particles/algebra.rs +++ b/crates/processing_render/src/particles/algebra.rs @@ -5,7 +5,7 @@ use bevy::prelude::Entity; use processing_core::error::{ProcessingError, Result}; use crate::shader_value::ShaderValue; -use crate::{buffer_size, compute_dispatch, compute_set, shader_create_with_features}; +use crate::{buffer_size, compute_dispatch_no_update, compute_set, shader_create_with_features}; const WG: u32 = 64; @@ -96,7 +96,7 @@ static MAP: Mutex> = Mutex::new(None); fn dispatch_particles(compute: Entity, floats: u64, components: u32) -> Result<()> { let n = (floats / components as u64) as u32; - compute_dispatch(compute, n.div_ceil(WG), 1, 1) + compute_dispatch_no_update(compute, n.div_ceil(WG), 1, 1) } fn check_components(verb: &str, components: u32) -> Result<()> { @@ -373,7 +373,7 @@ pub fn lookup( compute_set(c, "tex", ShaderValue::Texture(tex))?; compute_set(c, "samp", ShaderValue::Texture(tex))?; let n = (buffer_size(dst)? / 16) as u32; - compute_dispatch(c, n.div_ceil(WG), 1, 1) + compute_dispatch_no_update(c, n.div_ceil(WG), 1, 1) } pub const REDUCE_LENGTH: u32 = 0; @@ -430,7 +430,7 @@ pub fn reduce_components(dst: Entity, src: Entity, components: u32, op: u32) -> compute_set(c, "components", ShaderValue::UInt(components))?; compute_set(c, "op", ShaderValue::UInt(op))?; let n = (buffer_size(dst)? / 4) as u32; - compute_dispatch(c, n.div_ceil(WG), 1, 1) + compute_dispatch_no_update(c, n.div_ceil(WG), 1, 1) } const EXTRACT_SRC: &str = r#" @@ -465,7 +465,7 @@ pub fn extract(dst: Entity, src: Entity, components: u32, index: u32) -> Result< compute_set(c, "components", ShaderValue::UInt(components))?; compute_set(c, "index", ShaderValue::UInt(index))?; let n = (buffer_size(dst)? / 4) as u32; - compute_dispatch(c, n.div_ceil(WG), 1, 1) + compute_dispatch_no_update(c, n.div_ceil(WG), 1, 1) } const PACK_SRC: &str = r#" @@ -534,7 +534,7 @@ pub fn pack(dst: Entity, sources: &[Entity]) -> Result<()> { compute_set(c, "dst", ShaderValue::Buffer(dst))?; compute_set(c, "components", ShaderValue::UInt(components))?; let n = (buffer_size(sources[0])? / 4) as u32; - compute_dispatch(c, n.div_ceil(WG), 1, 1) + compute_dispatch_no_update(c, n.div_ceil(WG), 1, 1) } pub const GEN_UNIFORM: u32 = 0;