From a24bb9c6e14fe9e1582814ac4b1668b024b8c805 Mon Sep 17 00:00:00 2001 From: haixuantao Date: Wed, 8 Jul 2026 11:04:28 +0200 Subject: [PATCH 1/2] shaders: panic-free Mat3 column access for the cuda-oxide device build glam 0.33's Mat3/Mat2::col panics with a message on an out-of-range index, which the cuda-oxide backend rejects (no panic message formatting on the GPU). Add a ColBranchless::col_b extension (out-of-range folds to the last column) and use it at every dynamic-index col() site in the joint constraint builders. Co-Authored-By: Claude Fable 5 --- .../dynamics/joint_constraint_builder.rs | 48 +++++++++---------- src_rbd_shaders/lib.rs | 42 ++++++++++++++++ 2 files changed, 66 insertions(+), 24 deletions(-) diff --git a/src_rbd_shaders/dynamics/joint_constraint_builder.rs b/src_rbd_shaders/dynamics/joint_constraint_builder.rs index 84a4c38..7ed0bdd 100644 --- a/src_rbd_shaders/dynamics/joint_constraint_builder.rs +++ b/src_rbd_shaders/dynamics/joint_constraint_builder.rs @@ -12,7 +12,7 @@ use crate::Rotation; #[cfg(feature = "dim2")] use crate::rotation_angle; use crate::utils::{Slice, SliceMut}; -use crate::{AngVector, MAX_FLT, Pose, Vector, gdot, rotation_to_matrix}; +use crate::{AngVector, ColBranchless, MAX_FLT, Pose, Vector, gdot, rotation_to_matrix}; use khal_std::index::MaybeIndexUnchecked; #[cfg(feature = "dim2")] @@ -122,7 +122,7 @@ pub fn new_helper( // Then snap the locked ones. for i in 0..DIM { if (locked_lin_axes & (1u32 << i)) != 0 { - let axis = basis.col(i); + let axis = basis.col_b(i); new_center1 -= axis * lin_err.dot(axis); } } @@ -141,8 +141,8 @@ pub fn new_helper( basis, // In 2D, cmat is a Vec2 representing [-r.y, r.x], and we need the dot product // with each column of basis to get the angular Jacobian components. - cmat1_basis: [cmat1.dot(basis.col(0)), cmat1.dot(basis.col(1))], - cmat2_basis: [cmat2.dot(basis.col(0)), cmat2.dot(basis.col(1))], + cmat1_basis: [cmat1.dot(basis.col_b(0)), cmat1.dot(basis.col_b(1))], + cmat2_basis: [cmat2.dot(basis.col_b(0)), cmat2.dot(basis.col_b(1))], lin_err, ang_err, } @@ -170,7 +170,7 @@ pub fn new_helper( // Then snap the locked ones. for i in 0..DIM { if (locked_lin_axes & (1u32 << i)) != 0 { - let axis = basis.col(i); + let axis = basis.col_b(i); new_center1 -= axis * lin_err.dot(axis); } } @@ -241,9 +241,9 @@ impl JointConstraintHelper { params: &RbdSimParams, ) -> JointConstraintElement { #[cfg(feature = "dim2")] - let lin_jac = self.basis.col(locked_axis); + let lin_jac = self.basis.col_b(locked_axis); #[cfg(feature = "dim3")] - let lin_jac = self.basis.col(locked_axis); + let lin_jac = self.basis.col_b(locked_axis); #[cfg(feature = "dim2")] let ang_jac1 = self.cmat1_basis.read(locked_axis); @@ -251,9 +251,9 @@ impl JointConstraintHelper { let ang_jac2 = self.cmat2_basis.read(locked_axis); #[cfg(feature = "dim3")] - let ang_jac1 = self.cmat1_basis.col(locked_axis); + let ang_jac1 = self.cmat1_basis.col_b(locked_axis); #[cfg(feature = "dim3")] - let ang_jac2 = self.cmat2_basis.col(locked_axis); + let ang_jac2 = self.cmat2_basis.col_b(locked_axis); let rhs_wo_bias = 0.0; let erp_inv_dt = params.joint_erp_inv_dt(); @@ -294,7 +294,7 @@ impl JointConstraintHelper { #[cfg(feature = "dim2")] let ang_jac = 1.0; #[cfg(feature = "dim3")] - let ang_jac = self.ang_basis.col(_locked_axis); + let ang_jac = self.ang_basis.col_b(_locked_axis); let rhs_wo_bias = 0.0; let erp_inv_dt = params.joint_erp_inv_dt(); @@ -407,8 +407,8 @@ impl JointConstraintHelper { #[cfg(feature = "dim2")] for i in 0..DIM { if (coupled_axes & (1u32 << i)) != 0 { - let coeff = self.basis.col(i).dot(self.lin_err); - lin_jac += self.basis.col(i) * coeff; + let coeff = self.basis.col_b(i).dot(self.lin_err); + lin_jac += self.basis.col_b(i) * coeff; ang_jac1 += self.cmat1_basis.read(i) * coeff; ang_jac2 += self.cmat2_basis.read(i) * coeff; } @@ -417,10 +417,10 @@ impl JointConstraintHelper { #[cfg(feature = "dim3")] for i in 0..DIM { if (coupled_axes & (1u32 << i)) != 0 { - let coeff = self.basis.col(i).dot(self.lin_err); - lin_jac += self.basis.col(i) * coeff; - ang_jac1 += self.cmat1_basis.col(i) * coeff; - ang_jac2 += self.cmat2_basis.col(i) * coeff; + let coeff = self.basis.col_b(i).dot(self.lin_err); + lin_jac += self.basis.col_b(i) * coeff; + ang_jac1 += self.cmat1_basis.col_b(i) * coeff; + ang_jac2 += self.cmat2_basis.col_b(i) * coeff; } } @@ -517,8 +517,8 @@ impl JointConstraintHelper { #[cfg(feature = "dim2")] for i in 0..DIM { if (coupled_axes & (1u32 << i)) != 0 { - let coeff = self.basis.col(i).dot(self.lin_err); - lin_jac += self.basis.col(i) * coeff; + let coeff = self.basis.col_b(i).dot(self.lin_err); + lin_jac += self.basis.col_b(i) * coeff; ang_jac1 += self.cmat1_basis.read(i) * coeff; ang_jac2 += self.cmat2_basis.read(i) * coeff; } @@ -527,10 +527,10 @@ impl JointConstraintHelper { #[cfg(feature = "dim3")] for i in 0..DIM { if (coupled_axes & (1u32 << i)) != 0 { - let coeff = self.basis.col(i).dot(self.lin_err); - lin_jac += self.basis.col(i) * coeff; - ang_jac1 += self.cmat1_basis.col(i) * coeff; - ang_jac2 += self.cmat2_basis.col(i) * coeff; + let coeff = self.basis.col_b(i).dot(self.lin_err); + lin_jac += self.basis.col_b(i) * coeff; + ang_jac1 += self.cmat1_basis.col_b(i) * coeff; + ang_jac2 += self.cmat2_basis.col_b(i) * coeff; } } @@ -602,7 +602,7 @@ impl JointConstraintHelper { #[cfg(feature = "dim2")] let ang_jac = 1.0; #[cfg(feature = "dim3")] - let ang_jac = self.ang_basis.col(_limited_axis); + let ang_jac = self.ang_basis.col_b(_limited_axis); let rhs_wo_bias = 0.0; let erp_inv_dt = params.joint_erp_inv_dt(); @@ -643,7 +643,7 @@ impl JointConstraintHelper { #[cfg(feature = "dim2")] let ang_jac = 1.0; #[cfg(feature = "dim3")] - let ang_jac = self.basis.col(_motor_axis); + let ang_jac = self.basis.col_b(_motor_axis); let mut rhs_wo_bias = 0.0; if motor_params.erp_inv_dt != 0.0 { diff --git a/src_rbd_shaders/lib.rs b/src_rbd_shaders/lib.rs index ec93722..913ad97 100644 --- a/src_rbd_shaders/lib.rs +++ b/src_rbd_shaders/lib.rs @@ -395,6 +395,48 @@ pub fn abs(x: f32) -> f32 { Float::abs(x) } +/// Panic-free matrix column access for GPU shader code. +/// +/// glam's `col` panics with a message on an out-of-range index, which GPU +/// backends that cannot lower panic-message formatting (e.g. cuda-oxide / +/// PTX) reject at device-codegen time — even when the panic branch is +/// unreachable at runtime. +/// +/// `index` **must** be in range (`0..2` for `Mat2`, `0..3` for `Mat3`); every +/// caller in this crate derives it from an axis mask, so this always holds. +/// An out-of-range index is a bug: debug (host) builds catch it with a +/// `debug_assert!`, release/device builds fold it to the last column rather +/// than carrying panic machinery into the kernel. +pub trait ColBranchless { + type Column; + fn col_b(&self, index: usize) -> Self::Column; +} + +impl ColBranchless for glamx::Mat3 { + type Column = glamx::Vec3; + #[inline(always)] + fn col_b(&self, index: usize) -> glamx::Vec3 { + debug_assert!(index < 3, "Mat3 column index out of range"); + match index { + 0 => self.x_axis, + 1 => self.y_axis, + _ => self.z_axis, + } + } +} + +impl ColBranchless for glamx::Mat2 { + type Column = glamx::Vec2; + #[inline(always)] + fn col_b(&self, index: usize) -> glamx::Vec2 { + debug_assert!(index < 2, "Mat2 column index out of range"); + match index { + 0 => self.x_axis, + _ => self.y_axis, + } + } +} + // // Modules // From b02e035c4e2fc84c106838fd09af6e2a6181f9cc Mon Sep 17 00:00:00 2001 From: haixuantao Date: Thu, 9 Jul 2026 09:11:09 +0200 Subject: [PATCH 2/2] review: ColBranchless::col_b -> ColumnIndex::col_at; direct axis access where the index is constant --- .../dynamics/joint_constraint_builder.rs | 48 +++++++++---------- src_rbd_shaders/lib.rs | 12 ++--- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src_rbd_shaders/dynamics/joint_constraint_builder.rs b/src_rbd_shaders/dynamics/joint_constraint_builder.rs index 7ed0bdd..7ce3cdb 100644 --- a/src_rbd_shaders/dynamics/joint_constraint_builder.rs +++ b/src_rbd_shaders/dynamics/joint_constraint_builder.rs @@ -12,7 +12,7 @@ use crate::Rotation; #[cfg(feature = "dim2")] use crate::rotation_angle; use crate::utils::{Slice, SliceMut}; -use crate::{AngVector, ColBranchless, MAX_FLT, Pose, Vector, gdot, rotation_to_matrix}; +use crate::{AngVector, ColumnIndex, MAX_FLT, Pose, Vector, gdot, rotation_to_matrix}; use khal_std::index::MaybeIndexUnchecked; #[cfg(feature = "dim2")] @@ -122,7 +122,7 @@ pub fn new_helper( // Then snap the locked ones. for i in 0..DIM { if (locked_lin_axes & (1u32 << i)) != 0 { - let axis = basis.col_b(i); + let axis = basis.col_at(i); new_center1 -= axis * lin_err.dot(axis); } } @@ -141,8 +141,8 @@ pub fn new_helper( basis, // In 2D, cmat is a Vec2 representing [-r.y, r.x], and we need the dot product // with each column of basis to get the angular Jacobian components. - cmat1_basis: [cmat1.dot(basis.col_b(0)), cmat1.dot(basis.col_b(1))], - cmat2_basis: [cmat2.dot(basis.col_b(0)), cmat2.dot(basis.col_b(1))], + cmat1_basis: [cmat1.dot(basis.x_axis), cmat1.dot(basis.y_axis)], + cmat2_basis: [cmat2.dot(basis.x_axis), cmat2.dot(basis.y_axis)], lin_err, ang_err, } @@ -170,7 +170,7 @@ pub fn new_helper( // Then snap the locked ones. for i in 0..DIM { if (locked_lin_axes & (1u32 << i)) != 0 { - let axis = basis.col_b(i); + let axis = basis.col_at(i); new_center1 -= axis * lin_err.dot(axis); } } @@ -241,9 +241,9 @@ impl JointConstraintHelper { params: &RbdSimParams, ) -> JointConstraintElement { #[cfg(feature = "dim2")] - let lin_jac = self.basis.col_b(locked_axis); + let lin_jac = self.basis.col_at(locked_axis); #[cfg(feature = "dim3")] - let lin_jac = self.basis.col_b(locked_axis); + let lin_jac = self.basis.col_at(locked_axis); #[cfg(feature = "dim2")] let ang_jac1 = self.cmat1_basis.read(locked_axis); @@ -251,9 +251,9 @@ impl JointConstraintHelper { let ang_jac2 = self.cmat2_basis.read(locked_axis); #[cfg(feature = "dim3")] - let ang_jac1 = self.cmat1_basis.col_b(locked_axis); + let ang_jac1 = self.cmat1_basis.col_at(locked_axis); #[cfg(feature = "dim3")] - let ang_jac2 = self.cmat2_basis.col_b(locked_axis); + let ang_jac2 = self.cmat2_basis.col_at(locked_axis); let rhs_wo_bias = 0.0; let erp_inv_dt = params.joint_erp_inv_dt(); @@ -294,7 +294,7 @@ impl JointConstraintHelper { #[cfg(feature = "dim2")] let ang_jac = 1.0; #[cfg(feature = "dim3")] - let ang_jac = self.ang_basis.col_b(_locked_axis); + let ang_jac = self.ang_basis.col_at(_locked_axis); let rhs_wo_bias = 0.0; let erp_inv_dt = params.joint_erp_inv_dt(); @@ -407,8 +407,8 @@ impl JointConstraintHelper { #[cfg(feature = "dim2")] for i in 0..DIM { if (coupled_axes & (1u32 << i)) != 0 { - let coeff = self.basis.col_b(i).dot(self.lin_err); - lin_jac += self.basis.col_b(i) * coeff; + let coeff = self.basis.col_at(i).dot(self.lin_err); + lin_jac += self.basis.col_at(i) * coeff; ang_jac1 += self.cmat1_basis.read(i) * coeff; ang_jac2 += self.cmat2_basis.read(i) * coeff; } @@ -417,10 +417,10 @@ impl JointConstraintHelper { #[cfg(feature = "dim3")] for i in 0..DIM { if (coupled_axes & (1u32 << i)) != 0 { - let coeff = self.basis.col_b(i).dot(self.lin_err); - lin_jac += self.basis.col_b(i) * coeff; - ang_jac1 += self.cmat1_basis.col_b(i) * coeff; - ang_jac2 += self.cmat2_basis.col_b(i) * coeff; + let coeff = self.basis.col_at(i).dot(self.lin_err); + lin_jac += self.basis.col_at(i) * coeff; + ang_jac1 += self.cmat1_basis.col_at(i) * coeff; + ang_jac2 += self.cmat2_basis.col_at(i) * coeff; } } @@ -517,8 +517,8 @@ impl JointConstraintHelper { #[cfg(feature = "dim2")] for i in 0..DIM { if (coupled_axes & (1u32 << i)) != 0 { - let coeff = self.basis.col_b(i).dot(self.lin_err); - lin_jac += self.basis.col_b(i) * coeff; + let coeff = self.basis.col_at(i).dot(self.lin_err); + lin_jac += self.basis.col_at(i) * coeff; ang_jac1 += self.cmat1_basis.read(i) * coeff; ang_jac2 += self.cmat2_basis.read(i) * coeff; } @@ -527,10 +527,10 @@ impl JointConstraintHelper { #[cfg(feature = "dim3")] for i in 0..DIM { if (coupled_axes & (1u32 << i)) != 0 { - let coeff = self.basis.col_b(i).dot(self.lin_err); - lin_jac += self.basis.col_b(i) * coeff; - ang_jac1 += self.cmat1_basis.col_b(i) * coeff; - ang_jac2 += self.cmat2_basis.col_b(i) * coeff; + let coeff = self.basis.col_at(i).dot(self.lin_err); + lin_jac += self.basis.col_at(i) * coeff; + ang_jac1 += self.cmat1_basis.col_at(i) * coeff; + ang_jac2 += self.cmat2_basis.col_at(i) * coeff; } } @@ -602,7 +602,7 @@ impl JointConstraintHelper { #[cfg(feature = "dim2")] let ang_jac = 1.0; #[cfg(feature = "dim3")] - let ang_jac = self.ang_basis.col_b(_limited_axis); + let ang_jac = self.ang_basis.col_at(_limited_axis); let rhs_wo_bias = 0.0; let erp_inv_dt = params.joint_erp_inv_dt(); @@ -643,7 +643,7 @@ impl JointConstraintHelper { #[cfg(feature = "dim2")] let ang_jac = 1.0; #[cfg(feature = "dim3")] - let ang_jac = self.basis.col_b(_motor_axis); + let ang_jac = self.basis.col_at(_motor_axis); let mut rhs_wo_bias = 0.0; if motor_params.erp_inv_dt != 0.0 { diff --git a/src_rbd_shaders/lib.rs b/src_rbd_shaders/lib.rs index 913ad97..1c6e520 100644 --- a/src_rbd_shaders/lib.rs +++ b/src_rbd_shaders/lib.rs @@ -407,15 +407,15 @@ pub fn abs(x: f32) -> f32 { /// An out-of-range index is a bug: debug (host) builds catch it with a /// `debug_assert!`, release/device builds fold it to the last column rather /// than carrying panic machinery into the kernel. -pub trait ColBranchless { +pub trait ColumnIndex { type Column; - fn col_b(&self, index: usize) -> Self::Column; + fn col_at(&self, index: usize) -> Self::Column; } -impl ColBranchless for glamx::Mat3 { +impl ColumnIndex for glamx::Mat3 { type Column = glamx::Vec3; #[inline(always)] - fn col_b(&self, index: usize) -> glamx::Vec3 { + fn col_at(&self, index: usize) -> glamx::Vec3 { debug_assert!(index < 3, "Mat3 column index out of range"); match index { 0 => self.x_axis, @@ -425,10 +425,10 @@ impl ColBranchless for glamx::Mat3 { } } -impl ColBranchless for glamx::Mat2 { +impl ColumnIndex for glamx::Mat2 { type Column = glamx::Vec2; #[inline(always)] - fn col_b(&self, index: usize) -> glamx::Vec2 { + fn col_at(&self, index: usize) -> glamx::Vec2 { debug_assert!(index < 2, "Mat2 column index out of range"); match index { 0 => self.x_axis,