gfx-rs/wgpu · error
Unexpected saturate({:?})
Error message
Unexpected saturate({:?}) What it means
Panic from the `unimplemented!()` arm handling `MathFunction::Saturate` in the SPIR-V backend. `saturate` is clamped between constants 0 and 1, which requires a float scalar (or float vector); if the argument type is any other TypeInner (e.g. integer scalar), the backend panics instead of producing a clean diagnostic.
Source
Thrown at naga/src/back/spv/block.rs:1453
max_id,
&[arg0_id, arg1_id],
));
MathOp::Custom(Instruction::ext_inst_gl_op(
self.writer.gl450_ext_inst_id,
min_op,
result_type_id,
id,
&[max_id, arg2_id],
))
}
other => unimplemented!("Unexpected max({:?})", other),
},
Mf::Saturate => {
let (maybe_size, scalar) = match *arg_ty {
crate::TypeInner::Vector { size, scalar } => (Some(size), scalar),
crate::TypeInner::Scalar(scalar) => (None, scalar),
ref other => unimplemented!("Unexpected saturate({:?})", other),
};
let scalar = crate::Scalar::float(scalar.width);
let mut arg1_id = self.writer.get_constant_scalar_with(0, scalar)?;
let mut arg2_id = self.writer.get_constant_scalar_with(1, scalar)?;
if let Some(size) = maybe_size {
let ty =
LocalType::Numeric(NumericType::Vector { size, scalar }).into();
self.temp_list.clear();
self.temp_list.resize(size as _, arg1_id);
arg1_id = self.writer.get_constant_composite(ty, &self.temp_list);
self.temp_list.fill(arg2_id);
arg2_id = self.writer.get_constant_composite(ty, &self.temp_list);
}View on GitHub (pinned to 3e11ff59bf)
Solutions
- Explicitly convert the argument to a float type: `saturate(f32(x))`
- Verify the shader validates in the naga CLI before targeting SPIR-V to catch the type earlier
- Update to a newer wgpu/naga where this may be surfaced as a validation error
- Report a minimal reproducer to the wgpu repository if the shader looks valid
Example fix
// before let s = saturate(i); // after let s = saturate(f32(i));
Defensive patterns
Strategy: validation
Validate before calling
fn check_saturate_operand(module: &naga::Module) -> bool {
// saturate requires a float scalar or vector operand
module.global_expressions.iter().all(|(_, e)| match e {
naga::Expression::Math { fun: naga::MathFunction::Saturate, args, .. } => args.iter().all(|a| {
matches!(ty_of(module, *a).inner,
naga::TypeInner::Scalar(s) | naga::TypeInner::Vector { scalar: s, .. }
if s.kind == naga::ScalarKind::Float)
}),
_ => true,
})
} Type guard
fn is_float_operand(t: &naga::TypeInner) -> bool {
matches!(t, naga::TypeInner::Scalar(s) | naga::TypeInner::Vector { scalar: s, .. } if s.kind == naga::ScalarKind::Float)
} Prevention
- Always call saturate on f32 values; insert f32() casts for ints
- Run frontend validation before SPIR-V emission
- Wrap shader compilation in catch_unwind for untrusted input
- Reduce failing shaders with the naga CLI to find bad types
When it happens
Trigger: A `saturate(x)` call in a shader whose argument type is neither a float vector nor a float scalar, e.g. `saturate(some_i32)` reaching the SPIR-V writer via `cache_expression_value`.
Common situations: Generated or transpiled shaders (from GLSL/SPIR-V cross-compilation) where implicit conversions were expected but not present; WGSL authors usually get a validator error first, so this mostly affects non-WGSL frontends.
Related errors
- Unexpected sign({:?})
- Unexpected firstLeadingBit({:?})
- Unexpected pointer expression {:?}
- not implemented
- not implemented
AI-assisted analysis of gfx-rs/wgpu@3e11ff59bf (2026-09-03).
Data as JSON: /api/errors/905ee5c0427316ab.
Report an issue: GitHub.