Files
rustyJ/crates/ri-metal/shaders/bilateral.metal
T
Omar SobhandClaude Opus 4.6 0fd39d7fd3 Add M9-M14: UI redesign, plugins, macros, 3D viz, binary/segmentation ops, Metal GPU, scientific LUTs
M9: TailwindCSS + shadcn/ui frontend redesign
M10: Dynamic plugin system with shared library loading
M11: Macro recording, playback, and batch processing
M12: 3D volume visualization, marching cubes, STL/OBJ export, rivol:// protocol
M13: Additional GPU shaders, enhanced auto-threshold, merge channels
M14: Binary image processing (EDT, watershed, skeleton, connected components,
     voronoi), segmentation & analysis (particles, colocalization, find maxima),
     math/noise/filter/transform ops (60+ total), scientific LUTs (Fire, Ice,
     Spectrum, Jet, Phase, HiLo + .lut file I/O), Apple Metal GPU optimization
     (metal-only feature, Apple Silicon detection, lower dispatch threshold),
     native Metal compute crate (ri-metal with MSL shaders via objc2-metal),
     7 new WGSL GPU shaders (bilateral, variance, mean, math_ops, outline,
     minmax_filter, affine transform). 25 crates, 162 tests, 75+ IPC commands.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-10 07:41:56 -07:00

78 lines
2.9 KiB
Metal

#include <metal_stdlib>
using namespace metal;
/// Bilateral filter with shared memory tiling.
/// Spatial Gaussian * range Gaussian weighting for edge-preserving smoothing.
struct BilateralParams {
uint width;
uint height;
float sigma_space;
float sigma_range;
};
// Tile size for shared memory caching
constant uint TILE_SIZE = 16;
// Maximum filter radius (determined by sigma_space, capped for shared memory)
constant uint MAX_RADIUS = 8;
// Shared memory tile dimension includes apron
constant uint SHARED_DIM = TILE_SIZE + 2 * MAX_RADIUS;
kernel void bilateral_filter(
device const float* input [[buffer(0)]],
device float* output [[buffer(1)]],
constant BilateralParams& params [[buffer(2)]],
threadgroup float* tile [[threadgroup(0)]],
uint2 gid [[thread_position_in_grid]],
uint2 tid [[thread_position_in_threadgroup]],
uint2 tg_pos [[threadgroup_position_in_grid]],
uint2 tg_size [[threads_per_threadgroup]]
) {
uint w = params.width;
uint h = params.height;
int radius = (int)clamp((int)ceil(params.sigma_space * 3.0f), 1, (int)MAX_RADIUS);
float inv_2sigma_s2 = -0.5f / (params.sigma_space * params.sigma_space);
float inv_2sigma_r2 = -0.5f / (params.sigma_range * params.sigma_range);
// Load tile with apron into shared memory
int tile_origin_x = (int)(tg_pos.x * TILE_SIZE) - radius;
int tile_origin_y = (int)(tg_pos.y * TILE_SIZE) - radius;
uint shared_dim = TILE_SIZE + 2 * (uint)radius;
uint total_shared = shared_dim * shared_dim;
uint linear_tid = tid.y * tg_size.x + tid.x;
uint threads_in_group = tg_size.x * tg_size.y;
for (uint i = linear_tid; i < total_shared; i += threads_in_group) {
int sy = tile_origin_y + (int)(i / shared_dim);
int sx = tile_origin_x + (int)(i % shared_dim);
sx = clamp(sx, 0, (int)w - 1);
sy = clamp(sy, 0, (int)h - 1);
tile[i] = input[(uint)sy * w + (uint)sx];
}
threadgroup_barrier(mem_flags::mem_threadgroup);
if (gid.x >= w || gid.y >= h) return;
float center = tile[(tid.y + (uint)radius) * shared_dim + (tid.x + (uint)radius)];
float weight_sum = 0.0f;
float value_sum = 0.0f;
for (int dy = -radius; dy <= radius; dy++) {
for (int dx = -radius; dx <= radius; dx++) {
uint sy = (uint)((int)tid.y + radius + dy);
uint sx = (uint)((int)tid.x + radius + dx);
float neighbor = tile[sy * shared_dim + sx];
float spatial_dist = (float)(dx * dx + dy * dy);
float range_dist = (neighbor - center) * (neighbor - center);
float weight = exp(spatial_dist * inv_2sigma_s2 + range_dist * inv_2sigma_r2);
weight_sum += weight;
value_sum += weight * neighbor;
}
}
output[gid.y * w + gid.x] = value_sum / weight_sum;
}