oidn-wgpu 2.4.1

Open Image Denoise (OIDN) integration for wgpu — denoise path-traced or ray-traced images from wgpu textures
Documentation
# oidn-wgpu

[Intel Open Image Denoise](https://www.openimagedenoise.org) (OIDN) integration for [wgpu](https://docs.rs/wgpu). Denoise path-traced or ray-traced images produced on the GPU by copying to CPU, running OIDN, and copying back.

- **OIDN 2.4.x** — uses the latest API (quality modes, etc.).
- **GPU support** — `OidnDevice::cuda()`, `sycl()`, `hip()`, `metal()` for GPU backends (when OIDN is built with them).
- **wgpu 27** — compatible with wgpu 27.

This crate is generic and can be used from any Rust project using wgpu (e.g. game engines, renderers, tools).

## Setup

You need a built **OIDN 2.4.x** library (e.g. from [OpenImageDenoise/oidn](https://github.com/OpenImageDenoise/oidn)).

1. **Option A — `OIDN_DIR`**  
   Set the environment variable to your OIDN **install** directory (containing `include/` and `lib/`), or to the build output directory that contains `OpenImageDenoise.lib` (Windows) / `libOpenImageDenoise.a` or `.so` (Unix).

   ```bash
   # Windows (PowerShell) — set OIDN_DIR then build in the same session
   $env:OIDN_DIR = "C:\path\to\oidn\build"   # or install dir; must contain OpenImageDenoise.lib
   cargo build

   # Linux / macOS
   export OIDN_DIR=/path/to/oidn/install
   ```

2. **Option B — pkg-config**  
   Install OIDN so that `pkg-config --libs OpenImageDenoise` works (common on Linux).

**Windows (dynamic build):** If OIDN was built as a DLL, ensure `OpenImageDenoise.dll` (and any device DLLs, e.g. `OpenImageDenoise_device_cuda.dll`) are on your `PATH` or next to your executable at run time.

Then add to your `Cargo.toml`:

```toml
[dependencies]
oidn-wgpu = "0.1"
```

## Usage

### Denoise a wgpu texture

Use when your path tracer writes to a `wgpu::Texture` (e.g. `Rgba16Float` or `Rgba32Float`). This will read back the texture to CPU, run OIDN, and upload the result.

```rust
use oidn_wgpu::{
    OidnDevice, denoise_texture,
    DenoiseTextureFormat, DenoiseOptions,
};

// One-time setup: create OIDN device (reuse across frames).
let oidn = OidnDevice::new()?;

// When you want to denoise a frame:
let format = DenoiseTextureFormat::Rgba16Float; // or Rgba32Float
denoise_texture(
    &oidn,
    &wgpu_device,
    &wgpu_queue,
    &noisy_texture,
    &output_texture,  // can be the same as input for in-place
    format,
    &DenoiseOptions {
        quality: oidn_wgpu::Quality::Balanced, // Fast | Balanced | High
        hdr: true,
        srgb: false,
        input_scale: None,  // Some(scale) for HDR exposure
    },
)?;
```

**Device types:** `OidnDevice::new()` (auto), `OidnDevice::cpu()`, `OidnDevice::cuda()`, `OidnDevice::sycl()`, `OidnDevice::hip()`, `OidnDevice::metal()`.

Supported texture formats: **`Rgba16Float`**, **`Rgba32Float`**. Alpha is preserved; only RGB is denoised.

### Denoise CPU buffers (no wgpu)

If you already have RGB float data (e.g. from a different backend):

```rust
use oidn_wgpu::{OidnDevice, RtFilter};

let device = OidnDevice::new()?;
let mut filter = RtFilter::new(&device)?;
filter
    .set_dimensions(width, height)
    .set_hdr(true)
    .set_quality(oidn_wgpu::Quality::High);
// color: &mut [f32] with length width * height * 3 (RGB)
filter.execute_in_place(&mut color_rgb_f32)?;
```

### Denoise with albedo and normal (wgpu textures)

For higher quality, pass optional albedo and normal textures (same size/format as color):

```rust
use oidn_wgpu::denoise_texture_with_aux;

denoise_texture_with_aux(
    &oidn,
    &wgpu_device,
    &wgpu_queue,
    &noisy_texture,
    &output_texture,
    format,
    &options,
    Some(&albedo_texture),  // None if not used
    Some(&normal_texture),
)?;
```

### Albedo and normal on CPU (RtFilter)

```rust
filter.execute_in_place_with_aux(&mut color, Some(&albedo[..]), Some(&normal[..]))?;
// or
filter.execute_with_aux(Some(&color), &mut output, Some(&albedo), Some(&normal))?;
```

### Full API (physical devices, buffers, generic filter)

- **Physical devices:** `num_physical_devices()`, `get_physical_device_bool/int/string/data()`, `is_cpu_device_supported()`, `is_cuda_device_supported()`, etc.
- **Device creation:** `OidnDevice::new_by_id()`, `new_by_uuid()`, `new_by_luid()`, `new_by_pci_address()`, `new_cuda_device()`, `new_hip_device()`, `new_metal_device()` (see docs for raw pointer/stream args). Device params: `set_bool()`, `set_int()`, `get_bool()`, `get_int()`, `commit()`, `set_error_function_raw()`.
- **Buffers:** `OidnBuffer::new()`, `new_with_storage()`, `new_shared()`, `new_shared_from_fd()`, `new_shared_from_win32_handle()`, `new_shared_from_metal()` (all return `Result<OidnBuffer, Error>`). Methods: `size()`, `storage()`, `data()`, `read()`/`write()`, `read_async()`/`write_async()`.
- **Generic filter:** `Filter::new(device, "RT")` or `"RTLightmap"` — then `set_image()` or `set_shared_image()`, `set_shared_data()`, `set_progress_monitor_raw()`, `commit()`, `execute()` or `execute_async()`. `RtFilter`/`RtLightmapFilter` also expose `get_bool`, `get_int`, `get_float`, `set_progress_monitor_raw`.

### Lightmap denoising (RTLightmap filter)

For baked lightmaps (requires OIDN built with RTLightmap support):

```rust
use oidn_wgpu::{OidnDevice, RtLightmapFilter};

let device = OidnDevice::new()?;
let mut filter = RtLightmapFilter::new(&device)?;
filter.set_dimensions(w, h).set_directional(false); // false = HDR, true = directional
filter.execute_in_place(&mut lightmap_rgb_f32)?;
```

## Tests and examples

```bash
# Set OIDN_DIR to your OIDN install or build directory (e.g. oidn/build/Release on Windows)
export OIDN_DIR=/path/to/oidn/install   # or on Windows: $env:OIDN_DIR = "C:\path\to\oidn\build\Release"

cargo test
cargo run --example cpu_denoise
cargo run --example wgpu_denoise
```

## Status

- **Implemented:** Full OIDN C API coverage: RT and RTLightmap filters (typed + generic `Filter`), all device creation paths, physical device queries, buffers (host/device/managed/shared/FD/Win32/Metal), async execute and buffer read/write, progress monitor, device/filter get/set parameters, error callback.
- **Limitations:** `OIDN_DIR` required on Windows (no pkg-config). RTLightmap requires OIDN built with `OIDN_FILTER_RTLIGHTMAP`. SYCL queue/device creation and `oidnExecuteSYCLFilterAsync` are C++-only in OIDN (no Rust binding).

### OIDN API coverage (complete)

| Area | API | oidn-wgpu |
|------|-----|-----------|
| **Physical device** | `oidnGetNumPhysicalDevices`, `oidnGetPhysicalDeviceBool/Int/String/Data` | `num_physical_devices()`, `get_physical_device_bool/int/string/data()` |
| **Device support** | `oidnIsCPUDeviceSupported`, `oidnIsCUDADeviceSupported`, `oidnIsHIPDeviceSupported`, `oidnIsMetalDeviceSupported` | `is_cpu_device_supported()`, `is_cuda_device_supported()`, etc. |
| **Device creation** | `oidnNewDevice`, `ByID`, `ByUUID`, `ByLUID`, `ByPCIAddress`, `oidnNewCUDADevice`, `oidnNewHIPDevice`, `oidnNewMetalDevice` | `OidnDevice::new()`, `new_by_id()`, `new_by_uuid()`, `new_by_luid()`, `new_by_pci_address()`, `new_cuda_device()`, `new_hip_device()`, `new_metal_device()` |
| **Device params** | `oidnSetDeviceBool/Int`, `oidnGetDeviceBool/Int`, `oidnCommitDevice`, `oidnSetDeviceErrorFunction`, `oidnSyncDevice` | `set_bool()`, `set_int()`, `get_bool()`, `get_int()`, `commit()`, `set_error_function_raw()`, `sync()` |
| **Buffer** | `oidnNewBuffer`, `oidnNewBufferWithStorage`, `oidnNewSharedBuffer`, `FromFD`, `FromWin32Handle`, `FromMetal`, get size/storage/data, read/write sync and async | `OidnBuffer::new()`, `new_with_storage()`, `new_shared()`, `new_shared_from_fd()`, `new_shared_from_win32_handle()`, `new_shared_from_metal()`, `size()`, `storage()`, `data()`, `read()`/`write()`, `read_async()`/`write_async()` |
| **Filter** | `oidnNewFilter`, set image (buffer or shared), unset image, set/get shared data, update data, unset data, set/get bool/int/float, progress monitor, commit, execute, execute async | `Filter::new()`, `set_image()`/`set_shared_image()`, `unset_image()`, `set_shared_data()`/`update_data()`/`unset_data()`, `set_*`/`get_*`, `set_progress_monitor_raw()`, `commit()`, `execute()`/`execute_async()`. Same on `RtFilter`/`RtLightmapFilter` where applicable. |

**Not bound (C++ only):** `oidnIsSYCLDeviceSupported`, `oidnNewSYCLDevice`, `oidnExecuteSYCLFilterAsync` (SYCL types). Use type-based `OidnDevice::sycl()` instead.

**Coverage audit (vs `oidn.h`):** Every C API function and type is either bound in `sys` and exposed via device/buffer/filter wrappers, or is C++-only (SYCL). Inline helpers in the header (`oidnGetDeviceUInt`, `oidnGetPhysicalDeviceUInt`, `oidnSetDeviceUInt`) are covered by `get_uint()`, `get_physical_device_int()` (cast to u32), and `set_int()` (pass `value as i32`). Refcounting: `oidnRetainDevice/Buffer/Filter` → `retain()` on each type; release in `Drop`. Global error: `take_global_error()`. Format enum: `OIDNFormat` (and alias `ImageFormat`) re-exported so variants (e.g. `OIDNFormat::Float3`) are constructible for `Filter::set_image()`.

**Symbol checklist (every `oidn.h` C symbol):**

| oidn.h symbol | Rust |
|---------------|------|
| `OIDN_UUID_SIZE`, `OIDN_LUID_SIZE` | `OIDN_UUID_SIZE`, `OIDN_LUID_SIZE` (lib) |
| `oidnGetNumPhysicalDevices` | `num_physical_devices()` |
| `oidnGetPhysicalDeviceBool`, `Int`, `String`, `Data` | `get_physical_device_bool/int/string/data()` |
| `oidnGetPhysicalDeviceUInt` (inline) | `get_physical_device_int()` → cast to u32 |
| `OIDNDeviceType` | `OidnDeviceType` |
| `OIDNError` | `sys::OIDNError` (used in `Error::OidnError`; code as u32) |
| `OIDNErrorFunction` | `set_error_function_raw()` |
| `oidnIsCPUDeviceSupported` | `is_cpu_device_supported()` |
| `oidnIsCUDADeviceSupported`, `IsHIPDeviceSupported`, `IsMetalDeviceSupported` | `is_cuda/hip/metal_device_supported()` |
| `oidnNewDevice`, `ByID`, `ByUUID`, `ByLUID`, `ByPCIAddress` | `OidnDevice::new()`, `new_by_id/uuid/luid/pci_address()` |
| `oidnNewCUDADevice`, `oidnNewHIPDevice`, `oidnNewMetalDevice` | `new_cuda_device()`, `new_hip_device()`, `new_metal_device()` |
| `oidnRetainDevice`, `oidnReleaseDevice` | `retain()`, `Drop` |
| `oidnSetDeviceBool`, `oidnSetDeviceInt`, `oidnGetDeviceBool`, `oidnGetDeviceInt` | `set_bool/int()`, `get_bool/int()` |
| `oidnGetDeviceUInt` (inline) | `get_uint()` |
| `oidnSetDeviceErrorFunction`, `oidnGetDeviceError` | `set_error_function_raw()`, `take_error()` / `take_global_error()` |
| `oidnCommitDevice`, `oidnSyncDevice` | `commit()`, `sync()` |
| `OIDNFormat` | `OIDNFormat` / `ImageFormat` (re-exported) |
| `OIDNStorage`, `OIDNExternalMemoryTypeFlag` | `BufferStorage`, `ExternalMemoryTypeFlag` |
| All `oidnNewBuffer*`, `oidnGetBufferSize/Storage/Data`, `oidnRead/WriteBuffer*`, `oidnRetain/ReleaseBuffer` | `OidnBuffer` methods |
| `OIDNQuality` | `Quality` |
| `OIDNProgressMonitorFunction` | `set_progress_monitor_raw()` |
| All `oidnNewFilter`, `oidnSet*FilterImage`, `oidnSetSharedFilterData`, `oidnUpdateFilterData`, `oidnUnset*`, `oidnSet/GetFilterBool/Int/Float`, `oidnSetFilterProgressMonitorFunction`, `oidnCommitFilter`, `oidnExecuteFilter*`, `oidnRetain/ReleaseFilter` | `Filter` (+ `RtFilter` / `RtLightmapFilter`) |
| `oidnIsSYCLDeviceSupported`, `oidnNewSYCLDevice`, `oidnExecuteSYCLFilterAsync` | **C++ only** — use `OidnDevice::sycl()` |

## Building OIDN from source

Clone the repo (requires [Git LFS](https://git-lfs.github.com/)):

```bash
git clone --recursive https://github.com/OpenImageDenoise/oidn.git
cd oidn
```

Then build with CMake (see [oidn documentation](https://github.com/OpenImageDenoise/oidn#compilation)). For a minimal CPU-only build you need CMake, a C++ compiler, and oneTBB. After building, set `OIDN_DIR` to the install prefix or the build directory where the library is produced.

## License

Licensed under either of **Apache-2.0** or **MIT** at your option.  
OIDN itself is under Apache-2.0; see [Intel OIDN](https://www.openimagedenoise.org).