diff --git a/.gitignore b/.gitignore index 3815407..d1eb5ac 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ Cargo.lock # VL Assembly VL*.dll !VL.OpenEXR.dll +!VL.OpenEXR.Native.dll # stride assembly Stride*.dll \ No newline at end of file diff --git a/lib/net8.0/VL.OpenEXR.dll b/lib/net8.0/VL.OpenEXR.dll index 762f683..2bf23f6 100644 Binary files a/lib/net8.0/VL.OpenEXR.dll and b/lib/net8.0/VL.OpenEXR.dll differ diff --git a/resources/0270_Ocean_Commission_Canyon_NLD_11.Depth.0001.exr b/resources/0270_Ocean_Commission_Canyon_NLD_11.Depth.0001.exr new file mode 100644 index 0000000..541a9f2 Binary files /dev/null and b/resources/0270_Ocean_Commission_Canyon_NLD_11.Depth.0001.exr differ diff --git a/runtimes/win-x64/native/VL.OpenEXR.Native.dll b/runtimes/win-x64/native/VL.OpenEXR.Native.dll old mode 100644 new mode 100755 index 3705aa5..c39c861 Binary files a/runtimes/win-x64/native/VL.OpenEXR.Native.dll and b/runtimes/win-x64/native/VL.OpenEXR.Native.dll differ diff --git a/src/VL.OpenEXR.cs b/src/VL.OpenEXR.cs index 7264655..708566d 100644 --- a/src/VL.OpenEXR.cs +++ b/src/VL.OpenEXR.cs @@ -10,7 +10,6 @@ enum ExrPixelFormat U32 = 0, F16 = 1, F32 = 2, - RGBF32 = 3 } public enum ExrEncoding { @@ -27,13 +26,19 @@ public static class ExrLoader [DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory)] [DllImport("VL.OpenEXR.Native.dll")] - static extern IntPtr load_from_path(string path, out int width, out int height, out ExrPixelFormat format); + static extern Int32 load_from_path(string path, out int width, out int height, out int num_channels, out ExrPixelFormat format, out IntPtr data); public static Texture LoadFromPath(string path, GraphicsDevice device) { ExrPixelFormat exrFormat; PixelFormat format; - IntPtr ptr = load_from_path(path, out var width, out var height, out exrFormat); + IntPtr ptr; + var result = load_from_path(path, out var width, out var height, out var numChannels, out exrFormat, out ptr); + + if(result != 0) { + format = PixelFormat.None; + return null; + } if(exrFormat == ExrPixelFormat.Unknown || ptr == IntPtr.Zero) { @@ -42,17 +47,26 @@ public static Texture LoadFromPath(string path, GraphicsDevice device) } int sizeInBytes = 0; - bool hasAlpha = true; - (format, sizeInBytes, hasAlpha) = exrFormat switch + (format, sizeInBytes) = (exrFormat, numChannels) switch { - ExrPixelFormat.F16 => (PixelFormat.R16G16B16A16_Float, 2, true), - ExrPixelFormat.F32 => (PixelFormat.R32G32B32A32_Float, 4, true), - ExrPixelFormat.U32 => (PixelFormat.R32G32B32A32_UInt , 4, true), - ExrPixelFormat.RGBF32 => (PixelFormat.R32G32B32_Float, 4, false), - _ => (PixelFormat.None, 0, false), + (ExrPixelFormat.F16, 4) => (PixelFormat.R16G16B16A16_Float, 2), + (ExrPixelFormat.F32, 4) => (PixelFormat.R32G32B32A32_Float, 4), + (ExrPixelFormat.U32, 4) => (PixelFormat.R32G32B32A32_UInt , 4), + + (ExrPixelFormat.F32, 3) => (PixelFormat.R32G32B32_Float, 4), + (ExrPixelFormat.U32, 3) => (PixelFormat.R32G32B32_UInt , 4), + + (ExrPixelFormat.F16, 2) => (PixelFormat.R16G16_Float, 2), + (ExrPixelFormat.F32, 2) => (PixelFormat.R32G32_Float, 4), + (ExrPixelFormat.U32, 2) => (PixelFormat.R32G32_UInt , 4), + + (ExrPixelFormat.F16, 1) => (PixelFormat.R16_Float, 2), + (ExrPixelFormat.F32, 1) => (PixelFormat.R32_Float, 4), + (ExrPixelFormat.U32, 1) => (PixelFormat.R32_UInt , 4), + _ => (PixelFormat.None, 0), }; - var rowPitch = width * (hasAlpha ? 4 : 3) * sizeInBytes; + var rowPitch = width * numChannels * sizeInBytes; var texture = Texture.New( device, @@ -73,9 +87,9 @@ public static unsafe class ExrWriter [DllImport("VL.OpenEXR.Native.dll")] static extern int write_texture(string path, int width, int height, ExrPixelFormat format, ExrEncoding encoding, IntPtr data); - public static int WriteTexture(byte[] data, string path, int width, int height, PixelFormat format, ExrEncoding encoding) - { - return WriteTexture((ReadOnlySpan)data, path, width, height, format, encoding); + public static int WriteTexture(byte[] data, string path, int width, int height, PixelFormat format, ExrEncoding encoding) + { + return WriteTexture((ReadOnlySpan)data, path, width, height, format, encoding); } public static int WriteTexture(ReadOnlySpan data, string path, int width, int height, PixelFormat format, ExrEncoding encoding) @@ -90,9 +104,9 @@ public static int WriteTexture(ReadOnlySpan data, string path, int width, if(exrFormat == ExrPixelFormat.Unknown) return 1; //return with error - fixed (byte* pointer = data) - { - return write_texture(path, width, height, exrFormat, encoding, new IntPtr(pointer)); + fixed (byte* pointer = data) + { + return write_texture(path, width, height, exrFormat, encoding, new IntPtr(pointer)); } } } diff --git a/src/build.sh b/src/build.sh index 2dc4482..9c2722c 100644 --- a/src/build.sh +++ b/src/build.sh @@ -2,9 +2,9 @@ echo "\033[32mbuild native plugins\033[m" cd `dirname $0` cd native cargo build --target x86_64-pc-windows-gnu --release -cp target/x86_64-pc-windows-gnu/release/vl_openexr_native.dll ../../lib/native/VL.OpenEXR.Native.dll +cp target/x86_64-pc-windows-gnu/release/vl_openexr_native.dll ../../runtimes/win-x64/native/VL.OpenEXR.Native.dll echo "" echo "\033[32mbuild managed plugins\033[m" cd ../ -dotnet.exe build -c release -o ../lib/netstandard2.0 \ No newline at end of file +dotnet.exe build -c release \ No newline at end of file diff --git a/src/native/.cargo/config.toml b/src/native/.cargo/config.toml deleted file mode 100644 index 8d22ea8..0000000 --- a/src/native/.cargo/config.toml +++ /dev/null @@ -1,13 +0,0 @@ -#paths = ["/home/arturo/Code/na"] - -[build] -target-dir = "./target" -#rustflags = ["-Clink-arg=-fuse-ld=lld", "-Zshare-generics"] -#rustflags = ["-Clink-arg=-fuse-ld=lld"] -#rustflags = ["-Clink-arg=-fuse-ld=mold"] -#pipelining = true - -[target.x86_64-unknown-linux-gnu] -linker = "clang" -#rustflags = ["-C", "link-arg=-fuse-ld=/usr/local/bin/mold", "-Zshare-generics"] -rustflags = ["-C", "link-arg=-fuse-ld=/usr/local/bin/mold"] diff --git a/src/native/Cargo.toml b/src/native/Cargo.toml index 85e8fa6..7cca52b 100644 --- a/src/native/Cargo.toml +++ b/src/native/Cargo.toml @@ -9,6 +9,7 @@ name = "vl_openexr_native" crate-type = ["cdylib"] [dependencies] +anyhow = { version = "1.0.86", features = ["backtrace"] } exr = "1.72.0" radiant = "0.3.0" diff --git a/src/native/src/lib.rs b/src/native/src/lib.rs index a604fdf..12df69d 100644 --- a/src/native/src/lib.rs +++ b/src/native/src/lib.rs @@ -3,13 +3,25 @@ use std::fs::File; use std::io::BufReader; use std::os::raw::c_char; use std::mem; -use std::ffi::CStr; +use std::ffi::{c_void, CStr}; use std::path::Path; use std::slice::from_raw_parts; use exr::error::UnitResult; use exr::prelude::*; +macro_rules! unwrap_or_return_err { + ($e: expr) => { + match $e { + Ok(e) => e, + Err(err) => { + println!("{err}"); + return 1; + } + } + }; +} + #[derive(Clone, Copy, Debug)] #[repr(u32)] pub enum ExrEncoding { @@ -28,18 +40,21 @@ pub enum ExrPixelFormat U32 = 0, F16 = 1, F32 = 2, - RGBF32 = 3 +} + +impl From for ExrPixelFormat { + fn from(value: SampleType) -> Self { + match value { + SampleType::F16 => ExrPixelFormat::F16, + SampleType::F32 => ExrPixelFormat::F32, + SampleType::U32 => ExrPixelFormat::U32, + } + } } #[no_mangle] pub unsafe extern fn write_texture(path: *const c_char, width: i32, height: i32, format: ExrPixelFormat, encoding: ExrEncoding, data: *const Sample) -> i32 { - let path = match CStr::from_ptr(path).to_str() { - Ok(path) => path, - Err(err) => { - println!("{err}"); - return 1 - } - }; + let path = Path::new(unwrap_or_return_err!(CStr::from_ptr(path).to_str())); let result = match format { ExrPixelFormat::U32 => { @@ -118,125 +133,156 @@ fn write_exr(path: impl AsRef, array: &[T], width: usize, h } #[no_mangle] -pub unsafe extern fn load_from_path(path: *const c_char, width: *mut i32, height: *mut i32, format: *mut i32) -> *mut [Sample;4] { - let path_str = CStr::from_ptr(path).to_str().unwrap(); - let extension = Path::new(path_str).extension().unwrap().to_str().unwrap(); +pub unsafe extern fn load_from_path(path: *const c_char, width: *mut u32, height: *mut u32, num_channels: *mut u32, format: *mut ExrPixelFormat, data: *mut *mut c_void) -> i32 { + let path = Path::new(unwrap_or_return_err!(CStr::from_ptr(path).to_str())); + + *data = unwrap_or_return_err!(load(path, &mut *width, &mut *height, &mut *num_channels, &mut *format)); + + 0 +} + + +fn load(path: &Path, width: &mut u32, height: &mut u32, num_channels: &mut u32, format: &mut ExrPixelFormat) -> anyhow::Result<*mut c_void> { + let extension = match path + .extension() + .and_then(|extension| extension.to_str()) + { + Some(extension) => extension, + None => "" + }; match extension { "hdr" => { - let r = BufReader::new(File::open(path_str).unwrap()); - let mut image = radiant::load(r).unwrap(); + let f = File::open(path)?; + let r = BufReader::new(f); + let mut image = radiant::load(r)?; - *width = image.width as i32; - *height = image.height as i32; - *format = 3; + *width = image.width as u32; + *height = image.height as u32; + *num_channels = 3; + *format = ExrPixelFormat::F32; let ptr = image.data.as_mut_ptr(); mem::forget(image); - ptr as *mut [Sample;4] + Ok(ptr as *mut c_void) }, _ => { - match MetaData::read_from_file(path_str, false) { + match MetaData::read_from_file(path, false) { Ok(meta) => { let size = meta.headers[0].layer_size; - *width = size.0 as i32; - *height = size.1 as i32; + *width = size.0 as u32; + *height = size.1 as u32; let sample_type = meta.headers[0].channels.uniform_sample_type; match sample_type { - Some(v) => { - *format = v as i32; - - match v { - SampleType::F16 => load_exr_f16(path_str) as *mut [Sample;4], - SampleType::F32 => load_exr_f32(path_str) as *mut [Sample;4], - SampleType::U32 => load_exr_u32(path_str) as *mut [Sample;4] - } + Some(sample_type) => { + *format = sample_type.into(); + Ok(match sample_type { + SampleType::F16 => { + let (mut image, channels) = load_exr_f16(path, &meta)?; + *num_channels = channels as u32; + let ret = image.as_mut_ptr() as *mut c_void; + mem::forget(image); + ret + }, + SampleType::F32 => { + let (mut image, channels) = load_exr_f32(path, &meta)?; + *num_channels = channels as u32; + let ret = image.as_mut_ptr() as *mut c_void; + mem::forget(image); + ret + }, + SampleType::U32 => { + let (mut image, channels) = load_exr_u32(path, &meta)?; + *num_channels = channels as u32; + let ret = image.as_mut_ptr() as *mut c_void; + mem::forget(image); + ret + }, + }) }, None => { - *format = -1; - std::ptr::null_mut() as *mut [Sample;4] + *format = ExrPixelFormat::Unknown; + *num_channels = 0; + Err(Error::NotSupported("Sample type".into()).into()) } } }, - Err(_e) => { - *width = -1; - *height = -1; - *format = -1; - - std::ptr::null_mut() as *mut [Sample;4] + Err(err) => { + *width = 0; + *height = 0; + *num_channels = 0; + *format = ExrPixelFormat::Unknown; + Err(err.into()) } } } } } -fn load_exr_f16(path: &str) -> usize { - let image = read_first_rgba_layer_from_file( - path, - |resolution, _| { - let default_pixel: [f16;4] = [f16::from_f32(0.0), f16::from_f32(0.0), f16::from_f32(0.0), f16::from_f32(1.0)]; - let empty_line = vec![ default_pixel; resolution.width() ]; - let empty_image = vec![ empty_line; resolution.height() ]; - empty_image - }, - |pixel_vector, position, (r,g,b, a): (f16, f16, f16, f16)| { - pixel_vector[position.y()][position.x()] = [r, g, b, a] - }, - - ).unwrap(); - - let mut pixel = image.layer_data.channel_data.pixels.into_iter().flatten().collect::>(); - let ptr = pixel.as_mut_ptr(); - mem::forget(pixel); +fn load_exr_f16(path: &Path, meta: &MetaData) -> Result<(Vec, usize)> { + let image = read_first_flat_layer_from_file(path)?; + let w = meta.headers[0].layer_size.0; + let h = meta.headers[0].layer_size.1; + let num_channels = image.layer_data.channel_data.list.len(); + let mut flat_data = vec![ + f16::from_f32(0.); + w * h * num_channels + ]; + + for i in 0 .. w*h { + for (channel_index, channel) in image.layer_data.channel_data.list.iter().enumerate() { + if let FlatSamples::F16(samples) = &channel.sample_data { + flat_data[i * num_channels + (num_channels - 1 - channel_index)] = samples[i] + }else{ + unreachable!() + } + } + } - return unsafe { mem::transmute(ptr) }; + Ok((flat_data, num_channels)) } -fn load_exr_f32(path: &str) -> usize { - let image = read_first_rgba_layer_from_file( - path, - |resolution, _| { - let default_pixel: [f32;4] = [0.0, 0.0, 0.0, 1.0]; - let empty_line = vec![ default_pixel; resolution.width() ]; - let empty_image = vec![ empty_line; resolution.height() ]; - empty_image - }, - |pixel_vector, position, (r,g,b, a): (f32, f32, f32, f32)| { - pixel_vector[position.y()][position.x()] = [r, g, b, a] - }, - - ).unwrap(); - - let mut pixel = image.layer_data.channel_data.pixels.into_iter().flatten().collect::>(); - let ptr = pixel.as_mut_ptr(); - mem::forget(pixel); +fn load_exr_f32(path: &Path, meta: &MetaData) -> Result<(Vec, usize)> { + let image = read_first_flat_layer_from_file(path)?; + let w = meta.headers[0].layer_size.0; + let h = meta.headers[0].layer_size.1; + let num_channels = image.layer_data.channel_data.list.len(); + let mut flat_data = vec![0.; w * h * num_channels]; + + for i in 0 .. w*h { + for (channel_index, channel) in image.layer_data.channel_data.list.iter().enumerate() { + if let FlatSamples::F32(samples) = &channel.sample_data { + flat_data[i * num_channels + (num_channels - 1 - channel_index)] = samples[i] + }else{ + unreachable!() + } + } + } - return unsafe { mem::transmute(ptr) }; + Ok((flat_data, num_channels)) } -fn load_exr_u32(path: &str) -> usize { - let image = read_first_rgba_layer_from_file( - path, - |resolution, _| { - let default_pixel: [u32;4] = [0, 0, 0, 1]; - let empty_line = vec![ default_pixel; resolution.width() ]; - let empty_image = vec![ empty_line; resolution.height() ]; - empty_image - }, - |pixel_vector, position, (r,g,b, a): (u32, u32, u32, u32)| { - pixel_vector[position.y()][position.x()] = [r, g, b, a] - }, - - ).unwrap(); - - let mut pixel = image.layer_data.channel_data.pixels.into_iter().flatten().collect::>(); - let ptr = pixel.as_mut_ptr(); - mem::forget(pixel); +fn load_exr_u32(path: &Path, meta: &MetaData) -> Result<(Vec, usize)> { + let image = read_first_flat_layer_from_file(path)?; + let w = meta.headers[0].layer_size.0; + let h = meta.headers[0].layer_size.1; + let num_channels = image.layer_data.channel_data.list.len(); + let mut flat_data = vec![0; w * h * num_channels]; + + for i in 0 .. w*h { + for (channel_index, channel) in image.layer_data.channel_data.list.iter().enumerate() { + if let FlatSamples::U32(samples) = &channel.sample_data { + flat_data[i * num_channels + (num_channels - 1 - channel_index)] = samples[i] + }else{ + unreachable!() + } + } + } - return unsafe { mem::transmute(ptr) }; + Ok((flat_data, num_channels)) } // The use of exr::Sample is stored in memory at compile time according to the largest element, f32 @@ -260,5 +306,28 @@ fn load_exr_u32(path: &str) -> usize { // let ptr = pixel.as_mut_ptr(); // mem::forget(pixel); -// return unsafe { mem::transmute(ptr) }; -// } \ No newline at end of file +// return ptr as usize; +// } + +#[test] +fn test_depth_image() { + let path = Path::new("../../resources/0270_Ocean_Commission_Canyon_NLD_11.Depth.0001.exr"); + let mut width = 0; + let mut height = 0; + let mut num_channels = 0; + let mut format = ExrPixelFormat::Unknown; + let data = load(path, &mut width, &mut height, &mut num_channels, &mut format).unwrap(); + assert_eq!(num_channels, 1); +} + + +#[test] +fn test_rgba16_image() { + let path = Path::new("../../resources/OutdoorHDRI016_2K-HDR.exr"); + let mut width = 0; + let mut height = 0; + let mut num_channels = 0; + let mut format = ExrPixelFormat::Unknown; + let data = load(path, &mut width, &mut height, &mut num_channels, &mut format).unwrap(); + assert_eq!(num_channels, 4); +} \ No newline at end of file