├── .github └── workflows │ └── main.yml ├── .gitignore ├── Cargo.toml ├── LICENSE.md ├── README.md ├── build.rs ├── examples ├── buffer.rs ├── denoise_exr.rs └── simple.rs ├── rustfmt.toml ├── scripts ├── build-examples-linux-mac.sh ├── build-test-mac.sh ├── build-test-windows.ps1 └── generate-sys-bindings.sh └── src ├── buffer.rs ├── device.rs ├── filter.rs ├── lib.rs ├── sys.rs └── tests.rs /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | pull_request: 5 | schedule: 6 | - cron: "0 0 * * 1" 7 | env: 8 | CARGO_TERM_COLOR: always 9 | OIDN_VERSION: 2.3.2 10 | jobs: 11 | build_linux: 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | rust_channel: [stable, beta, nightly] 16 | steps: 17 | - uses: actions/checkout@v4 18 | - name: Set up Rust toolchain 19 | uses: actions-rs/toolchain@v1 20 | with: 21 | toolchain: ${{ matrix.rust_channel }} 22 | override: true 23 | components: rustfmt, clippy 24 | - name: Cache OIDN download (Linux) 25 | id: cache-oidn 26 | uses: actions/cache@v4 27 | with: 28 | path: oidn-${{ env.OIDN_VERSION }}.x86_64.linux.tar.gz 29 | key: ${{ runner.os }}-oidn-${{ env.OIDN_VERSION }}-x86_64 30 | - name: Download OIDN (Linux) 31 | if: steps.cache-oidn.outputs.cache-hit != 'true' 32 | run: wget https://github.com/OpenImageDenoise/oidn/releases/download/v${OIDN_VERSION}/oidn-${OIDN_VERSION}.x86_64.linux.tar.gz 33 | - run: tar -xf oidn-${OIDN_VERSION}.x86_64.linux.tar.gz 34 | - run: echo "OIDN_DIR=`pwd`/oidn-${OIDN_VERSION}.x86_64.linux" >> $GITHUB_ENV 35 | - run: echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${OIDN_DIR}/lib" >> $GITHUB_ENV 36 | - run: rustup update 37 | - run: cargo build --all --all-targets 38 | - run: cargo clippy --all --all-targets -- -D warnings 39 | - run: cargo test --all --all-targets 40 | - run: cargo doc --all --no-deps --document-private-items 41 | env: 42 | RUSTDOCFLAGS: -Dwarnings 43 | - name: Format Core 44 | run: cargo fmt -- --check 45 | build_mac: 46 | runs-on: macos-latest 47 | strategy: 48 | matrix: 49 | rust_channel: [stable, beta, nightly] 50 | steps: 51 | - uses: actions/checkout@v4 52 | - name: Set up Rust toolchain 53 | uses: actions-rs/toolchain@v1 54 | with: 55 | toolchain: ${{ matrix.rust_channel }} 56 | override: true 57 | components: rustfmt, clippy 58 | - name: Cache OIDN download (macOS) 59 | id: cache-oidn 60 | uses: actions/cache@v4 61 | with: 62 | path: oidn-${{ env.OIDN_VERSION }}.arm64.macos.tar.gz 63 | key: ${{ runner.os }}-oidn-${{ env.OIDN_VERSION }}-arm64 64 | - name: Download OIDN (macOS) 65 | if: steps.cache-oidn.outputs.cache-hit != 'true' 66 | run: wget https://github.com/OpenImageDenoise/oidn/releases/download/v${OIDN_VERSION}/oidn-${OIDN_VERSION}.arm64.macos.tar.gz 67 | - run: tar -xf oidn-${OIDN_VERSION}.arm64.macos.tar.gz 68 | - run: echo "OIDN_DIR=`pwd`/oidn-${OIDN_VERSION}.arm64.macos" >> $GITHUB_ENV 69 | - run: echo "DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:${OIDN_DIR}/lib" >> $GITHUB_ENV 70 | - run: cp $OIDN_DIR/lib/*dylib . 71 | - run: rustup update 72 | - run: cargo build --all --all-targets 73 | - run: cargo clippy --all --all-targets -- -D warnings 74 | build_windows: 75 | runs-on: windows-latest 76 | strategy: 77 | matrix: 78 | rust_channel: [stable, beta, nightly] 79 | steps: 80 | - uses: actions/checkout@v4 81 | - name: Set up Rust toolchain 82 | uses: actions-rs/toolchain@v1 83 | with: 84 | toolchain: ${{ matrix.rust_channel }} 85 | override: true 86 | components: rustfmt, clippy 87 | - name: Cache OIDN download (Windows) 88 | id: cache-oidn 89 | uses: actions/cache@v4 90 | with: 91 | path: oidn-${{ env.OIDN_VERSION }}.x64.windows.zip 92 | key: ${{ runner.os }}-oidn-${{ env.OIDN_VERSION }}-x64-windows 93 | - name: Install wget (Windows) 94 | if: steps.cache-oidn.outputs.cache-hit != 'true' 95 | run: choco install wget 96 | - name: Download OIDN (Windows) 97 | if: steps.cache-oidn.outputs.cache-hit != 'true' 98 | run: wget https://github.com/OpenImageDenoise/oidn/releases/download/v${env:OIDN_VERSION}/oidn-${env:OIDN_VERSION}.x64.windows.zip 99 | - run: 7z x oidn-${env:OIDN_VERSION}.x64.windows.zip -y 100 | - run: rustup update 101 | - run: | 102 | $env:OIDN_DIR="${env:GITHUB_WORKSPACE}/oidn-${env:OIDN_VERSION}.x64.windows/" 103 | cargo build --all --all-targets 104 | - run: | 105 | $env:OIDN_DIR="${env:GITHUB_WORKSPACE}/oidn-${env:OIDN_VERSION}.x64.windows/" 106 | cargo clippy --all --all-targets -- -D warnings 107 | 108 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | target 3 | *.ppm 4 | *.png 5 | *.bmp 6 | *.obj 7 | *.mtl 8 | *.binary 9 | *.jpg 10 | Cargo.lock 11 | *.so* 12 | *.dll 13 | *.out 14 | .vscode/ 15 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "oidn" 3 | version = "2.3.3" 4 | edition = "2024" 5 | authors = ["Will Usher "] 6 | build = "build.rs" 7 | homepage = "https://github.com/Twinklebear/oidn-rs" 8 | documentation = "https://docs.rs/oidn" 9 | repository = "https://github.com/Twinklebear/oidn-rs" 10 | readme = "README.md" 11 | license = "MIT" 12 | description = "A wrapper for the Intel OpenImageDenoise image denoising library." 13 | keywords = ["openimagedenoise", "denoise", "denoising"] 14 | exclude = [ 15 | ".travis.yml", 16 | "*.png", 17 | "*.jpg", 18 | ".gitignore", 19 | "examples/*", 20 | "scripts/*", 21 | ] 22 | links = "OpenImageDenoise" 23 | 24 | [dependencies] 25 | num_enum = "0.7.3" 26 | 27 | [build-dependencies] 28 | pkg-config = "0.3.31" 29 | 30 | [dev-dependencies] 31 | rand = "0.9.0" 32 | image = "0.25.5" 33 | exr = "0.9.0" 34 | docopt = "1.1.1" 35 | rayon = "1.10.0" 36 | serde = { version = "1.0.218", features = ["derive"] } 37 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Will Usher 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `oidn` 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/oidn.svg)](https://crates.io/crates/oidn) 4 | [![CI](https://github.com/Twinklebear/oidn-rs/actions/workflows/main.yml/badge.svg)](https://github.com/Twinklebear/oidn-rs/actions/workflows/main.yml) 5 | 6 | Rust bindings to Intel’s [Open Image Denoise library](https://github.com/OpenImageDenoise/oidn). 7 | Crate version numbers track the OIDN version they correspond to. 8 | 9 | ## Documentation 10 | 11 | Rust docs can be found [here](https://docs.rs/oidn). 12 | 13 | Open Image Denoise documentation can be found [here](https://openimagedenoise.github.io/documentation.html). 14 | 15 | ## Example 16 | 17 | The crate provides a lightweight wrapper over the Open Image Denoise library, 18 | along with raw C bindings exposed under `oidn::sys`. Below is an example of 19 | using the `RT` filter from Open Image Denoise (the `RayTracing` filter) to 20 | denoise an image. 21 | 22 | ```rust 23 | extern crate oidn; 24 | 25 | fn main() { 26 | // Load scene, render image, etc. 27 | 28 | let input_img: Vec = // A float3 RGB image produced by your renderer 29 | let mut filter_output = vec![0.0f32; input_img.len()]; 30 | 31 | let device = oidn::Device::new(); 32 | oidn::RayTracing::new(&device) 33 | // Optionally add float3 normal and albedo buffers as well 34 | .srgb(true) 35 | .image_dimensions(input.width() as usize, input.height() as usize); 36 | .filter(&input_img[..], &mut filter_output[..]) 37 | .expect("Filter config error!"); 38 | 39 | if let Err(e) = device.get_error() { 40 | println!("Error denosing image: {}", e.1); 41 | } 42 | 43 | // Save out or display filter_output image 44 | } 45 | ``` 46 | 47 | The [simple](examples/simple.rs) example loads a JPG, denoises it, and saves the 48 | output image to a JPG. The [denoise_exr](examples/denoise_exr.rs) example loads an 49 | HDR color EXR file, denoises it and saves the tonemapped result out to a JPG. 50 | The `denoise_exr` app can also take albedo and normal data through additional 51 | EXR files. 52 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::path::PathBuf; 3 | 4 | use pkg_config::Config; 5 | 6 | fn main() { 7 | if env::var("DOCS_RS").is_err() { 8 | if let Ok(dir) = env::var("OIDN_DIR") { 9 | let mut lib_path = PathBuf::from(dir); 10 | lib_path.push("lib"); 11 | println!("cargo:rustc-link-search=native={}", lib_path.display()); 12 | } else { 13 | Config::new().probe("OpenImageDenoise").unwrap_or_else(|e| { 14 | println!( 15 | "cargo:error=Could not find OpenImageDenoise via pkg-config: {}", 16 | e 17 | ); 18 | panic!("Failed to find OpenImageDenoise"); 19 | }); 20 | } 21 | println!("cargo:rerun-if-env-changed=OIDN_DIR"); 22 | println!("cargo:rustc-link-lib=OpenImageDenoise"); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/buffer.rs: -------------------------------------------------------------------------------- 1 | extern crate rand; 2 | 3 | use rand::Rng; 4 | 5 | const WIDTH: usize = 128; 6 | const HEIGHT: usize = 9; 7 | const BUFFER_LEN: usize = WIDTH * HEIGHT * 3; 8 | 9 | fn main() { 10 | let mut input = [0.0; BUFFER_LEN]; 11 | let mut rng = rand::rng(); 12 | for float in input.iter_mut() { 13 | let rand = rng.random(); 14 | *float = rand; 15 | } 16 | println!("randomized:"); 17 | for y in 0..HEIGHT { 18 | for x in 0..WIDTH { 19 | let idx = ((y * WIDTH) + x) * 3; 20 | let colour = &input[idx..idx + 3]; 21 | print!( 22 | "\x1b[38;2;{};{};{}m#\x1b[0m", 23 | (colour[0] * 255.0) as u8, 24 | (colour[1] * 255.0) as u8, 25 | (colour[2] * 255.0) as u8 26 | ) 27 | } 28 | println!(); 29 | } 30 | let device = oidn::Device::new(); 31 | let mut filter = oidn::filter::RayTracing::new(&device); 32 | let buffer = device.create_buffer(&input).unwrap(); 33 | let output_buffer = device.create_buffer(&[0.0; BUFFER_LEN]).unwrap(); 34 | filter 35 | .image_dimensions(WIDTH, HEIGHT) 36 | .filter_buffer(&buffer, &output_buffer) 37 | .unwrap(); 38 | let slice = output_buffer.read(); 39 | println!(); 40 | println!("denoised:"); 41 | for y in 0..HEIGHT { 42 | for x in 0..WIDTH { 43 | let idx = ((y * WIDTH) + x) * 3; 44 | let colour = &slice[idx..idx + 3]; 45 | print!( 46 | "\x1b[38;2;{};{};{}m#\x1b[0m", 47 | (colour[0] * 255.0) as u8, 48 | (colour[1] * 255.0) as u8, 49 | (colour[2] * 255.0) as u8 50 | ) 51 | } 52 | println!(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /examples/denoise_exr.rs: -------------------------------------------------------------------------------- 1 | extern crate docopt; 2 | extern crate exr; 3 | extern crate image; 4 | extern crate oidn; 5 | extern crate rayon; 6 | extern crate serde; 7 | 8 | use docopt::Docopt; 9 | use exr::prelude::rgba_image as rgb_exr; 10 | use rayon::prelude::*; 11 | use serde::Deserialize; 12 | use std::f32; 13 | 14 | /// An example application that shows opening an HDR EXR image with optional 15 | /// additional normal and albedo EXR images and denoising it with OIDN. 16 | /// The denoised image is then tonemaped and saved out as a JPG 17 | const USAGE: &str = " 18 | denoise_exr 19 | 20 | Usage: 21 | denoise_exr -c -o -e [-a ] 22 | denoise_exr -c -o -e [(-a -n )] 23 | 24 | Options: 25 | -c , --color Specify the input color image 26 | -o Specify the output file for the denoised and tonemapped JPG 27 | -e , --exposure Specify the exposure to apply to the image 28 | -a , --albedo Specify the albedo image 29 | -n , --normal Specify the normal image (requires albedo) 30 | "; 31 | 32 | #[derive(Debug, Deserialize)] 33 | struct Args { 34 | flag_c: String, 35 | flag_o: String, 36 | flag_e: f32, 37 | flag_n: Option, 38 | flag_a: Option, 39 | } 40 | 41 | fn linear_to_srgb(x: f32) -> f32 { 42 | if x <= 0.0031308 { 43 | 12.92 * x 44 | } else { 45 | 1.055 * f32::powf(x, 1.0 / 2.4) - 0.055 46 | } 47 | } 48 | 49 | fn tonemap_kernel(x: f32) -> f32 { 50 | let a = 0.22; 51 | let b = 0.30; 52 | let c = 0.10; 53 | let d = 0.20; 54 | let e = 0.01; 55 | let f = 0.30; 56 | ((x * (a * x + c * b) + d * e) / (x * (a * x + b) + d * f)) - e / f 57 | } 58 | 59 | fn tonemap(x: f32) -> f32 { 60 | let w = 11.2; 61 | let scale = 1.758141; 62 | tonemap_kernel(x * scale) / tonemap_kernel(w) 63 | } 64 | 65 | struct EXRData { 66 | img: Vec, 67 | width: usize, 68 | height: usize, 69 | } 70 | 71 | impl EXRData { 72 | fn new(width: usize, height: usize) -> EXRData { 73 | EXRData { 74 | img: vec![0f32; width * height * 3], 75 | width, 76 | height, 77 | } 78 | } 79 | fn set_pixel(&mut self, x: usize, y: usize, pixel: &rgb_exr::Pixel) { 80 | let i = (y * self.width + x) * 3; 81 | self.img[i] = pixel.red.to_f32(); 82 | self.img[i + 1] = pixel.green.to_f32(); 83 | self.img[i + 2] = pixel.blue.to_f32(); 84 | } 85 | } 86 | 87 | /// Load an EXR file to an RGB f32 buffer 88 | fn load_exr(file: &str) -> EXRData { 89 | let (_info, image) = rgb_exr::ImageInfo::read_pixels_from_file( 90 | file, 91 | rgb_exr::read_options::high(), 92 | |info: &rgb_exr::ImageInfo| -> EXRData { 93 | EXRData::new(info.resolution.width(), info.resolution.height()) 94 | }, 95 | // set each pixel in the png buffer from the exr file 96 | |image: &mut EXRData, pos: rgb_exr::Vec2, pixel: rgb_exr::Pixel| { 97 | image.set_pixel(pos.x(), pos.y(), &pixel); 98 | }, 99 | ) 100 | .unwrap(); 101 | image 102 | } 103 | 104 | fn main() { 105 | let args: Args = Docopt::new(USAGE) 106 | .and_then(|d| d.deserialize()) 107 | .unwrap_or_else(|e| e.exit()); 108 | 109 | let mut color = load_exr(&args.flag_c); 110 | 111 | let device = oidn::Device::new(); 112 | 113 | let albedo: EXRData; 114 | let normal: EXRData; 115 | 116 | let mut denoiser = oidn::RayTracing::new(&device); 117 | denoiser 118 | .srgb(false) 119 | .hdr(true) 120 | .image_dimensions(color.width, color.height); 121 | 122 | if let Some(albedo_exr) = args.flag_a.clone() { 123 | albedo = load_exr(&albedo_exr); 124 | 125 | if let Some(normal_exr) = args.flag_n.clone() { 126 | normal = load_exr(&normal_exr); 127 | denoiser.albedo_normal(&albedo.img[..], &normal.img[..]); 128 | } else { 129 | denoiser.albedo(&albedo.img[..]); 130 | } 131 | } 132 | 133 | denoiser 134 | .filter_in_place(&mut color.img[..]) 135 | .expect("Invalid input image dimensions?"); 136 | 137 | if let Err(e) = device.get_error() { 138 | println!("Error denosing image: {}", e.1); 139 | } 140 | 141 | let exposure = 2.0_f32.powf(args.flag_e); 142 | let output_img = (0..color.img.len()) 143 | .into_par_iter() 144 | .map(|i| { 145 | let p = linear_to_srgb(tonemap(color.img[i] * exposure)); 146 | if p < 0.0 { 147 | 0u8 148 | } else if p > 1.0 { 149 | 255u8 150 | } else { 151 | (p * 255.0) as u8 152 | } 153 | }) 154 | .collect::>(); 155 | 156 | image::save_buffer( 157 | &args.flag_o, 158 | &output_img[..], 159 | color.width as u32, 160 | color.height as u32, 161 | image::ColorType::Rgb8, 162 | ) 163 | .expect("Failed to save output image"); 164 | } 165 | -------------------------------------------------------------------------------- /examples/simple.rs: -------------------------------------------------------------------------------- 1 | extern crate image; 2 | extern crate oidn; 3 | 4 | use std::env; 5 | 6 | /// A simple test application that shows opening a color image and passing 7 | /// it to OIDN for denoising. The denoised image is then saved out. 8 | fn main() { 9 | let args: Vec<_> = env::args().collect(); 10 | let input = image::open(&args[1][..]) 11 | .expect("Failed to open input image") 12 | .to_rgb8(); 13 | 14 | // OIDN works on float images only, so convert this to a floating point image 15 | let mut input_img = vec![0.0f32; (3 * input.width() * input.height()) as usize]; 16 | for y in 0..input.height() { 17 | for x in 0..input.width() { 18 | let p = input.get_pixel(x, y); 19 | for c in 0..3 { 20 | input_img[3 * ((y * input.width() + x) as usize) + c] = p[c] as f32 / 255.0; 21 | } 22 | } 23 | } 24 | 25 | println!("Image dims {}x{}", input.width(), input.height()); 26 | 27 | let mut filter_output = vec![0.0f32; input_img.len()]; 28 | 29 | let device = oidn::Device::new(); 30 | let mut filter = oidn::RayTracing::new(&device); 31 | filter 32 | .srgb(true) 33 | .image_dimensions(input.width() as usize, input.height() as usize); 34 | filter 35 | .filter(&input_img[..], &mut filter_output[..]) 36 | .expect("Invalid input image dimensions?"); 37 | 38 | if let Err(e) = device.get_error() { 39 | println!("Error denosing image: {}", e.1); 40 | } 41 | 42 | let mut output_img = vec![0u8; filter_output.len()]; 43 | for i in 0..filter_output.len() { 44 | let p = filter_output[i] * 255.0; 45 | if p < 0.0 { 46 | output_img[i] = 0; 47 | } else if p > 255.0 { 48 | output_img[i] = 255; 49 | } else { 50 | output_img[i] = p as u8; 51 | } 52 | } 53 | 54 | image::save_buffer( 55 | &args[2][..], 56 | &output_img[..], 57 | input.width(), 58 | input.height(), 59 | image::ColorType::Rgb8, 60 | ) 61 | .expect("Failed to save output image"); 62 | } 63 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2024" 2 | unstable_features = true 3 | wrap_comments = true 4 | format_code_in_doc_comments = true 5 | -------------------------------------------------------------------------------- /scripts/build-examples-linux-mac.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # build the examples 4 | cargo build --examples 5 | if [[ "$?" != "0" ]]; then 6 | exit 1 7 | fi 8 | 9 | -------------------------------------------------------------------------------- /scripts/build-test-mac.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:${OIDN_DIR}/lib 4 | 5 | echo "Building oidn-rs tests" 6 | cargo test 7 | if [[ "$?" != "0" ]]; then 8 | exit 1 9 | fi 10 | 11 | cargo test --examples 12 | if [[ "$?" != "0" ]]; then 13 | exit 1 14 | fi 15 | -------------------------------------------------------------------------------- /scripts/build-test-windows.ps1: -------------------------------------------------------------------------------- 1 | $env:WORK_DIR=(get-location) 2 | $env:OIDN_DIR="${env:WORK_DIR}\oidn-${env:OIDN_VERSION}.x64.vc14.windows\" 3 | 4 | Write-Output "Building oidn-rs" 5 | cargo build 6 | if (!$?) { 7 | exit 1 8 | } 9 | 10 | Write-Output "Running oidn-rs Tests" 11 | cargo test 12 | if (!$?) { 13 | exit 1 14 | } 15 | 16 | # build the examples 17 | cd examples 18 | Get-ChildItem .\ -Directory | ForEach-Object { 19 | Write-Output $_ 20 | cd $_ 21 | cargo build 22 | if (!$?) { 23 | exit 1 24 | } 25 | cd .. 26 | } 27 | 28 | -------------------------------------------------------------------------------- /scripts/generate-sys-bindings.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This should be run on nightly rust, as it requires 4 | # rustfmt-nightly to do the formatting 5 | 6 | bindgen $1 -o $2 \ 7 | --no-doc-comments \ 8 | --distrust-clang-mangling \ 9 | --allowlist-function "oidn.*" \ 10 | --allowlist-type "OIDN.*" \ 11 | --rust-target nightly 12 | -------------------------------------------------------------------------------- /src/buffer.rs: -------------------------------------------------------------------------------- 1 | use crate::Device; 2 | use crate::sys::{ 3 | OIDNBuffer, oidnGetBufferSize, oidnNewBuffer, oidnReadBuffer, oidnReleaseBuffer, 4 | oidnWriteBuffer, 5 | }; 6 | use std::mem; 7 | use std::sync::Arc; 8 | 9 | pub struct Buffer { 10 | pub(crate) buf: OIDNBuffer, 11 | pub(crate) size: usize, 12 | pub(crate) device_arc: Arc, 13 | } 14 | 15 | impl Device { 16 | /// Creates a new buffer from a slice, returns None if buffer creation 17 | /// failed 18 | pub fn create_buffer(&self, contents: &[f32]) -> Option { 19 | let byte_size = mem::size_of_val(contents); 20 | let buffer = unsafe { 21 | let buf = oidnNewBuffer(self.0, byte_size); 22 | if buf.is_null() { 23 | return None; 24 | } else { 25 | oidnWriteBuffer(buf, 0, byte_size, contents.as_ptr() as *const _); 26 | buf 27 | } 28 | }; 29 | Some(Buffer { 30 | buf: buffer, 31 | size: contents.len(), 32 | device_arc: self.1.clone(), 33 | }) 34 | } 35 | /// # Safety 36 | /// Raw buffer must not be invalid (e.g. destroyed, null ect.) 37 | /// 38 | /// Raw buffer must have been created by this device 39 | pub unsafe fn create_buffer_from_raw(&self, buffer: OIDNBuffer) -> Buffer { 40 | let size = unsafe { oidnGetBufferSize(buffer) } / mem::size_of::(); 41 | Buffer { 42 | buf: buffer, 43 | size, 44 | device_arc: self.1.clone(), 45 | } 46 | } 47 | 48 | pub(crate) fn same_device_as_buf(&self, buf: &Buffer) -> bool { 49 | self.1.as_ref() as *const _ as isize == buf.device_arc.as_ref() as *const _ as isize 50 | } 51 | } 52 | 53 | impl Buffer { 54 | /// Writes to the buffer, returns [None] if the sizes mismatch 55 | pub fn write(&self, contents: &[f32]) -> Option<()> { 56 | if self.size != contents.len() { 57 | None 58 | } else { 59 | let byte_size = mem::size_of_val(contents); 60 | unsafe { 61 | oidnWriteBuffer(self.buf, 0, byte_size, contents.as_ptr() as *const _); 62 | } 63 | Some(()) 64 | } 65 | } 66 | 67 | /// Reads from the buffer to the array, returns [None] if the sizes mismatch 68 | pub fn read_to_slice(&self, contents: &mut [f32]) -> Option<()> { 69 | if self.size != contents.len() { 70 | None 71 | } else { 72 | let byte_size = mem::size_of_val(contents); 73 | unsafe { 74 | oidnReadBuffer(self.buf, 0, byte_size, contents.as_ptr() as *mut _); 75 | } 76 | Some(()) 77 | } 78 | } 79 | 80 | /// Reads from the buffer 81 | pub fn read(&self) -> Vec { 82 | let contents = vec![0.0; self.size]; 83 | unsafe { 84 | oidnReadBuffer( 85 | self.buf, 86 | 0, 87 | self.size * mem::size_of::(), 88 | contents.as_ptr() as *mut _, 89 | ); 90 | } 91 | contents 92 | } 93 | /// # Safety 94 | /// Raw buffer must not be made invalid (e.g. by destroying it) 95 | pub unsafe fn raw(&self) -> OIDNBuffer { 96 | self.buf 97 | } 98 | pub fn size(&self) -> usize { 99 | self.size 100 | } 101 | } 102 | 103 | impl Drop for Buffer { 104 | fn drop(&mut self) { 105 | unsafe { oidnReleaseBuffer(self.buf) } 106 | } 107 | } 108 | 109 | unsafe impl Send for Buffer {} 110 | -------------------------------------------------------------------------------- /src/device.rs: -------------------------------------------------------------------------------- 1 | use crate::Error; 2 | use crate::sys::*; 3 | use std::sync::Arc; 4 | use std::{ffi::CStr, os::raw::c_char, ptr}; 5 | 6 | /// An Open Image Denoise device (e.g. a CPU). 7 | /// 8 | /// Open Image Denoise supports a device concept, which allows different 9 | /// components of the application to use the API without interfering with each 10 | /// other. 11 | /// 12 | /// While all API calls on a device are thread-safe, they may be serialized. 13 | /// Therefor, it is recommended to call from the same thread. 14 | pub struct Device(pub(crate) OIDNDevice, pub(crate) Arc); 15 | 16 | impl Device { 17 | /// Create a device using the fastest device available to run denoising 18 | pub fn new() -> Self { 19 | Self::create(OIDNDeviceType_OIDN_DEVICE_TYPE_DEFAULT) 20 | } 21 | 22 | fn create(device_type: OIDNDeviceType) -> Self { 23 | let handle = get_handle(device_type); 24 | unsafe { 25 | oidnCommitDevice(handle); 26 | } 27 | Self(handle, Arc::new(0)) 28 | } 29 | 30 | fn try_create(device_type: OIDNDeviceType) -> Option { 31 | let handle = get_handle(device_type); 32 | if !handle.is_null() { 33 | unsafe { 34 | oidnCommitDevice(handle); 35 | Some(Self(handle, Arc::new(0))) 36 | } 37 | } else { 38 | None 39 | } 40 | } 41 | 42 | pub fn cpu() -> Self { 43 | Self::create(OIDNDeviceType_OIDN_DEVICE_TYPE_CPU) 44 | } 45 | 46 | pub fn sycl() -> Option { 47 | Self::try_create(OIDNDeviceType_OIDN_DEVICE_TYPE_SYCL) 48 | } 49 | 50 | pub fn cuda() -> Option { 51 | Self::try_create(OIDNDeviceType_OIDN_DEVICE_TYPE_CUDA) 52 | } 53 | 54 | pub fn hip() -> Option { 55 | Self::try_create(OIDNDeviceType_OIDN_DEVICE_TYPE_HIP) 56 | } 57 | 58 | pub fn metal() -> Option { 59 | Self::try_create(OIDNDeviceType_OIDN_DEVICE_TYPE_METAL) 60 | } 61 | 62 | /// # Safety 63 | /// Raw device must not be invalid (e.g. destroyed, null, etc.) 64 | /// Raw device must be committed using [oidnCommitDevice]. 65 | pub unsafe fn from_raw(device: OIDNDevice) -> Self { 66 | Self(device, Arc::new(0)) 67 | } 68 | 69 | /// # Safety 70 | /// Raw device must not be made invalid (e.g. by destroying it). 71 | pub unsafe fn raw(&self) -> OIDNDevice { 72 | self.0 73 | } 74 | 75 | pub fn get_error(&self) -> Result<(), (Error, String)> { 76 | let mut err_msg = ptr::null(); 77 | let err = unsafe { oidnGetDeviceError(self.0, &mut err_msg as *mut *const c_char) }; 78 | if OIDNError_OIDN_ERROR_NONE == err { 79 | Ok(()) 80 | } else { 81 | let msg = unsafe { CStr::from_ptr(err_msg).to_string_lossy().to_string() }; 82 | Err(((err as u32).try_into().unwrap(), msg)) 83 | } 84 | } 85 | } 86 | 87 | impl Drop for Device { 88 | fn drop(&mut self) { 89 | unsafe { 90 | oidnReleaseDevice(self.0); 91 | } 92 | } 93 | } 94 | 95 | impl Default for Device { 96 | fn default() -> Self { 97 | Self::new() 98 | } 99 | } 100 | 101 | unsafe impl Send for Device {} 102 | 103 | fn get_handle(device_type: u32) -> *mut OIDNDeviceImpl { 104 | unsafe { oidnNewDevice(device_type) } 105 | } 106 | -------------------------------------------------------------------------------- /src/filter.rs: -------------------------------------------------------------------------------- 1 | use crate::{Error, Quality, buffer::Buffer, device::Device, sys::*}; 2 | use std::mem; 3 | 4 | /// A generic ray tracing denoising filter for denoising 5 | /// images produces with Monte Carlo ray tracing methods 6 | /// such as path tracing. 7 | pub struct RayTracing<'a> { 8 | handle: OIDNFilter, 9 | device: &'a Device, 10 | albedo: Option, 11 | normal: Option, 12 | hdr: bool, 13 | input_scale: f32, 14 | srgb: bool, 15 | clean_aux: bool, 16 | img_dims: (usize, usize, usize), 17 | filter_quality: OIDNQuality, 18 | } 19 | 20 | impl<'a> RayTracing<'a> { 21 | pub fn new(device: &'a Device) -> RayTracing<'a> { 22 | unsafe { 23 | oidnRetainDevice(device.0); 24 | } 25 | let filter = unsafe { oidnNewFilter(device.0, b"RT\0" as *const _ as _) }; 26 | RayTracing { 27 | handle: filter, 28 | device, 29 | albedo: None, 30 | normal: None, 31 | hdr: false, 32 | input_scale: f32::NAN, 33 | srgb: false, 34 | clean_aux: false, 35 | img_dims: (0, 0, 0), 36 | filter_quality: 0, 37 | } 38 | } 39 | 40 | /// Sets the quality of the output, the default is high. 41 | /// 42 | /// Balanced lowers the precision, if possible, however 43 | /// some devices will not support this and so 44 | /// the result (and performance) will stay the same as high. 45 | /// Balanced is recommended for realtime usages. 46 | pub fn filter_quality(&mut self, quality: Quality) -> &mut RayTracing<'a> { 47 | self.filter_quality = quality.as_raw_oidn_quality(); 48 | self 49 | } 50 | 51 | /// Set input auxiliary images containing the albedo and normals. 52 | /// 53 | /// Albedo must have three channels per pixel with values in [0, 1]. 54 | /// Normal must contain the shading normal as three channels per pixel 55 | /// *world-space* or *view-space* vectors with arbitrary length, values 56 | /// in `[-1, 1]`. 57 | /// 58 | /// # Panics 59 | /// - if resource creation fails 60 | pub fn albedo_normal(&mut self, albedo: &[f32], normal: &[f32]) -> &mut RayTracing<'a> { 61 | match self.albedo.as_mut().and_then(|buf| { 62 | if buf.size == albedo.len() { 63 | Some(buf) 64 | } else { 65 | None 66 | } 67 | }) { 68 | None => { 69 | self.albedo = Some(self.device.create_buffer(albedo).unwrap()); 70 | } 71 | Some(buf) => { 72 | buf.write(albedo) 73 | .expect("we check if the size is the same already"); 74 | } 75 | } 76 | match self.normal.as_mut().and_then(|buf| { 77 | if buf.size == normal.len() { 78 | Some(buf) 79 | } else { 80 | None 81 | } 82 | }) { 83 | None => { 84 | self.normal = Some(self.device.create_buffer(normal).unwrap()); 85 | } 86 | Some(buf) => { 87 | buf.write(normal) 88 | .expect("we check if the size is the same already"); 89 | } 90 | } 91 | self 92 | } 93 | 94 | /// Set an input auxiliary image containing the albedo per pixel (three 95 | /// channels, values in `[0, 1]`). 96 | /// 97 | /// # Panics 98 | /// - if resource creation fails 99 | pub fn albedo(&mut self, albedo: &[f32]) -> &mut RayTracing<'a> { 100 | match self.albedo.as_mut().and_then(|buf| { 101 | if buf.size == albedo.len() { 102 | Some(buf) 103 | } else { 104 | None 105 | } 106 | }) { 107 | None => { 108 | self.albedo = Some(self.device.create_buffer(albedo).unwrap()); 109 | } 110 | Some(buf) => { 111 | buf.write(albedo) 112 | .expect("we check if the size is the same already"); 113 | } 114 | } 115 | self 116 | } 117 | /// Set input auxiliary buffer containing the albedo and normals. 118 | /// 119 | /// Albedo buffer must have three channels per pixel with values in [0, 1]. 120 | /// Normal must contain the shading normal as three channels per pixel 121 | /// *world-space* or *view-space* vectors with arbitrary length, values 122 | /// in `[-1, 1]`. 123 | /// 124 | /// This function is the same as [RayTracing::albedo_normal] but takes 125 | /// buffers instead 126 | /// 127 | /// Returns [None] if either buffer was not created by this device 128 | pub fn albedo_normal_buffer( 129 | &mut self, 130 | albedo: Buffer, 131 | normal: Buffer, 132 | ) -> Option<&mut RayTracing<'a>> { 133 | if !self.device.same_device_as_buf(&albedo) || !self.device.same_device_as_buf(&normal) { 134 | return None; 135 | } 136 | self.albedo = Some(albedo); 137 | self.normal = Some(normal); 138 | Some(self) 139 | } 140 | 141 | /// Set an input auxiliary buffer containing the albedo per pixel (three 142 | /// channels, values in `[0, 1]`). 143 | /// 144 | /// This function is the same as [RayTracing::albedo] but takes buffers 145 | /// instead 146 | /// 147 | /// Returns [None] if albedo buffer was not created by this device 148 | pub fn albedo_buffer(&mut self, albedo: Buffer) -> Option<&mut RayTracing<'a>> { 149 | if !self.device.same_device_as_buf(&albedo) { 150 | return None; 151 | } 152 | self.albedo = Some(albedo); 153 | Some(self) 154 | } 155 | 156 | /// Set whether the color is HDR. 157 | pub fn hdr(&mut self, hdr: bool) -> &mut RayTracing<'a> { 158 | self.hdr = hdr; 159 | self 160 | } 161 | 162 | #[deprecated(since = "1.3.1", note = "Please use RayTracing::input_scale instead")] 163 | pub fn hdr_scale(&mut self, hdr_scale: f32) -> &mut RayTracing<'a> { 164 | self.input_scale = hdr_scale; 165 | self 166 | } 167 | 168 | /// Sets a scale to apply to input values before filtering, without scaling 169 | /// the output too. 170 | /// 171 | /// This can be used to map color or auxiliary feature values to the 172 | /// expected range. E.g. for mapping HDR values to physical units (which 173 | /// affects the quality of the output but not the range of the output 174 | /// values). If not set, the scale is computed implicitly for HDR images 175 | /// or set to 1 otherwise 176 | pub fn input_scale(&mut self, input_scale: f32) -> &mut RayTracing<'a> { 177 | self.input_scale = input_scale; 178 | self 179 | } 180 | 181 | /// Set whether the color is encoded with the sRGB (or 2.2 gamma) curve (LDR 182 | /// only) or is linear. 183 | /// 184 | /// The output will be encoded with the same curve. 185 | pub fn srgb(&mut self, srgb: bool) -> &mut RayTracing<'a> { 186 | self.srgb = srgb; 187 | self 188 | } 189 | 190 | /// Set whether the auxiliary feature (albedo, normal) images are 191 | /// noise-free. 192 | /// 193 | /// Recommended for highest quality but should not be enabled for noisy 194 | /// auxiliary images to avoid residual noise. 195 | pub fn clean_aux(&mut self, clean_aux: bool) -> &mut RayTracing<'a> { 196 | self.clean_aux = clean_aux; 197 | self 198 | } 199 | 200 | /// sets the dimensions of the denoising image, if new width * new height 201 | /// does not equal old width * old height 202 | pub fn image_dimensions(&mut self, width: usize, height: usize) -> &mut RayTracing<'a> { 203 | let buffer_dims = 3 * width * height; 204 | match &self.albedo { 205 | None => {} 206 | Some(buffer) => { 207 | if buffer.size != buffer_dims { 208 | self.albedo = None; 209 | } 210 | } 211 | } 212 | match &self.normal { 213 | None => {} 214 | Some(buffer) => { 215 | if buffer.size != buffer_dims { 216 | self.normal = None; 217 | } 218 | } 219 | } 220 | self.img_dims = (width, height, buffer_dims); 221 | self 222 | } 223 | 224 | pub fn filter(&self, color: &[f32], output: &mut [f32]) -> Result<(), Error> { 225 | self.execute_filter(Some(color), output) 226 | } 227 | 228 | pub fn filter_buffer(&self, color: &Buffer, output: &Buffer) -> Result<(), Error> { 229 | self.execute_filter_buffer(Some(color), output) 230 | } 231 | 232 | pub fn filter_in_place(&self, color: &mut [f32]) -> Result<(), Error> { 233 | self.execute_filter(None, color) 234 | } 235 | 236 | pub fn filter_in_place_buffer(&self, color: &Buffer) -> Result<(), Error> { 237 | self.execute_filter_buffer(None, color) 238 | } 239 | 240 | fn execute_filter(&self, color: Option<&[f32]>, output: &mut [f32]) -> Result<(), Error> { 241 | let color = match color { 242 | None => None, 243 | Some(color) => Some(self.device.create_buffer(color).ok_or(Error::OutOfMemory)?), 244 | }; 245 | let out = self 246 | .device 247 | .create_buffer(output) 248 | .ok_or(Error::OutOfMemory)?; 249 | self.execute_filter_buffer(color.as_ref(), &out)?; 250 | unsafe { 251 | oidnReadBuffer( 252 | out.buf, 253 | 0, 254 | out.size * mem::size_of::(), 255 | output.as_mut_ptr() as *mut _, 256 | ) 257 | }; 258 | Ok(()) 259 | } 260 | 261 | fn execute_filter_buffer(&self, color: Option<&Buffer>, output: &Buffer) -> Result<(), Error> { 262 | if let Some(alb) = &self.albedo { 263 | if alb.size != self.img_dims.2 { 264 | return Err(Error::InvalidImageDimensions); 265 | } 266 | unsafe { 267 | oidnSetFilterImage( 268 | self.handle, 269 | b"albedo\0" as *const _ as _, 270 | alb.buf, 271 | OIDNFormat_OIDN_FORMAT_FLOAT3, 272 | self.img_dims.0 as _, 273 | self.img_dims.1 as _, 274 | 0, 275 | 0, 276 | 0, 277 | ); 278 | } 279 | 280 | // No use supplying normal if albedo was 281 | // not also given. 282 | if let Some(norm) = &self.normal { 283 | if norm.size != self.img_dims.2 { 284 | return Err(Error::InvalidImageDimensions); 285 | } 286 | unsafe { 287 | oidnSetFilterImage( 288 | self.handle, 289 | b"normal\0" as *const _ as _, 290 | norm.buf, 291 | OIDNFormat_OIDN_FORMAT_FLOAT3, 292 | self.img_dims.0 as _, 293 | self.img_dims.1 as _, 294 | 0, 295 | 0, 296 | 0, 297 | ); 298 | } 299 | } 300 | } 301 | let color_buffer = match color { 302 | Some(color) => { 303 | if !self.device.same_device_as_buf(color) { 304 | return Err(Error::InvalidArgument); 305 | } 306 | if color.size != self.img_dims.2 { 307 | return Err(Error::InvalidImageDimensions); 308 | } 309 | color 310 | } 311 | None => { 312 | if output.size != self.img_dims.2 { 313 | return Err(Error::InvalidImageDimensions); 314 | } 315 | // actually this is a needed borrow, the compiler complains otherwise 316 | #[allow(clippy::needless_borrow)] 317 | &output 318 | } 319 | }; 320 | unsafe { 321 | oidnSetFilterImage( 322 | self.handle, 323 | b"color\0" as *const _ as _, 324 | color_buffer.buf, 325 | OIDNFormat_OIDN_FORMAT_FLOAT3, 326 | self.img_dims.0 as _, 327 | self.img_dims.1 as _, 328 | 0, 329 | 0, 330 | 0, 331 | ); 332 | } 333 | if !self.device.same_device_as_buf(output) { 334 | return Err(Error::InvalidArgument); 335 | } 336 | if output.size != self.img_dims.2 { 337 | return Err(Error::InvalidImageDimensions); 338 | } 339 | unsafe { 340 | oidnSetFilterImage( 341 | self.handle, 342 | b"output\0" as *const _ as _, 343 | output.buf, 344 | OIDNFormat_OIDN_FORMAT_FLOAT3, 345 | self.img_dims.0 as _, 346 | self.img_dims.1 as _, 347 | 0, 348 | 0, 349 | 0, 350 | ); 351 | oidnSetFilterBool(self.handle, b"hdr\0" as *const _ as _, self.hdr); 352 | oidnSetFilterFloat( 353 | self.handle, 354 | b"inputScale\0" as *const _ as _, 355 | self.input_scale, 356 | ); 357 | oidnSetFilterBool(self.handle, b"srgb\0" as *const _ as _, self.srgb); 358 | oidnSetFilterBool(self.handle, b"clean_aux\0" as *const _ as _, self.clean_aux); 359 | 360 | oidnSetFilterInt( 361 | self.handle, 362 | b"quality\0" as *const _ as _, 363 | self.filter_quality as i32, 364 | ); 365 | 366 | oidnCommitFilter(self.handle); 367 | oidnExecuteFilter(self.handle); 368 | } 369 | Ok(()) 370 | } 371 | } 372 | 373 | impl Drop for RayTracing<'_> { 374 | fn drop(&mut self) { 375 | unsafe { 376 | oidnReleaseFilter(self.handle); 377 | oidnReleaseDevice(self.device.0); 378 | } 379 | } 380 | } 381 | 382 | unsafe impl Send for RayTracing<'_> {} 383 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Rust bindings to Intel's 2 | //! [Open Image Denoise](https://github.com/OpenImageDenoise/oidn). 3 | //! 4 | //! Open Image Denoise documentation can be found 5 | //! [here](https://openimagedenoise.github.io/documentation.html). 6 | //! 7 | //! ## Example 8 | //! 9 | //! The crate provides a lightweight wrapper over the Open Image Denoise 10 | //! library, along with raw C bindings exposed under [`oidn::sys`](sys). Below 11 | //! is an example of using the the [`RayTracing`] filter to denoise an image. 12 | //! 13 | //! ```ignore 14 | //! // Load scene, render image, etc. 15 | //! 16 | //! let input_img: Vec = // A float3 RGB image produced by your renderer. 17 | //! let mut filter_output = vec![0.0f32; input_img.len()]; 18 | //! 19 | //! let device = oidn::Device::new(); 20 | //! oidn::RayTracing::new(&device) 21 | //! // Optionally add float3 normal and albedo buffers as well. 22 | //! .srgb(true) 23 | //! .image_dimensions(input.width() as usize, input.height() as usize); 24 | //! .filter(&input_img[..], &mut filter_output[..]) 25 | //! .expect("Filter config error!"); 26 | //! 27 | //! if let Err(e) = device.get_error() { 28 | //! println!("Error denosing image: {}", e.1); 29 | //! } 30 | //! 31 | //! // Save out or display filter_output image. 32 | //! ``` 33 | 34 | use num_enum::TryFromPrimitive; 35 | 36 | pub mod buffer; 37 | pub mod device; 38 | pub mod filter; 39 | #[allow(non_upper_case_globals, non_camel_case_types, non_snake_case)] 40 | pub mod sys; 41 | #[cfg(test)] 42 | mod tests; 43 | 44 | #[doc(inline)] 45 | pub use buffer::Buffer; 46 | #[doc(inline)] 47 | pub use device::Device; 48 | #[doc(inline)] 49 | pub use filter::RayTracing; 50 | 51 | #[repr(u32)] 52 | #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, TryFromPrimitive)] 53 | pub enum Error { 54 | None = sys::OIDNError_OIDN_ERROR_NONE, 55 | Unknown = sys::OIDNError_OIDN_ERROR_UNKNOWN, 56 | InvalidArgument = sys::OIDNError_OIDN_ERROR_INVALID_ARGUMENT, 57 | InvalidOperation = sys::OIDNError_OIDN_ERROR_INVALID_OPERATION, 58 | OutOfMemory = sys::OIDNError_OIDN_ERROR_OUT_OF_MEMORY, 59 | UnsupportedFormat = sys::OIDNError_OIDN_ERROR_UNSUPPORTED_HARDWARE, 60 | Canceled = sys::OIDNError_OIDN_ERROR_CANCELLED, 61 | InvalidImageDimensions, 62 | } 63 | 64 | #[repr(u32)] 65 | #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, TryFromPrimitive, Default)] 66 | pub enum Quality { 67 | #[default] 68 | Default = sys::OIDNQuality_OIDN_QUALITY_DEFAULT, 69 | Balanced = sys::OIDNQuality_OIDN_QUALITY_BALANCED, 70 | High = sys::OIDNQuality_OIDN_QUALITY_HIGH, 71 | Fast = sys::OIDNQuality_OIDN_QUALITY_FAST, 72 | } 73 | 74 | impl Quality { 75 | pub fn as_raw_oidn_quality(&self) -> sys::OIDNQuality { 76 | match self { 77 | Quality::Default => sys::OIDNQuality_OIDN_QUALITY_DEFAULT, 78 | Quality::Balanced => sys::OIDNQuality_OIDN_QUALITY_BALANCED, 79 | Quality::High => sys::OIDNQuality_OIDN_QUALITY_HIGH, 80 | Quality::Fast => sys::OIDNQuality_OIDN_QUALITY_FAST, 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/sys.rs: -------------------------------------------------------------------------------- 1 | /* automatically generated by rust-bindgen 0.71.1 */ 2 | 3 | #[repr(C)] 4 | #[derive(Debug, Copy, Clone)] 5 | pub struct sycl_device { 6 | _unused: [u8; 0], 7 | } 8 | #[repr(C)] 9 | #[derive(Debug, Copy, Clone)] 10 | pub struct sycl_queue { 11 | _unused: [u8; 0], 12 | } 13 | #[repr(C)] 14 | #[derive(Debug, Copy, Clone)] 15 | pub struct sycl_event { 16 | _unused: [u8; 0], 17 | } 18 | #[repr(C)] 19 | #[derive(Debug, Copy, Clone)] 20 | pub struct CUstream_st { 21 | _unused: [u8; 0], 22 | } 23 | pub type cudaStream_t = *mut CUstream_st; 24 | #[repr(C)] 25 | #[derive(Debug, Copy, Clone)] 26 | pub struct ihipStream_t { 27 | _unused: [u8; 0], 28 | } 29 | pub type hipStream_t = *mut ihipStream_t; 30 | pub type MTLDevice_id = *mut ::std::os::raw::c_void; 31 | pub type MTLCommandQueue_id = *mut ::std::os::raw::c_void; 32 | pub type MTLBuffer_id = *mut ::std::os::raw::c_void; 33 | unsafe extern "C" { 34 | pub fn oidnGetNumPhysicalDevices() -> ::std::os::raw::c_int; 35 | } 36 | unsafe extern "C" { 37 | pub fn oidnGetPhysicalDeviceBool( 38 | physicalDeviceID: ::std::os::raw::c_int, 39 | name: *const ::std::os::raw::c_char, 40 | ) -> bool; 41 | } 42 | unsafe extern "C" { 43 | pub fn oidnGetPhysicalDeviceInt( 44 | physicalDeviceID: ::std::os::raw::c_int, 45 | name: *const ::std::os::raw::c_char, 46 | ) -> ::std::os::raw::c_int; 47 | } 48 | unsafe extern "C" { 49 | pub fn oidnGetPhysicalDeviceString( 50 | physicalDeviceID: ::std::os::raw::c_int, 51 | name: *const ::std::os::raw::c_char, 52 | ) -> *const ::std::os::raw::c_char; 53 | } 54 | unsafe extern "C" { 55 | pub fn oidnGetPhysicalDeviceData( 56 | physicalDeviceID: ::std::os::raw::c_int, 57 | name: *const ::std::os::raw::c_char, 58 | byteSize: *mut usize, 59 | ) -> *const ::std::os::raw::c_void; 60 | } 61 | pub const OIDNDeviceType_OIDN_DEVICE_TYPE_DEFAULT: OIDNDeviceType = 0; 62 | pub const OIDNDeviceType_OIDN_DEVICE_TYPE_CPU: OIDNDeviceType = 1; 63 | pub const OIDNDeviceType_OIDN_DEVICE_TYPE_SYCL: OIDNDeviceType = 2; 64 | pub const OIDNDeviceType_OIDN_DEVICE_TYPE_CUDA: OIDNDeviceType = 3; 65 | pub const OIDNDeviceType_OIDN_DEVICE_TYPE_HIP: OIDNDeviceType = 4; 66 | pub const OIDNDeviceType_OIDN_DEVICE_TYPE_METAL: OIDNDeviceType = 5; 67 | pub type OIDNDeviceType = ::std::os::raw::c_uint; 68 | pub const OIDNError_OIDN_ERROR_NONE: OIDNError = 0; 69 | pub const OIDNError_OIDN_ERROR_UNKNOWN: OIDNError = 1; 70 | pub const OIDNError_OIDN_ERROR_INVALID_ARGUMENT: OIDNError = 2; 71 | pub const OIDNError_OIDN_ERROR_INVALID_OPERATION: OIDNError = 3; 72 | pub const OIDNError_OIDN_ERROR_OUT_OF_MEMORY: OIDNError = 4; 73 | pub const OIDNError_OIDN_ERROR_UNSUPPORTED_HARDWARE: OIDNError = 5; 74 | pub const OIDNError_OIDN_ERROR_CANCELLED: OIDNError = 6; 75 | pub type OIDNError = ::std::os::raw::c_uint; 76 | pub type OIDNErrorFunction = ::std::option::Option< 77 | unsafe extern "C" fn( 78 | userPtr: *mut ::std::os::raw::c_void, 79 | code: OIDNError, 80 | message: *const ::std::os::raw::c_char, 81 | ), 82 | >; 83 | #[repr(C)] 84 | #[derive(Debug, Copy, Clone)] 85 | pub struct OIDNDeviceImpl { 86 | _unused: [u8; 0], 87 | } 88 | pub type OIDNDevice = *mut OIDNDeviceImpl; 89 | unsafe extern "C" { 90 | pub fn oidnIsCPUDeviceSupported() -> bool; 91 | } 92 | unsafe extern "C" { 93 | pub fn oidnIsSYCLDeviceSupported(device: *const sycl_device) -> bool; 94 | } 95 | unsafe extern "C" { 96 | pub fn oidnIsCUDADeviceSupported(deviceID: ::std::os::raw::c_int) -> bool; 97 | } 98 | unsafe extern "C" { 99 | pub fn oidnIsHIPDeviceSupported(deviceID: ::std::os::raw::c_int) -> bool; 100 | } 101 | unsafe extern "C" { 102 | pub fn oidnIsMetalDeviceSupported(device: MTLDevice_id) -> bool; 103 | } 104 | unsafe extern "C" { 105 | pub fn oidnNewDevice(type_: OIDNDeviceType) -> OIDNDevice; 106 | } 107 | unsafe extern "C" { 108 | pub fn oidnNewDeviceByID(physicalDeviceID: ::std::os::raw::c_int) -> OIDNDevice; 109 | } 110 | unsafe extern "C" { 111 | pub fn oidnNewDeviceByUUID(uuid: *const ::std::os::raw::c_void) -> OIDNDevice; 112 | } 113 | unsafe extern "C" { 114 | pub fn oidnNewDeviceByLUID(luid: *const ::std::os::raw::c_void) -> OIDNDevice; 115 | } 116 | unsafe extern "C" { 117 | pub fn oidnNewDeviceByPCIAddress( 118 | pciDomain: ::std::os::raw::c_int, 119 | pciBus: ::std::os::raw::c_int, 120 | pciDevice: ::std::os::raw::c_int, 121 | pciFunction: ::std::os::raw::c_int, 122 | ) -> OIDNDevice; 123 | } 124 | unsafe extern "C" { 125 | pub fn oidnNewSYCLDevice( 126 | queues: *const sycl_queue, 127 | numQueues: ::std::os::raw::c_int, 128 | ) -> OIDNDevice; 129 | } 130 | unsafe extern "C" { 131 | pub fn oidnNewCUDADevice( 132 | deviceIDs: *const ::std::os::raw::c_int, 133 | streams: *const cudaStream_t, 134 | numPairs: ::std::os::raw::c_int, 135 | ) -> OIDNDevice; 136 | } 137 | unsafe extern "C" { 138 | pub fn oidnNewHIPDevice( 139 | deviceIDs: *const ::std::os::raw::c_int, 140 | streams: *const hipStream_t, 141 | numPairs: ::std::os::raw::c_int, 142 | ) -> OIDNDevice; 143 | } 144 | unsafe extern "C" { 145 | pub fn oidnNewMetalDevice( 146 | commandQueues: *const MTLCommandQueue_id, 147 | numQueues: ::std::os::raw::c_int, 148 | ) -> OIDNDevice; 149 | } 150 | unsafe extern "C" { 151 | pub fn oidnRetainDevice(device: OIDNDevice); 152 | } 153 | unsafe extern "C" { 154 | pub fn oidnReleaseDevice(device: OIDNDevice); 155 | } 156 | unsafe extern "C" { 157 | pub fn oidnSetDeviceBool(device: OIDNDevice, name: *const ::std::os::raw::c_char, value: bool); 158 | } 159 | unsafe extern "C" { 160 | pub fn oidnSetDeviceInt( 161 | device: OIDNDevice, 162 | name: *const ::std::os::raw::c_char, 163 | value: ::std::os::raw::c_int, 164 | ); 165 | } 166 | unsafe extern "C" { 167 | pub fn oidnGetDeviceBool(device: OIDNDevice, name: *const ::std::os::raw::c_char) -> bool; 168 | } 169 | unsafe extern "C" { 170 | pub fn oidnGetDeviceInt( 171 | device: OIDNDevice, 172 | name: *const ::std::os::raw::c_char, 173 | ) -> ::std::os::raw::c_int; 174 | } 175 | unsafe extern "C" { 176 | pub fn oidnSetDeviceErrorFunction( 177 | device: OIDNDevice, 178 | func: OIDNErrorFunction, 179 | userPtr: *mut ::std::os::raw::c_void, 180 | ); 181 | } 182 | unsafe extern "C" { 183 | pub fn oidnGetDeviceError( 184 | device: OIDNDevice, 185 | outMessage: *mut *const ::std::os::raw::c_char, 186 | ) -> OIDNError; 187 | } 188 | unsafe extern "C" { 189 | pub fn oidnCommitDevice(device: OIDNDevice); 190 | } 191 | unsafe extern "C" { 192 | pub fn oidnSyncDevice(device: OIDNDevice); 193 | } 194 | pub const OIDNFormat_OIDN_FORMAT_UNDEFINED: OIDNFormat = 0; 195 | pub const OIDNFormat_OIDN_FORMAT_FLOAT: OIDNFormat = 1; 196 | pub const OIDNFormat_OIDN_FORMAT_FLOAT2: OIDNFormat = 2; 197 | pub const OIDNFormat_OIDN_FORMAT_FLOAT3: OIDNFormat = 3; 198 | pub const OIDNFormat_OIDN_FORMAT_FLOAT4: OIDNFormat = 4; 199 | pub const OIDNFormat_OIDN_FORMAT_HALF: OIDNFormat = 257; 200 | pub const OIDNFormat_OIDN_FORMAT_HALF2: OIDNFormat = 258; 201 | pub const OIDNFormat_OIDN_FORMAT_HALF3: OIDNFormat = 259; 202 | pub const OIDNFormat_OIDN_FORMAT_HALF4: OIDNFormat = 260; 203 | pub type OIDNFormat = ::std::os::raw::c_uint; 204 | pub const OIDNStorage_OIDN_STORAGE_UNDEFINED: OIDNStorage = 0; 205 | pub const OIDNStorage_OIDN_STORAGE_HOST: OIDNStorage = 1; 206 | pub const OIDNStorage_OIDN_STORAGE_DEVICE: OIDNStorage = 2; 207 | pub const OIDNStorage_OIDN_STORAGE_MANAGED: OIDNStorage = 3; 208 | pub type OIDNStorage = ::std::os::raw::c_uint; 209 | pub const OIDNExternalMemoryTypeFlag_OIDN_EXTERNAL_MEMORY_TYPE_FLAG_NONE: 210 | OIDNExternalMemoryTypeFlag = 0; 211 | pub const OIDNExternalMemoryTypeFlag_OIDN_EXTERNAL_MEMORY_TYPE_FLAG_OPAQUE_FD: 212 | OIDNExternalMemoryTypeFlag = 1; 213 | pub const OIDNExternalMemoryTypeFlag_OIDN_EXTERNAL_MEMORY_TYPE_FLAG_DMA_BUF: 214 | OIDNExternalMemoryTypeFlag = 2; 215 | pub const OIDNExternalMemoryTypeFlag_OIDN_EXTERNAL_MEMORY_TYPE_FLAG_OPAQUE_WIN32: 216 | OIDNExternalMemoryTypeFlag = 4; 217 | pub const OIDNExternalMemoryTypeFlag_OIDN_EXTERNAL_MEMORY_TYPE_FLAG_OPAQUE_WIN32_KMT: 218 | OIDNExternalMemoryTypeFlag = 8; 219 | pub const OIDNExternalMemoryTypeFlag_OIDN_EXTERNAL_MEMORY_TYPE_FLAG_D3D11_TEXTURE: 220 | OIDNExternalMemoryTypeFlag = 16; 221 | pub const OIDNExternalMemoryTypeFlag_OIDN_EXTERNAL_MEMORY_TYPE_FLAG_D3D11_TEXTURE_KMT: 222 | OIDNExternalMemoryTypeFlag = 32; 223 | pub const OIDNExternalMemoryTypeFlag_OIDN_EXTERNAL_MEMORY_TYPE_FLAG_D3D11_RESOURCE: 224 | OIDNExternalMemoryTypeFlag = 64; 225 | pub const OIDNExternalMemoryTypeFlag_OIDN_EXTERNAL_MEMORY_TYPE_FLAG_D3D11_RESOURCE_KMT: 226 | OIDNExternalMemoryTypeFlag = 128; 227 | pub const OIDNExternalMemoryTypeFlag_OIDN_EXTERNAL_MEMORY_TYPE_FLAG_D3D12_HEAP: 228 | OIDNExternalMemoryTypeFlag = 256; 229 | pub const OIDNExternalMemoryTypeFlag_OIDN_EXTERNAL_MEMORY_TYPE_FLAG_D3D12_RESOURCE: 230 | OIDNExternalMemoryTypeFlag = 512; 231 | pub type OIDNExternalMemoryTypeFlag = ::std::os::raw::c_uint; 232 | #[repr(C)] 233 | #[derive(Debug, Copy, Clone)] 234 | pub struct OIDNBufferImpl { 235 | _unused: [u8; 0], 236 | } 237 | pub type OIDNBuffer = *mut OIDNBufferImpl; 238 | unsafe extern "C" { 239 | pub fn oidnNewBuffer(device: OIDNDevice, byteSize: usize) -> OIDNBuffer; 240 | } 241 | unsafe extern "C" { 242 | pub fn oidnNewBufferWithStorage( 243 | device: OIDNDevice, 244 | byteSize: usize, 245 | storage: OIDNStorage, 246 | ) -> OIDNBuffer; 247 | } 248 | unsafe extern "C" { 249 | pub fn oidnNewSharedBuffer( 250 | device: OIDNDevice, 251 | devPtr: *mut ::std::os::raw::c_void, 252 | byteSize: usize, 253 | ) -> OIDNBuffer; 254 | } 255 | unsafe extern "C" { 256 | pub fn oidnNewSharedBufferFromFD( 257 | device: OIDNDevice, 258 | fdType: OIDNExternalMemoryTypeFlag, 259 | fd: ::std::os::raw::c_int, 260 | byteSize: usize, 261 | ) -> OIDNBuffer; 262 | } 263 | unsafe extern "C" { 264 | pub fn oidnNewSharedBufferFromWin32Handle( 265 | device: OIDNDevice, 266 | handleType: OIDNExternalMemoryTypeFlag, 267 | handle: *mut ::std::os::raw::c_void, 268 | name: *const ::std::os::raw::c_void, 269 | byteSize: usize, 270 | ) -> OIDNBuffer; 271 | } 272 | unsafe extern "C" { 273 | pub fn oidnNewSharedBufferFromMetal(device: OIDNDevice, buffer: MTLBuffer_id) -> OIDNBuffer; 274 | } 275 | unsafe extern "C" { 276 | pub fn oidnGetBufferSize(buffer: OIDNBuffer) -> usize; 277 | } 278 | unsafe extern "C" { 279 | pub fn oidnGetBufferStorage(buffer: OIDNBuffer) -> OIDNStorage; 280 | } 281 | unsafe extern "C" { 282 | pub fn oidnGetBufferData(buffer: OIDNBuffer) -> *mut ::std::os::raw::c_void; 283 | } 284 | unsafe extern "C" { 285 | pub fn oidnReadBuffer( 286 | buffer: OIDNBuffer, 287 | byteOffset: usize, 288 | byteSize: usize, 289 | dstHostPtr: *mut ::std::os::raw::c_void, 290 | ); 291 | } 292 | unsafe extern "C" { 293 | pub fn oidnReadBufferAsync( 294 | buffer: OIDNBuffer, 295 | byteOffset: usize, 296 | byteSize: usize, 297 | dstHostPtr: *mut ::std::os::raw::c_void, 298 | ); 299 | } 300 | unsafe extern "C" { 301 | pub fn oidnWriteBuffer( 302 | buffer: OIDNBuffer, 303 | byteOffset: usize, 304 | byteSize: usize, 305 | srcHostPtr: *const ::std::os::raw::c_void, 306 | ); 307 | } 308 | unsafe extern "C" { 309 | pub fn oidnWriteBufferAsync( 310 | buffer: OIDNBuffer, 311 | byteOffset: usize, 312 | byteSize: usize, 313 | srcHostPtr: *const ::std::os::raw::c_void, 314 | ); 315 | } 316 | unsafe extern "C" { 317 | pub fn oidnRetainBuffer(buffer: OIDNBuffer); 318 | } 319 | unsafe extern "C" { 320 | pub fn oidnReleaseBuffer(buffer: OIDNBuffer); 321 | } 322 | pub const OIDNQuality_OIDN_QUALITY_DEFAULT: OIDNQuality = 0; 323 | pub const OIDNQuality_OIDN_QUALITY_FAST: OIDNQuality = 4; 324 | pub const OIDNQuality_OIDN_QUALITY_BALANCED: OIDNQuality = 5; 325 | pub const OIDNQuality_OIDN_QUALITY_HIGH: OIDNQuality = 6; 326 | pub type OIDNQuality = ::std::os::raw::c_uint; 327 | pub type OIDNProgressMonitorFunction = ::std::option::Option< 328 | unsafe extern "C" fn(userPtr: *mut ::std::os::raw::c_void, n: f64) -> bool, 329 | >; 330 | #[repr(C)] 331 | #[derive(Debug, Copy, Clone)] 332 | pub struct OIDNFilterImpl { 333 | _unused: [u8; 0], 334 | } 335 | pub type OIDNFilter = *mut OIDNFilterImpl; 336 | unsafe extern "C" { 337 | pub fn oidnNewFilter(device: OIDNDevice, type_: *const ::std::os::raw::c_char) -> OIDNFilter; 338 | } 339 | unsafe extern "C" { 340 | pub fn oidnRetainFilter(filter: OIDNFilter); 341 | } 342 | unsafe extern "C" { 343 | pub fn oidnReleaseFilter(filter: OIDNFilter); 344 | } 345 | unsafe extern "C" { 346 | pub fn oidnSetFilterImage( 347 | filter: OIDNFilter, 348 | name: *const ::std::os::raw::c_char, 349 | buffer: OIDNBuffer, 350 | format: OIDNFormat, 351 | width: usize, 352 | height: usize, 353 | byteOffset: usize, 354 | pixelByteStride: usize, 355 | rowByteStride: usize, 356 | ); 357 | } 358 | unsafe extern "C" { 359 | pub fn oidnSetSharedFilterImage( 360 | filter: OIDNFilter, 361 | name: *const ::std::os::raw::c_char, 362 | devPtr: *mut ::std::os::raw::c_void, 363 | format: OIDNFormat, 364 | width: usize, 365 | height: usize, 366 | byteOffset: usize, 367 | pixelByteStride: usize, 368 | rowByteStride: usize, 369 | ); 370 | } 371 | unsafe extern "C" { 372 | pub fn oidnUnsetFilterImage(filter: OIDNFilter, name: *const ::std::os::raw::c_char); 373 | } 374 | unsafe extern "C" { 375 | pub fn oidnSetSharedFilterData( 376 | filter: OIDNFilter, 377 | name: *const ::std::os::raw::c_char, 378 | hostPtr: *mut ::std::os::raw::c_void, 379 | byteSize: usize, 380 | ); 381 | } 382 | unsafe extern "C" { 383 | pub fn oidnUpdateFilterData(filter: OIDNFilter, name: *const ::std::os::raw::c_char); 384 | } 385 | unsafe extern "C" { 386 | pub fn oidnUnsetFilterData(filter: OIDNFilter, name: *const ::std::os::raw::c_char); 387 | } 388 | unsafe extern "C" { 389 | pub fn oidnSetFilterBool(filter: OIDNFilter, name: *const ::std::os::raw::c_char, value: bool); 390 | } 391 | unsafe extern "C" { 392 | pub fn oidnGetFilterBool(filter: OIDNFilter, name: *const ::std::os::raw::c_char) -> bool; 393 | } 394 | unsafe extern "C" { 395 | pub fn oidnSetFilterInt( 396 | filter: OIDNFilter, 397 | name: *const ::std::os::raw::c_char, 398 | value: ::std::os::raw::c_int, 399 | ); 400 | } 401 | unsafe extern "C" { 402 | pub fn oidnGetFilterInt( 403 | filter: OIDNFilter, 404 | name: *const ::std::os::raw::c_char, 405 | ) -> ::std::os::raw::c_int; 406 | } 407 | unsafe extern "C" { 408 | pub fn oidnSetFilterFloat(filter: OIDNFilter, name: *const ::std::os::raw::c_char, value: f32); 409 | } 410 | unsafe extern "C" { 411 | pub fn oidnGetFilterFloat(filter: OIDNFilter, name: *const ::std::os::raw::c_char) -> f32; 412 | } 413 | unsafe extern "C" { 414 | pub fn oidnSetFilterProgressMonitorFunction( 415 | filter: OIDNFilter, 416 | func: OIDNProgressMonitorFunction, 417 | userPtr: *mut ::std::os::raw::c_void, 418 | ); 419 | } 420 | unsafe extern "C" { 421 | pub fn oidnCommitFilter(filter: OIDNFilter); 422 | } 423 | unsafe extern "C" { 424 | pub fn oidnExecuteFilter(filter: OIDNFilter); 425 | } 426 | unsafe extern "C" { 427 | pub fn oidnExecuteFilterAsync(filter: OIDNFilter); 428 | } 429 | unsafe extern "C" { 430 | pub fn oidnExecuteSYCLFilterAsync( 431 | filter: OIDNFilter, 432 | depEvents: *const sycl_event, 433 | numDepEvents: ::std::os::raw::c_int, 434 | doneEvent: *mut sycl_event, 435 | ); 436 | } 437 | -------------------------------------------------------------------------------- /src/tests.rs: -------------------------------------------------------------------------------- 1 | use std::mem; 2 | 3 | #[cfg(test)] 4 | #[test] 5 | fn buffer_read_write() { 6 | let device = crate::Device::new(); 7 | let buffer = match device.create_buffer(&[0.0]) { 8 | Some(buffer) => buffer, 9 | // resources failing to be created is not the fault of this library 10 | None => { 11 | eprintln!("Test skipped due to buffer creation failing"); 12 | return; 13 | } 14 | }; 15 | buffer.write(&[1.0]).unwrap(); 16 | assert_eq!(buffer.read(), vec![1.0]); 17 | let mut slice = vec![0.0]; 18 | buffer.read_to_slice(&mut slice).unwrap(); 19 | assert_eq!(slice, vec![1.0]); 20 | if let Err((err, str)) = device.get_error() { 21 | panic!("test failed with {err:?}: {str}") 22 | } 23 | } 24 | 25 | #[cfg(test)] 26 | #[test] 27 | fn buffer_import_read_write() { 28 | let device = crate::Device::new(); 29 | let raw_buffer = unsafe { crate::sys::oidnNewBuffer(device.raw(), mem::size_of::()) }; 30 | if raw_buffer.is_null() { 31 | eprintln!("Test skipped due to buffer creation failing"); 32 | return; 33 | } 34 | let buffer = unsafe { device.create_buffer_from_raw(raw_buffer) }; 35 | buffer.write(&[1.0]).unwrap(); 36 | assert_eq!(buffer.read(), vec![1.0]); 37 | let mut slice = vec![0.0]; 38 | buffer.read_to_slice(&mut slice).unwrap(); 39 | assert_eq!(slice, vec![1.0]); 40 | if let Err((err, str)) = device.get_error() { 41 | panic!("test failed with {err:?}: {str}") 42 | } 43 | } 44 | --------------------------------------------------------------------------------