use super::{Vec2f, Vec3f, Vec4f};
pub trait DerivativeBuiltinDpdx {
fn dpdx(self) -> Self;
}
pub trait DerivativeBuiltinDpdxCoarse {
fn dpdx_coarse(self) -> Self;
}
pub trait DerivativeBuiltinDpdxFine {
fn dpdx_fine(self) -> Self;
}
pub trait DerivativeBuiltinDpdy {
fn dpdy(self) -> Self;
}
pub trait DerivativeBuiltinDpdyCoarse {
fn dpdy_coarse(self) -> Self;
}
pub trait DerivativeBuiltinDpdyFine {
fn dpdy_fine(self) -> Self;
}
pub trait DerivativeBuiltinFwidth {
fn fwidth(self) -> Self;
}
pub trait DerivativeBuiltinFwidthCoarse {
fn fwidth_coarse(self) -> Self;
}
pub trait DerivativeBuiltinFwidthFine {
fn fwidth_fine(self) -> Self;
}
pub fn dpdx<T: DerivativeBuiltinDpdx>(e: T) -> T {
<T as DerivativeBuiltinDpdx>::dpdx(e)
}
pub fn dpdx_coarse<T: DerivativeBuiltinDpdxCoarse>(e: T) -> T {
<T as DerivativeBuiltinDpdxCoarse>::dpdx_coarse(e)
}
pub fn dpdx_fine<T: DerivativeBuiltinDpdxFine>(e: T) -> T {
<T as DerivativeBuiltinDpdxFine>::dpdx_fine(e)
}
pub fn dpdy<T: DerivativeBuiltinDpdy>(e: T) -> T {
<T as DerivativeBuiltinDpdy>::dpdy(e)
}
pub fn dpdy_coarse<T: DerivativeBuiltinDpdyCoarse>(e: T) -> T {
<T as DerivativeBuiltinDpdyCoarse>::dpdy_coarse(e)
}
pub fn dpdy_fine<T: DerivativeBuiltinDpdyFine>(e: T) -> T {
<T as DerivativeBuiltinDpdyFine>::dpdy_fine(e)
}
pub fn fwidth<T: DerivativeBuiltinFwidth>(e: T) -> T {
<T as DerivativeBuiltinFwidth>::fwidth(e)
}
pub fn fwidth_coarse<T: DerivativeBuiltinFwidthCoarse>(e: T) -> T {
<T as DerivativeBuiltinFwidthCoarse>::fwidth_coarse(e)
}
pub fn fwidth_fine<T: DerivativeBuiltinFwidthFine>(e: T) -> T {
<T as DerivativeBuiltinFwidthFine>::fwidth_fine(e)
}
#[cfg(feature = "dispatch-runtime")]
fn with_quad_or<R>(fallback: R, quad_fn: impl FnOnce(&super::runtime::QuadContext, u8) -> R) -> R {
super::runtime::with_quad_context(|opt| match opt {
Some((ctx, idx)) => quad_fn(ctx, idx),
None => fallback,
})
}
#[cfg(feature = "dispatch-runtime")]
impl DerivativeBuiltinDpdxFine for f32 {
fn dpdx_fine(self) -> Self {
with_quad_or(0.0, |ctx, idx| ctx.dpdx_fine_f32(idx, self))
}
}
#[cfg(not(feature = "dispatch-runtime"))]
impl DerivativeBuiltinDpdxFine for f32 {
fn dpdx_fine(self) -> Self {
0.0
}
}
#[cfg(feature = "dispatch-runtime")]
impl DerivativeBuiltinDpdxCoarse for f32 {
fn dpdx_coarse(self) -> Self {
with_quad_or(0.0, |ctx, idx| ctx.dpdx_coarse_f32(idx, self))
}
}
#[cfg(not(feature = "dispatch-runtime"))]
impl DerivativeBuiltinDpdxCoarse for f32 {
fn dpdx_coarse(self) -> Self {
0.0
}
}
impl DerivativeBuiltinDpdx for f32 {
fn dpdx(self) -> Self {
self.dpdx_fine()
}
}
#[cfg(feature = "dispatch-runtime")]
impl DerivativeBuiltinDpdyFine for f32 {
fn dpdy_fine(self) -> Self {
with_quad_or(0.0, |ctx, idx| ctx.dpdy_fine_f32(idx, self))
}
}
#[cfg(not(feature = "dispatch-runtime"))]
impl DerivativeBuiltinDpdyFine for f32 {
fn dpdy_fine(self) -> Self {
0.0
}
}
#[cfg(feature = "dispatch-runtime")]
impl DerivativeBuiltinDpdyCoarse for f32 {
fn dpdy_coarse(self) -> Self {
with_quad_or(0.0, |ctx, idx| ctx.dpdy_coarse_f32(idx, self))
}
}
#[cfg(not(feature = "dispatch-runtime"))]
impl DerivativeBuiltinDpdyCoarse for f32 {
fn dpdy_coarse(self) -> Self {
0.0
}
}
impl DerivativeBuiltinDpdy for f32 {
fn dpdy(self) -> Self {
self.dpdy_fine()
}
}
impl DerivativeBuiltinFwidthFine for f32 {
fn fwidth_fine(self) -> Self {
self.dpdx_fine().abs() + self.dpdy_fine().abs()
}
}
impl DerivativeBuiltinFwidthCoarse for f32 {
fn fwidth_coarse(self) -> Self {
self.dpdx_coarse().abs() + self.dpdy_coarse().abs()
}
}
impl DerivativeBuiltinFwidth for f32 {
fn fwidth(self) -> Self {
self.fwidth_fine()
}
}
macro_rules! impl_dpdx_fine_vec {
($ty:ty, $n:expr) => {
#[cfg(feature = "dispatch-runtime")]
impl DerivativeBuiltinDpdxFine for $ty {
fn dpdx_fine(self) -> Self {
let arr = self.to_array();
let mut buf = [0.0f32; 4];
buf[..$n].copy_from_slice(&arr);
let result = with_quad_or([0.0f32; 4], |ctx, idx| {
ctx.dpdx_fine_components(idx, &buf, $n)
});
let mut out = [0.0f32; $n];
out.copy_from_slice(&result[..$n]);
Self::from_array(out)
}
}
#[cfg(not(feature = "dispatch-runtime"))]
impl DerivativeBuiltinDpdxFine for $ty {
fn dpdx_fine(self) -> Self {
Self::default()
}
}
};
}
macro_rules! impl_dpdx_coarse_vec {
($ty:ty, $n:expr) => {
#[cfg(feature = "dispatch-runtime")]
impl DerivativeBuiltinDpdxCoarse for $ty {
fn dpdx_coarse(self) -> Self {
let arr = self.to_array();
let mut buf = [0.0f32; 4];
buf[..$n].copy_from_slice(&arr);
let result = with_quad_or([0.0f32; 4], |ctx, idx| {
ctx.dpdx_coarse_components(idx, &buf, $n)
});
let mut out = [0.0f32; $n];
out.copy_from_slice(&result[..$n]);
Self::from_array(out)
}
}
#[cfg(not(feature = "dispatch-runtime"))]
impl DerivativeBuiltinDpdxCoarse for $ty {
fn dpdx_coarse(self) -> Self {
Self::default()
}
}
};
}
macro_rules! impl_dpdy_fine_vec {
($ty:ty, $n:expr) => {
#[cfg(feature = "dispatch-runtime")]
impl DerivativeBuiltinDpdyFine for $ty {
fn dpdy_fine(self) -> Self {
let arr = self.to_array();
let mut buf = [0.0f32; 4];
buf[..$n].copy_from_slice(&arr);
let result = with_quad_or([0.0f32; 4], |ctx, idx| {
ctx.dpdy_fine_components(idx, &buf, $n)
});
let mut out = [0.0f32; $n];
out.copy_from_slice(&result[..$n]);
Self::from_array(out)
}
}
#[cfg(not(feature = "dispatch-runtime"))]
impl DerivativeBuiltinDpdyFine for $ty {
fn dpdy_fine(self) -> Self {
Self::default()
}
}
};
}
macro_rules! impl_dpdy_coarse_vec {
($ty:ty, $n:expr) => {
#[cfg(feature = "dispatch-runtime")]
impl DerivativeBuiltinDpdyCoarse for $ty {
fn dpdy_coarse(self) -> Self {
let arr = self.to_array();
let mut buf = [0.0f32; 4];
buf[..$n].copy_from_slice(&arr);
let result = with_quad_or([0.0f32; 4], |ctx, idx| {
ctx.dpdy_coarse_components(idx, &buf, $n)
});
let mut out = [0.0f32; $n];
out.copy_from_slice(&result[..$n]);
Self::from_array(out)
}
}
#[cfg(not(feature = "dispatch-runtime"))]
impl DerivativeBuiltinDpdyCoarse for $ty {
fn dpdy_coarse(self) -> Self {
Self::default()
}
}
};
}
macro_rules! impl_dpdx_default_vec {
($ty:ty) => {
impl DerivativeBuiltinDpdx for $ty {
fn dpdx(self) -> Self {
self.dpdx_fine()
}
}
};
}
macro_rules! impl_dpdy_default_vec {
($ty:ty) => {
impl DerivativeBuiltinDpdy for $ty {
fn dpdy(self) -> Self {
self.dpdy_fine()
}
}
};
}
macro_rules! impl_fwidth_fine_vec {
($ty:ty, $n:expr) => {
impl DerivativeBuiltinFwidthFine for $ty {
fn fwidth_fine(self) -> Self {
let dx = self.dpdx_fine();
let dy = self.dpdy_fine();
let dx_arr = dx.to_array();
let dy_arr = dy.to_array();
let mut out = [0.0f32; $n];
for i in 0..$n {
out[i] = dx_arr[i].abs() + dy_arr[i].abs();
}
Self::from_array(out)
}
}
};
}
macro_rules! impl_fwidth_coarse_vec {
($ty:ty, $n:expr) => {
impl DerivativeBuiltinFwidthCoarse for $ty {
fn fwidth_coarse(self) -> Self {
let dx = self.dpdx_coarse();
let dy = self.dpdy_coarse();
let dx_arr = dx.to_array();
let dy_arr = dy.to_array();
let mut out = [0.0f32; $n];
for i in 0..$n {
out[i] = dx_arr[i].abs() + dy_arr[i].abs();
}
Self::from_array(out)
}
}
};
}
macro_rules! impl_fwidth_default_vec {
($ty:ty) => {
impl DerivativeBuiltinFwidth for $ty {
fn fwidth(self) -> Self {
self.fwidth_fine()
}
}
};
}
macro_rules! impl_all_derivative_vec {
($ty:ty, $n:expr) => {
impl_dpdx_fine_vec!($ty, $n);
impl_dpdx_coarse_vec!($ty, $n);
impl_dpdx_default_vec!($ty);
impl_dpdy_fine_vec!($ty, $n);
impl_dpdy_coarse_vec!($ty, $n);
impl_dpdy_default_vec!($ty);
impl_fwidth_fine_vec!($ty, $n);
impl_fwidth_coarse_vec!($ty, $n);
impl_fwidth_default_vec!($ty);
};
}
impl_all_derivative_vec!(Vec2f, 2);
impl_all_derivative_vec!(Vec3f, 3);
impl_all_derivative_vec!(Vec4f, 4);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dpdx_outside_dispatch_returns_zero() {
assert_eq!(dpdx(42.0f32), 0.0);
assert_eq!(dpdx_fine(42.0f32), 0.0);
assert_eq!(dpdx_coarse(42.0f32), 0.0);
}
#[test]
fn dpdy_outside_dispatch_returns_zero() {
assert_eq!(dpdy(42.0f32), 0.0);
assert_eq!(dpdy_fine(42.0f32), 0.0);
assert_eq!(dpdy_coarse(42.0f32), 0.0);
}
#[test]
fn fwidth_outside_dispatch_returns_zero() {
assert_eq!(fwidth(42.0f32), 0.0);
assert_eq!(fwidth_fine(42.0f32), 0.0);
assert_eq!(fwidth_coarse(42.0f32), 0.0);
}
#[test]
fn vec_derivatives_outside_dispatch_return_zero() {
let v2 = Vec2f { x: 1.0, y: 2.0 };
let v3 = Vec3f {
x: 1.0,
y: 2.0,
z: 3.0,
};
let v4 = Vec4f {
x: 1.0,
y: 2.0,
z: 3.0,
w: 4.0,
};
assert_eq!(dpdx(v2), Vec2f::default());
assert_eq!(dpdy(v3), Vec3f::default());
assert_eq!(fwidth(v4), Vec4f::default());
}
#[cfg(feature = "dispatch-runtime")]
mod dispatch {
use super::*;
use crate::std::runtime::dispatch_fragments;
#[test]
fn dpdx_fine_scalar_in_dispatch() {
let results = dispatch_fragments(
2,
2,
|_, _| (),
|builtins, _| dpdx_fine(builtins.position.x),
);
for row in &results {
for val in row {
let v = val.expect("non-helper should have output");
assert!(
(v - 1.0).abs() < 1e-6,
"dpdx_fine(position.x) should be 1.0, got {v}"
);
}
}
}
#[test]
fn dpdy_fine_scalar_in_dispatch() {
let results = dispatch_fragments(
2,
2,
|_, _| (),
|builtins, _| dpdy_fine(builtins.position.y),
);
for row in &results {
for val in row {
let v = val.expect("non-helper should have output");
assert!(
(v - 1.0).abs() < 1e-6,
"dpdy_fine(position.y) should be 1.0, got {v}"
);
}
}
}
#[test]
fn dpdx_coarse_scalar_in_dispatch() {
let results = dispatch_fragments(
2,
2,
|_, _| (),
|builtins, _| dpdx_coarse(builtins.position.x),
);
for row in &results {
for val in row {
let v = val.expect("non-helper should have output");
assert!(
(v - 1.0).abs() < 1e-6,
"dpdx_coarse(position.x) should be 1.0, got {v}"
);
}
}
}
#[test]
fn dpdy_coarse_scalar_in_dispatch() {
let results = dispatch_fragments(
2,
2,
|_, _| (),
|builtins, _| dpdy_coarse(builtins.position.y),
);
for row in &results {
for val in row {
let v = val.expect("non-helper should have output");
assert!(
(v - 1.0).abs() < 1e-6,
"dpdy_coarse(position.y) should be 1.0, got {v}"
);
}
}
}
#[test]
fn dpdx_default_delegates_to_fine() {
let results =
dispatch_fragments(2, 2, |_, _| (), |builtins, _| dpdx(builtins.position.x));
for row in &results {
for val in row {
let v = val.expect("non-helper should have output");
assert!(
(v - 1.0).abs() < 1e-6,
"dpdx(position.x) should be 1.0, got {v}"
);
}
}
}
#[test]
fn fwidth_scalar_in_dispatch() {
let results =
dispatch_fragments(2, 2, |_, _| (), |builtins, _| fwidth(builtins.position.x));
for row in &results {
for val in row {
let v = val.expect("non-helper should have output");
assert!(
(v - 1.0).abs() < 1e-6,
"fwidth(position.x) should be 1.0, got {v}"
);
}
}
}
#[test]
fn fwidth_scalar_both_axes() {
let results = dispatch_fragments(
2,
2,
|_, _| (),
|builtins, _| {
let v = builtins.position.x + builtins.position.y;
fwidth(v)
},
);
for row in &results {
for val in row {
let v = val.expect("non-helper should have output");
assert!(
(v - 2.0).abs() < 1e-6,
"fwidth(position.x + position.y) should be 2.0, got {v}"
);
}
}
}
#[test]
fn dpdx_fine_vec2_in_dispatch() {
let results = dispatch_fragments(
2,
2,
|_, _| (),
|builtins, _| {
dpdx_fine(Vec2f {
x: builtins.position.x,
y: builtins.position.y,
})
},
);
for row in &results {
for val in row {
let v = val.expect("non-helper should have output");
assert!(
(v.x - 1.0).abs() < 1e-6,
"dpdx_fine(position).x should be 1.0, got {}",
v.x
);
assert!(
v.y.abs() < 1e-6,
"dpdx_fine(position).y should be 0.0, got {}",
v.y
);
}
}
}
#[test]
fn dpdy_fine_vec3_in_dispatch() {
let results = dispatch_fragments(
2,
2,
|_, _| (),
|builtins, _| {
dpdy_fine(Vec3f {
x: builtins.position.x,
y: builtins.position.y,
z: 5.0,
})
},
);
for row in &results {
for val in row {
let v = val.expect("non-helper should have output");
assert!(v.x.abs() < 1e-6, "dpdy(position.x) should be 0.0");
assert!((v.y - 1.0).abs() < 1e-6, "dpdy(position.y) should be 1.0");
assert!(v.z.abs() < 1e-6, "dpdy(constant) should be 0.0");
}
}
}
#[test]
fn fwidth_vec4_in_dispatch() {
let results = dispatch_fragments(
2,
2,
|_, _| (),
|builtins, _| {
fwidth(Vec4f {
x: builtins.position.x,
y: builtins.position.y,
z: 0.0,
w: 0.0,
})
},
);
for row in &results {
for val in row {
let v = val.expect("non-helper should have output");
assert!((v.x - 1.0).abs() < 1e-6, "fwidth(position).x should be 1.0");
assert!((v.y - 1.0).abs() < 1e-6, "fwidth(position).y should be 1.0");
assert!(v.z.abs() < 1e-6, "fwidth(0).z should be 0.0");
assert!(v.w.abs() < 1e-6, "fwidth(0).w should be 0.0");
}
}
}
#[test]
fn larger_grid_dpdx() {
let results =
dispatch_fragments(4, 4, |_, _| (), |builtins, _| dpdx(builtins.position.x));
assert_eq!(results.len(), 4);
for row in &results {
assert_eq!(row.len(), 4);
for val in row {
let v = val.expect("non-helper should have output");
assert!(
(v - 1.0).abs() < 1e-6,
"dpdx(position.x) should be 1.0 on larger grid, got {v}"
);
}
}
}
#[test]
fn odd_grid_derivatives() {
let results = dispatch_fragments(
3,
3,
|_, _| (),
|builtins, _| (dpdx(builtins.position.x), dpdy(builtins.position.y)),
);
assert_eq!(results.len(), 3);
for row in &results {
assert_eq!(row.len(), 3);
for val in row {
let (dx, dy) = val.expect("non-helper should have output");
assert!(
(dx - 1.0).abs() < 1e-6,
"dpdx(position.x) should be 1.0, got {dx}"
);
assert!(
(dy - 1.0).abs() < 1e-6,
"dpdy(position.y) should be 1.0, got {dy}"
);
}
}
}
#[test]
fn nonlinear_value_derivatives() {
let results = dispatch_fragments(
2,
2,
|_, _| (),
|builtins, _| {
let x = builtins.position.x;
dpdx_fine(x * x)
},
);
for row in &results {
for val in row {
let v = val.expect("non-helper should have output");
assert!(
(v - 2.0).abs() < 1e-6,
"dpdx_fine(x*x) on [0.5,1.5] quad should be 2.0, got {v}"
);
}
}
}
}
}