use cubecl::prelude::*;
use super::helpers::{
BLK_STEP,
R,
RingFixture,
deterministic_texture,
make_client,
noisy_ring,
planted_ring,
};
use crate::collab::STEP;
use crate::collab::geometry::{fused_cubes_x, ref_count, refs_along};
use crate::collab::kernels::aggregate::{cross_frame_accum_scale, weight_scale};
use crate::collab::kernels::fused::collab_fused;
use crate::collab::kernels::transforms::dct_noise_profile;
pub(super) const BLKSIZE: u32 = 16;
pub(super) const THSAD: f32 = (BLKSIZE * BLKSIZE) as f32 * 0.02;
const REFINE: u32 = 2;
const K_MAX: u32 = 8;
const SPATIAL_RADIUS: u32 = 4;
struct Knobs {
c_min: f32,
k_max: u32,
sigma: f32,
lambda_ht: f32,
}
impl Default for Knobs {
fn default() -> Self {
Knobs {
c_min: 0.05,
k_max: K_MAX,
sigma: 0.02,
lambda_ht: 2.7,
}
}
}
struct FusedRun {
wsum: Vec<i32>,
group_weight: Vec<f32>,
pixels: usize,
}
impl FusedRun {
fn frame_weight_sum(&self, slot: u32) -> i64 {
let start = slot as usize * self.pixels;
self.wsum[start..start + self.pixels]
.iter()
.map(|&v| v as i64)
.sum()
}
fn total_weight(&self) -> i64 {
self.wsum.iter().map(|&v| v as i64).sum()
}
}
fn run_fused_over(fx: &RingFixture, k: Knobs) -> FusedRun {
let client = make_client();
let w = fx.width;
let h = fx.height;
let pixels = (w * h) as usize;
let frames = fx.ring.len() / pixels;
let refs = ref_count(w, h);
let refs_x = refs_along(w);
let profile = dct_noise_profile(0.0);
let ring_buf = client.create_from_slice(f32::as_bytes(&fx.ring));
let mv_buf = client.create_from_slice(i32::as_bytes(&fx.mv_field));
let conf_buf = client.create_from_slice(f32::as_bytes(&fx.confidence));
let slots_buf = client.create_from_slice(u32::as_bytes(&fx.neighbour_slots));
let sigma_buf = client.create_from_slice(f32::as_bytes(&[k.sigma]));
let profile_buf = client.create_from_slice(f32::as_bytes(&profile));
let accum = client.create_from_slice(i32::as_bytes(&vec![0i32; pixels * frames]));
let wsum = client.create_from_slice(i32::as_bytes(&vec![0i32; pixels * frames]));
let group_weight = client.empty(refs * size_of::<f32>());
unsafe {
collab_fused::launch_unchecked::<R>(
&client,
CubeCount::new_2d(fused_cubes_x(w), refs_along(h)),
CubeDim::new_1d(64),
1usize,
ArrayArg::from_raw_parts(ring_buf, fx.ring.len()),
ArrayArg::from_raw_parts(mv_buf, fx.mv_field.len()),
ArrayArg::from_raw_parts(conf_buf, fx.confidence.len()),
ArrayArg::from_raw_parts(slots_buf, fx.neighbour_slots.len()),
ArrayArg::from_raw_parts(sigma_buf, 1),
ArrayArg::from_raw_parts(profile_buf, 8),
ArrayArg::from_raw_parts(accum, pixels * frames),
ArrayArg::from_raw_parts(wsum.clone(), pixels * frames),
ArrayArg::from_raw_parts(group_weight.clone(), refs),
fx.centre_slot,
0.0f32,
k.c_min,
THSAD,
k.lambda_ht,
weight_scale(k.sigma, &profile),
cross_frame_accum_scale(SPATIAL_RADIUS, fx.radius),
false,
fx.radius,
REFINE,
fx.mv_stride,
fx.conf_stride,
BLK_STEP,
BLKSIZE,
fx.blocks_x,
fx.blocks_y,
w,
h,
1u32,
k.k_max,
1u32,
SPATIAL_RADIUS,
refs_x,
);
}
let wsum_bytes = client.read_one(wsum).expect("wsum readback failed");
let weight_bytes = client
.read_one(group_weight)
.expect("group_weight readback failed");
FusedRun {
wsum: i32::from_bytes(&wsum_bytes)[..pixels * frames].to_vec(),
group_weight: f32::from_bytes(&weight_bytes)[..refs].to_vec(),
pixels,
}
}
#[test]
fn temporal_members_are_found_at_the_mv_prediction() {
let (w, h) = (96u32, 96u32);
let radius = 2u32;
let ref_pos = (64u32, 64u32);
let patch = deterministic_texture(7);
let predicted = planted_ring(w, h, radius, ref_pos, 3, &patch, 0.2, |_| 1.0);
let mut blind = planted_ring(w, h, radius, ref_pos, 3, &patch, 0.2, |_| 1.0);
blind.mv_field.fill(0);
let refs_x = refs_along(w);
let ref_idx = ((ref_pos.1 / STEP) * refs_x + (ref_pos.0 / STEP)) as usize;
let with_prediction = run_fused_over(&predicted, Knobs::default()).group_weight[ref_idx];
let without = run_fused_over(&blind, Knobs::default()).group_weight[ref_idx];
assert!(
with_prediction > without * 1.5,
"expected the group at {ref_pos:?} to agree far better when the motion field points at \
the planted copies, got weight {with_prediction} with the prediction and {without} \
with a zeroed field"
);
}
#[test]
fn low_confidence_neighbours_contribute_no_candidates() {
let (w, h) = (96u32, 96u32);
let radius = 2u32;
let ref_pos = (64u32, 64u32);
let patch = deterministic_texture(11);
let fx = planted_ring(w, h, radius, ref_pos, 3, &patch, 0.2, |k| {
if k > 0 { 0.0 } else { 1.0 }
});
let run = run_fused_over(&fx, Knobs::default());
for k in -(radius as i32)..=(radius as i32) {
let slot = (k + radius as i32) as u32;
let weight = run.frame_weight_sum(slot);
if k > 0 {
assert_eq!(
weight, 0,
"slot {slot} (k={k}) is gated by c_min, so it must receive no scatter at all"
);
} else {
assert!(
weight > 0,
"slot {slot} (k={k}) is ungated, so it must receive members"
);
}
}
}
#[test]
fn no_admission_gate_means_the_group_always_fills() {
let (w, h) = (64u32, 64u32);
let radius = 2u32;
let fx = noisy_ring(w, h, radius, 1.0);
let full = run_fused_over(
&fx,
Knobs {
lambda_ht: 1.0e6,
..Knobs::default()
},
);
let single = run_fused_over(
&fx,
Knobs {
k_max: 1,
lambda_ht: 1.0e6,
..Knobs::default()
},
);
let one = single.total_weight();
assert!(one > 0, "the k_max = 1 run deposited no weight at all");
assert_eq!(
full.total_weight(),
one * K_MAX as i64,
"expected every group to carry {K_MAX} members, so {K_MAX}x the weight the \
one-member run deposited"
);
}