fast-canny 0.1.0

Industrial-grade Zero-Allocation SIMD Canny Edge Detector
Documentation
use crate::kernel::SobelKernel;
use crate::nms::nms_and_threshold_slice;
use crate::pipeline_ptr::{SendConstPtr, SendPtr};
use crate::workspace::CannyWorkspace;
use rayon::prelude::*;

const TILE_SIZE: usize = 64;

/// L1 Cache 友好的多核 Tiling 流水线。
pub fn execute_tiled_pipeline(
    src:        &[f32],
    workspace:  &mut CannyWorkspace,
    low_thresh: f32,
    high_thresh: f32,
) {
    let w = workspace.width;
    let h = workspace.height;

    // 将各缓冲区裸指针包装为 Send+Sync 的包装类型,使闭包可跨线程捕获。
    // SAFETY:
    // - src 只读,生命周期覆盖整个 par_iter
    // - mag/dir/edge 各自指向独立缓冲区,Tile 按行分区,不同 ty 写入不同行,无数据竞争
    let src_sp  = SendConstPtr::new(src.as_ptr());
    let mag_sp  = SendPtr::new(workspace.buffer_a.as_mut_ptr());
    let dir_sp  = SendPtr::new(workspace.dir_map.as_mut_ptr());
    let edge_sp = SendPtr::new(workspace.edge_map.as_mut_ptr());

    // 将 kernel fat pointer 拆为两个 usize,规避生命周期推断导致的 E0521。
    // SAFETY: kernel 生命周期与 workspace 相同,闭包执行期间 workspace 不被 drop
    let kernel_ref = workspace.kernel();
    let kernel_parts: [usize; 2] = unsafe {
        std::mem::transmute(kernel_ref as *const dyn SobelKernel)
    };

    let num_tiles_x = (w + TILE_SIZE - 1) / TILE_SIZE;
    let num_tiles_y = (h + TILE_SIZE - 1) / TILE_SIZE;

    (0..num_tiles_y).into_par_iter().for_each(move |ty| {
        // SAFETY: 还原 kernel fat pointer,原始对象在 workspace 中,生命周期有效
        let kernel: &dyn SobelKernel = unsafe {
            let ptr: *const dyn SobelKernel = std::mem::transmute(kernel_parts);
            &*ptr
        };

        let start_y = (ty * TILE_SIZE).max(1);
        let end_y   = ((ty + 1) * TILE_SIZE).min(h - 1);

        for tx in 0..num_tiles_x {
            let start_x = (tx * TILE_SIZE).max(1);
            let end_x   = ((tx + 1) * TILE_SIZE).min(w - 1);

            // ── 步骤 1:Sobel ──────────────────────────────────────
            // SAFETY:
            // - 从 SendConstPtr/SendPtr 取出裸指针后立即重建为切片
            // - 不同 ty 的行范围不重叠,无写-写竞争
            // - src 只读,无读-写竞争
            let src_slice  = unsafe { std::slice::from_raw_parts(src_sp.get(), w * h) };
            let mag_slice  = unsafe { std::slice::from_raw_parts_mut(mag_sp.get(), w * h) };
            let dir_slice  = unsafe { std::slice::from_raw_parts_mut(dir_sp.get(), w * h) };

            for y in start_y..end_y {
                kernel.process_row_slice(
                    src_slice, mag_slice, dir_slice,
                    w, start_x, end_x, y,
                );
            }

            // ── 步骤 2:NMS + 双阈值 ──────────────────────────────
            let nms_start_y = start_y.max(2);
            let nms_end_y   = end_y.min(h - 2);
            let nms_start_x = start_x.max(2);
            let nms_end_x   = end_x.min(w - 2);

            // SAFETY: mag/dir 只读,edge 独立写入,dir_map 与 edge_map 是不同缓冲区
            let mag_slice  = unsafe { std::slice::from_raw_parts(mag_sp.get(), w * h) };
            let dir_slice  = unsafe { std::slice::from_raw_parts(dir_sp.get(), w * h) };
            let edge_slice = unsafe { std::slice::from_raw_parts_mut(edge_sp.get(), w * h) };

            for y in nms_start_y..nms_end_y {
                nms_and_threshold_slice(
                    mag_slice, dir_slice, edge_slice,
                    w, nms_start_x, nms_end_x, y,
                    low_thresh, high_thresh,
                );
            }
        }
    });
}