use cubecl::prelude::*;
use super::aggregate::scatter_patch;
use super::group::{clamp_top_left, pack_pos_t, unpack_t};
use super::plane_ops::{group_base, plane_ssd_reduce8, shift_insert8_gated, transpose8};
use super::transforms::{
RECIPROCAL_FLOOR,
dct8_reg_fwd,
dct8_reg_inv,
fill_dct8_basis,
haar_reg_fwd_level,
haar_reg_inv_level,
safe_reciprocal,
variance_reg_level,
};
use crate::collab::{MAX_K, MAX_TEMPORAL_RADIUS, PATCH_AREA, PATCH_SIZE, STEP};
use crate::nlmeans::kernels::helpers::{channel_scale, read_line};
const _: () = assert!(
2 * MAX_TEMPORAL_RADIUS < 64,
"pack_pos_t's 6-bit t field must hold every neighbour index collab_fused packs"
);
const _: () = assert!(
MAX_K == PATCH_SIZE && MAX_K == 8,
"collab_fused's per-lane group array and its three-level stack transform are written for \
MAX_K == PATCH_SIZE == 8"
);
#[cube]
pub(crate) fn mismatch_sigma2(confidence: f32, mismatch_thsad: f32, blksize_area: f32) -> f32 {
let ratio = (1.0f32 - confidence) / (1.0f32 + confidence);
let e2 = mismatch_thsad * mismatch_thsad * ratio;
let eps = f32::sqrt(e2) / blksize_area;
std::f32::consts::FRAC_PI_2 * eps * eps
}
pub(crate) const MEMBER_SIGMA2_CAP: f32 = 64.0;
#[cube(launch_unchecked)]
#[allow(clippy::too_many_arguments)]
#[expect(
clippy::collapsible_if,
reason = "the outer condition of the group-DC exception is comptime, so nesting elides the \
inner test in 63 of the 64 unrolled positions rather than emitting it and ANDing \
a constant false into it"
)]
pub fn collab_fused<N: Size>(
ring: &Array<Vector<f32, N>>,
mv_field: &Array<i32>,
confidence: &Array<f32>,
neighbour_slots: &Array<u32>,
sigma: &Array<f32>,
dct_profile: &Array<f32>,
accum: &mut Array<Atomic<i32>>,
wsum: &mut Array<Atomic<i32>>,
group_weight: &mut Array<f32>,
centre_slot: u32,
noise_floor: f32,
c_min: f32,
mismatch_thsad: f32,
lambda_ht: f32,
weight_scale: f32,
accum_scale: f32,
#[comptime] use_member_sigma: bool,
#[comptime] radius: u32,
#[comptime] refine: u32,
#[comptime] mv_stride: u32,
#[comptime] conf_stride: u32,
#[comptime] blk_step: u32,
#[comptime] blksize: u32,
#[comptime] blocks_x: u32,
#[comptime] blocks_y: u32,
#[comptime] width: u32,
#[comptime] height: u32,
#[comptime] channels: u32,
#[comptime] k_max: u32,
#[comptime] stored_ch: u32,
#[comptime] spatial_radius: u32,
#[comptime] refs_x: u32,
) {
let tid = UNIT_POS_X;
let grp = tid / 8u32;
let sub = tid % 8u32;
let base = group_base();
let max_x = comptime!(width - PATCH_SIZE);
let max_y = comptime!(height - PATCH_SIZE);
let mut basis = SharedMemory::<f32>::new(PATCH_AREA as usize);
let mut tbuf = SharedMemory::<f32>::new(comptime!(8 * 65) as usize);
fill_dct8_basis(&mut basis, tid);
sync_cube();
let ref_x_index = CUBE_POS_X * 8u32 + grp;
let live = ref_x_index < refs_x;
let ref_x_clamped = ref_x_index.min(refs_x - 1u32);
let rx = (ref_x_clamped * STEP).min(max_x);
let ry = (CUBE_POS_Y * STEP).min(max_y);
let mut current = Array::<f32>::new(comptime!(PATCH_SIZE * channels) as usize);
#[unroll]
for r in 0..PATCH_SIZE {
let px = read_line(ring, rx + sub, ry + r, centre_slot, width, height);
#[unroll]
for c in 0..channels {
current[(r * channels + c) as usize] = px[c as usize];
}
}
let mut best_d = 3.0e38f32;
let mut best_pos = 0u32;
let scale = channel_scale(channels);
let bx = (rx / blk_step).min(blocks_x - 1);
let by = (ry / blk_step).min(blocks_y - 1);
let block = by * blocks_x + bx;
let mut n_live = 0u32;
let s_left = clamp_top_left(rx as i32 - spatial_radius as i32, max_x);
let s_right = clamp_top_left(rx as i32 + spatial_radius as i32, max_x);
let s_top = clamp_top_left(ry as i32 - spatial_radius as i32, max_y);
let s_bot = clamp_top_left(ry as i32 + spatial_radius as i32, max_y);
n_live += (s_right - s_left + 1u32) * (s_bot - s_top + 1u32);
let mut cy = s_top;
while cy <= s_bot {
let mut cx = s_left;
while cx <= s_right {
let mut partial = 0.0f32;
#[unroll]
for r in 0..PATCH_SIZE {
let px = read_line(ring, cx + sub, cy + r, centre_slot, width, height);
#[unroll]
for c in 0..channels {
let d = current[(r * channels + c) as usize] - px[c as usize];
partial += d * d;
}
}
let mut dist = plane_ssd_reduce8(partial) * scale - noise_floor;
if cx == rx && cy == ry {
dist = -1.0e38f32;
}
shift_insert8_gated(
&mut best_d,
&mut best_pos,
dist,
pack_pos_t(cx, cy, 0u32),
sub,
base,
);
cx += 1u32;
}
cy += 1u32;
}
let n_neighbours = comptime!(2 * radius);
let mut t = 0u32;
while t < n_neighbours {
let conf = confidence[(t * conf_stride + block) as usize];
if conf >= c_min {
let slot = neighbour_slots[t as usize];
let mv = (t * mv_stride + block * 2u32) as usize;
let px0 = rx as i32 + mv_field[mv];
let py0 = ry as i32 + mv_field[mv + 1];
let t_left = clamp_top_left(px0 - refine as i32, max_x);
let t_right = clamp_top_left(px0 + refine as i32, max_x);
let t_top = clamp_top_left(py0 - refine as i32, max_y);
let t_bot = clamp_top_left(py0 + refine as i32, max_y);
n_live += (t_right - t_left + 1u32) * (t_bot - t_top + 1u32);
let packed_t = t + 1u32;
let mut ny = t_top;
while ny <= t_bot {
let mut nx = t_left;
while nx <= t_right {
let mut partial = 0.0f32;
#[unroll]
for r in 0..PATCH_SIZE {
let px = read_line(ring, nx + sub, ny + r, slot, width, height);
#[unroll]
for c in 0..channels {
let d = current[(r * channels + c) as usize] - px[c as usize];
partial += d * d;
}
}
let dist = plane_ssd_reduce8(partial) * scale - noise_floor;
shift_insert8_gated(
&mut best_d,
&mut best_pos,
dist,
pack_pos_t(nx, ny, packed_t),
sub,
base,
);
nx += 1u32;
}
ny += 1u32;
}
}
t += 1u32;
}
let ref_idx = CUBE_POS_Y * refs_x + ref_x_clamped;
let blksize_area = comptime!(blksize * blksize) as f32;
let mut k_use = 1u32;
while k_use * 2u32 <= n_live && k_use * 2u32 <= k_max {
k_use *= 2u32;
}
let mut member_pos = Array::<u32>::new(MAX_K as usize);
let mut member_slot = Array::<u32>::new(MAX_K as usize);
let mut member_sig2 = Array::<f32>::new(MAX_K as usize);
#[unroll]
for m in 0..MAX_K {
let packed = plane_shuffle(best_pos, base + m);
let mt = unpack_t(packed);
let n = u32::max(mt, 1u32) - 1u32;
member_pos[m as usize] = packed;
member_slot[m as usize] = select(mt > 0u32, neighbour_slots[n as usize], centre_slot);
let mut sig2 = 0.0f32;
if use_member_sigma {
if mt > 0u32 {
sig2 = mismatch_sigma2(
confidence[(n * conf_stride + block) as usize],
mismatch_thsad,
blksize_area,
);
}
}
member_sig2[m as usize] = sig2;
}
let prof_sub = dct_profile[sub as usize];
let mut gw = 0.0f32;
#[unroll]
for c in 0..channels {
let sigma_c = sigma[c as usize];
let base_sig2 = sigma_c * sigma_c;
let mut stack = Array::<f32>::new(PATCH_AREA as usize);
let mut v = Array::<f32>::new(MAX_K as usize);
#[unroll]
for m in 0..MAX_K {
let packed = member_pos[m as usize];
let mx = packed & 0x1FFFu32;
let my = (packed >> 13u32) & 0x1FFFu32;
let src_slot = member_slot[m as usize];
let extra = f32::min(member_sig2[m as usize], MEMBER_SIGMA2_CAP * base_sig2);
v[m as usize] = base_sig2 + extra;
#[unroll]
for r in 0..PATCH_SIZE {
let px = read_line(ring, mx + sub, my + r, src_slot, width, height);
stack[(m * PATCH_SIZE + r) as usize] = px[c as usize];
}
}
if k_use >= 8u32 {
variance_reg_level(&mut v, 8u32);
}
if k_use >= 4u32 {
variance_reg_level(&mut v, 4u32);
}
if k_use >= 2u32 {
variance_reg_level(&mut v, 2u32);
}
#[unroll]
for m in 0..MAX_K {
let mut line = Array::<f32>::new(PATCH_SIZE as usize);
#[unroll]
for i in 0..PATCH_SIZE {
line[i as usize] = stack[(m * PATCH_SIZE + i) as usize];
}
dct8_reg_fwd(&basis, &mut line);
transpose8(&mut tbuf, &mut line, sub, grp);
dct8_reg_fwd(&basis, &mut line);
#[unroll]
for i in 0..PATCH_SIZE {
stack[(m * PATCH_SIZE + i) as usize] = line[i as usize];
}
}
if k_use >= 8u32 {
haar_reg_fwd_level(&mut stack, 8u32);
}
if k_use >= 4u32 {
haar_reg_fwd_level(&mut stack, 4u32);
}
if k_use >= 2u32 {
haar_reg_fwd_level(&mut stack, 2u32);
}
let mut retained_v = 0.0f32;
#[unroll]
for i in 0..PATCH_SIZE {
let factor = dct_profile[i as usize] * prof_sub;
#[unroll]
for j in 0..MAX_K {
if j < k_use {
let vj = v[j as usize] * factor;
let slot = (j * PATCH_SIZE + i) as usize;
let mut keep = f32::abs(stack[slot]) >= lambda_ht * f32::sqrt(vj);
if comptime!(j == 0u32 && i == 0u32) {
if sub == 0u32 {
keep = true;
}
}
if keep {
retained_v += vj;
} else {
stack[slot] = 0.0f32;
}
}
}
}
if comptime!(c == 0u32) {
let sum = plane_ssd_reduce8(retained_v);
let w = safe_reciprocal(sum, RECIPROCAL_FLOOR);
if live && sub == 0u32 {
group_weight[ref_idx as usize] = w;
}
gw = w * weight_scale;
}
if k_use >= 2u32 {
haar_reg_inv_level(&mut stack, 2u32);
}
if k_use >= 4u32 {
haar_reg_inv_level(&mut stack, 4u32);
}
if k_use >= 8u32 {
haar_reg_inv_level(&mut stack, 8u32);
}
#[unroll]
for m in 0..MAX_K {
let mut line = Array::<f32>::new(PATCH_SIZE as usize);
#[unroll]
for i in 0..PATCH_SIZE {
line[i as usize] = stack[(m * PATCH_SIZE + i) as usize];
}
dct8_reg_inv(&basis, &mut line);
transpose8(&mut tbuf, &mut line, sub, grp);
dct8_reg_inv(&basis, &mut line);
#[unroll]
for i in 0..PATCH_SIZE {
stack[(m * PATCH_SIZE + i) as usize] = line[i as usize];
}
}
#[unroll]
for m in 0..MAX_K {
if live && m < k_use {
let packed = member_pos[m as usize];
let mx = packed & 0x1FFFu32;
let my = (packed >> 13u32) & 0x1FFFu32;
let dst_slot = member_slot[m as usize];
#[unroll]
for r in 0..PATCH_SIZE {
scatter_patch(
accum,
wsum,
stack[(m * PATCH_SIZE + r) as usize],
gw,
mx,
my,
r * PATCH_SIZE + sub,
comptime!(c == 0u32),
c,
width,
stored_ch,
dst_slot,
comptime!(width * height),
accum_scale,
);
}
}
}
}
}