use cubecl::prelude::*;
use cubecl::server::Handle;
use super::denoiser::NlmDenoiser;
use super::kernels::{
gpu_copy,
gpu_zero_buffers,
nlm_accumulate,
nlm_distance,
nlm_distance_pair,
nlm_distance_pair_ref,
nlm_distance_ref,
nlm_finish,
nlm_fused_pair_accumulate_window,
nlm_fused_pair_accumulate_window_ref,
nlm_fused_single_window,
nlm_fused_single_window_ref,
nlm_horizontal_sum,
nlm_horizontal_sum_pair,
nlm_vertical_weight,
nlm_vweight_pair_accumulate,
};
use super::motion::{
self,
MotionEstimation,
confidence_byte_offset,
run_analyse,
run_compensate,
run_confidence_for_neighbour,
run_seeded_refine,
};
use super::noise::{build_spatial_offset_lut, spatial_offset_factor, spatial_offset_lut_len};
use super::prefilter::PrefilterMode;
use super::{BLOCK_1D, BLOCK_X, BLOCK_X_THIN, BLOCK_Y, BLOCK_Y_THIN, MAX_GRID_1D};
pub(super) struct LaunchCtx {
pub(super) total_frame_data: usize,
pub(super) frame_size: usize,
pub(super) pixels: usize,
pub(super) cube_count: CubeCount,
pub(super) cube_dim: CubeDim,
pub(super) thin_cube_count: CubeCount,
pub(super) thin_cube_dim: CubeDim,
}
fn neighbour_idx_for_k(radius: u32, k: i32) -> u32 {
debug_assert_ne!(k, 0, "k=0 is the spatial pair, it has no neighbour index");
debug_assert!(
k.unsigned_abs() <= radius,
"k={k} outside the temporal window ±{radius}"
);
if k < 0 {
(k + radius as i32) as u32
} else {
(radius as i32 - 1 + k) as u32
}
}
const NLM_SPATIAL_RESIDUAL_FRACTION: f32 = 0.0;
const BILATERAL_RESIDUAL_FRACTION: f32 = 0.0;
fn mc_sad_noise_floor_sigma(prefilter: PrefilterMode, sigma_y: f32) -> f32 {
match prefilter {
PrefilterMode::NlmSpatial { .. } => sigma_y * NLM_SPATIAL_RESIDUAL_FRACTION,
PrefilterMode::Bilateral { .. } => sigma_y * BILATERAL_RESIDUAL_FRACTION,
PrefilterMode::External | PrefilterMode::None => sigma_y,
}
}
struct ConfidenceArgs<R: Runtime> {
use_confidence: bool,
conf_fwd: ArrayArg<R>,
conf_bwd: ArrayArg<R>,
step: u32,
blocks_x: u32,
blocks_y: u32,
}
impl<R: Runtime> NlmDenoiser<R> {
fn input_arg(&self, ctx: &LaunchCtx) -> ArrayArg<R> {
unsafe { ArrayArg::from_raw_parts(self.input_buf.clone(), ctx.total_frame_data) }
}
fn reference_arg(&self, ctx: &LaunchCtx) -> ArrayArg<R> {
let buf = self
.reference_buf
.as_ref()
.expect("reference buffer must exist when use_reference is set");
unsafe { ArrayArg::from_raw_parts(buf.clone(), ctx.total_frame_data) }
}
fn input_arg_for_temporal(&self, ctx: &LaunchCtx) -> ArrayArg<R> {
match self.compensated_input_buf.as_ref() {
Some(buf) => unsafe { ArrayArg::from_raw_parts(buf.clone(), ctx.total_frame_data) },
None => self.input_arg(ctx),
}
}
fn reference_arg_for_temporal(&self, ctx: &LaunchCtx) -> ArrayArg<R> {
match self.compensated_reference_buf.as_ref() {
Some(buf) => unsafe { ArrayArg::from_raw_parts(buf.clone(), ctx.total_frame_data) },
None => self.reference_arg(ctx),
}
}
fn accum_arg(&self, ctx: &LaunchCtx) -> ArrayArg<R> {
unsafe { ArrayArg::from_raw_parts(self.accum.clone(), ctx.frame_size) }
}
fn output_arg(&self, ctx: &LaunchCtx, slot: usize) -> ArrayArg<R> {
unsafe { ArrayArg::from_raw_parts(self.outputs[slot].clone(), ctx.frame_size) }
}
fn reference_ring_arg(&self, ctx: &LaunchCtx) -> ArrayArg<R> {
let buf = self
.reference_buf
.as_ref()
.expect("reference buffer must exist for the nlm spatial pilot");
unsafe { ArrayArg::from_raw_parts(buf.clone(), ctx.total_frame_data) }
}
fn weight_sum_arg(&self, ctx: &LaunchCtx) -> ArrayArg<R> {
unsafe { ArrayArg::from_raw_parts(self.weight_sum.clone(), ctx.pixels) }
}
fn max_weight_arg(&self, ctx: &LaunchCtx) -> ArrayArg<R> {
unsafe { ArrayArg::from_raw_parts(self.max_weight.clone(), ctx.pixels) }
}
fn weight_buf_arg(&self, ctx: &LaunchCtx) -> ArrayArg<R> {
unsafe { ArrayArg::from_raw_parts(self.weight_buf.clone(), ctx.pixels) }
}
fn raw_fwd_arg(&self, ctx: &LaunchCtx) -> ArrayArg<R> {
unsafe { ArrayArg::from_raw_parts(self.raw_fwd.clone(), ctx.pixels) }
}
fn raw_bwd_arg(&self, ctx: &LaunchCtx) -> ArrayArg<R> {
unsafe { ArrayArg::from_raw_parts(self.raw_bwd.clone(), ctx.pixels) }
}
fn tmp_hsum_arg(&self, ctx: &LaunchCtx) -> ArrayArg<R> {
unsafe { ArrayArg::from_raw_parts(self.tmp_hsum.clone(), ctx.pixels) }
}
fn tmp_hsum_bwd_arg(&self, ctx: &LaunchCtx) -> ArrayArg<R> {
unsafe { ArrayArg::from_raw_parts(self.tmp_hsum_bwd.clone(), ctx.pixels) }
}
fn spatial_offset_lut_arg(&self) -> ArrayArg<R> {
let len = spatial_offset_lut_len(self.params.search_radius);
unsafe { ArrayArg::from_raw_parts(self.spatial_offset_lut.clone(), len) }
}
fn confidence_pair_args(&self, q_k: i32) -> ConfidenceArgs<R> {
let geometry = self.mc_ctx.as_ref().or(self.confidence_ctx.as_ref());
if let (Some(buf), Some(mc)) = (self.confidence_buf.as_ref(), geometry) {
let radius = self.params.temporal_radius;
let fwd_idx = neighbour_idx_for_k(radius, q_k);
let bwd_idx = neighbour_idx_for_k(radius, -q_k);
let conf_len = (mc.blocks_x * mc.blocks_y) as usize;
let fwd_handle = buf.clone().offset_start(confidence_byte_offset(mc, fwd_idx));
let bwd_handle = buf.clone().offset_start(confidence_byte_offset(mc, bwd_idx));
ConfidenceArgs {
use_confidence: true,
conf_fwd: unsafe { ArrayArg::from_raw_parts(fwd_handle, conf_len) },
conf_bwd: unsafe { ArrayArg::from_raw_parts(bwd_handle, conf_len) },
step: mc.step,
blocks_x: mc.blocks_x,
blocks_y: mc.blocks_y,
}
} else {
ConfidenceArgs {
use_confidence: false,
conf_fwd: unsafe { ArrayArg::from_raw_parts(self.confidence_dummy.clone(), 1) },
conf_bwd: unsafe { ArrayArg::from_raw_parts(self.confidence_dummy.clone(), 1) },
step: 1,
blocks_x: 1,
blocks_y: 1,
}
}
}
fn dispatch_fused_window_iter(
&self,
ctx: &LaunchCtx,
center_t: u32,
q_k: i32,
) -> Result<(), anyhow::Error> {
let channels = self.params.channels.count();
let _stored = self.params.channels.storage_count();
let frame_t = self.phys_frame(center_t as i32);
let frame_fwd = self.phys_frame(center_t as i32 + q_k);
let frame_bwd = self.phys_frame(center_t as i32 - q_k);
let confidence = self.confidence_pair_args(q_k);
if self.use_reference {
unsafe {
nlm_fused_pair_accumulate_window_ref::launch_unchecked::<R>(
&self.client,
ctx.cube_count.clone(),
ctx.cube_dim,
self.params.channels.storage_count() as usize,
self.input_arg_for_temporal(ctx),
self.reference_arg_for_temporal(ctx),
self.accum_arg(ctx),
self.weight_sum_arg(ctx),
self.max_weight_arg(ctx),
confidence.conf_fwd,
confidence.conf_bwd,
confidence.use_confidence,
frame_t,
frame_fwd,
frame_bwd,
self.h2_inv_norm,
self.noise_offset,
self.width,
self.height,
channels,
self.params.patch_radius,
self.params.search_radius,
BLOCK_X,
BLOCK_Y,
confidence.step,
confidence.blocks_x,
confidence.blocks_y,
);
}
} else {
unsafe {
nlm_fused_pair_accumulate_window::launch_unchecked::<R>(
&self.client,
ctx.cube_count.clone(),
ctx.cube_dim,
self.params.channels.storage_count() as usize,
self.input_arg_for_temporal(ctx),
self.accum_arg(ctx),
self.weight_sum_arg(ctx),
self.max_weight_arg(ctx),
confidence.conf_fwd,
confidence.conf_bwd,
confidence.use_confidence,
frame_t,
frame_fwd,
frame_bwd,
self.h2_inv_norm,
self.noise_offset,
self.width,
self.height,
channels,
self.params.patch_radius,
self.params.search_radius,
BLOCK_X,
BLOCK_Y,
confidence.step,
confidence.blocks_x,
confidence.blocks_y,
);
}
}
Ok(())
}
fn dispatch_fused_single_window_iter(&self, ctx: &LaunchCtx, center_t: u32) -> Result<(), anyhow::Error> {
let channels = self.params.channels.count();
let _stored = self.params.channels.storage_count();
let frame_t = self.phys_frame(center_t as i32);
if self.use_reference {
unsafe {
nlm_fused_single_window_ref::launch_unchecked::<R>(
&self.client,
ctx.cube_count.clone(),
ctx.cube_dim,
self.params.channels.storage_count() as usize,
self.input_arg(ctx),
self.reference_arg(ctx),
self.accum_arg(ctx),
self.weight_sum_arg(ctx),
self.max_weight_arg(ctx),
frame_t,
self.h2_inv_norm,
self.spatial_offset_lut_arg(),
self.width,
self.height,
channels,
self.params.patch_radius,
self.params.search_radius,
BLOCK_X,
BLOCK_Y,
);
}
} else {
unsafe {
nlm_fused_single_window::launch_unchecked::<R>(
&self.client,
ctx.cube_count.clone(),
ctx.cube_dim,
self.params.channels.storage_count() as usize,
self.input_arg(ctx),
self.accum_arg(ctx),
self.weight_sum_arg(ctx),
self.max_weight_arg(ctx),
frame_t,
self.h2_inv_norm,
self.spatial_offset_lut_arg(),
self.width,
self.height,
channels,
self.params.patch_radius,
self.params.search_radius,
BLOCK_X,
BLOCK_Y,
);
}
}
Ok(())
}
fn dispatch_separable_iter(
&self,
ctx: &LaunchCtx,
center_t: u32,
q_x: i32,
q_y: i32,
q_k: i32,
) -> Result<(), anyhow::Error> {
let channels = self.params.channels.count();
let frame_t = self.phys_frame(center_t as i32);
let frame_fwd = self.phys_frame(center_t as i32 + q_k);
let frame_bwd = self.phys_frame(center_t as i32 - q_k);
if self.use_reference {
unsafe {
nlm_distance_pair_ref::launch_unchecked::<R>(
&self.client,
ctx.cube_count.clone(),
ctx.cube_dim,
self.params.channels.storage_count() as usize,
self.reference_arg_for_temporal(ctx),
self.raw_fwd_arg(ctx),
self.raw_bwd_arg(ctx),
frame_t,
frame_fwd,
frame_bwd,
q_x,
q_y,
self.width,
self.height,
channels,
);
}
} else {
unsafe {
nlm_distance_pair::launch_unchecked::<R>(
&self.client,
ctx.cube_count.clone(),
ctx.cube_dim,
self.params.channels.storage_count() as usize,
self.input_arg_for_temporal(ctx),
self.raw_fwd_arg(ctx),
self.raw_bwd_arg(ctx),
frame_t,
frame_fwd,
frame_bwd,
q_x,
q_y,
self.width,
self.height,
channels,
);
}
}
unsafe {
nlm_horizontal_sum_pair::launch_unchecked::<R>(
&self.client,
ctx.cube_count.clone(),
ctx.cube_dim,
self.raw_fwd_arg(ctx),
self.raw_bwd_arg(ctx),
self.tmp_hsum_arg(ctx),
self.tmp_hsum_bwd_arg(ctx),
self.width,
self.height,
self.params.patch_radius,
BLOCK_X,
BLOCK_Y,
);
}
let confidence = self.confidence_pair_args(q_k);
unsafe {
nlm_vweight_pair_accumulate::launch_unchecked::<R>(
&self.client,
ctx.cube_count.clone(),
ctx.cube_dim,
self.params.channels.storage_count() as usize,
self.tmp_hsum_arg(ctx),
self.tmp_hsum_bwd_arg(ctx),
self.input_arg_for_temporal(ctx),
self.accum_arg(ctx),
self.weight_sum_arg(ctx),
self.max_weight_arg(ctx),
confidence.conf_fwd,
confidence.conf_bwd,
confidence.use_confidence,
frame_fwd,
frame_bwd,
q_x,
q_y,
self.h2_inv_norm,
self.noise_offset,
self.width,
self.height,
self.params.patch_radius,
BLOCK_X,
BLOCK_Y,
confidence.step,
confidence.blocks_x,
confidence.blocks_y,
);
}
Ok(())
}
fn dispatch_separable_iter_k0(
&self,
ctx: &LaunchCtx,
center_t: u32,
q_x: i32,
q_y: i32,
) -> Result<(), anyhow::Error> {
let channels = self.params.channels.count();
let frame_t = self.phys_frame(center_t as i32);
if self.use_reference {
unsafe {
nlm_distance_ref::launch_unchecked::<R>(
&self.client,
ctx.cube_count.clone(),
ctx.cube_dim,
self.params.channels.storage_count() as usize,
self.reference_arg(ctx),
self.raw_fwd_arg(ctx),
frame_t,
frame_t,
q_x,
q_y,
self.width,
self.height,
channels,
);
}
} else {
unsafe {
nlm_distance::launch_unchecked::<R>(
&self.client,
ctx.cube_count.clone(),
ctx.cube_dim,
self.params.channels.storage_count() as usize,
self.input_arg(ctx),
self.raw_fwd_arg(ctx),
frame_t,
frame_t,
q_x,
q_y,
self.width,
self.height,
channels,
);
}
}
unsafe {
nlm_horizontal_sum::launch_unchecked::<R>(
&self.client,
ctx.cube_count.clone(),
ctx.cube_dim,
self.raw_fwd_arg(ctx),
self.tmp_hsum_arg(ctx),
self.width,
self.height,
self.params.patch_radius,
BLOCK_X,
BLOCK_Y,
);
}
let offset = self.noise_offset * spatial_offset_factor(q_x, q_y, self.rho_smoothed.unwrap_or(0.0));
unsafe {
nlm_vertical_weight::launch_unchecked::<R>(
&self.client,
ctx.cube_count.clone(),
ctx.cube_dim,
self.tmp_hsum_arg(ctx),
self.weight_buf_arg(ctx),
self.h2_inv_norm,
offset,
self.width,
self.height,
self.params.patch_radius,
BLOCK_X,
BLOCK_Y,
);
}
unsafe {
nlm_accumulate::launch_unchecked::<R>(
&self.client,
ctx.thin_cube_count.clone(),
ctx.thin_cube_dim,
self.params.channels.storage_count() as usize,
self.input_arg(ctx),
self.accum_arg(ctx),
self.weight_sum_arg(ctx),
self.weight_buf_arg(ctx),
self.weight_buf_arg(ctx),
self.max_weight_arg(ctx),
frame_t,
frame_t,
q_x,
q_y,
self.width,
self.height,
);
}
Ok(())
}
fn run_motion_compensation(&self, center_t: u32) -> Result<(), anyhow::Error> {
let Some(mc) = self.mc_ctx.as_ref() else {
return Ok(());
};
let temporal_radius = self.params.temporal_radius;
if temporal_radius == 0 {
return Ok(());
}
let frame_count = self.params.total_frames();
let centre_slot = self.phys_frame(center_t as i32);
let stored_ch = self.params.channels.storage_count();
let pyramid_input = self
.pyramid_input
.as_ref()
.expect("pyramid_input allocated when mc_ctx is Some");
let mv_field = self
.mv_field_buf
.as_ref()
.expect("mv_field allocated when mc_ctx is Some");
let compensated_input = self
.compensated_input_buf
.as_ref()
.expect("compensated_input allocated when mc_ctx is Some");
let (confidence_arg, write_confidence): (&Handle, bool) = match self.confidence_buf.as_ref() {
Some(buf) => (buf, true),
None => (&self.confidence_dummy, false),
};
let thsad_scale = self.params.hq.map_or(1.0, |hq| hq.thsad_scale);
let mc_sigma_y = mc_sad_noise_floor_sigma(self.params.prefilter, self.sigma_y);
let sad_noise_floor = motion::sad_noise_floor(mc.blksize, mc_sigma_y);
let thsad = motion::thsad(mc.blksize, thsad_scale);
copy_frame_into_slot_handle::<R>(
&self.client,
&self.input_buf,
compensated_input,
centre_slot as usize,
self.params.total_frames(),
self.width,
self.height,
stored_ch,
);
if let (Some(ref_src), Some(ref_dst)) = (
self.reference_buf.as_ref(),
self.compensated_reference_buf.as_ref(),
) {
copy_frame_into_slot_handle::<R>(
&self.client,
ref_src,
ref_dst,
centre_slot as usize,
self.params.total_frames(),
self.width,
self.height,
stored_ch,
);
}
let analyse_pyramid = self.pyramid_reference.as_ref().unwrap_or(pyramid_input);
let is_chained = self.is_chained();
let radius = temporal_radius as i32;
let mut neighbour_idx: u32 = 0;
for k in -radius..=radius {
if k == 0 {
continue;
}
let neighbour_slot = self.phys_frame(center_t as i32 + k);
if is_chained {
self.run_chain_compose(center_t, k)?;
let refine_radius = match self
.params
.motion_compensation
.resolved_estimation(self.params.temporal_radius)
{
Some(MotionEstimation::Chained { refine_radius }) => refine_radius,
_ => unreachable!("is_chained() guarantees a resolved Chained estimation"),
};
run_seeded_refine::<R>(
&self.client,
mc,
self.width,
self.height,
frame_count,
centre_slot,
neighbour_slot,
neighbour_idx,
refine_radius,
analyse_pyramid,
mv_field,
confidence_arg,
write_confidence,
sad_noise_floor,
thsad,
)?;
} else {
run_analyse::<R>(
&self.client,
mc,
self.width,
self.height,
frame_count,
centre_slot,
neighbour_slot,
neighbour_idx,
analyse_pyramid,
mv_field,
confidence_arg,
write_confidence,
sad_noise_floor,
thsad,
)?;
}
run_compensate::<R>(
&self.client,
mc,
self.params.channels.count(),
stored_ch,
self.width,
self.height,
frame_count,
neighbour_slot,
neighbour_idx,
&self.input_buf,
compensated_input,
mv_field,
)?;
if let (Some(ref_src), Some(ref_dst)) = (
self.reference_buf.as_ref(),
self.compensated_reference_buf.as_ref(),
) {
run_compensate::<R>(
&self.client,
mc,
self.params.channels.count(),
stored_ch,
self.width,
self.height,
frame_count,
neighbour_slot,
neighbour_idx,
ref_src,
ref_dst,
mv_field,
)?;
}
neighbour_idx += 1;
}
Ok(())
}
fn run_confidence_pass(&self, center_t: u32) -> Result<(), anyhow::Error> {
let Some(ctx) = self.confidence_ctx.as_ref() else {
return Ok(());
};
let temporal_radius = self.params.temporal_radius;
let frame_count = self.params.total_frames();
let centre_slot = self.phys_frame(center_t as i32);
let luma_pyramid = self
.confidence_pyramid
.as_ref()
.expect("confidence_pyramid allocated when confidence_ctx is Some");
let mv_scratch = self
.confidence_mv_scratch
.as_ref()
.expect("confidence_mv_scratch allocated when confidence_ctx is Some");
let confidence_buf = self
.confidence_buf
.as_ref()
.expect("confidence_buf allocated when confidence_ctx is Some");
let thsad_scale = self.params.hq.map_or(1.0, |hq| hq.thsad_scale);
let sad_noise_floor = motion::sad_noise_floor(ctx.blksize, self.sigma_y);
let thsad = motion::thsad(ctx.blksize, thsad_scale);
let radius = temporal_radius as i32;
let mut neighbour_idx: u32 = 0;
for k in -radius..=radius {
if k == 0 {
continue;
}
let neighbour_slot = self.phys_frame(center_t as i32 + k);
run_confidence_for_neighbour::<R>(
&self.client,
ctx,
self.width,
self.height,
frame_count,
centre_slot,
neighbour_slot,
neighbour_idx,
luma_pyramid,
mv_scratch,
confidence_buf,
sad_noise_floor,
thsad,
)?;
neighbour_idx += 1;
}
Ok(())
}
fn zero_accumulators(&self, ctx: &LaunchCtx) -> Result<(), anyhow::Error> {
let grid = (ctx.frame_size as u32).div_ceil(BLOCK_1D).min(MAX_GRID_1D);
let total_threads = grid * BLOCK_1D;
unsafe {
gpu_zero_buffers::launch_unchecked::<R>(
&self.client,
CubeCount::new_1d(grid),
CubeDim::new_1d(BLOCK_1D),
ArrayArg::from_raw_parts(self.accum.clone(), ctx.frame_size),
self.weight_sum_arg(ctx),
self.max_weight_arg(ctx),
ctx.frame_size as u32,
ctx.pixels as u32,
total_threads,
);
}
Ok(())
}
fn run_finish_to(
&self,
ctx: &LaunchCtx,
center_frame: u32,
output_frame: u32,
output: ArrayArg<R>,
) -> Result<(), anyhow::Error> {
let channels = self.params.channels.count();
unsafe {
nlm_finish::launch_unchecked::<R>(
&self.client,
ctx.cube_count.clone(),
ctx.cube_dim,
self.params.channels.storage_count() as usize,
self.input_arg(ctx),
output,
ArrayArg::from_raw_parts(self.accum.clone(), ctx.frame_size),
self.weight_sum_arg(ctx),
self.max_weight_arg(ctx),
center_frame,
output_frame,
self.params.self_weight,
self.width,
self.height,
channels,
);
}
Ok(())
}
fn run_finish(&self, ctx: &LaunchCtx, center_t: u32, output_slot: usize) -> Result<(), anyhow::Error> {
self.run_finish_to(
ctx,
self.phys_frame(center_t as i32),
0,
self.output_arg(ctx, output_slot),
)
}
fn launch_ctx(&self) -> LaunchCtx {
let width = self.width;
let height = self.height;
let stored_ch = self.params.channels.storage_count();
let total_frames = self.params.total_frames();
let pixels = (width * height) as usize;
let frame_size = pixels * stored_ch as usize;
LaunchCtx {
total_frame_data: frame_size * total_frames as usize,
frame_size,
pixels,
cube_count: CubeCount::new_2d(width.div_ceil(BLOCK_X), height.div_ceil(BLOCK_Y)),
cube_dim: CubeDim::new_2d(BLOCK_X, BLOCK_Y),
thin_cube_count: CubeCount::new_2d(width.div_ceil(BLOCK_X_THIN), height.div_ceil(BLOCK_Y_THIN)),
thin_cube_dim: CubeDim::new_2d(BLOCK_X_THIN, BLOCK_Y_THIN),
}
}
pub(super) fn run_nlm_spatial_pilot(&self, slot: u32, strength_scale: f32) -> Result<(), anyhow::Error> {
let ctx = self.launch_ctx();
self.zero_accumulators(&ctx)?;
let channels = self.params.channels.count();
let pilot_h2 = self.h2_inv_norm / (strength_scale * strength_scale);
let pilot_lut = build_spatial_offset_lut(self.params.search_radius, 0.0, self.input_noise_offset);
let pilot_lut_handle = self.client.create_from_slice(f32::as_bytes(&pilot_lut));
let pilot_lut_arg = unsafe { ArrayArg::<R>::from_raw_parts(pilot_lut_handle, pilot_lut.len()) };
unsafe {
nlm_fused_single_window::launch_unchecked::<R>(
&self.client,
ctx.cube_count.clone(),
ctx.cube_dim,
self.params.channels.storage_count() as usize,
self.input_arg(&ctx),
self.accum_arg(&ctx),
self.weight_sum_arg(&ctx),
self.max_weight_arg(&ctx),
slot,
pilot_h2,
pilot_lut_arg,
self.width,
self.height,
channels,
self.params.patch_radius,
self.params.search_radius,
BLOCK_X,
BLOCK_Y,
);
}
self.run_finish_to(&ctx, slot, slot, self.reference_ring_arg(&ctx))
}
pub(super) fn run_denoise_kernels(&mut self, output_slot: usize) -> Result<(), anyhow::Error> {
let temporal_radius = self.params.temporal_radius;
let search_radius = self.params.search_radius as i32;
let ctx = self.launch_ctx();
let center_t = temporal_radius;
self.run_motion_compensation(center_t)?;
self.run_confidence_pass(center_t)?;
self.zero_accumulators(&ctx)?;
let window_side = 2 * search_radius + 1;
let window_area = window_side * window_side;
let k_start = -(temporal_radius as i32);
let use_windowed = !self.use_separable;
for q_k in k_start..=0 {
if use_windowed {
if q_k != 0 {
self.dispatch_fused_window_iter(&ctx, center_t, q_k)?;
} else {
self.dispatch_fused_single_window_iter(&ctx, center_t)?;
}
continue;
}
for q_y in -search_radius..=search_radius {
for q_x in -search_radius..=search_radius {
let linear = q_k * window_area + q_y * window_side + q_x;
if linear >= 0 {
continue;
}
if q_k == 0 {
self.dispatch_separable_iter_k0(&ctx, center_t, q_x, q_y)?;
} else {
self.dispatch_separable_iter(&ctx, center_t, q_x, q_y, q_k)?;
}
}
}
}
self.run_finish(&ctx, center_t, output_slot)?;
Ok(())
}
}
#[allow(clippy::too_many_arguments)]
fn copy_frame_into_slot_handle<R: Runtime>(
client: &ComputeClient<R>,
src: &Handle,
dst: &Handle,
slot: usize,
frame_count: u32,
width: u32,
height: u32,
stored_ch: u32,
) {
let frame_size = width * height * stored_ch;
let ring_len = frame_count as usize * frame_size as usize;
let offset = slot as u32 * frame_size;
let grid = frame_size.div_ceil(BLOCK_1D).min(MAX_GRID_1D);
let total_threads = grid * BLOCK_1D;
unsafe {
gpu_copy::launch_unchecked::<R>(
client,
CubeCount::new_1d(grid),
CubeDim::new_1d(BLOCK_1D),
ArrayArg::from_raw_parts(src.clone(), ring_len),
ArrayArg::from_raw_parts(dst.clone(), ring_len),
offset,
offset,
frame_size,
total_threads,
);
}
}
#[cfg(test)]
mod tests {
use super::{
BILATERAL_RESIDUAL_FRACTION,
NLM_SPATIAL_RESIDUAL_FRACTION,
PrefilterMode,
mc_sad_noise_floor_sigma,
neighbour_idx_for_k,
};
#[test]
fn matches_the_sequential_fill_order() {
for radius in 1..=8u32 {
let mut expected = 0u32;
for k in -(radius as i32)..=(radius as i32) {
if k == 0 {
continue;
}
assert_eq!(neighbour_idx_for_k(radius, k), expected, "radius={radius} k={k}");
expected += 1;
}
}
}
#[test]
fn forward_and_backward_indices_are_distinct_and_in_range() {
for radius in 1..=8u32 {
for q_k in -(radius as i32)..0 {
let fwd = neighbour_idx_for_k(radius, q_k);
let bwd = neighbour_idx_for_k(radius, -q_k);
assert_ne!(fwd, bwd, "radius={radius} q_k={q_k}");
assert!(fwd < 2 * radius, "radius={radius} q_k={q_k} fwd={fwd}");
assert!(bwd < 2 * radius, "radius={radius} q_k={q_k} bwd={bwd}");
}
}
}
#[test]
fn radius_two_explicit_indices() {
assert_eq!(neighbour_idx_for_k(2, -2), 0);
assert_eq!(neighbour_idx_for_k(2, -1), 1);
assert_eq!(neighbour_idx_for_k(2, 1), 2);
assert_eq!(neighbour_idx_for_k(2, 2), 3);
}
#[test]
fn nlm_spatial_residual_fraction_is_calibrated_to_zero() {
assert_eq!(NLM_SPATIAL_RESIDUAL_FRACTION, 0.0);
}
#[test]
fn bilateral_residual_fraction_is_calibrated_to_zero() {
assert_eq!(BILATERAL_RESIDUAL_FRACTION, 0.0);
}
#[test]
fn mc_sad_noise_floor_sigma_scales_nlm_spatial_by_the_calibrated_fraction() {
let raw = 0.02f32;
assert_eq!(
mc_sad_noise_floor_sigma(PrefilterMode::NlmSpatial { strength_scale: 1.0 }, raw),
raw * NLM_SPATIAL_RESIDUAL_FRACTION
);
}
#[test]
fn mc_sad_noise_floor_sigma_scales_bilateral_by_the_calibrated_fraction() {
let raw = 0.02f32;
assert_eq!(
mc_sad_noise_floor_sigma(
PrefilterMode::Bilateral {
sigma_s: 3.0,
sigma_r: 0.02
},
raw
),
raw * BILATERAL_RESIDUAL_FRACTION
);
}
#[test]
fn mc_sad_noise_floor_sigma_keeps_raw_sigma_for_none_and_external() {
let raw = 0.02f32;
assert_eq!(mc_sad_noise_floor_sigma(PrefilterMode::None, raw), raw);
assert_eq!(mc_sad_noise_floor_sigma(PrefilterMode::External, raw), raw);
}
}